张家港安监站网址,app在地区国家未提供怎么办,商业店铺设计,flash做ppt的模板下载网站目录 前言一、效果展示二、实现步骤1. 复制2. 删除3. 锁定4. 层叠顺序 三、实现过程中发现的bug1. clone方法不复制自定义属性2. 复制「锁定」状态的对象#xff0c;得到的新对象也是「锁定」状态 四、Show u the code后记 前言
上一篇博文中#xff0c;我们细致的讲解了实现… 目录 前言一、效果展示二、实现步骤1. 复制2. 删除3. 锁定4. 层叠顺序 三、实现过程中发现的bug1. clone方法不复制自定义属性2. 复制「锁定」状态的对象得到的新对象也是「锁定」状态 四、Show u the code后记 前言
上一篇博文中我们细致的讲解了实现文字的加粗、斜体、下划线、删除线这些功能时遇到的Bug以及优化点。
这篇博文是《前端canvas项目实战——在线图文编辑器》付费专栏系列博文的第八篇——复制、删除、锁定、层叠顺序主要的内容有
实现一组通用的功能按钮复制、删除、锁定和层叠顺序用户可以通过点击这些按钮来对画布中的对象进行
复制: 复制选中的对象并将新对象添加到画布上。删除: 删除选中的对象。锁定: 使对象不可以被拖拽移动位置、不可以通过控制点来进行缩放、不可以旋转等。层叠顺序: 更改对象在z轴上的顺序处于上层的对象会遮盖住下层的对象。
如有需要你可以 点击这里返回第一篇《前端canvas项目实战——在线图文编辑器(一)——左侧工具栏》点击这里返回上一篇《前端canvas项目实战——在线图文编辑器(七)加粗、斜体、下划线、删除线下》 一、效果展示 动手体验 CodeSandbox会自动对代码进行编译并提供地址以供体验代码效果 由于CSDN的链接跳转有问题会导致页面无法工作请复制以下链接在浏览器打开 https://fjf3h6.csb.app/ 动态效果演示 二、实现步骤
1. 复制
「复制」是一个常用的功能。比如我们创建一个简历时设置好了一个文本框的字体、字号、颜色等属性此时如果想要再创建一个相同属性但文字不同的文本框可以有以下两种实现方式
点击左侧工具栏生成一个默认的文本框然后依次设置字体、字号、颜色等属性然后修改文字。复制这个文本框然后修改文字。
显然「复制」是一个非常便捷的功能省去了使用者很多重复的点击和操作。
前文中的动态图已经展示了这个按钮的功能就不再做图示。以下是代码
import store from ../modules/store;const cloneActiveObjects () {const {canvas} store.getState();const activeObject canvas.getActiveObject();const handleCloneObject (newObject) {// Bug点1for (const key in activeObject) {if (activeObject.hasOwnProperty(key) !newObject.hasOwnProperty(key) typeof activeObject[key] ! function) {newObject.set(key, activeObject[key]);}}// Bug点2lockUnlockObject(newObject, false);canvas.add(newObject);newObject.set({left: activeObject.left 25, top: activeObject.top 25});canvas.setActiveObject(newObject);canvas.renderAll();};activeObject.clone((newObject) handleCloneObject(newObject));
};以上是复制按钮的点击事件处理方法代码逻辑比较清晰以下做简要的说明
获取当前选中的对象: 从中央数据仓库取得因此使这个方法不需要入参。复制选中的对象: 通过fabric.Object原生的clone方法复制对象。复制对象之后的动作: 在回调方法handleCloneObject中我们 首先将「原对象」中的所有属性设置给「新对象」这里是一个Bug点下文中会详细讲解。然后解锁「新对象」。无论「原对象」是否锁定状态新复制出来的对象都应该是非锁定的。将「新对象」添加到画布中并移动到「原对象」右下角一定距离这里设置为25像素。将「新对象」设置为画布中当前选中的对象。
2. 删除
「删除」即从画布中移除当前选中的对象代码如下
const deleteActiveObjects () {const {canvas} store.getState();const activeObject canvas.getActiveObject();canvas.remove(activeObject);canvas.discardActiveObject();canvas.renderAll();
};以上是点击「删除」按钮后的事件处理方法代码逻辑分为3个部分
获取当前选中的对象: 从中央数据仓库取得因此使这个方法不需要入参。移除选中的对象: 通过fabric.Canvas原生的remove方法移除对象。画布丢弃当前选中对象: 调用canvas.discardActiveObject()方法使canvas将当前选中的对象置为空即表示当前画布中没有选中的对象。
3. 锁定
「锁定」是一个逻辑上的功能我们首先要定义当用户点击这个按钮时我们应该锁住哪些操作
起初我定义一个「被锁定的对象」是一个除了「解锁」之外不可以进行任何编辑操作的对象。但在实现过程中发现这样的定义不合理且实现起来十分复杂。原因如下 不合理性: 一般意义上「锁定」功能只是锁住一个对象的位移、缩放等操作。如果用户拿到一个别人设计好的精美的简历模板想要通过替换其中的文字来快速制作自己的简历那TA需要进行的操作有 逐个解锁Textbox、Image等对象修改各个对象的文本、图片等内容锁定这些对象避免误操作使其发生位移或缩放影响简历的美观。 可见这样的定义会使用户徒增「加锁」和「解锁」的操作增加操作的复杂性。 实现中的困难: 根据这种定义当对象被锁定时需要逐个「屏蔽」用户可以对其进行的操作难免有遗漏且如果有新增的操作能力也需要同步添加「屏蔽」的能力。
基于这样的实践经验和思考我们将「被锁定的对象」定义为一个不能移动、不能被缩放的对象。
下面我们来实现它
// 部分控制点可见
const _fewControlsVisible {tl: false,tr: false,ml: true,mr: true,mt: false,mb: false,bl: false,br: false
};// 全部控制点可见
const _allControlsVisible {tl: true,tr: true,ml: true,mr: true,mt: true,mb: true,bl: true,br: true
};// 对象的控制点可见情况
const objectControlsVisibility {object: _allControlsVisible,rect: _allControlsVisible,circle: _allControlsVisible,activeSelection: _allControlsVisible,line: _fewControlsVisible,textbox: _fewControlsVisible,group: _fewControlsVisible
};const lockUnlockObject (object, locked) {object.set({lockMovementX: locked,lockMovementY: locked,lockRotation: locked,lockScalingX: locked,lockScalingY: locked,lockSkewingX: locked,lockSkewingY: locked,lockScalingFlip: locked,locked});// 根据锁定状态设置选择框的3个「自定义」控制点隐藏或显示object.setControlsVisibility({lock: locked,mtr: !locked,del: !locked});// 根据锁定状态设置选择框的8个「基础」控制点的隐藏或显示let controlsVisibility objectControlsVisibility[object.type] || objectControlsVisibility[object];let {tl, tr, ml, mr, mt, mb, bl, br} controlsVisibility;object.setControlsVisibility({tl: !locked tl,tr: !locked tr,ml: !locked ml,mr: !locked mr,mt: !locked mt,mb: !locked mb,bl: !locked bl,br: !locked br});
};const lockUnlockActiveObjects () {const {canvas} store.getState();const activeObject canvas.getActiveObject();const locked !(activeSelection.locked || false);// 设置选中的对象的锁定状态lockUnlockObject(activeObject, locked);canvas.renderAll();store.dispatch(Actions.updateActiveObjectProperty(locked, locked));
};以上是「锁定」按钮的点击事件处理方法代码比较多但是结构是清晰简洁的以下逐段进行介绍
objectControlsVisibility字典: 定义了fabric.js种不同的对象类型其选择框显示和隐藏的控制点设置其中Line线条和Textbox文本框只显示ml和mr两个控制点其他的对象都显示全部的控制点。 具体效果如下图所示 lockUnlockObject方法: 「锁定/解锁」一个对象需要经过以下3个步骤 设置对象属性: 通过设置对象的锁定相关的属性值为true或false使对象可以/不可以被移动、缩放、旋转、扭曲显示/隐藏3个自定义控制点: 根据对象的locked属性设置旋转、删除、锁定等3个自定义控制点隐藏或者显示。 locked为false时显示旋转和删除隐藏锁定locked为true时隐藏旋转和删除显示锁定。 显示/隐藏8个基础控制点: 根据对象的locked属性和上述的objectControlsVisibility字典设置8个基础控制点隐藏或者显示。 locked为false时仅显示当前对象类型可以显示的控制点隐藏其他控制点locked为true时隐藏所有8个基础控制点。 具体效果如下图所示 lockUnlockActiveObjects方法: 这个方法中获取了画布中当前选中的对象然后调用了上述的lockUnlockObject方法来 「加锁/解锁」 这个对象。
4. 层叠顺序
「层叠顺序」也称为z-index。即除了二维画布的x和y两个坐标轴外想象有一条从屏幕里穿出垂直于屏幕的坐标轴称作「z轴」。
当用户在画布中创建了多个对象时位置相近的对象间可能会互相遮挡。处在上层的对象会遮住处在下层的对象的部分或全部区域。
在画布中默认「后创建的对象」在z轴上高于「先创建的对象」。一般情况下我们不会一开始就想好所有对象的创建顺序然后依次创建它们。所以需要灵活得调整对象之间的层叠顺序。
那么我们来实现它 ...const zIndexProps {className: none,tip: 层叠顺序,menu: {items: [{key: toTop,icon: VerticalLeftOutlined style{{transform: rotate(-90deg)}}/,label: 移至顶层}, {key: up,icon: UpOutlined/,label: 向上一层}, {key: down,icon: DownOutlined/,label: 向下一层}, {key: toBottom,icon: VerticalRightOutlined style{{transform: rotate(-90deg)}}/,label: 移至底层}],onClick: adjustActiveObjectZIndex}};return (...SwitchValueButton {...zIndexProps}BlockOutlined classNameproperty-operation-img//SwitchValueButton...);...const adjustActiveObjectZIndex (selectedItem) {const {canvas} store.getState();const activeObject canvas.getActiveObject();if (activeObject) {if (selectedItem?.key toTop) {canvas.bringToFront(activeObject);} else if (selectedItem?.key up) {canvas.bringForward(activeObject);} else if (selectedItem?.key down) {canvas.sendBackwards(activeObject);} else {canvas.sendToBack(activeObject);}canvas.renderAll();}};代码逻辑很清晰下面我们分为两个部分来说明
视图部分: 这里和其他的按钮略有不同点击后会弹出一个下拉菜单。 我们传入了一个菜单项列表menu最后的onClick: adjustActiveObjectZIndex表示当菜单项被点击时响应的逻辑由adjustActiveObjectZIndex方法处理。逻辑部分: adjustActiveObjectZIndex方法的实现也很简洁根据用户点击的操作项的key来执行不同的操作 toTop: 置于顶层调用canvas的bringToFront方法up: 向上一层调用canvas的bringForward方法down: 向下一层调用canvas的sendBackwards方法toBottom: 置于底层调用canvas的sendToBack方法 三、实现过程中发现的bug
还记得前文中的handleCloneObject方法吗这个方法在我们实现复制功能时在新对象复制完成的回调方法中 ...const handleCloneObject (newObject) {// Bug点1clone方法不复制自定义属性for (const key in activeObject) {if (activeObject.hasOwnProperty(key) !newObject.hasOwnProperty(key) typeof activeObject[key] ! function) {newObject.set(key, activeObject[key]);}}// Bug点2复制「锁定」状态的对象得到的新对象也是「锁定」状态lockUnlockObject(newObject, false);...};这段代码包含了两个问题及其解决方案
1. clone方法不复制自定义属性
在实现的过程中我们对部分对象的属性进行了扩充。例如
fabric.Line线条对象的startPointType和endPointType: 为了实现线条的两个端点我们为它加上了这两个额外的属性。fabric.js原生的clone方法只会将默认的属性复制到新对象中这些我们后添加上去的属性则不处理。fabric.Object所有对象的locked是否锁定属性: 同理fabric.js原生的clone方法也不会把这个属性自动复制给「新对象」。
因此if判断条件的意思就是如果一个属性满足 「旧对象」有「新对象」没有且不是function就把这个属性赋值给「新对象」。
2. 复制「锁定」状态的对象得到的新对象也是「锁定」状态
在「复制」的代码中我们用以下方法限制了「新对象」的位置在「旧对象」右边25像素下边25像素 newObject.set({left: activeObject.left 25, top: activeObject.top 25});一般情况下用户会在复制出「新对象」后把它拖动到自己想要的位置。但如果「旧对象」是「锁定」状态我们就需要在复制完成后调用lockUnlockObject方法对「新对象」进行「解锁」。 四、Show u the code
按照惯例本节的完整代码我也托管在了CodeSandbox中点击前往查看完整代码 后记
这篇博文中我们实现一组通用的功能按钮复制、删除、锁定和层叠顺序。虽然是几个不算复杂的功能但也有很多细节方面的问题值得考量。
有了这些按钮会使用户在使用我们的编辑器时更加快捷、稳定得完成自己的需要。
如有需要你可以 点击这里返回第一篇《前端canvas项目实战——在线图文编辑器(一)——左侧工具栏》点击这里返回上一篇《前端canvas项目实战——在线图文编辑器(七)加粗、斜体、下划线、删除线下》