Fabric

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>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

工具推荐之不出网环境下上线CS

前言 在实战攻防演练中&#xff0c;我们经常会遇到目标不出网的情况&#xff0c;即便获取了目标权限也不方便在目标网络进行下一步横向移动。本期我们将会推荐两个常用的代理工具&#xff0c;使我们能在不出网的环境下让目标上线到CS&#xff0c;方便后渗透的工作。 工具1&…

吃瓜教程-Task05

目录 支持向量机 间隔与支持向量 SVM基本型 对偶问题 kkt条件 例子 对偶问题 例子 对偶问题原理解释 软间隔与正则化 替代损失函数 支持向量回归 例子 支持向量机 间隔与支持向量 在样本空间中&#xff0c;划分超平面可通过如下线性方程来描述: 样本空间中任意点x到…

opencv基础-33 图像平滑处理-中值滤波cv2.medianBlur()

中值滤波是一种常见的图像处理滤波技术&#xff0c;用于去除图像中的噪声。它的原理是用一个滑动窗口&#xff08;也称为卷积核&#xff09;在图像上移动&#xff0c;对窗口中的像素值进行排序&#xff0c;然后用窗口中像素值的中值来替换中心像素的值。这样&#xff0c;中值滤…

分布式测试插件 pytest-xdist 使用详解

目录 使用背景&#xff1a; 使用前提&#xff1a; 使用快速入门&#xff1a; 使用小结&#xff1a; 使用背景&#xff1a; 大型测试套件&#xff1a;当你的测试套件非常庞大&#xff0c;包含了大量的测试用例时&#xff0c;pytest-xdist可以通过并行执行来加速整体的测试过…

Linux tcpdump 命令详解

简介 用简单的话来定义tcpdump&#xff0c;就是&#xff1a;dump the traffic on a network&#xff0c;根据使用者的定义对网络上的数据包进行截获的包分析工具。 tcpdump可以将网络中传送的数据包的“头”完全截获下来提供分析。它支持针对网络层、协议、主机、网络或端口的…

时序预测 | MATLAB实现BO-GRU贝叶斯优化门控循环单元时间序列预测

时序预测 | MATLAB实现BO-GRU贝叶斯优化门控循环单元时间序列预测 目录 时序预测 | MATLAB实现BO-GRU贝叶斯优化门控循环单元时间序列预测效果一览基本介绍模型搭建程序设计参考资料 效果一览 基本介绍 MATLAB实现BO-GRU贝叶斯优化门控循环单元时间序列预测。基于贝叶斯(bayes)…

One-4-All: Neural Potential Fields for Embodied Navigation 论文阅读

论文信息 题目&#xff1a;One-4-All: Neural Potential Fields for Embodied Navigation 作者&#xff1a;Sacha Morin, Miguel Saavedra-Ruiz 来源&#xff1a;arXiv 时间&#xff1a;2023 Abstract 现实世界的导航可能需要使用高维 RGB 图像进行长视野规划&#xff0c;这…

【Windbg】通过网络调试windows内核

环境 windows版本&#xff1a;win10_x64 1901 windbg版本&#xff1a;1.2306.12001.0 HOST 1、windbg软件设置。 点击菜单文件&#xff0c;然后如下图操作。 2、等待连接。 ************* Waiting for Debugger Extensions Gallery to Initialize **************>>&…

“他“是怎么拿offer的?全网最全,性能测试面试题+答案(超全整理)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、什么是负载测试…

BenchmarkSQL 支持 TiDB 驱动以及 tidb-loadbalance

作者&#xff1a; GangShen 原文来源&#xff1a; https://tidb.net/blog/3c274180 使用 BenchmarkSQL 对 TiDB 进行 TPC-C 测试 众所周知 TiDB 是一个兼容 MySQL 协议的分布式关系型数据库&#xff0c;用户可以使用 MySQL 的驱动以及连接方式连接 TiDB 进行使用&#xff0…

了解HTTP代理日志:解读请求流量和响应信息

嗨&#xff0c;爬虫程序员们&#xff01;你们是否在了解爬虫发送的请求流量和接收的响应信息上有过困扰&#xff1f;今天&#xff0c;我们一起来了解一下。 首先&#xff0c;我们需要理解HTTP代理日志的基本结构和内容。HTTP代理日志是对爬虫发送的请求和接收的响应进行记录的文…

基于STM32微控制器的物联网(IoT)节点设计与实现

基于STM32微控制器的物联网(IoT)节点的设计和实现。我们讨论物联网节点的基本概念和功能,并详细介绍了STM32微控制器的特点和优势。然后,我们将探讨如何使用STM32开发环境和相关的硬件模块来设计和实现一个完整的物联网节点。最后,我们将提供一个示例代码,展示如何在STM3…

QToolButton内存提前释放导致mouseReleaseEvent崩溃问题

QToolButton内存提前释放导致mouseReleaseEvent崩溃问题 1、问题现象及原因分析 1.1、问题现象 如图所示&#xff0c;mouseReleaseEvent接口this指针地址为空&#xff0c;导致了Qt内部发生了Access violation异常。 1.2、问题原因 在项目中&#xff0c;使用该QToolButton…

图扑软件入选 2023 中国信通院“铸基计划”全景图

7 月 27 日&#xff0c;由中国信通院主办的“2023 数字生态发展大会”暨中国信通院“铸基计划”年中会议在北京召开。本次大会重磅发布了《高质量数字化转型产品及服务全景图&#xff08;2023 上半年度&#xff09;》。图扑软件凭借自研 HT for Web 数字孪生可视化产品成功入选…

Swagger技术-自动生成测试接口

简介 前端资源和后端资源分开&#xff0c;前端通过nginx运行&#xff0c;后端通过tomcat运行 前端技术框架&#xff1a; Swagger 作用&#xff1a;生成各种样式的接口文档&#xff0c;以及在线接口调试页面等 kinfe4j是基于mvc框架继承swagger生成api文档的增强解决方案 …

在oracle SQL中创建返回表的函数

这是我的职责 create or replace FUNCTION split(i_str IN VARCHAR2,i_delim IN VARCHAR2 DEFAULT : ) RETURN TABLE AS BEGINRETURN SELECT trim(regexp_substr(i_str, [^||i_delim||], 1, LEVEL)) str FROM projetCONNECT BY instr(i_str, i_delim, 1, LEVEL - 1) …

赛码网-Light 100%AC代码(C++)

———————————————————————————————————— ⏩ 大家好哇&#xff01;我是小光&#xff0c;嵌入式爱好者&#xff0c;一个想要成为系统架构师的大三学生。 ⏩最近在准备秋招&#xff0c;一直在练习编程。 ⏩本篇文章对赛码网的 Light 题目做一个…

QT 使用单例模式

目录 1. 单例模式介绍 2.单例模式实现 1. 单例模式介绍 有些时候我们在做 qt 项目的时候,要用到很多类. 例如我们用到的类有 A,B,C,D. 其中,A 是 B,C,D 中都需要用到的类,A 类非常的抢手. 但是,A 类非常的占内存,定义一个 A 对象需要 500M 内存,假如在 B,C,D 中都定义一个 A 类…

爬虫获取电影数据----以沈腾参演电影为例

数据可视化&分析实战 1.1 沈腾参演电影数据获取 文章目录 数据可视化&分析实战前言1. 网页分析2. 构建数据获取函数2.1 网页数据获取函数2.2 网页照片获取函数 3. 获取参演影视作品基本数据4. 电影详细数据获取4.1 导演、演员、描述、类型、投票人数、评分信息、电影海…

e6zzseo:外贸独立站怎么推广

外贸独立站的推广需要一系列综合性的策略和方法&#xff0c;以吸引目标市场的访问者&#xff0c;并将他们转化为潜在客户。以下是一些推广外贸独立站的建议&#xff1a; 1. 搜索引擎优化&#xff08;SEO&#xff09;&#xff1a; e6zzseo认为优化网站可以适应搜索引擎的要求&a…