L0 阶段的富文本(第一代传统编辑器)
L0 阶段也就是最原始的阶段,核心实现完全是依靠着execCommand来实现操作的能力,在搭配着contenteditable = true 来给元素提供可编辑的能力、range, selection 提供选区。 这样就形成了初代富文本。
什么是execCommand
文档 developer.mozilla.org/zh-CN/docs/…
document.execCommand() 作用就是当一个HTML文档切换到设计模式时,document 暴露 execCommand 方法,该方法允许运行命令来操纵可编辑内容区域的元素。
语法: bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)
参数:aCommandName 一个 DOMString ,命令的名称。可用命令列表请参阅 命令 。
aShowDefaultUI 一个 Boolean, 是否展示用户界面,一般为 false。Mozilla 没有实现。
aValueArgument 一些命令(例如insertImage)需要额外的参数(insertImage需要提供插入image的url),默认为null。
命令:背景颜色、粗体、字体大小、拷贝、字体颜色等等,命令有很多,可以根据自己需要的功能自己选择
execCommand 的优缺点
可以分两点来说
- 自定义命令:
性能稍差,代码量相对较大。为什么说性能稍差代码量较大呢,那是因为
execCommand本身自己实现的命令也就哪一些,并没有覆盖完全,比如我要实现一个创建表格的功能,是没有这个功能的,那怎么办呢,只能靠自己实现了
首先创建表格、添加行、删除行、添加列、删除列、设置表头、取消表头、删除表格等等操作,一个功能就要去实现一块代码,具体多少代码得看个人实现的方式了,当然,自己实现功能也就意味着bug可控(想怎么做就怎么做),也可自定义样式、兼容性可控。
- 原生命令:
性能佳,bug不可控,兼容性差,实现的功能有限,只接受有限的 commands 。下图就是浏览器兼容性
execCommand 提供的命令可能在谷歌中有用,但是到了ie上面并不支持,出现了bug也并不能去兼容他,浏览器的特性也是有很大的关系,execCommand 提供的命令是也是存在bug的, 在加上浏览器实现也不一样,出现问题就要去兼容它。但是呢用了原生命令功能也代表这代码量少,毕竟都不用自己实现,浏览器已经知道我们要执行什么命令了。
原生命令并不算太多,对于只要求文本编辑的话那确实是够用,但是要是想带一些复杂的功能呢。还有一个问题就是如果使用contenteditable 特性自带的undo/redo, 那么, 对内容(html)的所有操作都必须使用document.execCommand() 完成, 否则就会破坏内部的undo/redo栈, undo/redo 功能就会不正常。而且, document.execCommand() 能做的事情太少,也就意味着需要自己拓展,还要自己实现undo/redo。
总结:
-
对浏览器差异的屏蔽,和bug的规避,成本巨大,而且不稳定,时不时发现一些新问题(作为
wangEditor的一员深知这个问题……); -
对有限的命令集进行扩充,但不是基于execCommand() 进行扩展,而是自行封装实现效果,通过工具栏调用;
-
只是能力扩充,但并没有提供通用扩展接口,开发者无法自定义一种符合业务需要的格式,随着功能的越多,大小也随之变大; 总结一下,最大的问题是:缺乏扩展性。
不用execCommand,该如何去实现富文本?
个人想法(还没有参考别的富文本)
不使用document.execCommand()API去实现一个富文本,首先我想到的是使用JavaScript去封装功能实现。
举个例子,做一个标题 (h1) 的功能
// 代码示例
<div contenteditable = 'true'>
<p>
阿斯顿发斯蒂芬
</p>
</div>
图片效果
选中文字
然后通过选区selection来获取选中的dom (p)节点,在生成替换的节点。
const selection = window.getSelection ? window.getSelection() : document.getSelection()
// 获取焦点所在的容器 此时获取到的是text
const dom = selection.getRangeAt(0).commonAncestorContainer;
// 获取它的父级元素 p
const parentNode = dom.parentNode;
// 然后删除文本节点
parentNode.removeChild(dom)
// 在生成替换的html字符串
parentNode.innerHTML = `<h1>${dom.textContent}</h1>`;
// 最后的效果
<div contenteditable = 'true'>
<p>
<h1>阿斯顿发斯蒂芬</h1>
</p>
</div>
下面看图片的前后对比
以上就是整个思路,但并不代表最后的实现代码,只是描述一下思路