Fabric

news2024/12/25 12:50:00

Fabric


Fabric.js是一个非常好用的Javascript HTML5 canvas库,封装了canvas原生较为复杂的api,在canvas元素的顶部提供交互式对象模型,用于实现图片的变形旋转拖拉拽等功能。
在这里插入图片描述

在线demo: 官网链接


下载

npm install fabric --save

yarn add fabric

期间下载到canvas会用时比较长,推荐使用npm,用yarn有时候会安装失败


初始化

如果无需设置图片边框 也可以将canvas.on整个函数删除

<div class="section" :style="{width: `800px`, height: `500px`}" ref="canvasSectionRef">
    <canvas ref="canvasRef" width="800" height="500" id="fabricCanvas"></canvas>
</div>
// vue3
import {onMounted, onUnmounted, ref} from 'vue';
import {fabric} from 'fabric';

const canvasSectionRef = ref(null)  // canvas 父元素引用
const canvasRef = ref(null)  // canvas 的引用
let canvas     // 用于保存fabric canvas 对象, 不能用ref

/**
 * @description 根据 id 在 canvas 对象中查找出对应的图片对象和其索引(索引同时也是显示层级 即 z-index),在callBack中做对应处理
 * @param id {string} 图片对象的id
 * @param callBack {(selectedObject: Object, zIndex: number) => any} 查找到对象后的回调
 * @returns {undefined}
 */
const editImageState = (id, callBack) => {
    const objList = canvas.getObjects()
    const zIndex = objList.findIndex((obj) => {
        return obj.customId === id
    });
    const selectedObject = objList[zIndex]
    if (!selectedObject) return
    callBack?.(selectedObject, zIndex)
}

/**
 * @description
 *  初始化fabric canvas 对象 设置边框颜色 和 四个角控制点的样式
 *  - 点击时显示蓝色虚线边框
 *  - 锁定时点击为灰色虚线边框
 *  - 默认没有选中为透明边框
 */
const fabricInit = () => {
    let selectedObjects = {}; // 使用对象来存储不同图片的选中状态 存储方式为 `key - value` -> `id - boolean`

    canvas = new fabric.Canvas(canvasRef.value);

    // 自定义控制点样式
    fabric.Object.prototype.set({
        cornerStyle: 'square',
        cornerColor: '#4191fa',
        cornerSize: 10,
        transparentCorners: false,
        cornerStrokeColor: '#fff',
        cornerStrokeWidth: 2
    });

	// 点击时图片添加边框 不需要可以去除 
    canvas.on('mouse:down', function (options) {
        // 当前鼠标点击时获取的对象
        const targetObject = canvas.findTarget(options.e);

        if (targetObject && targetObject.type === 'image') {
            // 获取选中图片的id 用于设置边框样式
            const currentId = targetObject.customId;

            if (selectedObjects[currentId]) {
                // 当前点击的图片已经是选中状态,不执行任何操作
            } else {
                // 取消显示当前所有图片的边框 (先全部取消 再按id查找当前点击的图片设置为选中)
                for (const id in selectedObjects) {
                    // 处理取消选中的逻辑
                    if (selectedObjects.hasOwnProperty(id) && selectedObjects[id]) {
                        editImageState(id, (imageItem) => {
                            imageItem.set({
                                stroke: 'transparent',
                                strokeWidth: 2
                            });
                        })
                    }
                }
                selectedObjects = {};

                // 将当前点击的图片设置为选中状态  显示边框
                selectedObjects[currentId] = true;
                editImageState(currentId, (imageItem) => {
                    let color = imageItem.selectable ? '#4191fa' : '#ccc'
                    imageItem.set({
                        stroke: color, // 显示边框
                        strokeWidth: 2 // 边框宽度为2
                    });
                })
            }
        } else {
            // 点击的是画布空白处,清空选中对象
            for (const id in selectedObjects) {
                if (selectedObjects.hasOwnProperty(id) && selectedObjects[id]) {
                    editImageState(id, (imageItem) => {
                        imageItem.set({
                            stroke: 'transparent', // 隐藏边框
                            strokeWidth: 2 // 边框宽度为0
                        });
                    })
                }
            }
            selectedObjects = {};
        }
        // 处理完成重新渲染 否则不是马上生效
        canvas.renderAll();
    });
}

onMounted(() => {
    fabricInit()
});

onUnmounted(() => {
    canvas = null
})

添加图片

添加图片时需要添加唯一id,方便后续根据id对图片进行操作

// 生成唯一的 ID 用于图片升成
function generateUniqueId() {
    return 'id_' + +new Date();
}

/**
 * @description 根据url地址在canvas对象中添加图片
 * @param url {string} 图片地址 在线 or 离线
 */
const addImg = (url) => {
    fabric.Image.fromURL(url, function (img) {
        img.set({
            selectable: true, // 禁用选中效果 -> true为不禁用 | false为禁用
            hasControls: true, // 禁用控制点 -> true为不禁用 | false为禁用
            borderColor: 'transparent', // 边框颜色
            strokeDashArray: [5, 5],
            strokeWidth: 2
        });

        // 添加唯一id
        img.customId = generateUniqueId();
        canvas.add(img);
    });
}

新增了addImg这个函数后就可以在onMounted中使用addImg(图片地址),在canvas中就可以看到效果。

锁定 / 解锁图片(不可选中不可移动)

/**
 * @description 将canvas对象中对应id的图片对象设置为禁用 并设置边框颜色灰色
 * @param id {string} 图片对象id
 */
const lockImg = (id) => {
    editImageState(id, (imageItem) => {
        imageItem.set({
            selectable: false, // 禁用选中效果
            hasControls: false, // 禁用控制点
            stroke: '#ccc',
        });
        canvas.discardActiveObject()
        canvas.renderAll()
    })
}

/**
 * @description 将canvas对象中对应id的图片对象设置取消禁用 并设置边框颜色蓝色
 * @param id {string} 图片对象id
 */
const unLockImg = (id) => {
    editImageState(id, (imageItem) => {
        imageItem.set({
            selectable: true, // 禁用选中效果
            hasControls: true, // 禁用控制点
            stroke: '#4191fa'
        });
        canvas.renderAll()
    })
}

删除图片

/**
 * @description 将canvas对象中对应id的图片对象删除
 * @param id {string} 图片对象id
 */
const deleteImg = (id) => {
    editImageState(id, (imageItem) => {
        canvas.remove(imageItem);
        canvas.renderAll()
    })
}



获取图片id

根据id进行修改删除图片的方法都有了,然后就是获取图片id的方法

const selectImgId = ref() // 保存点击时的图片id

// 判断元素是否为Canvas元素或其子元素
const isCanvasElement = (element) => {
    return element instanceof HTMLCanvasElement || element.closest('canvas') !== null;
}

/**
 * @description 查找canvas所有生成的对象(图片)中 点击位置上的对象
 * @param x {number} 鼠标点击的相对于canvas的 x 坐标
 * @param y {number} 鼠标点击的相对于canvas的 Y 坐标
 * @returns {Object|null} 点击处的对象 没有则返回null
 */
const findClickedObject = (x, y) => {
    // 遍历Canvas上的所有对象,判断点击位置是否在对象范围内
    for (let i = canvas.getObjects().length - 1; i >= 0; i--) {
        const obj = canvas.item(i);
        if (obj.containsPoint({x: x, y: y})) {
            return obj;
        }
    }
    return null;
}

const getImgId = (event) => {
	 if (isCanvasElement(event.target)) {

        // 获取鼠标点击位置相对于 Canvas 元素的坐标
        const canvasRect = canvasSectionRef.value.getBoundingClientRect();
        const x = event.clientX - canvasRect.left;
        const y = event.clientY - canvasRect.top;

        // 查找点击位置上的对象
        const targetObject = findClickedObject(x, y);

        if (targetObject && targetObject.type === 'image') {

            // 选中的图片id保存起来
            selectImgId.value = targetObject.customId
        }
    }
}

onMounted(() => {
    document.addEventListener('click', getImgId);
});

当点击鼠标左键时会获取到图片id并保存在selectImgId变量中,后续需要设置锁定或删除,只需要调用对应的函数传入selectImgId即可。

同时可以通过设置图片对象的索引值更改显示层级,类似cssz-index,这里不过多赘述。

完整示例代码

<template>
    <div class="section" :style="{width: `800px`, height: `500px`}" ref="canvasSectionRef">
        <canvas ref="canvasRef" width="800" height="500" id="fabricCanvas"></canvas>
    </div>
</template>

<script setup>
import {onMounted, onUnmounted, ref, watchEffect} from 'vue';
import {fabric} from 'fabric';

const canvasSectionRef = ref(null)  // canvas 父元素引用
const canvasRef = ref(null)  // canvas 的引用
let canvas     // 用于保存fabric canvas 对象, 不能用ref

const selectImgId = ref() // 保存点击时的图片id

watchEffect(() => {
    console.log('selectImgId 更改了', selectImgId.value)
})

/**
 * @description 根据 id 在 canvas 对象中查找出对应的图片对象和其索引(索引同时也是显示层级 即 z-index),在callBack中做对应处理
 * @param id {string} 图片对象的id
 * @param callBack {(selectedObject: Object, zIndex: number) => any} 查找到对象后的回调
 * @returns {undefined}
 */
const editImageState = (id, callBack) => {
    const objList = canvas.getObjects()
    const zIndex = objList.findIndex((obj) => {
        return obj.customId === id
    });
    const selectedObject = objList[zIndex]
    if (!selectedObject) return
    callBack?.(selectedObject, zIndex)
}

// 判断元素是否为Canvas元素或其子元素
const isCanvasElement = (element) => {
    return element instanceof HTMLCanvasElement || element.closest('canvas') !== null;
}

/**
 * @description 查找canvas所有生成的对象(图片)中 点击位置上的对象
 * @param x {number} 鼠标点击的相对于canvas的 x 坐标
 * @param y {number} 鼠标点击的相对于canvas的 Y 坐标
 * @returns {Object|null} 点击处的对象 没有则返回null
 */
const findClickedObject = (x, y) => {
    // 遍历Canvas上的所有对象,判断点击位置是否在对象范围内
    for (let i = canvas.getObjects().length - 1; i >= 0; i--) {
        const obj = canvas.item(i);
        if (obj.containsPoint({x: x, y: y})) {
            return obj;
        }
    }
    return null;
}

const getImgId = (event) => {
	 if (isCanvasElement(event.target)) {

        // 获取鼠标点击位置相对于 Canvas 元素的坐标
        const canvasRect = canvasSectionRef.value.getBoundingClientRect();
        const x = event.clientX - canvasRect.left;
        const y = event.clientY - canvasRect.top;

        // 查找点击位置上的对象
        const targetObject = findClickedObject(x, y);

        if (targetObject && targetObject.type === 'image') {

            // 选中的图片id保存起来
            selectImgId.value = targetObject.customId
        }
    }
}

/**
 * @description
 *  初始化fabric canvas 对象 设置边框颜色 和 四个角控制点的样式
 *  - 点击时显示蓝色虚线边框
 *  - 锁定时点击为灰色虚线边框
 *  - 默认没有选中为透明边框
 */
const fabricInit = () => {
    let selectedObjects = {}; // 使用对象来存储不同图片的选中状态 存储方式为 `key - value` -> `id - boolean`

    canvas = new fabric.Canvas(canvasRef.value);

    // 自定义控制点样式
    fabric.Object.prototype.set({
        cornerStyle: 'square',
        cornerColor: '#4191fa',
        cornerSize: 10,
        transparentCorners: false,
        cornerStrokeColor: '#fff',
        cornerStrokeWidth: 2
    });

    // 点击时图片添加边框 不需要可以去除
    canvas.on('mouse:down', function (options) {
        // 当前鼠标点击时获取的对象
        const targetObject = canvas.findTarget(options.e);

        if (targetObject && targetObject.type === 'image') {
            // 获取选中图片的id 用于设置边框样式
            const currentId = targetObject.customId;

            if (selectedObjects[currentId]) {
                // 当前点击的图片已经是选中状态,不执行任何操作
            } else {
                // 取消显示当前所有图片的边框 (先全部取消 再按id查找当前点击的图片设置为选中)
                for (const id in selectedObjects) {
                    // 处理取消选中的逻辑
                    if (selectedObjects.hasOwnProperty(id) && selectedObjects[id]) {
                        editImageState(id, (imageItem) => {
                            imageItem.set({
                                stroke: 'transparent',
                                strokeWidth: 2
                            });
                        })
                    }
                }
                selectedObjects = {};

                // 将当前点击的图片设置为选中状态  显示边框
                selectedObjects[currentId] = true;
                editImageState(currentId, (imageItem) => {
                    let color = imageItem.selectable ? '#4191fa' : '#ccc'
                    imageItem.set({
                        stroke: color, // 显示边框
                        strokeWidth: 2 // 边框宽度为2
                    });
                })
            }
        } else {
            // 点击的是画布空白处,清空选中对象
            for (const id in selectedObjects) {
                if (selectedObjects.hasOwnProperty(id) && selectedObjects[id]) {
                    editImageState(id, (imageItem) => {
                        imageItem.set({
                            stroke: 'transparent', // 隐藏边框
                            strokeWidth: 2 // 边框宽度为0
                        });
                    })
                }
            }
            selectedObjects = {};
        }
        // 处理完成重新渲染 否则不是马上生效
        canvas.renderAll();
    });
}

// 生成唯一的 ID 用于图片升成
function generateUniqueId() {
    return 'id_' + +new Date();
}

/**
 * @description 根据url地址在canvas对象中添加图片
 * @param url {string} 图片地址 在线 or 离线
 */
const addImg = (url) => {
    fabric.Image.fromURL(url, function (img) {
        img.set({
            selectable: true, // 禁用选中效果 -> true为不禁用 | false为禁用
            hasControls: true, // 禁用控制点 -> true为不禁用 | false为禁用
            borderColor: 'transparent', // 边框颜色
            strokeDashArray: [5, 5],
            strokeWidth: 2
        });

        // 添加唯一id
        img.customId = generateUniqueId();
        canvas.add(img);
    });
}

onMounted(() => {
    fabricInit()
    addImg(new URL('../assets/图片地址.png', import.meta.url).href)
    document.addEventListener('click', getImgId);
});

onUnmounted(() => {
    canvas = null
})
</script>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/842756.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

实力认证 | 百分点科技蝉联中国大数据企业50强

近日&#xff0c;第八届中国大数据产业生态大会在京召开 &#xff0c;本届大会以“数实共融 生态共建”为主题&#xff0c;由赛迪传媒、大数据产业生态联盟、《软件和集成电路 》杂志社联合主办。会上颁布多个奖项&#xff0c;百分点科技斩获2023中国大数据企业50强、金沙奖“…

由于找不到vcruntime140_1.dll,无法继续执行代码重新安装程序,怎么解决

vcruntime140_1.dll是Microsoft Visual C Redistributable for Visual Studio 2015中的一个动态链接库文件。它是用于支持在Windows操作系统上运行使用Visual C编写的应用程序或游戏所必需的文件之一。当出现vcruntime140_1.dll丢失的错误时&#xff0c;通常是由于缺少或损坏了…

作者推荐 | 【深入了解系统性能优化】「实战技术专题」全方面带你透彻探索服务优化技术方案(系统服务调优)

全方面带你透彻探索服务优化技术方案&#xff08;服务器系统性能调优&#xff09; 调优意义计划分析 流程相关分析优化分析Nginx请求服务日志将请求热度最高的接口进行优化异步调用优化方式注意要点 分析调用链路追踪体系建立切面操作分析性能和数据统计存储相关的调用以及耗时…

拓展知识 启望未来 | 记内蒙古移动《AntDB ACA初级认证培训》活动

炎炎六月&#xff0c;迎来了备受期待的“亚信科技AntDB数据库初级认证培训”活动。通过培训&#xff0c;希望内蒙古移动及项目组的伙伴们能够系统学习到国产数据库的核心知识&#xff0c;提升专业技能&#xff0c;为服务感知提升、运维团队培育注入新的活力。 26号上午&#xf…

Pytest测试框架3

目录&#xff1a; pytest结合数据驱动-yamlpytest结合数据驱动-excelpytest结合数据驱动-csvpytest结合数据驱动-jsonpytest测试用例生命周期管理&#xff08;一&#xff09;pytest测试用例生命周期管理&#xff08;二&#xff09;pytest测试用例生命周期管理&#xff08;三&a…

rust-异步学习

rust获取future中的结果 两种主要的方法使用 async: async fn 和 async 块 async 体以及其他 future 类型是惰性的&#xff1a;除非它们运行起来&#xff0c;否则它们什么都不做。 运行 Future 最常见的方法是 .await 它。 当 .await 在 Future 上调用时&#xff0c;它会尝试把…

vue2.29-Vue3跟vue2的区别

1、vue3介绍 更新&#xff08;和重写&#xff09;Vue的主要版本时&#xff0c;主要考虑两点因素&#xff1a;首先是新的JavaScript语言特性在主流浏览器中的受支持水平&#xff1b;其次是当前代码库中随时间推移而逐渐暴露出来的一些设计和架构问题。 相较于vue2&#xff0c;vu…

从零开始打造你的书店小程序商城

随着互联网的发展&#xff0c;线上商城成为了书店经营的重要方式之一。如何快速搭建一个符合书店特点的小程序商城呢&#xff1f;下面将为您详细介绍利用乔拓云平台搭建一个符合书店特点的小程序商城的步骤。 首先&#xff0c;登录乔拓云平台&#xff0c;进入商城后台管理页面。…

uniapp两个单页面之间进行传参

1.单页面传参&#xff1a;A --> B url: .....?code JSON.stringify(param), 2.单页面传参B–>Auni.$emit() uni.$on()

Aspose.Imaging for Python via .NET Crack

Aspose.Imaging for Python via .NET Crack Aspose.Imaging for Python via.NET是一个提供高级图像处理功能的库。您可以使用此API轻松创建、加载、操作、转换或压缩图像。另外&#xff0c;Aspose.Imaging for Python通过.NET支持绘图和使用图形基元。图像导出和转换-API的核心…

使用XMLHttpRequest实现文件异步下载

1、问题描述 我想通过异步的方式实现下载文化&#xff0c;请求为post请求。一开始我打算用ajax。 $.ajax({type:post,contentType:application/json,url:http://xxx/downloadExcel,data:{data:JSON.stringify(<%oJsonResponse.JSONoutput()%>)},}).success(function(dat…

旅游系统开源

线上旅游电子商务系统 适用业务场景&#xff1a; 线路、酒店、门票、租车、邮轮、导游、团购、签证、特产、户外、商城等。 支持B2C\B2B2C\B2B业务&#xff0c;支持微信公会号、微信小程序、PC端等等。 源码介绍&#xff1a; 1、采用WEB软件应用最广泛的PHPMYSQL语言 采用B…

4大软件测试策略的特点和区别(单元测试、集成测试、确认测试和系统测试)

四大软件测试策略分别是单元测试、集成测试、确认测试和系统测试。 一、单元测试 单元测试也称为模块测试&#xff0c;它针对软件中的最小单元&#xff08;如函数、方法、类、模块等&#xff09;进行测试&#xff0c;以验证其是否符合预期的行为和结果。单元测试通常由开发人…

单例模式(C++)

定义 保证一个类仅有一个实例&#xff0c;并提供一个该实例的全局访问点。 应用场景 在软件系统中&#xff0c;经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例&#xff0c;才能确保它们的逻辑正确性、以及良好的效率。如何绕过常规的构造器&#xff0c;提供一种…

【Docker】Docker私有仓库的使用

目录 一、搭建私有仓库 二、上传镜像到私有仓库 三、从私有仓库拉取镜像 一、搭建私有仓库 首先我们需要拉取仓库的镜像 docker pull registry 然后创建私有仓库容器 docker run -it --namereg -p 5000:5000 registry 这个时候我们可以打开浏览器访问5000端口看是否成功&…

zookeeper和kafka

目录 一、zookeeper理论 1.1、zookeeper定义 1.2、zookeeper工作机制 1.3、zookeeper特点 1.4、zookeeper的数据结构 1.5、zookeeper应用场景 1.6、zookeeper的选举机制 二、部署Zookeeper 集群 2.1、环境准备 2.2、安装 Zookeeper 2.3、修改配置文件 2.4、配置…

编写第一个 React Native 程序

React Native 目录 使用React Native CLI命令创建的目录如下图所示&#xff1a; 重要目录说明 目录说明__tests__存放测试用例的目录.bundle / config配置文件&#xff08;一般不会用到&#xff09;android 和 IOS 文件夹这两个文件夹主要是存放安卓和 ios 相关的配置文件和…

ESP32学习笔记(52)————三轴加速度ADXL345使用(SPI方式)

一、简介 ADXL345 是一款 ADI 公司推出的基于 iMEMS 技术的超低功耗3轴加速度计&#xff0c;分辨率高(13位)&#xff0c;测量范围达 16g。数字输出数据为 16 位二进制补码格式&#xff0c;可通过 SPI(3线或4线) 或 I2C 数字接口访问。ADXL345 非常适合移动设备应用。它可以在倾…

小研究 - MySQL 分区技术在海量系统日志中的应用

随着信息技术的飞速发展&#xff0c;系统的业务功能不断扩大&#xff0c;产生的日志与日俱增&#xff0c;导致应用软件的运行速度越来越慢&#xff0c;不能很好地满足用户对软件性能的需求。基于此&#xff0c;重点研究了 MySQL 分区技术在大数据量软件日志中的应用&#xff0c…

NamedParameterJdbcTemplate类的作用和使用

NamedParameterJdbcTemplate是Spring框架提供的一个类&#xff0c;用于简化在JDBC操作中使用命名参数的过程。它是对JdbcTemplate的扩展&#xff0c;通过使用命名参数而不是传统的问号占位符&#xff0c;使SQL语句更易读和维护。 NamedParameterJdbcTemplate的作用是&#xff…