[译] ContentEditable 那些好的、坏的和坑
阅读原文时间:2023年07月10日阅读:2

我的上一篇译文 “[译] 通过 contentEditable 属性创建一个所见即所得的编辑器” 的原文 “Create a WYSIWYG Editor With the contentEditable Attribute” 被本文作者叼了一翻,说会误导吃瓜群众,让初学者误以为富文本编辑器很简单(见原文第一条评论),吓得我赶紧在译文头部郑重申明了一翻。

顺着评论过去看了下这篇文章,虽然有点 CKEditor 软文的嫌疑,但确实有些点值得思考下,遂译之。

不过 CKEditor 确实牛逼,搞了十几年了,编辑器里的老前辈。

本文对于 contentEditable 有点危言耸听,本人观点是基于实际需求出发,如果需要各种格式化功能、甚至是对 Excel/Word 内容的支持,那么使用 CKEditor 这类成熟的框架无疑是非常合适的选择,也是唯一的选择,除非你有拯救世界的梦想;如果开发的只是一个简单的回复功能,只需要支持文本和 Emoji 表情的混排,用框架的确有点重,通过 contentEditable 自己实现也无可厚非,但确实要做好踩坑的心理准备,如果这篇文章可以让你避开一些坑,那就功德无量了。

文中提到的另一篇文章 “Why ContentEditable is Terrible”,有兴趣的可以看下萝卜哥的译文 “【译】为什么web富文本编辑器是天坑?”。

格式说明:链接,原文,说明


原文:ContentEditable — The Good, the Bad and the Ugly

每隔一段时间,就会有一些开发者发现,业界还没有一款完美的网页 WYSIWYG 编辑器,然后就会发生下面的场景:

这简直太奇怪了,嘿,哥们,我们有一点时间,要不自己搞一个?

WYSIWYG 编辑器有什么难的?现在我们已经有了 contentEditableexecCommand()queryCommandState(),只需加个工具栏,放些漂亮的按钮,贴上漂亮的 SVG 或字体图标,点击的时候应用下加粗或者文本链接,简单地排个版,再弄点 CSS 动画,主要工作不就搞定了吗?

剩下的就是一些细节问题了……

怎么让加粗命令使用 而不是 ,按回车键是创建一个新的

而不是插入一个恶心的

导致一大坨东西挤在一起。

StackOverflow 上提了几个问题,一个月后,项目里用了一堆好像能解决问题的黑科技(这其实是很可怕的),已经快要死翘翘了。然后开发者气急败坏地加入了 “contentEditable 你个大坑货” 小组,开始对 XYSIWYG 编辑器深恶痛绝。

加粗命令、回车操作和粘贴处理只是冰山一角,但足以让 JavaScript 开发老鸟对 contentEditable 感到厌烦。

我们再来看看如何改变加粗命令的行为,在所有的处理方法中,有一种是通过变化监视器监控编辑器内容的变更,重新标准化 HTML 结构,这是重新实现 execCommand() 行为的一个完美解决方案(在我看来,也是唯一的一个完美方案)。但这里再次强调,这只是冰山一角。

如果使用变化监视器方案,你可能需要处理保留选中区域的逻辑(因为当你修改 DOM 的时候它可能被重置),以及 撤销管理 的逻辑。

如果上面这些都处理好了,那么恭喜,终于搞定了加粗问题。但再想想,回车操作也要这么来一遍,我已经开始怀疑人生了……

如果你和我一样是一个纯粹主义者,可能会尝试自己实现一个 execCommand(),算法非常简单—— 获取选中内容获得选中区域(我有提过 Firefox 支持多选区(multi-range selections)吗?)、做些 DOM 处理、重建选区、实现撤销管理(这次可没人帮你做历史记录),搞定!

然而:

  • 你因为项目延迟了几个月被解雇。
  • 在其它浏览器上测试发现一大堆问题(哎呀,我有提过 Blink 和 WebKit 的选区机制不一样吗?嗯……这个问题已经存在 8 年了),你好像实现了一个史上最烂的编辑器。
  • bug 的出现让你开始意识到,他喵的到底有多少种情况要考虑啊。
  • 你仍然需要处理回车操作和该死的粘贴逻辑。
  • 你发现还需要重新实现 BlinkWebKit 的退格和删除操作,因为它们比你想象的更喜欢行内样式(inline styles)。
  • 你发现有些浏览器对于链接总是返回一个绝对路径的 href 属性,即使你使用的是相对路径。
  • 天了噜……我们这才聊了 4 个最基本的功能,你什么时候添加对图片的支持?
  • 你知道选区还有方向的区别吗?

够了,我想我已经很好的表达了一个观点:contentEditable 真可怕(ContentEditable is Terrible)。selection、clipboard、drag、drop 等相关 API 以及它们的实现是不完善的,并且(或者)不统一和有各种 bug,Range API 复杂而麻烦。

但冰箱还是空的(工作还得继续),现在你应该知道怎么做了。

既然 contentEditable 和 selection API 是恶魔双星,那就尽可能离它们远点。所以,方案是什么?

  1. 你需要一个自定义的选区系统,在 DOM 中按下鼠标/移动鼠标时,可以获取到位置信息(表示一个范围),你可以显示自定义的插入符号(哇……你现在可以控制它的样式了呢)和文本选区(就是一个简单的 )。
  2. 你需要捕获文字输入,侦听键盘事件,并把获取到的字符插入编辑器中。
  3. 你需要处理通过方向键导航光标,左和右比较简单,上和下有点蛋疼,但如果已经实现了第 1 点,这个就不是问题了。等等,按下 Alt 键时,左右方向键导航的是整个单词呀。你可以检索文本内容的空白字符呀,这都不是事儿。
  4. 然后,你需要处理下粘贴操作。参考 Clipboard API,你可以侦听 document 上的粘贴事件,从参数中获取到数据。你也可以使用 老的粘贴机制(paste bin mechanism)。

世界是多么美好,空气是多么清新。展现在你面前的,都是常用的 API 或者你自己的代码,全部是可控的。和可怕的绝望说拜拜。Selection API 和选区仅仅用于和浏览器进行交互,但在内部你实现了更适用于文本编辑的、不同的机制。再也不需要和那些不确定的 DOM 打交道,只需要操作 DOM 的文本、样式、索引(indexex)和变换(translation)。现在,应用加粗是一个非常简单的算法:给选中的内容添加一个样式,然后自动变换更新 DOM。

冰箱里的食物还不完美,所以你把项目发布到 GitHub 上,开始收到第一个 bug 反馈,不要紧张,没有一个软件是一天炼成的。

Q:我该怎样输入波兰文字?

当我按下 Alt+L,我希望插入的是 “ł”,但你的编辑器插入的是 “l”。

A:对的,一起来看看我们可以做些什么。我们不知道键盘布局,所以不能简单地检查 Alt 的状态。此外,语言的种类也太多了吧喂。好吧,DOM3(DOM level 3)已经有 KeyboardEvent.key 了,但目前仅 IE 和 Firefox 支持,Blink 很快也会支持(译者注:Chrome 51.0 已支持),所以你可以再等等,很有可能所有的主流浏览器会在合适的时间内支持该特性。

Q:我该怎样输入重音符号?

我有一个西班牙语的键盘布局,当我输入一个 “`” ,再输入一个字母(如 “u”),我希望先看到那个标记,然后在同个位置出现字母 “ù”。

A:等等,啥?他是说两次按键会合体成一个字母吗?你他喵的在逗我……我们可以对这种情况做特殊处理(前提是能够获取到当前正在使用的键盘布局),让我们祈祷不会有太多的语言需要这么搞。

Q:我该怎样输入平假名?

在输入时,相关内容可以自动添加下划线,并弹出这种组合弹框。

A:对的,复合事件(composition events)。你知道可以利用这个做点什么(如果浏览器支持的话)。但不幸的是,你会发现 输入法引擎(Input Method Engine)在每个操作系统中表现都不一样,而且通常是系统集成的(例如,它会学习新词汇并实现智能补全)。你再一次感到生无可恋。然后,突然灵光一闪:可以搞一个隐藏的文本区域(hidden textarea),从中读取输入呀,如果把它放到插入符号(caret)的位置,不就可以搞这个弹框了吗,只是失去了上下文建议。打开 IME API 还有些工作要做,你总有一天会放弃这些黑科技的。

译者注:

  • conposition events:复合事件,输入的字符重新组合成文字的过程:

    • 进入组字模式(compositionstart)
    • 组字内容更新(compositionupdate)
    • 组字模式结束(compositionend)

    参考资料:用 Composition Event 改進 CodeMirror 對輸入法的支援

  • IME:Input Method Editors,输入法编辑器。

  • caret:插入符号,就是编辑的时候那个一闪一闪的竖线。

Q:我该怎样使用 iPad 输入?

当我触碰编辑器时,键盘没有出现。

A:可编辑区域没有获得焦点 = 没有键盘。当然,通过一个隐藏的文本区域(hidden textarea)可以变相解决,真的吗?那你现在怎么粘贴?

Q:Alt + 左/右方向键导航时,光标跳过了不止一个单词

ພາສາຈີນແມ່ນພາສາໜຶ່ງທີ່ເວົ້າໃນປະເທດຈີນ.” 这个文本包含了很多个单词,但你的编辑器把它当成一个单词处理了。

A:问题是,空格在哪?!

Q:在 iPhone 上“摇一摇撤销操作” 不起作用

A:对的,必须用 Ctrl + Z,是时候试下加速器(accelerometer)了。

Q:在 iPhone/iPad 上,当我操作一个选取时,键盘不见了

A:对的,焦点从隐藏的文本区域(hidden textarea)移动到文档(document)了……

Q:拼写检查失效了

A:这太可怕了!太可怕了!

Q:你的编辑器不能同时使用键盘和屏幕阅读器(Screen Reader)

你的编辑器根本用不了。通常,当我进入编辑器时,屏幕阅读器会通知我,然后,当我导航文本时,它会阅读选中的单词,此外,它也会阅读我输入的内容。但在你的编辑器里,啥都木有发生。顺便说下,你有看过 http://www.w3.org/TR/2dcontext/#best-practices 吗?

A:是的,目前没有,但很快会有。

……

在上面的讨论中可以得出,contentEditable 也许真的很糟糕,但它已经在那儿。我相信所有上面提到的问题,总有一天原生 API 都会支持,但请相信我,那天还没到来。

标准化那些复杂的特性是一个非常艰辛的工作,因为你能看到的永远是冰山一角。虽然我已经和 contentEditable 打交道将近 4 年了(当然,和搞了 13 年的 Frederico Knabben 是完全没得比的 :D),但当看到 W3C 的 public-web-appspublic-editing-tf 的邮件列表时,仍感到大开眼界。此外,在浏览器中编辑有很多用例,甚至 XWSISWYG 文本编辑器之间也存在差异,所以,锁定浏览器只针对一个用例是不合理的。

说到标准,contentEditable 必然是其中之一,因为我们需要 contentEditable。也许几年之后,我们将有可能无需 contentEditable 就可以实现一个功能齐全、稳定可用的 XYSIWYG 编辑器。所以,W3C 编辑特攻队 在去年成立(我和 Frederico Knabben 都加入了),就“目前的情况该如何处理”展开了激烈的讨论。关于这方面,我将单独写个帖子,因为它看起来很有前景。

(编辑:你可以在 “Fixing ContentEditable” 一文中查看更多关于 contentEditable 标准化的信息。)

contentEditable 就像 JavaScript,除了道格拉斯(Douglas Crockford)还没有给它写一本书。它也有好的一面(是,我知道,到底好的多还是坏的多存在争议)。只需要给 HTML 元素添加一个属性,就能启用文本输入、选中内容、键盘导航、拼写检查、拖放、粘贴、撤销管理,太神奇了!这些都是系统集成的,使得编辑器可以使用屏幕阅读器(screen reader)或者在触控设备上运行,并且非常的 国际化。让我们聚焦在这些好的部分,暂时忘掉它不好的一面。

在过去的几年中,CKEditor 的工作让我逐渐意识到,我们正在逐渐使用自己的实现来替代原生的特性,源于一个回车操作的自定义行为、命令(像 execCommand() API,能够应用、移除、检查一个特定的样式(如加粗)的状态)、撤销管理和劫持粘贴操作以过滤粘贴内容。然后添加了一些选区系统的实现(比如在模态状态下锁定选中内容),加强在表格和列表编辑中的导航功能。从 4.0 版本开始,CKEditor 拥有了自己的 “插入 HTML 到选区” 机制和一个 允许到达不可编辑位置 的特性。CKEditor 4.1 引入了高定制化的 内容过滤器(粘贴操作不再乱七八糟)。CKEditor 4.3 带来对 不可编辑区块包含可编辑区块(non-editable islands with editable islands inside)的支持,这个需要重写许多原生的行为(selection、keyboard、focus、clipboard)。与此同时,我们实现了 BlinkWebKit 中对自定义退格和删除的支持。最后,就在几周前,我们完全接管了剪贴板,这意味着在一些浏览器中,复制、剪切和粘贴操作,完全在 CKEditor 的掌握之中。

也就是说,如今的 CKEditor 不仅不让浏览器对内容进行任何处理(除了输入、删除等基本操作),连选区系统、键盘导航和剪贴板、焦点管理等其它相关的 API 也一起接管了。

CKEditor 5 我们计划在编辑器的控制下,仅通过浏览器插入文本就可以完成这个过程(参考:CKEditor 5: The Future of Rich Text Editing)。讽刺的是,我们准备将所有的编辑算法都建立在一个自定义的数据模型之上,我们承认 DOM 不是完成这项任务的最佳工具。当然,我们也到了面临国际化问题的时候了(如对 Alt+退格的支持),我们都知道这一点。但是,这个功能很可能包含在不久后浏览器开放的第一批特性中,感谢编辑特攻队的辛苦付出。

这一切听起来都很美好,但要实现这个目标仍有大量的工作需要完成。即使知道这似乎不是一个独立开发者、甚至是中等规模公司可以完成的项目,但我们相信,终将会出现一个编辑框架,允许其他开发者在其基础上建立自己的定制化方案,这就是我们给 CKEditor 5 定下的目标,虽然 Alloy Editor 这样的项目已经证明即使基于 CKEditor 4 也是可行的。如果不是开发者一旦发现现有的编辑器无法满足需求,就立刻从一个光秃秃的 contentEditable 开始倒腾,我们今天可能在一个不同的地方 ;)。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章