背景最近我们在微信阅读中改写思路为基于webview的富文本编辑器,遇到了很多问题。这里我简单介绍一下我们在开发过程中踩过的东西。坑。富文本编辑器的实现有两种基本思路:基于Native的实现:比如coretext或者textkit实现第一个基于uiwebview的方案。webview中很多成熟的效果需要自己实现,比如链接、加粗字体、标题、Reference样式、列表样式等,这些的工作量相当大,而且ios/android两端都存在对齐问题。还有一个问题。这可能与我们的项目有关。我们在对富文本需求不多的情况下,在textview上做了一些链接处理的工作。只是这方面在当时不是很好。方便的。在第二种方案中,可以使用webview来省去第一种方案中提到的大量工作。同时webview有比较多的开源项目可以参考,不过webview也有游标控件。CSS冲突处理和兼容性问题,但是在最终选择解决方案的时候,我们权衡了好几次,最终还是选择了webview的解决方案。基于webview的富文本编辑器的游标和样式问题uiwebview实现了富文本编辑器,这是个大麻烦在游标的处理上,另一个大麻烦就是css样式的兼容问题,具体体现在以下几个方面:使光标保持在可见区域。插入表情时,uiwebview会失去焦点。原生命令会有bug,需要自己处理。风格兼容性。at和主题链接处理。如何让光标保持在可见区域这里有很多种情况。比如当我们在当前可见区域的最后一行再换行时,光标会跑到可见区域下方,webview不会自动将光标位置滚动到可见区域。需要手动触发webview的滚动机制。有两种思路可以解决这个问题。一种方法是破解本机滚动逻辑并更正滚动。还会有更多,实现起来可能会比较困难。另一种思路是直接在js中操作scrollview,比较简单。我们采用后一种方法,通过监测光标位置的变化来进行修正。代码如下:document.addEventListener("selectionchange",function(e){RE.calculateEditorHeightWithCaretPosition();//校正位置});这里修正的具体逻辑是每次光标位置发生变化时先计算当前光标位置,然后判断当前光标是否在可见区域内。如果没有,执行window.scrollTo滚动到响应区域。这里有一个小点需要注意,就是在判断光标位置的时候,top和bottom的判断略有不同。如果判断光标在可见区域上方,则需要判断光标顶部是否在可见区域范围内。如果光标破损在可视区域下方,需要判断光标底部是否在可视区域内。这里还有一个问题。最后一行,换行输入时,如果是汉字输入,会生成一个联想输入栏。当输入的内容还没有确定的时候,uiwebview是不知道你需要的高度的。这个时候因为触发了selectionchange,输入的时候整个界面会一直震动,所以这里用了一个tricky的方案。我们监听input的输入,强行在webview的末尾插入一个空白的div,让input一直在现有区域内。另外切记不要在js和native两端都使用滚动操作,这样会造成滚动逻辑混乱。插入表情,光标变化在我们的场景中,插入表情会弹出一个表情面板(这应该也是主流的做法),而这个表情面板会遮住键盘,会导致uiwebview失去焦点。这就意味着我们在插入表情的时候,不能直接使用Inserthtml命令来插入,因为这个命令在失焦状态下会失效,所以这里需要我们手动找到正在操作的节点,手动执行Insertnode操作,并记录更改光标位置,以便退出面板后重新focusus能找到正确的光标位置。简单的伪代码如下://此时游标的直接parent为编辑器,需要判断游标所在节点的nodeIndex,如果是则在parent[nodeIndex]前插入imgNode(currentOffset==currentNode.childNodes.length){//游标编辑器结束或者编辑器内容为空,直接appendcurrentNode.appendChild(imgNode);}else{currentNode.insertBefore(imgNode,currentNode.childNodes[currentOffset]);}然后更新当前选择,删除表达式面板中的表达式,同样需要做类似的操作。表情的另一个问题是,在webview中插入表情后,传到后台时,由于ios/android平台的本地文件名和表情样式不一样,必须转成[大泽]的格式.nativeapi的坑我之前提到过webview的优势。webview的一个吸引人的地方就是它提供了很多原生的接口来实现相同的风格。要深。举个简单的例子,如果要实现加粗和取消加粗,一条命令就可以搞定。document.execCommand('粗体',false,null);同样的,按照文档的介绍,如果要对quoteformat进行操作,只要改成命令document.execCommand('formatBlock',false,"
")即可,但是上面的命令只允许你添加参考格式。如果你想取消引用格式,你会发现它没有用,所以你需要另辟蹊径。这里有一个哥们关于引号的惨痛经历,但是我们最终的解决方案和他的略有不同。我们判断当前光标所在的Node是否有blockquote标签,或者是否为包含blockquote标签的Node的子节点。添加报价或取消报价,伪代码如下:if(inQuoteBlock.is){document.execCommand('formatBlock',false,"")}else{document.execCommand('formatBlock',false,"")}类似的还有标题设置等问题。样式兼容性当多种样式并存时就会出现这个问题,比如引文、标题、序列格式、高亮链接混合在一起,你会发现各种奇怪的问题。让我们用一个我们遇到的例子来说明,其中引用和突出显示的链接混合在一起。我们首先使用引用格式blockquote,然后在引用文本中插入一个a标签。那么当我们再次输入其他文本时,就会发现系统偷偷为我们添加了一个span标签。这个标签有自己的样式,导致下面的样式和前面的不一致。再举个例子,我们在开发的时候,在测试中发现了一个bug,就是当引用、标题、顺序格式同时应用于一段文字时,我们会发现系统会默默插入给你一个div标签,这也会引起一些莫名其妙的惊喜。格式化问题。当然,还有一些其他奇怪的问题。这些其实都是CSS样式问题。处理这些问题,必须自己维护一套CSS格式库,然后不要使用系统的document.execCommand命令自己封装。当然是最彻底的,但也是最费工夫的。另一种方法是限制某些组合的可能性,或者对某些场景进行特殊处理。当然,这只是一种非补救性的解决方案。具体怎么做,要看使用场景。毕竟我们不是在造词,可能不需要考虑得那么周全。at和topic的链接处理其实就是这里webview的链接处理问题。以@为例,我们的需求要求点击@后,会生成一个搜索框,用于搜索想要@的用户。如果我们使用textview,完全可以在textView的delegate中根据当前位置和输入内容进行搜索。但是你很难在webview中获取一段时间内用户输入的内容,所以我们直接使用链接代替。当输入@时,会生成一个链接,然后在链接中进行查找操作(这里有个小技巧,如果只是输入一个@字符,变成一个Link,那么光标就会在链接外了default,所以接下来的输入不会成为链接的一部分,所以我们这里生成的是一个“@”加空格的链接,手动将光标移到@后面)。不过这里还是有一个光标坑,因为我们选择了一个用户之后,需要替换输入部分,也就是替换链接内容,我们会发现光标会移动到链接的前面,光标又会跳来跳去!所以其实你需要自己移动光标!另外,在搜索的时候还有一个问题,就是在使用系统输入法输入中文的时候,会有一个联想输入栏(quicktype)。如果此时用户没有选择输入栏,而是我们直接选择用户名进行替换,那么我们会自动将当前链接替换为选中的内容,并将光标移动到链接的后面,但是此时这个时候,其实系统输入法的联想输入还没有结束,所以当用户再次点击输入的时候,系统会默认找原来联想输入开始时的Node位置,但是由于这个已经被替换了被我们发现了,这样游标就会跑到webview之外,所以我们这里还需要监听compositionupdate来修正游标的位置。总结总的来说,虽然系统基于webview的富文本为我们做了很多事情,但是在实践中,我们会发现问题远比我们想象的要多,所以千万不要怀疑word开发的工作很多年!另外,如果你想做一个基于webview的富文本编辑器,你必须对Js有一定的了解,不然你会觉得很头疼!但是对于大部分的app来说,我们的要求其实并没有那么高,所以找到一个适合自己webview的开源方案还是可以大大减少你的工作量的。