小程序富文本渲染那些事
1. 背景
在小程序业务开发中,常常会遇到各种各样的“配置化”需求,如商品详情展示、会员权益说明、推广落地文章等等,这些需求都没有固定的模板,需要运营人员在 B 端使用富文本编辑器自定义配置内容,然后由小程序端进行展示。
这就要求小程序提供足够的字符串解析能力,小程序方也提供了rich-text
组件来满足富文本渲染的需求,但其存在不支持部分语义化标签、不支持交互、不支持事件点击、不支持音视频标签等等不足,导致使用范围受限。
github 也开源了多个小程序富文本组件,其中mp-html因功能强大,使用最为广泛。
但在不同的业务开发中,研发人员在 B 端所使用的富文本编辑器不尽相同,富文本标签使用的侧重点也略有不同,且涉及到小程序性能方方面面的优化。因此mp-html
组件并不是拿来即用,需要根据具体的业务需求进行 定制化的“改造”。那么,了解富文本组件的渲染原理,掌握各种富文本标签的优化方法,就尤为重要,这也是本文的重点阐述内容。
2. 官方rich-text
组件
根据小程序开发者文档,rich-text
组件支持传入html string
和nodes 数组
两种格式。如下图所示,要使用rich-text
组件渲染富文本,可以传入左边的html string
或者右边的nodes 节点
。
可世界上哪有“我全都要”的福分,必定是有人在背后默默负重前行。根据官方Tips
说明,nodes
不推荐使用 String
类型,性能会有所下降。 可以推测,rich-text
组件底层必定封装了一个 HTML 字符串解析器,将html string
解析成易于处理的数据结构,即nodes 节点
。
事实的确如此,根据文章《LLParser: Web 环境下高性能 Parser 生成器及对 asm.js 的应用》,我们了解到,小程序组件系统内核
exparser
中封装了一个LLParser生成器,可以根据语法定义生成各种各样的字符串解析器。LLParser
所生成的最重要的一个解析器是 HTML 解析器,用于小程序的rich-text
组件和一些预编译期的分析。这个 HTML 解析器可以将html string
解析成易于处理的数据结构,以便rich-text
组件进行安全过滤。
nodes 节点
的数据结构如下所示,
属性 | 说明 | 类型 | 必填 | 备注 |
---|---|---|---|---|
name | 标签名 | string | 是 | 支持部分受信任的 HTML 节点 |
attrs | 属性 | object | 否 | 支持部分受信任的属性,遵循 Pascal 命名法,如style 、class |
children | 子节点列表 | array | 否 | 结构和 nodes 一致 |
出于安全性考虑,HTML 解析器仅解析部分受信任的节点,如果传入的html string
字符串中有不受信任的 HTML 节点,该节点及其所有子节点将会被移除。这会导致 rich-text
组件不支持部分语义化标签,造成部分富文本内容丢失的事故(缺陷 1)。
HTML 解析器在解析过程中,仅会为对应的html
标签添加部分受信任的属性,如style
、class
等,其它属性都会被过滤掉,且不支持id
。这就导致 rich-text
组件屏蔽了交互,如无法支持图片预览、链接跳转、锚点、事件点击等等(缺陷 2)。
由于nodes 节点
的数据结构局限性问题,rich-text
组件内的img
标签仅支持网络图片,不支持 base64,不支持 svg。对于table
标签,仅支持width
属性,对于嵌套复杂标签的表格、单元格合并表格则表现得束手无策。出于安全性问题,rich-text
组件过滤掉所有的媒体标签,如video
、audio
等音视频标签,导致富文本功能大大受限(缺陷 3、4、5)。
3. wxParse 组件
前面提到过,小程序组件系统内核exparser
中封装了一个LLParser
生成器(c
+javascript
),由此衍生的HTML 解析器可以将 html 字符串解析成代码容易理解的数据结构(node tree
)。这有点像浏览器解析 html 文档的过程,将 html 文档解析成DOM tree
。《How Browsers Work: Behind the scenes of modern web browsers》这篇神作详细阐述了HTML Parser
的过程,感兴趣可以详细了解。
这给我们启发,能否借助开源社区成熟的HTML Parser
解析器,将html string
解析成我们预期的数据结构即node tree
,然后通过迭代渲染的方式,将node tree
渲染成小程序支持的各种标签。
早期流行的wxParse 组件便是这么玩的,wxParse
组件通过正则匹配的方式解析html string
为node tree
(节点数组),然后迭代遍历数组,将每个 html
标签渲染成对应的小程序标签。
但这个方案也存在以下三个致命的缺陷:
一是容错性低,wxParse
组件的解析脚本,是通过 正则匹配 的方式解析,一旦出现错误的字符串不满足匹配规则,就会解析为文本,导致显示错误。
二是层级过深无法显示,为了达到性能和准确性的最佳平衡,wxParse
组件设置了最大层数,当要渲染的node tree
的层级超过设置的最大层数时,这些该层级下所有子节点标签就会被抛弃而无法显示,导致显示内容遗失。
三是功能限制,WxParse
对于 table
、ol
、ul
等支持性较差,类似于表格单元格合并、有序列表、多层列表等都无法渲染。对于音视频渲染也表现得束手无策。
4. mp-html 组件
4.1 实现方案
根据上述经验,借助HTML Parser
解析器可将html string
转换成我们想要的数据结构node tree
,然后通过迭代遍历的方式渲染每个节点对应的标签。但是当node tree
的层级过深时,对小程序性能的挑战是十分严峻的。
鉴于此,mp-html
组件的作者,提出了一个两全的方案:迭代遍历node tree
,每遍历到一个节点,就查询该节点的所有子节点是否包含图片、音视频、链接、表格、列表等需要“定制”的标签。如果不包含,则直接用rich-text
组件渲染该节点及其所有子节点。如果包含,则使用view
渲染该标签,并继续遍历其子节点。
如下图所示,遍历到第一层的节点div
,由于该节点的子孙节点包含img
、a
这两个需要“定制”的标签,则将div
标签渲染成view
标签,然后继续往下遍历其子节点。遍历到第二层的第一个节点h1
,由于该节点的子孙节点不含需要“定制”的标签,则直接用rich-text
组件渲染。第二个节点p
的所有子孙节点也不包含需要“定制”的标签,也用rich-text
组件渲染。遍历到第三个节点p
,该节点的子节点包含img
、a
这两个需要“定制”的标签,则将p
标签渲染成view
标签,然后遍历其子节点,将img
标签渲染成小程序支持的image
标签,将a
标签用小程序支持的方法实现。
通过这种方案,可以直观的发现,迭代的次数减少了,渲染的标签数变少了,可以显著提高渲染效率。
4.2 htmlparse2 原理
mp-html
组件底层采用目前市面上性能最佳的 JS 语言编写的 html 解析器htmlparser2来解析富文本字符串。
据了解,小程序组件系统内核
exparser
封装的LLParser
生成器,虽然在性能对比上相较于htmlparser2
略胜一筹,但是LLParser
生成器的核心算法使用 C 语言实现的,并不适用,且它并未对html
解析进行针对性的优化。而htmlparser2
是业内顶流的html
解析器,历经多次迭代,且使用Javascript
编写,完全符合开发小程序富文本渲染组件的业务场景。
htmlparser2
工具包底层封装了lexer
和parse
两个类,lexer
的作用就是将输入的内容转换成合法的 tokens
,比如开始标签、结束标签、属性名、属性值等等。parser
的作用就是根据语法规则分析字符串的结构进而构造 node tree
。
lexer
采用状态机的方式解析字符串。每个状态都会识别一个或多个字符,然后根据字符的结果来更新下一个状态,每一步都会受到当前识别字符的状态和Tree Construction
的影响。
解析的过程是不断重复的,通常 parser
会向 lexer
请求 token
,并且尝试将 token
与某一条语法规则匹配。如果匹配的话,与 token
对应的节点将会被添加到 node tree
,parser
继续向 lexer
请求 token
。
当然,如果 token
暂时没有匹配到规则的话,parser
会将 token
先保存在栈中,继续请求其他的 token
直到有规则可以匹配到存储在内部的 tokens
。通俗来讲,就是通过栈来存储未闭合的标签,标签闭合就出栈。如果最后确实还是没有匹配到规则的话,则 parser
将抛出异常,这意味着文档中存在着语法错误,可在对应的钩子函数进行错误处理。
可知,parser
类主要负责node tree
的构建,那么在node tree
的构建过程中,parser
暴露了许多不同状态的钩子函数,mp-html
组件底层正是对htmlparser2
包进行二次改造,在不同的钩子函数中针对不同的标签定制化逻辑处理,最终得到自己想要的数据结构。
想要进一步了解htmlparse2
的解析原理,可以查看html-parser。
4.3 mp-html 组件精妙之处
4.3.1 减少渲染节点数
比如,如何判断某个节点下的子节点是否包含图片、音视频、链接、表格、列表等需要“定制”的标签 ?这里运用到的原理是parser
解析过程中会将未闭合的标签节点先保存在栈中,也就是解析到某个需要“定制”的标签时,此刻栈中存储的节点都是该标签的父节点以及祖先节点,将栈中所有的标签都打上一个标记(比如continue
属性),就表示这些节点的子孙节点或者其本身包含需要“定制”的标签。
如上图所示,遍历到img
标签时,该标签是需要“定制”的,则将其父/祖先节点p
->div
都打上标记continue
属性为true
。当然a
标签也是同理。
通过这种方式,迭代遍历node tree
时,如果该节点的continue
属性为false
,则表示该节点的子孙节点不包含需要“定制”的标签,那么直接用template
渲染。相反,则用view
标签渲染,并继续迭代遍历。
4.3.2 优化遍历层级
之前提到过,通过continue
属性来判断是否用view
组件渲染该节点并继续迭代。对于不继续迭代的节点,要么渲染成“定制”的标签,要么渲染成rich-text
组件,我们可以将其抽象成一个template
。
封装一个use
工具函数,判断该标签是使用view
标签渲染并继续迭代,还是使用template
渲染。判断逻辑如下:
- 对于子节点拥有需要“定制”的标签,该节点渲染为
view
标签,继续迭代。 - 对于不存在子节点或者当前标签为
a
标签,用template
渲染成“定制”的标签。 - 对于存在子节点,如果当前节点是内联标签或者存在内联样式,无法直接用
rich-text
组件渲染(rich-text
组件不支持内联样式导致),则使用view
标签渲染,并继续迭代。相反,则用template
渲染成rich-text
组件。
template
的实现逻辑如下图所示,对img
、br
、a
、video
、audio
等标签以及文本进行定制渲染,其它则使用rich-text
组件渲染。
按照之前层级遍历的逻辑,我们可以实现代码如下,使用wx:for
层级遍历节点,通过use
函数判断该节点是否使用template
渲染,如若不用,则使用view
标签渲染,并迭代遍历下一层级。这里需要注意的是,每个template
都传入了i
属性,可以通过data-i
绑定到对应的标签上,如第一层就是i1
,第二层就是i1_i2
,以此类推,通过这个属性,可以在逻辑层事件处理函数中,通过e.currentTarget.dataset.i
获取到当前事件触发节点的索引,遍历node tree
获取到对应的节点,从而减少渲染层和逻辑层的数据传递消耗。
然而,解析的node tree
的深度是不可控的,按照层级遍历的写法,哪怕写了 20 层,也有可能会“抛弃”很多节点。为了突破层级限制,这里借鉴函数递归的思路,将该富文本节点渲染组件名命名为node
组件,当遍历到第 5 层时,如果仍然无法使用template
渲染,那么引用自身组件node
渲染。
4.3.3 不信任标签渲染
另外,之前提到过,rich-text
组件解析到不信任的节点,该节点及其所有子节点将会被移除,导致渲染的富文本内容缺失。
那么如何解决这个问题呢?
可以在parser
过程中 onCloseTag
钩子函数(解析到标签结束) 中对不信任的标签进行转换,力求尽可能的显示文本内容。比如,对于不信任的块级标签,如address
、article
、aside
、body
、caption
、center
、cite
、footer
、header
、html
、nav
、pre
、section
等,转换为div
标签。而对于其它不信任的标签,转换成span
内联标签,力求尽可能完整的显示文本。
除此之外,mp-html
在parser
类的原型上定义了多个钩子函数,如下图所示,其中,onOpenTag
、onCloseTag
函数,针对需要“定制”的标签,做了很多精妙的逻辑处理,在后续每个“定制”标签优化原理的介绍中作者会具体阐述。
函数名 | 说明 | 主要处理逻辑 |
---|---|---|
onTagName | 解析标签名 | 转换成小写,解决大小写不敏感的问题 |
onAttrName | 解析属性名 | 处理data- 等属性,转换成对应的属性名 |
onAttrVal | 解析属性值 | 部分属性实体解码、拼接域名 |
onText | 解析文本 | 合并空白符、实体解码 |
parseStyle | 解析样式表 | 转换 rpx 单位,转换 width、height 等属性 |
onOpenTag | 解析到标签开始 | 不同标签定制逻辑处理,添加需要的属性,入栈 |
onCloseTag | 解析到标签结束 | 不同标签定制逻辑处理,转换属性,出栈 |
4.3.4 样式设置
前两小节提到过,rich-text
组件不支持内联样式,比如如下写法:
在这种情况下,虽然对 rich-text
中的顶层 div
设置了 display:inline-block
,但没有对 rich-text
本身进行设置的情况下,无法实现行内元素的效果,类似的还有 float
、width
(设置为百分比时)等情况。
解决方案,就是在Parser
过程中将顶层标签的 display
、float
、width
等样式提取出来放在 rich-text
组件的 style
中。
那么问题来了,我们解析的style
属性是字符串格式如style='display:inline-block;padding:10px;color:#ff00ff;'
,如何解决样式属性名冲突的问题?
这时候,{key: value}
的数据结构就派上用场了。在parseStyle
钩子函数中,使用类似状态机的方式匹配style
属性的样式,将其转换为{key: value}
的数据结构,对重复的样式名进行覆盖,同时转换rich-text
组件无法识别的rpx
单位。
那么如何实现子节点的样式提升呢?
在onCloseTag
解析到标签闭合的钩子函数中,对特定的节点进行操作,此时栈中保存该节点的父/祖先节点,当该节点遇到特定样式时,对栈中元素的style
属性进行对应的操作,由于是{key: value}
的数据结构,所以能有效解决样式覆盖的问题。在节点出栈前,再将{key: value}
的数据结构转换成字符串,赋值给节点的attrs.style
属性。
如此处理,不仅仅解决样式提升问题,还能及时修正父节点的不合理样式。在onOpenTag
解析到标签开始的钩子函数中,比如,对于img
标签,如果标签样式存在flex:1
且不设置宽度,那么该样式的宽度就应该设置为100% !important
,同时其父/祖先节点不应该包含内联样式。
5. img 标签富文本渲染
5.1 存在问题
根据小程序开发者文档,rich-text
组件传入的img
标签仅支持class
、style
、alt
、src
、width
、height
六个属性。
用rich-text
组件渲染img
标签存在以下几个问题:
- 不支持交互,包括图片预览,图片长按保存。
- 不支持图片缩放、图片压缩。下载原图导致图片加载慢,占用带宽资源。
- 不支持懒加载。图片较多时会影响性能。
- 仅支持网络图片,不支持 svg,不支持 base64。
- 用户体验太差。图片加载过程中未显示标签,图片加载失败显示图裂了。
- 移动端适配差。图片宽度大于屏幕逻辑像素,会溢出屏幕。如设置宽度自适应,容易导致图片变形。
5.2 优化方案
实现原理是通过htmlparse2
解析成nodes 节点
数据结构,然后迭代遍历渲染各个节点标签,遇到标签名为img
的标签,渲染自定义的template
。
在parser
过程的onOpenTag
解析到标签开始的函数中,如果解析到img
标签,则执行以下几步操作:
- 当前栈中是该
img
标签的父/祖先节点,由于img
标签是需要自定义渲染的标签,遍历栈中的所有父/祖先节点,标记continue
属性为true
。并遍历他们的样式,如遇到flex
、inline-block
样式则需修改本img
标签的本身的样式,以及父/祖先节点对应的样式修复。 - 维护一个
imgList
数组,用于图片点击预览,每当遍历到一个img
标签,将该标签的src
属性push
进imgList
数组。由于预览调用的wx.previewImage
传入的current
属性表示当前图片的链接,那么富文本如果存在多张相同链接的图片,就可能导致图片错位。解决方法就是在图片push
进imgList
数组之前,判断是否重复,如若重复,则对图片域名进行随机大小写。 - 判断
img
标签是否传入width
、height
属性。当两者都存在合法的值时,mode
使用scaleToFill
(缩放模式,不保持纵横比缩放图片,使图片的宽高完全拉伸至填满 image 元素),其它情况则这默认为widthFix
(缩放模式,宽度不变,高度自动变化,保持原图宽高比不变)。同时,由于组件设置了max-width
兜底,当图片设置的宽度超出屏幕,为强制改成max-width
,那么按照scaleToFill
规则缩放会导致图片变形。所以当图片宽度超出屏幕时,则去掉高度,让其按widthFix
规则缩放。
在parser
过程的onCloseTag
函数中,对svg
标签的内容进行转换,添加 mime
头部,变成 Webview
可以识别的 Data URI
,便可使用image
组件渲染svg
标签,具体可了解《小程序里显示 svg 的方法》。
由于小程序image
组件不支持width
、height
属性传入,所以在parser
过程的parseStyle
函数中将这两个属性的值拼接到style
样式中。最终,nodes 节点
数据结构如下表格所示。
属性名 | 二级属性名 | 说明 | 值类型 | 默认值 |
---|---|---|---|---|
name | - | 标签名 | string | img |
attr | id | 跟rich-text 不同的是可以解析到标签的id 属性 | string | - |
attr | style | 样式,width 、height 整合到style 里面 | string | - |
attr | class | 类 | string | - |
attr | src | 图片链接 | string | - |
mode | - | 图片裁剪、缩放的模式 | string | scaleToFill /widthFix |
index | - | 图片索引,用于预览点击 | number | - |
拿到想要的数据结构后,便可以自定义img
标签渲染的template
。
5.2.1 图片处理
通常情况下,B 端富本文编辑器引入的素材图片,在上传素材库时,都是未进行图片处理的。因此小程序端渲染富文本时,img
标签引用的图片链接下载的都是高质量的原图,当下载图片数量过多时,就会造成带宽浪费,影响性能。
在实际的富文本展示中,也不需要展示过于“高清”的图片。针对这个问题,腾讯云对象存储通过数据万象 imageMogr2 接口提供图片处理功能。
可以在wxs
中封装一个图像处理函数imageMogr2(src,options)
,image
标签src
属性的值进行转换。
如上代码所示,如果img
标签传入width
和height
属性,为了防止图片变形,需要对图片进行裁剪。当图片实际像素大于传入的width
和height
属性,需要对图片进行缩放,前提是image
组件的webp
属性为true
。开启webp 压缩。当然也可以添加其它处理,比如去除元信息、对.jpg
文件进行渐进式加载等等。
5.2.2 体验优化
parser
解析时,已经将所有图片的链接放入imgList
数组进行维护,给image
组件绑定data-index
属性,当点击图片触发catchtap
事件时,可根据index
索引找到该图片的链接,调用wx.previewImage
函数进行图片预览。
当image
组件的lazy-load
属性设置为true
,可开启图片懒加载,在即将进入一定范围(上下三屏)时才开始加载。
当image
组件的show-menu-by-longpress
属性设置为true
,支持长按图片显示发送给朋友、收藏、保存图片、搜一搜、打开名片/前往群聊/打开小程序(若图片中包含对应二维码或小程序码)的菜单。
如果富文本内容全部(或大部分)是图片,由于其图片未加载时大小为零,即使数量很多也会全部进入视图范围,导致懒加载失效。所幸,image
标签也支持binderror
、bindload
事件绑定,可在其中添加逻辑,当图片未加载完成时,显示占位图,当图片加载失败时,显示加载出错的占位图。
6. a 标签富文本渲染
6.1 存在问题
前面提到过,rich-text
渲染a
标签,会过滤掉href
属性,仅支持class
和style
属性,也就是说,会将a
标签渲染成普通文本,从而失去跳转功能。
6.2 优化方案
parser
过程解析的a
标签的数据结构如下所示:
属性名 | 二级属性名 | 说明 | 值类型 | 默认值 |
---|---|---|---|---|
name | - | 标签名 | string | a |
attr | id | 跟rich-text 不同的是可以解析到标签的id 属性 | string | - |
attr | style | 样式 | string | - |
attr | class | 类 | string | - |
attr | href | 跳转链接 | string | - |
children | - | node 节点数组 | array | - |
在template
模板中,如果该节点是a
标签,则使用如下渲染:
使用view
标签来模拟a
标签的效果,定义_a
和_hover
两个默认类来模拟a
标签的原始样式和hover
效果。绑定catchtap
事件实现链接点击效果,这里通过data-i
属性传入当前节点索引,可在逻辑层遍历直接获取该节点。由于a
标签嵌套的子节点的内容不可预知,所以使用node
组件渲染子节点。
在linkTap
函数中,进行逻辑处理。支持以下几种跳转逻辑:
-
锚点跳转。支持跳转内部锚点,给
a
标签的href
属性设置为#id
,点击时即可跳转到对应id
的位置(设置为#
则跳转到开头)。 -
跳转内部路径。如果需要点击
a
标签跳转到小程序内的一个页面,直接将其href
属性设置为页面路径即可通过wx.navigateTo
或wx.switchTab
跳转到对应页面。 -
复制外部链接。对于外部链接,由于小程序无法直接打开,使用
wx.setClipboardData
将自动复制到剪贴板。
在实际应用中,锚点跳转包括页面锚点跳转和容器锚点跳转两种情况,对于容器锚点跳转,需要将锚点的跳转范围限定在容器内,常见容器为scroll-view
,那么就需要向mp-html
富文本渲染组件传入以下三个参数。
参数名 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
page | object | 是 | - | scroll-view 标签所在页面实例 |
selector | string | 是 | - | scroll-view 标签 的选择器 |
scrollTop | string | 是 | - | scroll-view 标签 scrollTop 属性绑定的变量名 |
比如在scroll-view
中显示富文本,写法如下:
那么可以在当前页面,根据 id=“article”获取mp-html
富文本渲染组件实例,调用in
方法传入三个参数,如下所示:
// 三个参数分别表示: page、selector、scrollTop
ctx.in(this, '#scroll', 'top');
那么锚点跳转实现逻辑如下图所示,this._in
存在值表示是在容器中进行锚点跳转,相反则是在页面。两者的实现逻辑略有不懂,原理都是获取要跳转的元素相对于页面/容器的scrollTop
值。对于容器锚点跳转则改变scroll-view
的scroll-top
属性进行滚动定位,对于页面锚点跳转则通过wx.pageScrollTo
方法进行滚动定位。
7. 其它定制标签优化
7.1 表格
当表格宽度过大时,超出正常手机屏幕的宽度,就会撑开容器的宽度,导致整个富文本内容横向滚动,从而影响用户体验。这种情况下,可以在onCloseTag
解析到标签闭合的钩子函数中,给表格节点包裹一个可以横向滚动的容器,比如view
标签,设置其样式为overflow-x:auto;padding:1px
。
由于小程序不支持table
标签,对于不同的table
,可以采用不同的渲染方案,如下所示:
显示方式 | 适用情况 | 说明 |
---|---|---|
rich-text 标签 | 表格内部没有链接、图片等特殊标签 | 效果最佳,几乎不需要进行转换 |
table 布局 | 表格内有特殊标签但没有使用合并单元格 | 需要进行一定转换,将 table , tr , td 等标签转为对应的布局 |
grid 布局 | 表格内有特殊标签且使用了合并单元格 | 需要进行复杂的转换将合并单元格用 grid 布局表现出来 |
7.2 列表
rich-text
组件无法支持列表多层嵌套,可以通过预定义样式来支持嵌套多层列表,对于无序列表,不同的层级会显示不同的样式,通过list-style-type
实现。
同时可以通过对外暴露type
属性,来支持用户选择显示数字、字母、罗马数字等多种形式的标号。也可以通过设置设置 list-style:none
的方式不显示 li
标签开头的标号。
7.3 音视频
通过htmlparse2
可以将embed
标签转换成对应的音视频标签,同时给音视频标签设置id
,用于获取上下文。用数组_source
存储所有可用的source
,用数组_videos
存储所有音视频实例。
在存在多个视频的情况下,同时播放可能会影响体验,可以在播放视频的回调中,通过获取当前事件的id
,通过遍历_videos
数组播放当前视频,同时暂停其它视频。
不同平台支持播放的格式不同,只设置一个 src
可能会出现兼容性问题导致无法播放,因此本组件支持像 html
中一样给 video
和 audio
设置多个 source
,将其遍历存储到_source
数组中,将按照顺序进行加载,直到可以播放,最大程度上避免无法播放。
8. 最后
本文基于业务需求对mp-html
组件进行改造,阐述了该组件的设计思路以及部分标签渲染的优化点。组件原作者也分享过文章《小程序富文本能力的深入研究与应用》,同时代码也在 github 平台上开源。想了解关于表格、列表、音频、视频等标签的更多优化思路,可以自行查阅源码。
创作不易,点个赞再走吧 ೭(˵ᴛ ʏ ᴛ˵)౨
转载自:https://juejin.cn/post/7077747390664900621