文本查找
将编辑器内的文本提取出来,使用正则匹配出搜索内容的位置,然后标记上即可。
const reg = new RegExp('搜索的内容', 'g')
const text = quill.getText()
let matches = [...text.matchAll(reg)]
if (matches.length > 0) {
const positions = matches.map(match => match.index)
// 标记查找的内容
// 通过位置索引,将该位置的内容背景颜色进行更改
}
(1)正则表达式问题
const reg = new RegExp('搜索的内容', 'g')
,这样子写有个问题,如果搜索的内容包含+
等正则匹配规则相关的字符时,查询会出现问题。因为new RegExp
第一个参数为字符串时,则会按照字符串模式进行构造,其中会将+ . *
等符号作为正则匹配规则解析出来,而不会仅视为一个字符。
解决方法,将特殊符号进行转义
'搜索的内容'.replace(/[.*+?^${}()|[]\]/g, '\$&')
(2)getText问题
在quill编辑器中,getText只获取文本信息。但编辑器中可能会包含图片、视频等元素,在位置索引时,这些元素会额外占用一个索引位置。如下图,文字4
的记录索引为3而实际索引为4。
因此需要进行额外的处理。
在quill编辑器中,采取了Delta
的格式存储文本的内容和样式。
[
{ insert: 'Gandalf', attributes: { bold: true } },
{ insert: ' the ' },
{ insert: 'Grey', attributes: { color: '#cccccc' } }
]
如果内容是图片、视频等非文字元素,其存储的Delta块中,insert
中对应的不是字符串。因此,可以通过quill编辑器的getContents
方法,获取编辑器中的所有Delta块,逐个Delta块判断是否为字符串,若不是,可以取巧地将其替换为\n
字符串,重新将文本内容进行拼接,得到一个二次处理的全文文本内容。
如此,在记录查询内容位置的时候,便可以使用\n
对图片、视频等元素进行占位,使得内容索引准确。
(3)标记查找的内容
看了一下quill的源码,发现它的getFormat
方法,它只会获取第一个Blot元素的样式。
let formatsArr = [lines, leaves].map(function(blots) {
if (blots.length === 0) return {};
let formats = bubbleFormats(blots.shift());
while (Object.keys(formats).length > 0) {
let blot = blots.shift();
if (blot == null) return formats;
formats = combineFormats(bubbleFormats(blot), formats);
}
return formats;
});
这会导致一个问题,如果需要标记的内容为多个Blot元素,那么后续的Blot的样式将会无法获取。
为了解决这个问题,需要逐个Blot存储样式,恢复样式时,再逐个恢复回去。
文本替换
这个根据需要替换元素的索引,进行对应的文本删除以及被替换文本的插入即可。
(1)多个文本替换问题
多个查找内容进行替换的时候,需要注意,替换的时候,后续需要替换的内容的位置需要进行更新。
如文本:你好啊,你好!
。
将你好
替换为你们好
,在替换第二个你好
的时候,该你好
的位置索引需要进行更新+1。
任务切片进行优化
文本查找与替换时,会进行元素的标记与取消标记,而当文本篇幅很大时或者查找到的元素很多时,频繁地调用getFormat
、formatText
进行样式获取以及设置,会导致页面的严重卡顿。
检查一番,发现与浏览器的事件循环机制相关。
事件循环执行流程
- 执行栈选择队列中的第一个宏任务,执行它的同步代码直至结束。
- 检查是否有微任务,若有,则执行所有队列中的微任务。
- 渲染页面。
- 开始下一轮的事件循环,执行宏任务中的异步代码。
宏任务与微任务
- 宏任务,一般由浏览器或Node.js发起,包括了setTimeout、setInterval、I/O操作等。
- 微任务,一般由JavaScript发起,包括了Promise的回调函数、nextTick函数等。
任务切片
任务切片是将运行时间长的任务,拆分为多个运行时间短的任务。
function test() {
for(let i=0;i<100000;i++){
console.log(i)
}
}
document.getElementsByTagName("button")[0].addEventListener("click",test)
在其中,会发现,该任务占用了较长的时间,且该时间内难以进行其它的交互,页面也很卡顿。因为事件循环一直在执行长任务。
这和前面说的卡顿是一致的,当我们调用的样式设置函数执行过多次时:
- 事件循环因为宏任务还未执行完成,用户难以进行其它的交互操作,造成卡顿
- 同样的,样式的更新,即浏览器新页面的渲染也会因为此而延迟,可能会导致等到所有函数调用完后,才一次性进行页面的渲染。
那么,我们可以考虑使用setTimeout
实现任务的切片。
// 使用setTimeout将其拆分为多个任务即可
如此,发现任务被拆分为多个小任务,同时页面的交互也不显得卡顿。
回到前面说的quill编辑器问题,尝试将setTimeout加入其中,发现渲染卡顿问题基本解决了。
requestAnimationFrame
除此之外,也可以使用requestAnimationFrame
这个函数来替代setTimeout
方法。
- 调用
requestAnimationFrame
,可以告诉浏览器希望执行一个动画,并要求浏览器在下次重绘之前调用指定的回调函数来更新动画。 - 需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
- 如果希望在浏览器下一次重绘之前继续更新下一帧动画,回调函数必须再次调用
requestAnimationFrame()
。这个方法是一次性的,适用于准备更新屏幕动画的场景。
前面提到的重绘等浏览器渲染相关的内容,这篇文章有详细的说明:juejin.cn/post/684490…