富文本分为富文本编辑器、富文本解析器,而今天讲到的是富文本解析器。
一、我们小程序里富文本的使用情况
青团社兼职小程序里的职位详情页,在16年到现在,一直有个精品兼职的功能,显示内容为:运营配置的头图+运营包装的富文本内容。查看链接:https://m.qtshe.com/app/partdetails?partJobId=1255343 , 目前仅在一些品宣类型的活动职位详情里出现。
二、小程序里是如何解析富文本的
对于小程序内解析富文本的需求,小程序官方有提供对应的组件《rich-text》,传入nodes数组节点文件,rich-text组件就能做到解析并渲染出来html页面,而这个组件有几个小的缺陷:
- 只能做到解析展示,不支持video、audio等多媒体组件功能。
- 对于a标签无法做到点击跳转。
- 图片无法预览
nodes数组包含以下三个属性:
属性 |
说明 |
类型 |
必填 |
备注 |
name |
标签名 |
string |
是 |
支持部分受信任的 HTML 节点 |
attrs |
属性 |
object |
否 |
支持部分受信任的属性,遵循 Pascal 命名法 |
children |
子节点列表 |
array |
否 |
结构和 nodes 一致 |
需要注意:attrs属性内只支持 class 和 style 属性,不支持 id 属性
Page({ data: { nodes: [{ name: 'div', attrs: { class: 'wrapper', style: 'color: #00cc88;', id: 'container' // 这是错误的 }, children: [{ type: 'text', text: 'Hello World!' }, { name: 'span', attrs: { class: 'wrapper', style: 'color: #00cc88;', }, children: [{ type: 'text', text: '111' }], }], } }); <div>Hello world<span>111<span></div>
由于服务端返回的是html string源文件,不是被rich-text组件识别的 nodes 数组节点文件,这就需要我们对html string做一层DOM Parser。
三、如何实现DOM Parser
针对html string格式的富文本,我们需要将它转换为nodes数组,才能被小程序的富文本组件rich-text进行识别并渲染。这里我们会使用到一个官方提供的插件:mini-html-parser2。
- 首先安装依赖
npm install mini-html-parser2 --save
- 然后代码里引入
<rich-text nodes="{{nodes}}"></rich-text>
const html = `<div>青团社兼职</div>` import parse from 'mini-html-parser2'; Page({ data: { nodes: [], }, onLoad() { parse(html, (err, nodes) => { if (!err) { this.setData({ nodes, }); } }) }, })
为什么我们的小程序里没有使用到mini-html-parser2,主要是因为我们的支付宝小程序之前是使用的wxParser插件,目前该插件已停止维护。微信小程序使用的是Parser插件【目前已改名mp-html】,一个由个人开发者维护的项目。(已邀请作者支持了支付宝小程序版本)
四、Parser [mp-html]的实现原理及使用指南
目前支持、微信、QQ、百度、支付宝、头条,支持第三方的Taro、chameleon、remax、kbone等框架。
mp-html插件的优点:
- 支持video、audio、table等组件的使用
- 支持a标签的锚点跳转、及navigateTo webview的跳转 <a herf="https://xxxx" >
- 支持rpx单位
- 支持占位图、图片预览、图片长按菜单显示
- 支持svg
- 稳定性强
具体稳定性强在哪:
- 标签名中可以含有 : 等特殊字符(如 o:p)
- 标签名和属性名大小写不敏感
- 属性值可以不加引号、加单引号、加双引号,也可以缺省(默认 true)
- 属性之间可以没有空格(通过引号划分)、有空格(可以多个)、有换行符
同时,对于一些错误情况,程序也能够自动处理:
- 标签首尾不匹配
- 属性值中冒号不匹配
- 标签未闭合
以下情况均能正确解析:
<!-- 不同的属性格式 --> <font face="宋体" color='green' size=7>Hello</font> <!-- 标签首尾不匹配或未闭合 --> <div> World</section> <!-- 大小写搭配 --> <dIv StYle="color:green">!</DIv>
说这么多,该直接上手了:
访问(https://github.com/jin-yufeng/mp-html/tree/master/dist/mp-alipay)将源码下载下来,拷贝到小程序的components目录,同时引入该组件,使用方法如下
<mp-html content="{{html}}" />
const html = `<div>青团社兼职</div>` Page({ data: { html }, onLoad() { }, })
最后了解下 实现原理:
通过html2nodes将html string转换为nodes数组后,根据遍历的当前node属性为‘img、span等html标签’进行判断,将匹配到的template模板进行循环渲染。
<template name="el"> <block a:if="{{n.name==='img'}}"> <image a:if="{{(opts[1]&&!ctrl[i])||ctrl[i]<0}}" class="_img" style="{{n.attrs.style}}" src="{{ctrl[i]<0?opts[2]:opts[1]}}" mode="widthFix" /> <image id="{{n.attrs.id}}" class="_img {{n.attrs.class}}" style="{{ctrl[i]===-1?'display:none;':''}}width:{{ctrl[i]||1}}px;height:1px;{{n.attrs.style}}" src="{{n.attrs.src}}" mode="{{!n.h?'widthFix':(!n.w?'heightFix':'')}}" lazy-load="{{opts[0]}}" data-i="{{i}}" onLoad="imgLoad" catchTap="imgTap" onLongTap="noop" /> </block> <text a:elif="{{n.name==='br'}}">\n</text> <view a:elif="{{n.name==='a'}}" id="{{n.attrs.id}}" class="{{n.attrs.href?'_a ':''}}{{n.attrs.class}}" hover-class="_hover" style="display:inline;{{n.attrs.style}}" data-i="{{i}}" catchTap="linkTap"> <template is="node" data="{{childs:n.children,path:i+'_',opts:opts,ctrl:ctrl}}"></template> </view> <video a:elif="{{n.name==='video'}}" id="{{n.attrs.id}}" class="{{n.attrs.class}}" style="{{n.attrs.style}}" autoplay="{{n.attrs.autoplay}}" controls="{{n.attrs.controls}}" loop="{{n.attrs.loop}}" muted="{{n.attrs.muted}}" object-fit="{{n.attrs['object-fit']}}" poster="{{n.attrs.poster}}" src="{{n.src[ctrl[i]||0]}}" data-i="{{i}}" /> <audio a:elif="{{n.name==='audio'}}" id="{{n.attrs.id}}" class="{{n.attrs.class}}" style="{{n.attrs.style}}" author="{{n.attrs.author}}" controls="{{n.attrs.controls}}" loop="{{n.attrs.loop}}" name="{{n.attrs.name}}" poster="{{n.attrs.poster}}" src="{{n.src[ctrl[i]||0]}}" data-i="{{i}}" /> <rich-text a:else id="{{n.attrs.id}}" style="{{n.f}};display:inline" nodes="{{[n]}}" /></template> <template name="node"> <block a:for="{{childs}}" a:for-item="n" a:for-index="i" a:key="i"> <template a:if="{{!n.c}}" is="el" data="{{n:n,i:path+i,opts:opts,ctrl:ctrl}}" /> <view a:else id="{{n.attrs.id}}" class="_{{n.name}} {{n.attrs.class}}" style="{{n.attrs.style}}"> <template is="node" data="{{childs:n.children,path:path+i+'_',opts:opts,ctrl:ctrl}}"></template> </view> </block> </template> <template is="node" data="{{childs:childs,path:'',opts:opts,ctrl:ctrl}}"></template>
以上就是本期分享的内容,欢迎大家留言讨论~