相关文章推荐
坚强的罐头  ·  Java Archive ...·  1 年前    · 
不爱学习的课本  ·  How to add an onClick ...·  1 年前    · 

我的上一篇译文 “ [译] 通过 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 动画,主要工作不就搞定了吗?
剩下的就是一些细节问题了……
怎么让加粗命令使用 <strong> 而不是 <b>,按回车键是创建一个新的 <p> 而不是插入一个恶心的 <br><div> 导致一大坨东西挤在一起。
在 StackOverflow 上提了几个问题,一个月后,项目里用了一堆好像能解决问题的黑科技(这其实是很可怕的),已经快要死翘翘了。然后开发者气急败坏地加入了 “contentEditable 你个大坑货” 小组,开始对 XYSIWYG 编辑器深恶痛绝。

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

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

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

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

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

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

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


    再见,contentEditable

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

  • 你需要一个自定义的选区系统,在 DOM 中按下鼠标/移动鼠标时,可以获取到位置信息(表示一个范围),你可以显示自定义的插入符号(哇……你现在可以控制它的样式了呢)和文本选区(就是一个简单的 <span> )。
  • 你需要捕获文字输入,侦听键盘事件,并把获取到的字符插入编辑器中。
  • 你需要处理通过方向键导航光标,左和右比较简单,上和下有点蛋疼,但如果已经实现了第 1 点,这个就不是问题了。等等,按下 Alt 键时,左右方向键导航的是整个单词呀。你可以检索文本内容的空白字符呀,这都不是事儿。
  • 然后,你需要处理下粘贴操作。参考 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)
  • 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-apps public-editing-tf 的邮件列表时,仍感到大开眼界。此外,在浏览器中编辑有很多用例,甚至 XWSISWYG 文本编辑器之间也存在差异,所以,锁定浏览器只针对一个用例是不合理的。

    编辑特攻队 (The Editing Task Force)

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

    (编辑:你可以在 “ Fixing ContentEditable ” 一文中查看更多关于 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)。与此同时,我们实现了 Blink WebKit 中对自定义退格和删除的支持。最后,就在几周前,我们完全接管了剪贴板,这意味着在一些浏览器中,复制、剪切和粘贴操作,完全在 CKEditor 的掌握之中。

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

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

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