一、前言
之前在《富文本编辑器之游戏角色升级 ing》一文中,跟大家分享了富文本编辑器的发展历程、选型技巧和扩展方案。今天将和大家一起聊一聊“富文本及编辑器跨平台方案”那些事。
大家应该注意到了,标题用的是“富文本及编辑器”,而非“富文本编辑器”。也就意味着本文将围绕富文本跨平台和编辑器跨平台两大部分进行介绍。
通过跨平台方案的分享,希望能给有富文本编辑器跨平台相关需求的小伙伴带来一些帮助。
二、为什么要跨平台
对于一个产品来说,用户的需求程度在一定程度上反映了其产品的价值。对于富文本编辑器而言,以 WEB 端(PC 浏览器、移动浏览器)、移动端(IOS 应用、Android 应用)、桌面端(windows、macOS)各自为战的系统生态,已经无法满足用户的需求。同时对于研发人员而言,各端都需要大量的资源投入进行重复能力的开发,这无疑是一种资源的消耗。因此越来越多的团队开始寻求突破,建立跨平台的编辑生态,在不同的平台、不同的终端上实现数据互通,展现体验一致的编辑能力。
(图片来源于网络)
上面是比较笼统的概念,举例子说明下:
社交类型的应用
以微博中的场景为例:假如你用电脑网页版微博发布了一篇长文,然后分享给了你的朋友,期间发现内容可能需要编辑下。这个时候问题来了,你是倾向于放下手机打开电脑编辑,还是直接切到手机微博来编辑。我猜绝大多数小伙伴应该会选择用手机微博进行编辑,那么我们就可以发现在这个过程中经历了 Web 端发表--微信小程序查看--APP 端编辑几个阶段,这其中的流转其实就是跨平台操作。
记录类型的应用
以手机端便签应用为例:越来越多的用户习惯用便签之类的应用记录一些生活事项,那这些记录是仅存储在本地设备中的吗?NO! 如果仅存储在本地,那么换台手机或者清除数据后,数据就无法找回了,这必然不符合大众的需求。大部分记录类应用的数据是存储在云端的,使用云端存储既能满足跨设备的数据迁移,同时带来了跨平台可浏览、可编辑、可删除的附加价值。
以上,简单介绍了富文本编辑器跨平台的两个应用场景,可以看出富文本编辑器跨平台已经成为一种必然的需要。既然已经清晰了为什么要跨平台,下一步我们就来探讨下如何实现跨平台。
三、富文本跨平台
富文本,在这里指代“编辑器所输出的数据”。富文本的跨平台,实质上就是使富文本在不同平台内以其原生的方式展示相同的效果。
注:在本章节中探讨的场景主要是 WEB 端的富文本 HTML 如何可以在 Android、小程序中展示原生的效果。
有朋友也许会问,HTML 在 Android 内可以用 HTML.fromHtml 方法解析展示富文本内容。微信小程序也可以用 rich-text 富文本组件直接渲染,既然用 HTML 就可以跨平台展示,为什么还要单独拆分一个章节探讨呢?对于这个问题,首先给大家分别展示下用 HTML 数据渲染在不同平台中可能出现的问题:
从上图中可以看出,HTML的优点是特性丰富,灵活多变。而正因如此,其很难严格的定义数据。因此若是将HTML作为流转的数据,很容易在不同平台内出现解析兼容问题。
那么要在不同平台间实现一致的展示效果,有两种方案作为参考:
- 方案一:将 HTML 强制转化为各平台都能正常适配的层级结构。
- 方案二:利用一种通用的可供各端解析的数据模型,各端用原生组件解析渲染。
方案一虽然可以通过枚举不兼容的场景正则替换,将源数据转化为各平台均可以正常解析的 HTML,但是从可扩展性的角度上来说,枚举替换的方案不太现实。既然如此,那就一起看看如何通过方案二实现。
通用的数据模型
考虑到 HTML 转化中存在的问题,那么通用的数据模型需要满足以下条件:
- 描述文档层级结构
- 严格定义嵌套规则
- 制定数据过滤机制
下图分别对比了使用 JSON、XML 作为数据模型的优缺点,可以根据项目需要酌情选择:
之前分享的文章中,L2 阶段的富文本编辑器的数据模型多是 JSON 结构,本节直接沿用之前的例子展开介绍下 JSON 数据模型是如何满足以上三个条件的:
遵循条件规范,定义好数据模型后,此时数据在各平台间的流转过程就如下图所示:
整个流程总结下来就是:以通用数据模型作为媒介,打通 WEB 端与 Android、小程序的数据互通,在各平台用原生的组件渲染页面,最终实现富文本的跨平台。
本节解决的问题是:WEB 端产出的富文本内容,在各平台如何得到最本源的展示效果。那么如何保证各个平台都是输出相同的数据模型呢?这也就引入了下节的内容——编辑器的跨平台。
四、编辑器跨平台
编辑器跨平台,是指由各平台提供功能模块,WEB 端提供排版编辑能力,最终运行在平台特定的浏览器环境中。本节将以 Android App 的编辑器实现为例进行展开,其他平台的编辑器实现原理相同。
以便签 APP 为例,富文本内容编辑模块运行在由Native App 提供的 Webview 环境中,其工具栏菜单、状态显示部分则由 Native App 原生控件组成。
为什么不选择直接用 Native 编辑器或者 Web 编辑器,而是选择这样组合的形式呢?
首先,如果选择单一的编辑器或多或少存在一些问题,比如说:
- Native 编辑器实现复杂富文本结构的开发成本较高,需要定制很多功能模块;
- Web 编辑器在 Native APP 中操作能力有限,且交互体验不及 Native 原生控件。
那么两者“取其精华”,选择保留 Web 端富文本丰富灵活的排版能力,同时用 Native 原生控件关联用户操作,最终实现 1 1>2 的效果。具体体现在:
- 灵活展示丰富的富文本内容;
- 不同平台的核心编辑代码可复用,降低跨平台编辑器的开发成本;
- 具备系统级控制权限,极大地扩展了编辑器的能力组成(语音、图片编辑等);
4.1 如何实现一个跨平台的编辑器?
跨平台编辑器重点需要解决的问题有两点:
Native App 与运行在 webview 中的编辑器如何数据通信? Native 工具栏如何跟随光标位置呈现不同的状态?
首先,介绍下跨平台的编辑器,各模块之间是如何交互的。
- 编辑器会开放一些接口如 setData、getData、execCommand,供 Native APP 调用,向编辑器内添加内容。
- Native APP 也会向编辑器提供一些接口,如 viewLoad、requestMedia、updateBtnStatus。编辑器可以根据自身的状态,通过这些接口向 APP 内传递一些数据或者信号,使得 APP 内的各种控件状态得到刷新。
- Web 编辑器仅与 Native APP 建立通信,与服务端的数据交互交由 Native APP 完成。
下面,将介绍几个跨平台编辑器的核心场景实现,供大家参考。
4.1.1 页面初始化
跨平台编辑器的编辑页由 Native APP 和 Webview 中的 Web Editor 组成,那么意味着页面的初始化需要两个模块协同实现。
一般情况下 Native APP 中原生控件的渲染速度是要快于 Webview 的渲染,这里可以在 Editor loaded 之后,调用 Native APP 提供的初始化方法,将 Native APP 从 Loading 状态切换至完成态。
假设此时保存有草稿,可以在页面 Load 后,直接调用 Editor 暴露的 setData 方法,初始化编辑器数据。
4.1.2 数据通信
在编辑过程中,必然存在 Native APP 与 编辑器的双向通信,就以简单的插入表情为例,整个操作流程分为以下几个步骤:
1、点击表情按钮,从键盘状态切换至表情选择面板,此时都属于 Native APP 内部操作流程。 2、当点击某个表情后,就需要 Native APP 主动与编辑器建立通信,通知编辑器需要执行插入表情的操作。 3、编辑器接收到插入表情的指令后,插入 Native APP 流转过来的表情数据,同时触发了编辑器内部的状态刷新,比如说字数计算、历史记录的刷新。 4、由于现在的撤销、重做按钮已经不在编辑器内部,当历史记录刷新时,需要对按钮的状态进行重置。这个时候就需要编辑器调用 Native APP 提供的状态刷新方法,通知 Native APP 进行按钮状态的更新。
这样就完成了两个模块的双向数据通信。
4.1.3 媒体嵌入
媒体嵌入是富文本编辑器中必不可少的一部分,这里单独拿出来介绍,主要是因为跨平台的富文本编辑器在上传资源到服务端时,并不是常规的通过编辑器本身来实现的。那中间的处理逻辑是怎样的呢?
Native APP 图片选择的流程就不过多赘述,直接进入到选择之后上传的部分。Native APP 选择图片之后,直接会调用服务端的接口将图片上传,与此同时还会携带选择的图片信息(本地路径、宽高信息等等)传递给编辑器。
由于图片上传与图片插入存在一定时间差,所以编辑器在最初接收到插入图片的命令后,默认处理为 loading 状态,等待 Native APP 上传完成的信号。
当服务端接口返回图片加载完成的信息后,Native APP 调用编辑器预先提供的接口,控制编辑器中某张图片刷新为完成时态。这样就实现了资源的上传及插入:
4.2 踩坑实践了解一下!
当然,不是所有的事情都是一帆风顺的。我在开发过程中,也踩了一些坑,跟大家分享下。
4.2.1 键盘的控制
预告:由于我使用的 Web 编辑器仍然依赖浏览器的 contenteditable 特性,所以下面的案例,并不具备普适性,大家仅供参考。
基于 contenteditable 的编辑器,在光标插入的时候,会自动唤起手机端的输入法键盘。有些场景下,比如插入图片后,预期键盘处于关闭状态。但是在实际操作时,键盘会默认唤起,即系统键盘不受编辑器控制。
针对这种情况,我尝试了一些解决方案,最终选择采用双管齐下的方式,增加双重保险:
- 在 Editor 插入操作执行前,增加禁用编辑和启用编辑的切换,利用切换的时间差,将系统键盘的自动唤起机制失效。
editor.setAttribute('contenteditable', 'false')
setTimeout(() => {
editor.setAttribute('contenteditable', 'true')
}, 150)
- Native APP 提供控制键盘弹出、收起的方法,在 Editor 需要的时候调用系统能力实现控制自由。
JsBridge.call('updateKeyBoardState',
{ keyBoardState: true/false })JsBridge.call('updateKeyBoardState',{ keyBoardState: true/false })
4.2.2 资源的失败重试
在编辑器中,资源上传失败均会配备重新上传的机制。在跨平台编辑器中,重新上传需要在 WEB 编辑器中触发,交由Native APP 重新上传。Native APP上传图片的前提是拿到图片的本地路径。因此在前期设计时就需要重点关注以下几个点:
- Native APP 在调用编辑器插入图片的接口时,就需要告知图片对应的本地路径,也作为后续状态刷新、失败重试的参照条件。
- 要增加本地路径异常失效的处理(本地图片删除、移动等)。
读到这里,各位小伙伴对于如何实现一个跨平台的富文本编辑器,是否已经胸有成竹了呢。本节只是探讨了 Android APP 这一种平台的场景,对于其他的平台其实也是如此,比如桌面端平台(Windows、Mac)的客户端中,可以选择用 CEF(Chromium Embedded FrameWork)提供浏览器环境。感兴趣的小伙伴可以动手尝试一下。