十八、编辑器组件图层面板功能开发
1、前言
* 引言
- 欢迎大家回到B端项目的开发,这周让我们着眼于编辑器右侧设置部分的编码工作,完成组件属性设
置,图层设置,以及页面设置的一系列功能。为了不让大家陷入业务的泥潭中,会挑选典型的几个
业务和知识点进行讲述和开发。
* 将收获什么
- 裁剪图片的实现
~ 借助阿里云OSS的图片处理
~ 重新获取裁剪的图片数据并且重新上传
- 创建Vue3钩子函数的原则
- 拖动排序的实现原理
- 复杂正则表达式的分析过程
* 主要内容
- 使用Cropper.js完成图片裁剪功能
- 创建LayerList组件以及完成InlineEdit组件的编码
- 自研一个简单的列表拖动方案,了解原理后,然后使用Vue Draggble Next进行替换
- 完成EditGroup的编码,了解使用伪代码分析问题的过程
- 完成BackgroundProcesser组件,学习正则表达式的分析过程
* 学习方案
- 先了解原理,自己简单实现,然后可以使用成熟的第三方工具
- 使用伪代码或者趁手的第三方工具,帮助完成复杂的数据转换的分析
2、选择图片裁剪工具
* 寻找合适开源库的经验之谈
- 使用google或者github来搜索
- 使用英文作为关键词
* 考量一个开源库是否符合标准
- star数量
- issue数量
- releases活跃度
- 查看它的DEMO
3、使用Cropper.js获取裁剪图片数据
* 实例上的一个方法-getCroppedCanvas
- https://github.com/fengyuanchen/cropperjs/blob/master/README.md#getcroppedcanvasoptions
* HTMLCanvasElement
- https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement
########
const c = document.getElementById("myCanvas");
const ctx = c.getContext("2d");
ctx.fillStyle = "#FF0000";
ctx.fillRect(0, 0, 150, 75);
// https://www.runoob.com/try/try.php?
filename = tryhtml5_canvas_first
########
* 两个有用的方法
- toDataURL-返回base64编码的URL
- toBlob-File继承Blob,const formData=new FormData();
formData.append(name,value,filename),当value为Blob,filename默认'blob'
当value为File,filename为文件名称
4、图层属性需求分析
* 图层锁定和隐藏/显示以及点击选中
- 在editor.ts的store中的components添加更多标识符
########
{
...
isLocked: boolean;
isHidden: boolean;
}
########
- 点击按钮切换为不同的值,使用这个值在页面上做判断。
- 点击选中,设置currentElement的值
* 图片名称编辑
- 添加更多属性-layerName
- 点击图层名称的时候,在input和普通标签之间切换。
- 添加按钮响应-对于esc和enter键的响应
~ 可能抽象一个通用的hooks函数-useKeyPress
- 点击到input外部区域的响应
~ 可能抽象一个通用的hooks函数-useClickOutside
* 拖动改变顺序
- 最有难度的一个需求,涉及到一个较复杂的交互
- 最终目的其实就是改变store中components数组的顺序
- 在这块功能进行编码的时候,再开始详细的分析
5、列表排序的演示和需求分析
* 新的学习方法
- 用手写简单的方法实现一个功能。
- 然后使用比较成熟的第三方解决方案。
- 技能学习原理又能学习第三方库的使用。
* 从两个DEMO开始
- Vue Draggable Next:https://sortablejs.github.io/vue.draggable.next/#/simple
- React Sortable HOC:https://clauderic.github.io/react-sortable-hoc/
* 列表排序的三个阶段
- 拖动开始(dragstart)
~ 被拖动图层的状态变化
~ 会出现一个浮层
- 拖动进行中(dragmove)
~ 浮层会随着鼠标移动
~ 条目发生**换位**:当浮层下沿超过被拖动条目二分之一的时候,触发换位
- 松开鼠标阶段(drop)
~ 浮层消失
~ 被拖动图层状态复原
~ 数据被更新
6、拖动排序功能开发
* 第一阶段Dragstart
- 被拖动图层的状态变化,常规做法
~ 添加mouseDown事件,检查当前的target是哪个元素,然后给他添
加特定的状态
~ 添加mouseMove事件,创建一个和被拖动元素一模一样的浮层,将
它的定位设置为绝对定位,并且随着鼠标的坐标更新。
- 使用HTML的Drag特性
~ 文档地址:https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations
~ 浏览器的默认拖拽行为:支持图像,链接和选择的文本。
~ 其他元素默认情况是不可拖拽的。
~ 如果想可以拖拽可以设置为draggable='true'
~ 使用dragstart事件监控拖动开始,并设置对应属性。
7、本周总结
* 过程回顾和要点
- 图片裁剪
~ 选择Cropper.js作为基本库
~ 初始化Cropper区域,并且通过事件拿到对应坐标
~ 第一种方式:使用阿里云OSS完成图片裁剪功能
~ 第二种方式:使用Cropper.js获取图片数据
~ getCroppedCanvas()->toBlob()->重新上传Blob对象
- 图层列表功能
~ 隐藏和锁定-画布元素的属性设计
~ InlineEdit组件
~ 测试驱动开发
~ 添加常用的钩子函数-useKeyPress//useClickOutside
~ 界面上测试并且带动优化
- 图层列表拖动排序的原理
~ 拖拽排序四部曲
设置draggable属性(浏览器帮你实现拖动效果)
监控dragstart事件(添加拖动开始时状态)
监控dragover事件(在特定时刻完成数据交互)
监控drp事件(恢复到初始状态)
~ 使用第三方库Vue Draggable Next实现排序
- EditGroup属性分组组件开发
~ 对于复杂的问题,先使用流程图和伪代码理清关系再开始动手
- 页面设置面板开发
~ 正则的分析和好帮手:https://regexr.com/
~ 属性修改表单组件的开发原则
十九、让元素动起来-编辑器画布交互功能开发
1、前言
* 引言
- 对于一个可视化编辑器,用户的易用性和交互在我看来非常重要,也是整个前端的难点。
这些内容,我觉得就是面试问到的或者简历里面可以写的,**在工作的项目中,你做了什么有挑战
性的工作**的完美答案,所以在以后的求职生涯中,如果要体现亮点,你不妨可以试试把这一周的
内容和经历写在里面。
* 将收获什么
- 拖拽移动图层的实现
- 拖拽改变图层大小的实现
- 复杂快捷键的实现
~ Hotkeys.js
~ 插件的概念
- 撤销和重做的实现
~ 单元测试
~ 函数截流
~ 等等很多优化的点
- 右键菜单的实现
~ 函数式创建组件的方式
* 学习方法
- 对于元素的交互操作原理,可以使用画图的方式来帮助你
- 对于Vuex的数据操作,不要忘记单元测试。
2、快捷键操作的需求
* **元素选择**前提都是在元素被选中的情况下
- 拷贝图层-⌘C/Ctrl+C:新建当前选择的元素的一个数据结构
- 粘贴图层-⌘V/Ctrl+V:将新建的元素添加到components数组中
- 删除图层-Backspace/Delete:在components数组中删除选择的元素
- 取消选中-ESC:currentElement设置为空
* **元素移动**
- 上下左右移动-像素-↑↓→←:更新选中元素props的top/left值
- 上下左右移动+像素-Shift+↑↓→←:更新选中元素props的top/left值
* **撤销/重做**
- 撤销-⌘Z/Ctrl+Z
- 重做-⌘⇧Z/Ctrl+Shift+Z
* 好用的按键响应库-HotKeys.js
- 项目地址:https://github.com/jaywcjlove/hotkeys
- 演示地址:https://wangchujiang.com/hotkeys/
3、回滚和重做-可以优化的点
* 功能开发演化过程
- 需求分析
~ 全量保存
~ 只保存修改的数据
~ 需要什么类型的数据(新增/删除/修改)
- 基本功能第一步-操作以后保存特定类型的数据
- 基本功能第二步-设置数据操作的指针,undo往前移动,并且对应不同数据做相反操作
- 基本功能第三部-添加测试,验证基本功能的运行
- 界面测试-提出改进意见
- 进阶功能-一次操作可以回退多个值(移动/修改大小)
~ 改进updateComponent这个mutation,让它支持多个值的更新
~ 改进undo,传入数组,可以完成多个值的回滚
- 进阶功能-支持添加记录的函数防抖
~ 主题简单的debounce函数
~ 缓存一开始修改的旧的值
* 可能完成的优化
- 代码结构的优化
~ state结构的优化
~ 一些帮助函数位置的优化
- 添加单元测试
- 快捷键的添加
- 添加页面设置的回滚功能
- 看看有没有什么bug
* 思考一下
- 对于一个简单的编辑器,实现undo/redo有什么思路?
- 对于一个复杂的编辑器,比如vscode,实现这个功能有什么思路?
4、右键菜单
* 需求分析
- 在编辑器区域在元素上**点击右键**弹出一个自定义的菜单
- 生成的菜单中有一系列的操作
- 点击对应的选项完成操作,并且自定义菜单消失
* 实现方法
- 在中间编辑器区域拦截默认的右键点击事件
~ https://developer.mozilla.org/zh-CN/docs/Web/API/Element/contextmenu_event
- 判断是否点击在组件元素上(判断鼠标事件的e.target是否在EditWrapper中)
- 显示(display:block)一个自定义菜单,其中包括操作项,显示在鼠标位置(使用e.clientX
和e.clientY)
- 点击完成操作-重用快捷键已经支持的mutation,并且关闭自定义菜单(display:none)
5、本周总结
* 过程回顾和要点
- 元素拖拽移动
~ 原理:修改top,left值
~ 注意计算gap值即可,鼠标在元素内部的偏移量
- 元素拖拽改变大小
~ 原理:修改width,height(有可能修改top,left)
~ 注意不同的位置,计算方式的不同,(鼠标位置和元素位置的关系)
- 快捷键的实现
~ 原理:在按下特定按键的时候触发对应的mutations
~ hotkeys.js作为第三方库
~ plugins插件的概念
- 撤销和重做
~ 历史记录的需求以及方案设计
~ 单元测试测试基本操作
~ 在界面上操作并发现可以优化的点
~ updateComponents一次可以更新多个属性和值
~ 函数截流的实现
- 右键菜单
~ contextmenu事件
~ 使用render函数将组件挂载到对应的节点上
~ 使其插件化
* 剩下的一些工作
- 一些快捷键的补充
- 一些单元测试的补充
- 右键菜单的一些条目的补充
- 代码的一些优化工作(精简state,以及提取通用函数到别的文件)
二十、前后端结合-编辑器整合后端接口
1、前言
* 引言
- 这周我们要正式和后端打交道,让我们的作品数据可以持久化的保存在数据库中。并且针对SPA项
目的一些前后端分离开发的通用难点和痛点进行攻坚。
* 完成的功能
- 前后端分离开发,以及分析接口的功能和需求
- 前端mock server的实现
~ 快速搭建
~ 自定义接口和返回数据
~ 使用JWT完成路由保护
- 使用AForm完成表单验证的以及源代码解析
- 应用通用状态的添加
~ 全局Loading状态
~ 细粒度的Loading状态
~ 全局错误状态
- 实现登陆过程以及登陆状态的持久化
- 路由权限验证
- 编辑器作品操作
~ 获取作品
~ 保存作品
点击保存
自动保存
未保存跳转前提示
* 知识点
- Vuex action
- JSON server
- Ant Design Vue
~ AForm
~ AFormItem
~ useForm
- axios拦截器
- JWT
- vue router的钩子函数
~ beforeEach
~ beforeRouteLeave
- **高阶函数!!**
* 提供的测试接口
- **http://182.92.168.192:8081/**
2、前后端分离开发
* 简单描述下过程
- 定制规范
~ RESTful协议
~ 将其文档化-https://www.yuque.com/docs/share/4c353b03-5973-440b-9041-b7edbef5e6f6?#
~ 使其版本化-每次升级以后,可以清楚的查看变动,修改-https://shopify.dev/concepts/about-apis/versioning/release-notes
~ 使用工具进行实时查看和运行
Postman:https://www.postman.com/
Swagger:https://swagger.io/
- 开发阶段-前端
~ 使用mock server
~ 并行开发,不依赖后端服务器,提升效率
- 连调阶段
~ 前端直接切换为后端的接口,进行检验
* 简单说一下这种方式的优点
- 打造精益团队
- 提升开发效率
- 增强代码的可维护性
3、需求以及接口分析
* 两类接口
- 需要特殊权限才能访问的(比如编辑器中的操作)
- 不需要权限可以直接访问的(比如获取热门海报)
* 获取权限的接口(用户)
- 我们使用手机号+验证码的方式来登陆
- 获取验证码接口-POST /users/genVeriCode
- 登陆接口-获取token-POST /users/loginByPhoneNumber
- 使用token访问需要权限的接口-比如获取当前用户信息- GET /api/users/getUserInfo
~ 使用authorization Header-添加Bearer+token信息
* 编辑器操作作品接口,需要权限
- 新建作品-POST /works
- 查询作品-GET /works
- 保存作品-PATCH /works/${id}
- 发布作品-POST /works/publish/${id}
4、Mock server的选择和搭建
* 引言
- 是否需要选用express,koa,egg.js等等成熟的框架?
* 好用的Mock Server需要有的特点
- **快速搭建**
- 支持标准Restful操作和路由规则
~ /templates-拿全部数据
~ /templates/${id}-拿一条数据
- 一些进阶扩展-自定义路由,中间件等等
* 隆重退出JSON Server
- https://github.com/typicode/json-server#access-control-example
* 安装
########
npm install --save-dev json-server
########
* 启动
########
npx json-server --watch db.json
########
5、Token实现权限管理的简单原理
* **token是无状态的**,服务器上不需要记录任何信息。每个发送到服务器的请求都会带上一个
token,服务器利用这个token检查确认请求的真实性。
* **token的组成**通过特定的加密算法,将用户登录后的一些信息(比如用户Id)和过期时间等信
息存储在一个加密过的字符串中。
* 在JSON Web Token官网感受一下。https://jwt.io/
二十一、前后端结合-完成剩余的需求
1、前言
* 引言
- 我们的整个应用的大体结构都已经完成,剩下的页面有很多重复的工作,这种需求我并不会和大家
一起编码完成。这周的内容是从中找到一些我认为比较独特,大家在工作中比较少见的需求进行完
成,扩宽大家的知识面,并且学习几个工具的原理。
* 完成的功能
- 完成作品发布的流程
~ 对编辑区域进行截图
~ 截图完成重新上传文件
~ 上传完成保存作品并且创建渠道
- 发布后渠道窗口的功能
~ 管理渠道-创建和删除渠道
~ 使用钩子函数整合发布流程-usePublishWork
~ 将渠道链接生成二维码
- 拷贝文本到剪贴板的功能
- 一个钩子函数的进化过程
~ 首页加载更多
~ 作品页实现翻页
- 下载文件的原理以及编码过程
* 功能点
- html2canvas
~ 基本使用
~ 踩坑之旅
- qrcode-生成二维码
- Clipboard.js-复制文本到剪切板
~ 基本使用
~ 原理
- useLoadMore-钩子函数的进化之旅
~ 支持点击加载更多
~ 支持无限滚动
~ 支持分页
- 前端下载文件的原理
~ 同域文件的下载
~ 跨域文件的下载
~ FileSaver.js的使用和原理
2、关于widnow.devicePixelRation
* 设备像素/物理像素
- 设备像素也被称为**物理**像素,他是显示设备中一个最微小的物理部件。在同一设备中,物理像素
的总数是固定的。
* 独立像素/css像素
- css像素是一个抽象的单位,主要使用在浏览器上,用来精确的度量(确定)Web页面上的内容。
- css像素被称为与设备无关的像素(device-independent像素),简称为“DIPs”。
- 在一个标准的显示密度下,一个css像素对应着一个设备像素。
* window.devicePixelRation
- https://developer.mozilla.org/zh-CN/docs/Web/API/Window/devicePixelRatio
- 返回当前显示设备的物理像素分辨率与css像素分辨率之比。简单来说,它告诉浏览器应使用多少
屏幕实际像素来绘制单个css像素。
- 所以在标准屏幕下,devicePixelRatio应该为**1**
* 特例
- 视网膜(Retina)显示屏,它会使用更多的屏幕像素绘制相同的对象,从而获得更清晰的图像。
devicePixelRatio为**2**。
- 所以虽然我们中间的元素css尺寸是375px,但是因为Apple是视网膜屏幕,所以使用了两倍于
css尺寸的设备像素来渲染它,这就是最后图片尺寸为750px的原因。
* html2canvas的处理
- https://html2canvas.hertzen.com/configuration/中的scale属性
3、HTML2Canvas截图的原理
* 目的:一个canvas元素,上面有绘制有一系列的HTML节点
* 局限:Canvas中没法添加具体的HTML节点,它只是一张画布
- 通过canvas.getContext("2d")可以拿到canvas提供的2D渲染上下文,然后在里面绘制形状,文
本,图像和其他对象。
- 文档地址:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D
~ 矩形-fillRect()
~ 文本-fillText()
~ 图像-drawImage()
~ 等等...
* SVG来拯救我们
- 可缩放矢量图形(Scalable Vector Graphics,SVG),是一种用于描述二维的矢量图形,基于XML
的标记语言。
* SVG中有一个神奇的元素称之为foreignObject
- 文档地址:https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/foreignObject
- foreignObject元素允许包含来自不同的XML命名空间的元素。在浏览器的上下文中,很可能是
XHTML/HTML
* 解题思路
- 创建一个canvas元素
- 创建svg文件,使用Blob构造函数
- 将svg中的值填充foreignObject,然后填充想要复制节点的HTML
- 创建image标签,将image.src = URL.createObjectURL(svg)
- 在image完成读取以后,调用canvas的drawImage方法,将图片绘制到画布上。
4、复制到剪贴板的原理
* 看起来很简单的问题,但是由于不同浏览器之间存在不同的API实现和各种hack,所以它的实
现很混乱
- 方法一最现代的**Clipboard API**
~ 文档地址:https://developer.mozilla.org/zh-CN/docs/Web/API/Clipboard_API
~ 还在working draft阶段,浏览器兼容性有待加强。
- 方案二document.execCommand()方法
~ 文档地址:https://developer.mozilla.org/zh-CN/docs/Web/API/Document/execCommand
~ 它不仅仅是解决复制的场景,而且是给可编辑区域的提供一系列功能
* document.execCommand('copy')解决思路分析
- 手动创建可编辑元素,比如textArea,然后将要拷贝的值设置为它的value
- 将它插入到页面中,调用textArea上的方法,对值进行选中
- 然后再调用document.execCommand('copy')
- 特别注意textArea要不可见,使用特殊的样式让它不出现在可见区域
- 最后要将textArea节点删除
5、分析B端剩余的需求
* 引言
- 剩余的需求难度并不大,我们解决了通用问题以后,剩下的需求只是罗列功能,发送异步请求而已
* 首页
- 显示templates列表(已完成)
- 点击加载更多(可否抽象成一个Hooks函数?)
~ 对应接口:/api/templates?pageIndex=${page}&pageSize=${size}&title=${title}
- 点击搜索
~ 对应接口:/api/templates?pageIndex=${page}&pageSize=${size}&title=${title}
- 显示最新的我的作品 -> 可以复用作品详情页
* 作品详情页
- 点击创建
~ 对应接口:POST /api/works/copy/${id}
~ 下载图片
* 我的作品
- 搜索
- 对应接口(我的模板):/api/works?pageIndex=${page}&pageSize=${size}&title=${title}&isTemplate=1
- 对应接口(我的作品):/api/works?pageIndex=${page}&pageSize=${size}&title=${title}&isTemplate=0
- 分页 -> ant-design-vue的pagination组件
- 上面有一系列操作
~ 删除:DELETE /api/works/${id}
~ 复制:POST /api/works/copy/${id}
~ 转赠:POST /api/works/transfer/${id}/${cellphone}
~ 下载图片
6、下载文件的原理
* A链接
- 可以创建通向其他网页、文件、同一页面内的位置、电子邮件地址或任何其他URL的超链接。
* A链接的一个特殊属性:download
- 文档地址:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/a
- 此属性指示浏览器下载URL而不是导航到它,因此将提示用户将其保存为本地文件
* A链接的另外一个特殊属性:rel
- 该属性指定了目标对象到链接对象的关系。
- noopener一个重要的属性,对于web安全来说非常关键。
- 当你使用target='_blank'打开一个新的标签页时,新页面的window对象上有一个属性opener,
它指向的是前一个页面的window对象,因此,后一个页面就获得了前一个页面的控制权
* 我们模拟这个过程来完成下载。
- 创建A链接
- 设置href以及download属性
- 触发A链接的点击事件
* download属性仅适用于同源URL
* 使用FileSaver.js完成下载
- 文档地址:https://github.com/eligrey/FileSaver.js/
* 借助HTTP特殊的响应头出发浏览器自动下载
- 文档地址:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Disposition
- Content-Disposition-最佳的下载方式,需要服务器端的支持,并且不需要任何的Javascript,
需要在HTTP头部添加
########
Content-Type: 'application/octet-stream; charset=utf-8'
Content-Disposition: attachment; filename="filename.jpg";
########
7、本周总结
* 过程回顾和要点
- html2canvas-DOM节点截图工具
~ 基本使用
~ 使用过程的一些坑(不支持box-shadow,图片要支持跨域访问)
~ html2canvas基本原理
使用svg的foreignObject标签插入对应的DOM元素,并且创建文件
创建image,使用对应的svg文件,并且使用canvas.getContext('2d').drawImage()方法
- qrcode-生成二维码
~ 基本操作
~ 特别注意:watch在reactive数组上的问题,怎样获得新的值和旧的值
- clipboard.js-实现内容的拷贝到剪贴板
~ 基本使用
~ clipboard.js的原理
使用现代的ClipboardAPI-navigator.clipboard.writeText
使用兼容性更好的document.execCommand('copy')
创建textarea节点,设置它的位置不可见
设置它的value,调用select选中,再调用docuemnt.execCommand('copy')
- useLoadMore改造
~ 实现基本功能支持加载更多和无限滚动
~ 添加上一页,下一页功能
~ 实现分页选取的功能
- FileSaver.js-浏览器端下载文件的方式
~ 浏览器下载文件的原理
同源:使用A链接的download属性
跨域:发送异步请求,返回Blob,将Blob存储在URL.createObjectURL()中,然后点击下载
~ 服务器端下载文件的原理
使用content-disposition响应头,并且将值设置为attachment;filename="filename.jpg"
~ FileSaver.js解析
简单实用
源码解析-添加更多的polyfill代码-比如使用msSaveOrOpenBlob或FileReader的方法