有道写作浏览器扩大作为一款为网页减少英文语法批改的辅助工具,容许用户在任意网页上绝大部分的富文本编辑器、多行文本输入框中编辑英文文本,可实时失去批改后果反馈,并自行承受倡议主动批改,实现完满写作 。
起源 / 有道技术团队公众号
作者 / 李靖雯
编辑 / 刘振宇
一、背景介绍
有道写作服务是有道出品的写作智能批改产品,为用户提供优质的作文拼写、语法、款式方面的批改服务。有道写作不仅仅反对浏览器扩大模式,还反对在其余平台应用:例如有道词典 APP- 作文批改、Web 在线端、Word 插件、PC 词典内。欢送各位体验。
http://write.youdao.com/
浏览器插件在浏览器外面的称说是 Browser Extension,也就是浏览器扩大,是一个扩大网页浏览器性能的插件。它次要基于 HTML、JavaScript、CSS 开发,同时因为是扩大个性,能够利用浏览器自身提供的底层 API 接口进行开发,能够给所用页面或者特定页面增加一些非凡性能而不影响本来页面逻辑。
每个反对扩大的浏览器有本人下载扩大的利用商店,能够间接在利用商店下载。有些产品本人提供浏览器扩大的 .crx 文件让用户下载并装置。
二、适配浏览器
有道写作在 Windows/Mac 零碎都可装置,适配 Chrome、360 平安浏览器、360 极速浏览器、Edge 新版浏览器等,在以上浏览器商店中搜寻有道写作,点击装置按钮即可。
三、性能介绍 & 成果展现
在介绍开发思路与实际之前,咱们先来直观地看一下有道写作浏览器扩大的实际效果,并对其性能进行简略的介绍。
3.1 体现形式
视觉效果就是,给谬误的文本字符上面画一条横线,在 hover 的时候,能够给文本减少一个高亮的成果。在选承受倡议的时候,能够替换成咱们想要的文本数据。
3.2 实用场景
>>> 在线邮件编辑:
163 邮箱
Outlook 邮箱
Gmail
>>> 社交动静、评论:
微博动静
评论
>>> 工具、笔记类:
有道翻译
Google 翻译
石墨文档
3.3 性能介绍
>>> 实时批改:
反对一边批改一边实时提供批改反馈,展现批改谬误数量。
>>> 语法检测:
![上传中 …]()
>>> 加强编辑框:
能够查看每一个谬误反馈具体内容,并可分谬误类型过滤查看后果。
>>> 承受倡议:
点击承受倡议时候替换正确文本。
![上传中 …]()
四、开发思路
需要:扩大须要针对页面上的可输出文本的编辑框赋予批改的性能
4.1 适配编辑器
那么,网页中可输出文本的编辑框都有哪些呢?
通常咱们常见可输出编辑框有:
- 基于 Web 的表单能够输出文本控件:input、textarea
<input value="123"/>
<textarea>123456</textarea>
- 可编辑属性的元素:contenteditable
<blockquote contenteditable="true">
<p>Edit this content to add your own quote</p>
</blockquote>
Input 元素通常是一行且输出范畴较短的内容,思考到批改交互的性能,咱们的扩大针对以下可输出较多文本的编辑器进行兼容:
- contenteditable 富文本编辑器
- textarea
- 其余文档编辑器
4.2 富文本编辑器
咱们常见基于 contenteditable 实现的富文本编辑器有百度编辑器、draft.js、有道云笔记(旧版)等等。
相比 textarea,富文本编辑器能够蕴含很多不同标签,能够以用来渲染成不同字体色彩的文本、图片、附件、视频、音频等等元素。
实现基于浏览器的富文本编辑器的四因素
四代编辑器的技术选型
- 第一代编辑器次要是通过无限的 execCommand 指令对 html 文档进行操作。
- 第二代则是在 execCommand 根底上,增加更多自定义指令甚至本人实现指令形式批改 html 文档。
- 第三代是引入数据模型(json/xml),绑定自定义实现指令从而渲染 html 文档。
- 第四代次要是间接摈弃整个 contenteditbale,独自制订选区和监听输出事件。
更多对于编辑器的介绍,可参考有道技术团队之前公布的文章:
- 有道云笔记新版编辑器架构设计(上)
- 有道云笔记新版编辑器架构设计(下)
为什么要介绍富文本编辑器内容呢,因为理解多这些编辑器实现形式和保留机制能够帮忙前面实现并优化扩大的性能。
4.3 初想
一开始的想法是,将原始编辑器的纯文本内容提取并发送到服务端,而后依据服务端返回的数据进行从新的拼接,在谬误节点地位应用非凡标记标签进行标注。
以有道写作 Web 端为例:
应用这种办法实现批改成果的还有 163 邮箱英文智能查看、Gmail 自带写信语法检测性能等。这种办法适宜咱们 自定义的编辑器,能够本人管制文本的渲染和指令。
但因为浏览器扩大是基于他人写的编辑器上进行的辅助工具,不能随便批改其文本格式和款式。比方复制带有划线的文本进行粘贴,会呈现冗余的划线(除非本来的编辑器有做粘贴文本的标签过滤),然而不能寄希望于他人写的编辑器都有这个性能。
4.4 实现
须要别离从两个局部进行思考:
- 如何定位画线
- 如何承受倡议替换正确文本
如何定位画线,并且能够给予其高亮的成果呢?
须要解决的问题是:须要在不影响原编辑器的格局以及性能前提下,将后果划线局部退出到界面上。
>>> contenteditabe:
- 第一步:虚构辅助器边框
虚构一个元素(大小雷同,地位绝对)在原始编辑器之上,将后果划线标注在这个元素之上,我称之为辅助器。
因为是笼罩在原始编辑器上,须要禁止辅助器的鼠标响应行为,在 hover 的时候须要将鼠标地位同步到辅助器之上。
辅助器须要和原编辑器雷同,能力定位精确,须要取得原编辑器以下属性。
- 第二步:找准定位
问题:如果单纯提取元素的 innerHTML/InnerText/textContent 作为批改申请的参数,无奈实现精确定位。
起因:服务端返回后果是依据纯文本定位,网页上的编辑器格局是 HTML 文档格局,蕴含不同字体不同格局的标签。
举个例子:html 中有两个块级元素,别离展现两句话,差别只在于 两句话 font-size 不一样。
通过 element.textContent 提取进去的内容都是雷同的,校验返回的谬误标记后果也雷同,如下:
因为无奈从纯文本的角度失去两种状况差别,难点就在于:须要解析 html 格局,将服务端数据转换到理论格局地位中。
要晓得,这相当于要在一个空白的白板元素里增加一个多个相对定位的高亮元素。须要晓得每个谬误绝对原编辑框的绝对位移,和本身宽高。
上面是一个反向斟酌的过程:
- 我须要失去的是 hightlightElement : {top, letf, width, height};
- 通过 range.getClientRects() 能够取得咱们想要的数据。
- 于是须要晓得如何获取一个谬误节点对应的 range。
- 须要找到对应的结尾节点和它的绝对位移、以及完结节点和它的绝对位移。range: {startNode, stratOffest, endNode, endOffest}。办法就是通过谬误节点在纯文本的开始(fixedposStart)跟完结地位(fixedposEnd)通过遍历全文每一个文本节点(textNodes)的数据长度(textNodes.nodeValue)进行计算得出。
- fixedposStart、fixedposEnd 依据服务端返回数据通过略微计算可得出。全文每一个文本节点(textNodes)须要通过过滤某些标签失去。
- 所以须要先思考如何解决 html 中各种标签问题。
所以划线的原理是:提取其纯文本的 textnode 节点,依据后果 position 匹配开始的节点和位移、完结节点和位移,获取其文本片段 range 对应编辑器的 x,y,height,width,画出高亮区域。
具体步骤如下:
a. 依据原文所有 html 标签加工过滤,提取纯文本和加工后的文本节点汇合:
html 内有各种标签节点,须要依据这些标签不同意义,对标签内的文本进行加工。比方针对 p 标签,通常是示意段落,须要将其包裹的内容前面增加一个换行符。
p 标签解决例子:
问题:这个换行符是一起发给服务端的,服务端返回来的数据定位也算上了这个换行符。
解决方案:过滤标签的同时记录文本处理过的地位,在前面的计算反向解决。同时还须要留神字符的本义问题,尤其留神零宽字符的解决。
b. 提取纯文本节点:
(上图文本内容依据标签内容分成 5 个纯文本节点)
c. 联合服务端数据计算每个谬误全文定位:
比方 has 谬误对应的谬误节点信息。
d. 依据定位获取每个谬误节点文本片段:
e. 通过文本片段获取绝对视口的地位:
划线步骤图
- 第三步:在 assist 范畴内画出线和高亮
contenteditable 汇合辅助器工作的流程图
>>> textarea:
textarea 自身是无奈获取其 textnode 的,它相当于只有一个节点。思考将其转换成文本节点:
- 创立一个隐形 mirror,这个 mirror 具备与原始编辑器雷同边框大小、可编辑区域。
- textarea 任何文本变动同步到 mirror
this.textarea.addEventListener('input',this.mirror.update);
- 再为这个 mirror 创立一个 assist,同理下面解决 contenteditbale 的流程相一致。
>>> 对于渐变:
编辑器其实就是一个一般的元素,以下编辑器的交互会引起咱们页面内文本节点的变动:
- 文本内容变动
- 尺寸变动(窗口变大变小)
- 地位变动
- 字体大小变动(加粗,居中)
- 滚动
这些变动也就影响咱们定位的变动,称之为渐变。须要解决每一个渐变引起的从新定位问题(重点难点)。
同时,须要监听原始编辑器的输出、字体变动、编辑器尺寸变动等等触发 assist 的从新定位办法。
// 通过 ResizeObserver 监听编辑器尺寸变动 objResizeObserver = new ResizeObserver((entries) => {var entry = entries[0];
this.elementResizeHandler(entry.target)
});
ResizeObserver 兼容性问题须要通过 polyfill 库文件解决。
从新定位办法(mutation):
- 通过新旧 textnode array 比对,正向遍历节点汇合和反向遍历节点汇合,失去被批改的 textnode 是哪一个段文本节点 textnode 汇合。
- 只须要解决被影响的 textnode 所对应的谬误节点汇合依据挪动的 offest 计算前面影响的节点位移。
- 其余谬误绝对本人 textnode 的位移是不会扭转的。
如何承受一个倡议,替换文本:
替换文本象征要批改原编辑器的数据甚至格局,就会造成方才说的对局部编辑器会引起格局错乱和保留失败的状况。
难点:不影响原始数据存储格局,不影响原始编辑器撤回操作,同时还能触发原编辑器保留机制。
解决办法:不间接用脚本批改 dom 节点,模仿用户批改数据的形式:选中文字,替换内容。
以新版有道云笔记为例子:
- 通过之前简单计算失去后果片段,依据后果片段计算出对于可视窗口的地位,失去 {top, left, height, width}。
- 模仿鼠标从左向右滑动的操作事件加在内容区域。
- 找到自定义的自绘区域。
- 一个谬误后果中可能波及不同的款式,咱们仅获取以后节点第一个片段的字体款式,模仿一个粘贴事件。
- 在自绘区域触发自定义粘贴事件。
4.5 加强编辑框
入口在点击右下角按钮或者 hover 呈现后果卡片时候,点击具体倡议进入
>>> 加强编辑框的作用:
- 提供更大的编辑空间
- 查看具体的批改后果
加强编辑框是一个非凡的 contenteditable 编辑器。
>>> 初始化、敞开赋值:
在初始化加强编辑器的时候,间接获取原编辑器数据,这里疏忽了原编辑器的一些款式、图片,只应用 html 数据局部。
在加强编辑器中编辑后返回原编辑器时候,须要将新数据返回赋值。
>>> 通信:
加强编辑框是嵌入页面的 iframe,只在顶层页面呈现。与原来页面的通信是通过 postMessage 形式。
(留神:postMessage 不能传递 html 元素和过于简单的 json object)
如果是本来编辑器是 iframe,须要找到最上层 window.top,利用 window.top 和加强编辑框进行通信。
五、整体流程
上图为有道写作浏览器扩大从注入到浏览器页面,以及运行的大抵流程。
为了在不影响用户操作前提下,扩大脚本只会在以后页面闲暇时候加载,并且批改性能只在局部被用户点击 focus 的编辑器中激活。
以上是开发有道写作浏览器扩大过程中的开发思路和局部技术实现细节,借此机会分享给大家,欢送与有道技术团队一起探讨更多对于前端、浏览器扩大的常识问题。