Day 3. Pre-Start × WYSIWYG × contenteditable

https://ithelp.ithome.com.tw/upload/images/20210918/201393591SYJu1MNKa.png
诚如上一篇结尾所说,我们今天要把目光聚焦在浏览器提供的 contenteditable 属性以及 execCommand api ,这两个浏览器默认,用於制作 WYSIWYG 编辑器的工具。

上次是 W ,这次这个 C 开头的又是 ... ?


contenteditable 基本上就是最基本最简易的 WYSIWYG 编辑器制作方式,它是 HTML5 新增的属性,读者现在就可以点开任意一个网站,将几乎任何的 HTML elements (除了像 <img> 这种无法塞入纯文本节点的 tag 以外)加入 attribute : contenteditable="true" ,接着就能编辑文字以及对反白後的文字透过快捷键,如: cmd + bcmd + z 等进行加粗或者复原等等的操作。

让我们拿 MDN 的 contenteditable 介绍页面 来示范

https://ithelp.ithome.com.tw/upload/images/20210918/20139359wnSAhvAfhd.png

当我们将 <p> tag 新增 contenteditable 属性後我们就能随意编辑里头的内容,如上图反白一段文字後按下 cmd + bcmd + z 就能加粗或将文字改成斜体,也能新增或修改文字,按 cmd + z 也能 redo ,读者可以自行玩玩看。

还有另一个与它相似的属性,也是在 HTML5 时新增的,叫做 designMode 。他们之间的差别就只是前者是用来指定 DOM 区块,而後者是将整个 HTML 文件切换为可编辑状态,我们拿 w3schools 的范例来举例

https://ithelp.ithome.com.tw/upload/images/20210918/20139359spvPvgPDPa.png

execCommand 则是浏览器提供开发者透过 js 操作 designMode: "on" 的 Document 或是 contentEditable: "true" 的可编辑元素。它支援了许多功能,几乎你想得到的所有功能诸如: deleteinsertHTML ,最常见的 undoredo ,以及像是粗体、斜体等功能也跟前面提到的快捷键一样,会搭配 Web API 提供的 Selection Object 找出浏览器当前的活跃区域与选取范围并执行操作。

这边提供读者 Selection Object 的 MDN 连结,因为不是本章的重点就先不特别介绍它了

这一切明明看似如此美好,理论上我们只要做好一条 toolbar 再另外搭配上 execCommand ,顶多额外加个一层逻辑去处理 api 的使用方式一切就大功告成,不过如果读者有前去查看 它的 MDN 的话会发现上头顶着一块红色大布条,写着 " This feature is obsolete (过时的)"

究竟事态是如何逐步发展至如此境地的呢?如此极致方便的功能竟遭人唾弃至此,甚至你 google : contenteditable 关键字跳出来的前几篇文章就出现了一篇标题上写着 " Why ContentEditable is Terrible " 的 medium 文章。

俗话说的好——事出必有因


首先是 execCommand 在各个浏览器的支援程度不一,我们可以透过 这份 codepen 去查看当前开启的浏览器支持哪些 execCommand 的指令。

https://ithelp.ithome.com.tw/upload/images/20210918/20139359VJpKwn9Seu.png

其次它跟 contenteditable 一样,指令所导致的行为很不受控,在不同的浏览器下甚至会产出不同的结果,例如快捷键 cmd + b 在 chrome 里面会插入 <b> tag ,而在 ie11 中则是增加了 <strong> tag ,在 safari 里甚至不支援快捷键功能。

除此之外,里面提供的许多修改样式的功能,它们的修改方式都是直接修改 tags 的 inline-style ,这显然也会造成开发人员不少的麻烦,与其使用这些功能然後另外找一堆方法修补造成的洞,那不如从一开始就避免使用这些会造成麻烦的功能,删完一轮的结果发现根本没剩多少功能是适合使用的 ?

有兴趣的读者也可以上这条 stackoverflow 去看其他人分享的 execCommand api 的开发经验

让我们再举几个常见,但却难以透过 contenteditable 实现的例子:

https://ithelp.ithome.com.tw/upload/images/20210918/20139359lAwqqTr66y.png

当我们要将上图里的段落内容转换成 DOM 形式的话,读者心里的结果可能如下:

The <a href=”http://en.wikipedia.org/wiki/The_Hobbit">hobbit</a> was a very well-to-do hobbit, and his name was <strong><em>Baggins</em></strong>.

但真的只有这样的编译结果吗?我们将目光聚焦在段落尾端的 " Baggins " ,它其实可以有非常多种的编译形式

<strong><em>Baggins</em></strong>
<em><strong>Baggins</strong></em>
<em><strong>Bagg</strong><strong>ins</strong></em>
<em><strong>Bagg</strong></em><strong><em>ins</em></strong>

我们可以发现它其实是以『多对一』的形式存在的,一种渲染结果可能是由多组不同可能性的 DOM 所转换成的,但编辑器应该要让 『 user 在针对相同的渲染结果使用相同的 api 时,有相同的行为发生』,这也意味着编辑器要有办法释读出不同的 DOM 结构并判断它们最後的渲染结果是否相同才有办法达成。

然而这件事实际上却难以实现,因为可条列出的可能性实在太多种了,即便开发者自认完成了这项功能也难以测试。

contenteditable 却无法保证相同的行为会导致什麽样的 DOM 输出结果,在某些时候它可能会插入一些 invisible 的 character ,或是完全 empty 的 span tag 。

再来是另一个也非常常见的功能:复制贴上。

之所以难以达成这项功能的原因其实与上述的问题症结点的本质是一样的,在使用 contenteditable 属性时,开发者难以预期编辑器当前的 DOM 结构,无法预期的话也就不会知道当前 user 复制的段落究竟包含了哪些 DOM tags 。

在同一个浏览器底下实作可能还看不太出问题,但如果今天是跨浏览器的开发,开发者就准备头大了!要处理的 edge cases 数不胜数,堪称无底洞,根本就不可行。


嗯 ... 我想如果是由我开发一组 WYSIWYG 编辑器的话,我应该还是会先试着去思考这些浏览器提供的属性以及功能有什麽是我能够取来用的吧?毕竟这开箱即用的效果实在太诱人了!


说得不错,其实就算是拉回现代,大多数的编辑器与套件都还是一定程度地依赖於浏览器提供的功能,团队们在研发自己的产品时一样有成本、时间、服务客群等因素上的考量。

而 Web WYSIWYG 这项议题也确实围绕着 contenteditable 打转了许久,延伸出了相应不同 世代 的套件类型,紧接着我们就要进入到下个篇章,也就是目前市面上几个知名的 libraries 相互的比较,但在那之前我们要先花些篇幅去为不同世代的套件们做点分类,了解它们各自有哪些主要的特徵。然後我们才会顺着时间线聊聊这些世代底下的 libraries 它们各自所面临的问题, Slate 的作者最後又是因为什麽理由决定制作 Slate 的。


<<:  DAY3:安装 Java JDK以及Android studio之步骤

>>:  Day 03 : Zettelkasten卡片盒笔记法,建立知识连结网路来活用笔记

Day10 用python写UI-聊聊文字方块Entry

耶~~~终於迈入第十天,完成了三分之一,今天要来讲文字方块,普遍常会看到的用法会在输入号密码的时候,...

Day 19 To Do List - 加入逻辑 2

第 19 天~ 昨天做到了,新增项目的部分, 像这样: 改变状态 当我们可以成功的展示新增的项目後,...

[Day 16] 从头开始-从入门到 Swift 语法梳理

开发必须的设备以及相关的设定 入门开发的几个必要条件:一台 Mac , 苹果帐号 ,以及一个一般人智...

JavaScript 从 100% 继续,再多程序语言也不是问题!

大家好! 自从系列开始到昨天,也已经流逝 45 天的时间了。 这期间,总是会怀疑自己写的文章够不够好...

Backend 台湾板板主 - Triton

这一篇,我很高兴邀请到台湾後端板板主 Triton 参与。我跟 Triton 之前不认识。刚好 AL...