图片占位符
需求:tinymce 编辑器中需要一个图片占位符,上面展示一些配置项,由后端根据配置项完成图片替换,前端可双击弹框修改配置项。
思路
- 绘制文字:使用 CanvasRenderingContext2.fillText() 绘制文本,使用 CanvasRenderingContext2D.measureText() 来判断一行长文字何时换行展示。
- 保存配置项数据:使用 data-json 自定义属性保存配置项,前端根据 data-json 动态生成图片占位符的 base64 替换页面中图片占位符的 src 属性;后端可根据 data-json 生成真实数据,或修改图片占位符。
- 双击修改:监听双击事件,过滤图片占位符,从 data-json 中取出数据回填到弹框中;每个图片占位符绑定一个 mceId,用于修改时回填配置项数据和重新生成 base64。
文本占位符
需求
用户在弹框中选择选项,确认后将在 tinymce 编辑器中插入一张图片占位符,上面展示刚才选择的选项,有后端根据配置项完成图片替换,前端可双击弹框修改配置项。
图片占位符上展示”标题” 和一行文字。例如 @{xxx/yyy/zzz}
实现
参考 canvas 文本绘制自动换行、字间距、竖排等实现:将文本分割为字符串,逐渐加字并测量长度,超过指定宽度时换行绘制。
写的过程中发现 MDN fillText 的中文翻译有歧义:fillText 参数 y 的中文描述歧义。good first issue 参与大型开源项目了,属于是 : )
1 | /** |
粘贴占位符
场景: 用户可能会复制、剪切、粘贴生成的图片占位符,若直接复制粘贴,tinymce 中会有两个一样的 mceId,无法完成双击修改配置项。
万幸!TinyMce 提供了粘贴处理事件 paste_postprocess,在这里检查粘贴的 mceId 是否已存在,已存在则更新。更巧的是,此时 tinymce 内容是复制/剪切后的,即剪切(ctrl+x)时原内容已去除了,直接粘贴即可。
paste_postprocess
This option enables you to modify the pasted content before it gets inserted into the editor, but after it’s been parsed into a DOM structure.
1 | /** 粘贴事件:粘贴的元素id唯一 */ |
1 | /** 业务工具 */ |
一行 n 个
需求变了:用户在系统弹框中选择了一些配置项,请求后端接口返回一系列配置项,要求展示这些图片占位符并且可以自定义一行展示几个(一行可选个数:1、2、3、4)。
- 批量生成图片占位符:循环生成即可。
- 一行 n 个:这些图片占位符外层套一层 grid 布局;由于 tinymce 宽度是确定的,可根据每行展示个数确认图片占位符的宽度,并修改字体大小以
美观不丑陋。
外层的 grid 布局会影响到输入正常内容,如在 grid 内部回车换行后输入新内容,则新行也存在 grid 样式,十分冗余难受。
可监听 tinymce 的 NewBlock 事件,以处理新行样式。
1 | // grid 样式 |
1 | // 图片大小 |
表单占位符
需求
在图片占位符上展示更多的信息,最好能把自定义弹框里的选项都展示出来,文字拼接展示太丑了。
设计与实现
第三方库 Fabric.js 有文字换行功能,拿来就用!
让后端把配置项整理成数组形式 [{label, content}, {label, content}]
1 | import {fabric} from 'fabric' |
异步优化
前置场景
系统提供了文章预览功能,展示一系列的文章标题,鼠标悬浮时展示文章内容(文章内放入图片也要展示)。
方案是:前端直接 v-html 展示算了,正好 tinymce 保存的就是 html 格式,请求到数据后生成图片占位符再塞到 v-html 中。
1 | // 请求文章列表 |
1 | renderContentImgHolder(item) { |
异步优化
有几篇文章插入了上百个图片占位符,回显单个文章有卡顿感,展示文章缩略图时甚至能卡黑屏。
测试项目中3.2方案 191 张图片占位符用了 7233ms,cpu 一下子就满了。
那就改成异步的:加载时先展示空白图片占位符(生成一次url, 不耗时),在 worker 线程中批量生成图片占位符(避免页面卡顿),在主线程中接收占位符的 url 并回填(使用 mceId 查找元素,替换 src)。Fabric 不支持在 worker 中,只能自己用 OffscreenCanvas 写。
页面中将数据发送给 worker 处理,监听自定义事件处理数据回填即可。
下附生成占位符的性能对比 (100 张图片)
| 方案 | 轮次 | 耗时(ms) | 平均耗时(ms) |
|---|---|---|---|
| OffscreenCanvas (await) | 1 | 519 | 538 |
| … | 2 | 547 | |
| … | 3 | 549 | |
| OffscreenCanvas (promise) | 1 | 510 | 513 |
| … | 2 | 518 | |
| … | 3 | 513 | |
| Canvas | 1 | 419 | 421 |
| … | 2 | 422 | |
| … | 3 | 422 | |
| fabric | 1 | 1589 | 1571 |
| … | 2 | 1541 | |
| … | 3 | 1584 |
测试程序
1 | import { TinyCanvas } from "./../lib/main"; |
图片表单
1 | interface ITinyCanvasOptions { |
worker.js
1 | /* eslint-disable @typescript-eslint/naming-convention */ |