一、rich-text组件

nodes: [{name: 'div',attrs: {class: 'div_class',style: 'line-height: 20px;padding:20px;'},children: [{type: 'text',text: '小程序实践'},{name: 'img',attrs: {src: 'http://t.cn/A622uHwp',style: 'width:100%'}}, {name: 'img',attrs: {src: 'http://t.cn/A622upBw',style: 'width:100%',// ,style:'width:100%;font-size:0;display:block;'//修改样式class: 'img'}}, {name: 'img',attrs: {src: 'http://t.cn/A622upBw',style: 'width:100%'}}]
}],

我们先看一下rich-text这个组件,它只有两个属性,nodes 和 space。
space代表空格策略,控制中文空格显示的大小,有三种值,在中文环境直接取emsp就好。
nodes可以取字符串,也可以取数组。但是取字符串的话一般会影响性能,所以一般情况下我们都是使用数组。

在nodes属性中有这样一些子属性,
name表示节点名称,例如p、div、span、img等等,大部分HTML标签都受支持,
attrs表示节点的属性,是定义在HTML标签上的属性,例如img标签的src、width、height属性等等,
children代表子节点列表,是一个数组,type代表节点类型,共有两种,node 与 text,默认是node,可以不写。
当类型是node时,有children属性。
如果是text,则只有一个text属性,text节点只能包括纯文本。

nodes是一个数组,数组中每个元素都可以是复合的node节点,也可以是末节的text节点。
这是一个树状结构,简单分辨节点类型的方法,
就是看节点有没有name属性,name代表标签名称。
有name代表是复合节点,如果没有并且type属性为text,代表的是简单的文本节点。
当时text节点时,它代表的是最基本的文本,没有样式,它所有的样式都是来自父节点的设定。

在Vue或WXML的模板中,它类似与带花括号的{{message}},
这样一个纯文本节点,解析到虚拟DOM列表中,都是一个独立的节点,都是可以直接改变内容的。

如果不是text节点,必须有一个name属性,例如一个img节点,
这相当于是一个img HTML标签,可以翻译为对等的HTML代码,从MDN文档中可以查到,img标签还有其他属性,
比如width、height、alt、ismap、longdesc、usemap等,这些HTML定义的属性,原则上都可以在node里定义,
但是在使用之前我们最好先查一下微信小程序rich-text组件的文档,里面有一个受信任的HTML节点及属性列表,
看看我们准备使用的属性,在不在支持的范围里,如果使用了不受信任的HTML节点,
该节点及所有子节点将会被移除,不是忽略,而是被移除,这可能会造成不易被发现的bug。

二、相关问题

2.1 如何预览保存rich-text富文本组件中的图片

如果可以拿到单击事件,以事件的currentTarget取到目标组件,再判断目标组件是不是img,如果是的话,取它的src属性,拿到图片的链接就可以实现预览和下载图片了,
但是rich-text中的所有节点,都是屏蔽单击事件的,所以呢通过添加网页的click事件,再对事件对象进行处理,这个方法是行不通的。

但我们仍然可以在rich-text组件上添加tap事件,在事件函数中,使用wx.previewImage这个接口预览图片,然后选择需要的图片下载。
在预览之前我们需要遍历rich-text中的nodes数据,将所有图片地址预先取出来,当单击rich-text富文本组件时,触发预览,
在tap事件句柄中,事件e是一个TouchEvent对象,使用它的pageX、pageY属性,还可以取到用户大概单击了什么位置,
如果以位置可以判断出图片是哪一张,就可以在调用wx.previewImage预览图片时,
作为第一个参数传递进去,这样就可以实现指定图片的预览与下载了。
如果不能确定,取第一张就可以了


<rich-text space="emsp" nodes="{{nodes}}" bindtap="tap"></rich-text>
Page({data: {// 示例 1 代码nodes: [{name: 'div',attrs: {class: 'div_class',style: 'line-height: 20px;padding:20px;'},children: [{type: 'text',text: '⼩程序实践'}, {name: 'img',attrs: {src: 'https://p.qqan.com/up/2021-6/16232893724729414.jpg',style: 'width:100%'}}, {name: 'img',attrs: {src: 'https://p.qqan.com/up/2021-6/16232893151517011.jpg',style: 'width:100%'}}, {name: 'img',attrs: {src: 'https://p.qqan.com/up/2021-6/16232893157513212.jpg',style: 'width:100%'}}]}],urls: [],},tap(e) {let urls = this.data.urlswx.previewImage({current: urls[0],urls: urls})},onReady() {// 取出 urlsfunction findUrl(nodes) {let urls = []nodes.forEach(item => {if (item.name == 'img' && item.attrs) {for (const key in item.attrs) {if (key == 'src') {urls.push(item.attrs[key])}}}if (item.children) {urls = urls.concat(findUrl(item.children))}})return urls}this.data.urls = findUrl(this.data.nodes)},
})

2.2 在富文本rich-text中如何解决图片之间的间隙问题

如果rich-text中有多张图片,上下图片间会有间隙,这是样式问题,
在HTML5开发中,我们可以通过查找img标签,然后修改它的样式,通过这样的方式可以解决这个问题,
但是在小程序中不可以。

小程序的rich-text是通过Web Component实现的,它不允许外部修改内部img元素样式,
我们可以直接修改nodes数据中的img样式,给它添加两个内敛样式,
这个缝隙是行内容引起的,通过设置元素为块元素,并设置字体为0,图片间的间隙就没有了。

如果每个img节点都添加内敛样式比较麻烦,
还可以在wxss文件中声明一个通用的类样式,然后在每个img节点上添加这个类样式名称。

2.3 在富文本rich-text里面怎么插入ad广告标签,如何将HTML文本解析呈现?

有人说nodes数据格式需要手动创建与维护比较麻烦,有没有办法直接将HTML源码解析并呈现呢?

答案是有的,有一个开源组件,可以直接解析HTML源码并在小程序中直接呈现,
使用方法也很简单,首先是下载源码,将parse文件夹放到项目中的适当位置,
接着在json配置文件中引用组件,再接着准备好数据,最后在WXML中使用,这样就可以了。
这个组件支持给每个特定的HTML标签,定义它自己的样式,
这个功能是通过tag-style属性完成的。


我们定义了img的无间隙样式,所以上下图片之间已经没有间隙了,这也算是问题二的另一种解决方案

使用parser组件解析HTML文本,并解决 问题2 的间隙问题


parser源码

通过对parser源码的查看,它对HTML源码文本的解析,是通过一个一个字符去解析的。
通过有限的switch遍历生成对应的nodes数据,
parser支持的组件列表是在代码内部写死的,它不能解析的tag,它也一定不能呈现。

在这个组件内部,有一个trees的自定义组件,在这个组件中对上一步生成的nodes数据做了绑定渲染。
parser的nodes数据,与rich-text的nodes并不是对等的,它是大于后者的。

parser不仅支持rich-text,还支持audio、video等标签,
并且它还支持递归,也就是说它支持div容器的嵌套,
它还支持广告,将unit-ad传递给它,它就能解析并呈现出一个Banner广告。
该功能具体是通过ad这个小程序组件实现的。

Comment函数和Text函数

matchAttr函数

支持解析哪些标签

单击图片时候,对外层派发的imgtap事件


还有 问题1 关于单击图片放大,下载的问题,通过这个组件可以完美地得到解决。并且不需要预先从nodes数据中解析urls。
因为parser这个组件对图片的呈现,是通过小程序的image组件实现的,所以它原生支持单击放大与下载。

从源码可以看出parser支持图片放大预览,并且单击时还向外层派发了imgtap事件,
我们在使用改这个组件的时,可以添加一个对imgtap事件的监听,
在运行时就能拿到被单击图片的网址了。

使用parser组件,添加imgtap事件,拿到具体图片的地址

parser这个开源组件库,不依赖于第三方库,自实现了词法解析和DOM重建。

https://github.com/icindy/wxParse
与parser组件类似的开源项目,还有一个wxParser开源项目, 他不仅支持解析HTML,还支持解析Markdown内容。


parser组件具体代码
/components/parser/parser.wxml

<!--parser 主组件-->
<slot wx:if="{{!html[0].name&&!html[0].type}}" />
<trees class="top" style="{{selectable?'user-select:text;-webkit-user-select:text;':''}}{{showAm}}" animation="{{scaleAm}}" lazy-load="{{lazyLoad}}" nodes="{{html[0].name||html[0].type?html:[]}}" bindtap="_tap" bindtouchstart="_touchstart" bindtouchmove="_touchmove" />
<image wx:for="{{imgs}}" wx:key="index" id="{{index}}" src="{{item}}" hidden bindload="_load" />

/components/parser/parser.wxss

:host {display: block;overflow: scroll;-webkit-overflow-scrolling: touch;
}
.top {display: inherit;
}
@keyframes show {0% {opacity: 0;}100% {opacity: 1;}
}

/components/parser/parser.js

/*parser 主组件github:https://github.com/jin-yufeng/Parserdocs:https://jin-yufeng.github.io/Parserauthor:JinYufengupdate:2020/03/26
*/
var cache = {},Parser = require('./libs/MpHtmlParser.js'),fs = wx.getFileSystemManager && wx.getFileSystemManager();
try {var dom = require('./libs/document.js.js');
} catch (e) {}
// 计算 cache 的 key
function hash(str) {for (var i = str.length, val = 5381; i--;)val += (val << 5) + str.charCodeAt(i);return val;
}
Component({options: {pureDataPattern: /^[acdgtux]|W/},properties: {'html': {type: null,observer(html) {if (this._refresh) this._refresh = false;else this.setContent(html, false, true);}},'autosetTitle': {type: Boolean,value: true},'autopause': {type: Boolean,value: true},'compress': Number,'domain': String,'gestureZoom': Boolean,'lazyLoad': Boolean,'selectable': Boolean,'tagStyle': Object,'showWithAnimation': Boolean,'useAnchor': Boolean,'useCache': Boolean,'xml': Boolean},relations: {'../parser-group/parser-group': {type: 'ancestor'}},created() {// 图片数组this.imgList = [];this.imgList.setItem = function(i, src) {if (!i || !src) return;// 去重if (src.indexOf('http') == 0 && this.includes(src)) {var newSrc = '';for (var j = 0, c; c = src[j]; j++) {if (c == '/' && src[j - 1] != '/' && src[j + 1] != '/') break;newSrc += Math.random() > 0.5 ? c.toUpperCase() : c;}newSrc += src.substr(j);return this[i] = newSrc;}this[i] = src;// 暂存 data srcif (src.includes('data:image')) {var info = src.match(/data:image\/(\S+?);(\S+?),(.+)/);if (!info) return;var filePath = `${wx.env.USER_DATA_PATH}/${Date.now()}.${info[1]}`;fs && fs.writeFile({filePath,data: info[3],encoding: info[2],success: () => this[i] = filePath})}}this.imgList.each = function(f) {for (var i = 0, len = this.length; i < len; i++)this.setItem(i, f(this[i], i, this));}},detached() {// 删除暂存this.imgList.each(src => {if (src && src.includes(wx.env.USER_DATA_PATH) && fs)fs.unlink({filePath: src})})clearInterval(this._timer);},methods: {// 锚点跳转navigateTo(obj) {if (!this.data.useAnchor)return obj.fail && obj.fail({errMsg: 'Anchor is disabled'})this.createSelectorQuery().select('.top' + (obj.id ? '>>>#' + obj.id : '')).boundingClientRect().selectViewport().scrollOffset().exec(res => {if (!res[0])return this.group ? this.group.navigateTo(this.i, obj) :obj.fail && obj.fail({errMsg: 'Label not found'});obj.scrollTop = res[1].scrollTop + res[0].top;wx.pageScrollTo(obj);})},// 获取文本getText(ns = this.data.html) {var txt = '';for (var i = 0, n; n = ns[i++];) {if (n.type == 'text') txt += n.text.replace(/&nbsp;/g, '\u00A0').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');else if (n.type == 'br') txt += '\n';else {// 块级标签前后加换行var br = n.name == 'p' || n.name == 'div' || n.name == 'tr' || n.name == 'li' || (n.name[0] == 'h' && n.name[1] > '0' && n.name[1] < '7');if (br && txt && txt[txt.length - 1] != '\n') txt += '\n';if (n.children) txt += this.getText(n.children);if (br && txt[txt.length - 1] != '\n') txt += '\n';else if (n.name == 'td' || n.name == 'th') txt += '\t';}}return txt;},// 获取视频 contextgetVideoContext(id) {if (!id) return this.videoContexts;for (var i = this.videoContexts.length; i--;)if (this.videoContexts[i].id == id) return this.videoContexts[i];},// 渲染富文本setContent(html, append, _watch) {var data = {};if (!html) {if (_watch || append) return;data.html = '';} else if (typeof html == 'string') {let parser = new Parser(html, this.data);// 缓存读取if (this.data.useCache) {var hashVal = hash(html);if (cache[hashVal]) data.html = cache[hashVal];else {data.html = parser.parse();cache[hashVal] = data.html;}} else data.html = parser.parse();this._refresh = true;this.triggerEvent('parse', data.html);} else if (html.constructor == Array) {// 转换不符合格式的 arrayif (html.length && html[0].PoweredBy != 'Parser') {let parser = new Parser('', this.data);(function f(ns) {for (var i = 0, n; n = ns[i]; i++) {if (n.type == 'text') continue;n.attrs = n.attrs || {};for (var key in n.attrs)if (typeof n.attrs[key] != 'string') n.attrs[key] = n.attrs[key].toString();parser.matchAttr(n);if (n.children) {parser.STACK.push(n);f(n.children);parser.popNode(parser.STACK.pop());}}})(html);data.html = html;}if (!_watch) data.html = html;} else if (typeof html == 'object' && html.nodes) {data.html = html.nodes;console.warn('错误的 html 类型:object 类型已废弃');} elsereturn console.warn('错误的 html 类型:' + typeof html);if (append) {this._refresh = true;data.html = (this.data.html || []).concat(data.html);} else if (this.data.showWithAnimation) data.showAm = 'animation: show .5s';if (data.html || data.showAm) this.setData(data);// 设置标题if (this.data.html.length && this.data.html[0].title && this.data.autosetTitle)wx.setNavigationBarTitle({title: this.data.html[0].title})this.imgList.length = 0;this.videoContexts = [];if (dom) this.document = new dom(this.data.html, 'html', this);var ns = this.selectAllComponents('.top,.top>>>._node');for (let i = 0, n; n = ns[i++];) {n.top = this;for (var j = 0, item; item = n.data.nodes[j++];) {if (item.c) continue;// 获取图片列表if (item.name == 'img')this.imgList.setItem(item.attrs.i, item.attrs.src);// 音视频控制else if (item.name == 'video' || item.name == 'audio') {var ctx;if (item.name == 'video') ctx = wx.createVideoContext(item.attrs.id, n);else ctx = n.selectComponent('#' + item.attrs.id);if (ctx) {ctx.id = item.attrs.id;this.videoContexts.push(ctx);}}}}(wx.nextTick || setTimeout)(() => this.triggerEvent('load'), 50);var height;clearInterval(this._timer);this._timer = setInterval(() => {this.createSelectorQuery().select('.top').boundingClientRect(res => {this.rect = res;if (res.height == height) {this.triggerEvent('ready', res)clearInterval(this._timer);}height = res.height;}).exec();}, 350)},// 预加载preLoad(html, num) {if (typeof html == 'string') {var id = hash(html);html = new Parser(html, this.data).parse();cache[id] = html;}var imgs, wait = [];(function f(ns) {for (var i = 0, n; n = ns[i++];) {if (n.name == 'img' && n.attrs.src && !wait.includes(n.attrs.src))wait.push(n.attrs.src);f(n.children || []);}})(html);if (num) wait = wait.slice(0, num);this._wait = (this._wait || []).concat(wait);if (!this.data.imgs) imgs = this._wait.splice(0, 15);else if (this.data.imgs.length < 15)imgs = this.data.imgs.concat(this._wait.splice(0, 15 - this.data.imgs.length));imgs && this.setData({imgs});},_load(e) {if (this._wait.length)this.setData({[`imgs[${e.target.id}]`]: this._wait.shift()})},// 事件处理_tap(e) {if (this.data.gestureZoom && e.timeStamp - this._lastT < 300) {var initY = e.detail.y - e.currentTarget.offsetTop;if (this._zoom) {this._scaleAm.translateX(0).scale(1).step();wx.pageScrollTo({scrollTop: (initY + this._initY) / 2 - e.touches[0].clientY,duration: 400})} else {var initX = e.detail.x - e.currentTarget.offsetLeft;this._initY = initY;this._scaleAm = wx.createAnimation({transformOrigin: `${initX}px${this._initY}px 0`,timingFunction: 'ease-in-out'});this._scaleAm.scale(2).step();this._tMax = initX / 2;this._tMin = (initX - this.rect.width) / 2;this._tX = 0;}this._zoom = !this._zoom;this.setData({scaleAm: this._scaleAm.export()})}this._lastT = e.timeStamp;},_touchstart(e) {if (e.touches.length == 1)this._initX = this._lastX = e.touches[0].pageX;},_touchmove(e) {var diff = e.touches[0].pageX - this._lastX;if (this._zoom && e.touches.length == 1 && Math.abs(diff) > 20) {this._lastX = e.touches[0].pageX;if ((this._tX <= this._tMin && diff < 0) || (this._tX >= this._tMax && diff > 0)) return;this._tX += diff * Math.abs(this._lastX - this._initX) * 0.05;if (this._tX < this._tMin) this._tX = this._tMin;if (this._tX > this._tMax) this._tX = this._tMax;this._scaleAm.translateX(this._tX).step();this.setData({scaleAm: this._scaleAm.export()})}}}
})

/components/parser/parser.json

{"component": true,"usingComponents": {"trees": "./trees/trees"}
}

/components/parser/libs/config.js

/* 配置文件 */
const canIUse = wx.canIUse('editor'); // 高基础库标识,用于兼容
module.exports = {// 过滤器函数filter: null,// 代码高亮函数highlight: null,// 文本处理函数onText: null,blankChar: makeMap(' ,\xA0,\t,\r,\n,\f'),// 块级标签,将被转为 divblockTags: makeMap('address,article,aside,body,caption,center,cite,footer,header,html,nav,section' + (canIUse ? '' : ',pre')),// 将被移除的标签ignoreTags: makeMap('area,base,basefont,canvas,command,embed,frame,iframe,input,isindex,keygen,link,map,meta,param,script,source,style,svg,textarea,title,track,use,wbr' + (canIUse ? ',rp' : '')),// 只能被 rich-text 显示的标签richOnlyTags: makeMap('a,colgroup,fieldset,legend,picture,table' + (canIUse ? ',bdi,bdo,rt,ruby' : '')),// 自闭合的标签selfClosingTags: makeMap('area,base,basefont,br,col,circle,ellipse,embed,frame,hr,img,input,isindex,keygen,line,link,meta,param,path,polygon,rect,source,track,use,wbr'),// 信任的属性trustAttrs: makeMap('align,alt,app-id,author,autoplay,border,cellpadding,cellspacing,class,color,colspan,controls,data-src,dir,face,height,href,id,ignore,loop,media,muted,name,path,poster,rowspan,size,span,src,start,style,type,unit-id,width,xmlns'),// bool 型的属性boolAttrs: makeMap('autoplay,controls,ignore,loop,muted'),// 信任的标签trustTags: makeMap('a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,ul,video' + (canIUse ? ',bdi,bdo,caption,pre,rt,ruby' : '')),// 默认的标签样式userAgentStyles: {address: 'font-style:italic',big: 'display:inline;font-size:1.2em',blockquote: 'background-color:#f6f6f6;border-left:3px solid #dbdbdb;color:#6c6c6c;padding:5px 0 5px 10px',caption: 'display:table-caption;text-align:center',center: 'text-align:center',cite: 'font-style:italic',dd: 'margin-left:40px',mark: 'background-color:yellow',pre: 'font-family:monospace;white-space:pre;overflow:scroll',s: 'text-decoration:line-through',small: 'display:inline;font-size:0.8em',u: 'text-decoration:underline'}
}function makeMap(str) {var map = {},list = str.split(',');for (var i = list.length; i--;)map[list[i]] = true;return map;
}

/components/parser/libs/CssHandler.js

/*解析和匹配 Css 的选择器github:https://github.com/jin-yufeng/Parserdocs:https://jin-yufeng.github.io/Parserauthor:JinYufengupdate:2020/03/15
*/
var cfg = require('./config.js');
class CssHandler {constructor(tagStyle) {var styles = Object.assign({}, cfg.userAgentStyles);for (var item in tagStyle)styles[item] = (styles[item] ? styles[item] + ';' : '') + tagStyle[item];this.styles = styles;}getStyle = data => this.styles = new CssParser(data, this.styles).parse();match(name, attrs) {var tmp, matched = (tmp = this.styles[name]) ? tmp + ';' : '';if (attrs.class) {var items = attrs.class.split(' ');for (var i = 0, item; item = items[i]; i++)if (tmp = this.styles['.' + item])matched += tmp + ';';}if (tmp = this.styles['#' + attrs.id])matched += tmp + ';';return matched;}
}
module.exports = CssHandler;
class CssParser {constructor(data, init) {this.data = data;this.floor = 0;this.i = 0;this.list = [];this.res = init;this.state = this.Space;}parse() {for (var c; c = this.data[this.i]; this.i++)this.state(c);return this.res;}section = () => this.data.substring(this.start, this.i);isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');// 状态机Space(c) {if (c == '.' || c == '#' || this.isLetter(c)) {this.start = this.i;this.state = this.Name;} else if (c == '/' && this.data[this.i + 1] == '*')this.Comment();else if (!cfg.blankChar[c] && c != ';')this.state = this.Ignore;}Comment() {this.i = this.data.indexOf('*/', this.i) + 1;if (!this.i) this.i = this.data.length;this.state = this.Space;}Ignore(c) {if (c == '{') this.floor++;else if (c == '}' && !--this.floor) this.state = this.Space;}Name(c) {if (cfg.blankChar[c]) {this.list.push(this.section());this.state = this.NameSpace;} else if (c == '{') {this.list.push(this.section());this.Content();} else if (c == ',') {this.list.push(this.section());this.Comma();} else if (!this.isLetter(c) && (c < '0' || c > '9') && c != '-' && c != '_')this.state = this.Ignore;}NameSpace(c) {if (c == '{') this.Content();else if (c == ',') this.Comma();else if (!cfg.blankChar[c]) this.state = this.Ignore;}Comma() {while (cfg.blankChar[this.data[++this.i]]);if (this.data[this.i] == '{') this.Content();else {this.start = this.i--;this.state = this.Name;}}Content() {this.start = ++this.i;if ((this.i = this.data.indexOf('}', this.i)) == -1) this.i = this.data.length;var content = this.section();for (var i = 0, item; item = this.list[i++];)if (this.res[item]) this.res[item] += ';' + content;else this.res[item] = content;this.list = [];this.state = this.Space;}
}

/components/parser/libs/MpHtmlParser.js

/*将 html 解析为适用于小程序 rich-text 的 DOM 结构github:https://github.com/jin-yufeng/Parserdocs:https://jin-yufeng.github.io/Parserauthor:JinYufengupdate:2020/03/26
*/
var cfg = require('./config.js'),blankChar = cfg.blankChar,CssHandler = require('./CssHandler.js'),screenWidth = wx.getSystemInfoSync().screenWidth;
try {var emoji = require('./emoji.js.js');
} catch (e) {}
class MpHtmlParser {constructor(data, options = {}) {this.attrs = {};this.compress = options.compress;this.CssHandler = new CssHandler(options.tagStyle, screenWidth);this.data = data;this.domain = options.domain;this.DOM = [];this.i = this.start = this.audioNum = this.imgNum = this.videoNum = 0;this.protocol = this.domain && this.domain.includes('://') ? this.domain.split('://')[0] : 'http';this.state = this.Text;this.STACK = [];this.useAnchor = options.useAnchor;this.xml = options.xml;}parse() {if (emoji) this.data = emoji.parseEmoji(this.data);for (var c; c = this.data[this.i]; this.i++)this.state(c);if (this.state == this.Text) this.setText();while (this.STACK.length) this.popNode(this.STACK.pop());if (this.DOM.length) {this.DOM[0].PoweredBy = 'Parser';if (this.title) this.DOM[0].title = this.title;}return this.DOM;}// 设置属性setAttr() {var name = this.getName(this.attrName);if (cfg.trustAttrs[name]) {if (!this.attrVal) {if (cfg.boolAttrs[name]) this.attrs[name] = 'T';} else if (name == 'src') this.attrs[name] = this.getUrl(this.attrVal.replace(/&amp;/g, '&'));else this.attrs[name] = this.attrVal;}this.attrVal = '';while (blankChar[this.data[this.i]]) this.i++;if (this.isClose()) this.setNode();else {this.start = this.i;this.state = this.AttrName;}}// 设置文本节点setText() {var back, text = this.section();if (!text) return;text = (cfg.onText && cfg.onText(text, () => back = true)) || text;if (back) {this.data = this.data.substr(0, this.start) + text + this.data.substr(this.i);let j = this.start + text.length;for (this.i = this.start; this.i < j; this.i++) this.state(this.data[this.i]);return;}if (!this.pre) {// 合并空白符var tmp = [];for (let i = text.length, c; c = text[--i];)if (!blankChar[c] || (!blankChar[tmp[0]] && (c = ' '))) tmp.unshift(c);text = tmp.join('');if (text == ' ') return;}// 处理实体var i = -1,j, en, siblings = this.siblings();while (1) {if ((i = text.indexOf('&', i + 1)) == -1) break;if ((j = text.indexOf(';', i + 2)) == -1) break;if (text[i + 1] == '#') {en = parseInt((text[i + 2] == 'x' ? '0' : '') + text.substring(i + 2, j));if (!isNaN(en)) text = text.substr(0, i) + String.fromCharCode(en) + text.substr(j + 1);} else {en = text.substring(i + 1, j);if (en == 'nbsp')text = text.substr(0, i) + '\xA0' + text.substr(j + 1); // 解决 &nbsp; 失效else if (en != 'lt' && en != 'gt' && en != 'amp' && en != 'ensp' && en != 'emsp' && en != 'quot' && en != 'apos') {i && siblings.push({type: 'text',text: text.substr(0, i)})siblings.push({type: 'text',text: `&${en};`,en: 1})text = text.substr(j + 1);i = -1;}}}text && siblings.push({type: 'text',text});}// 设置元素节点setNode() {var node = {name: this.getName(this.tagName),attrs: this.attrs},close = cfg.selfClosingTags[node.name] || (this.xml && this.data[this.i] == '/');this.attrs = {};if (!cfg.ignoreTags[node.name]) {this.matchAttr(node);if (!close) {node.children = [];if (node.name == 'pre' && cfg.highlight) {this.remove(node);this.pre = node.pre = true;}this.siblings().push(node);this.STACK.push(node);} else if (!cfg.filter || cfg.filter(node, this) != false)this.siblings().push(node);} else {if (!close) this.remove(node);else if (node.name == 'source') {var parent = this.parent(),attrs = node.attrs;if (parent && attrs.src)if (parent.name == 'video' || parent.name == 'audio')parent.attrs.source.push(attrs.src);else {var i, media = attrs.media;if (parent.name == 'picture' && !parent.attrs.src && (!media || (media.includes('px') &&(((i = media.indexOf('min-width')) != -1 && (i = media.indexOf(':', i + 8)) != -1 && screenWidth > parseInt(media.substr(i + 1))) ||((i = media.indexOf('max-width')) != -1 && (i = media.indexOf(':', i + 8)) != -1 && screenWidth < parseInt(media.substr(i + 1)))))))parent.attrs.src = attrs.src;}} else if (node.name == 'base' && !this.domain) this.domain = node.attrs.href;}if (this.data[this.i] == '/') this.i++;this.start = this.i + 1;this.state = this.Text;}// 移除标签remove(node) {var name = node.name,j = this.i;while (1) {if ((this.i = this.data.indexOf('</', this.i + 1)) == -1) {if (name == 'pre' || name == 'svg') this.i = j;else this.i = this.data.length;return;}this.start = (this.i += 2);while (!blankChar[this.data[this.i]] && !this.isClose()) this.i++;if (this.getName(this.section()) == name) {// 代码块高亮if (name == 'pre') {this.data = this.data.substr(0, j + 1) + cfg.highlight(this.data.substring(j + 1, this.i - 5), node.attrs) + this.data.substr(this.i - 5);return this.i = j;} else if (name == 'style')this.CssHandler.getStyle(this.data.substring(j + 1, this.i - 7));else if (name == 'title')this.title = this.data.substring(j + 1, this.i - 7);if ((this.i = this.data.indexOf('>', this.i)) == -1) this.i = this.data.length;// 处理 svgif (name == 'svg') {var src = this.data.substring(j, this.i + 1);if (!node.attrs.xmlns) src = ' xmlns="http://www.w3.org/2000/svg"' + src;var i = j;while (this.data[j] != '<') j--;src = this.data.substring(j, i) + src;var parent = this.parent();if (node.attrs.width == '100%' && parent && (parent.attrs.style || '').includes('inline'))parent.attrs.style = 'width:300px;max-width:100%;' + parent.attrs.style;this.siblings().push({name: 'img',attrs: {src: 'data:image/svg+xml;utf8,' + src.replace(/#/g, '%23')},svg: 1})}return;}}}// 处理属性matchAttr(node) {var attrs = node.attrs,style = this.CssHandler.match(node.name, attrs, node) + (attrs.style || ''),styleObj = {};if (attrs.id) {if (this.compress & 1) attrs.id = void 0;else if (this.useAnchor) this.bubble();}if ((this.compress & 2) && attrs.class) attrs.class = void 0;switch (node.name) { //+ 处理标签case 'a':case 'ad':this.bubble();break;case 'font':if (attrs.color) {styleObj['color'] = attrs.color;attrs.color = void 0;}if (attrs.face) {styleObj['font-family'] = attrs.face;attrs.face = void 0;}if (attrs.size) {var size = parseInt(attrs.size);if (size < 1) size = 1;else if (size > 7) size = 7;var map = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'];styleObj['font-size'] = map[size - 1];attrs.size = void 0;}break;case 'video':case 'audio':if (!attrs.id) attrs.id = node.name + (++this[`${node.name}Num`]);else this[`${node.name}Num`]++;if (node.name == 'video' && this.videoNum > 3) node.lazyLoad = 1;attrs.source = [];if (attrs.src) attrs.source.push(attrs.src);if (!attrs.controls && !attrs.autoplay)console.warn(`存在没有 controls 属性的${node.name}标签,可能导致无法播放`, node);this.bubble();break;case 'td':case 'th':if (attrs.colspan || attrs.rowspan)for (var k = this.STACK.length, item; item = this.STACK[--k];)if (item.name == 'table') {item.c = void 0;break;}}if (attrs.align) {styleObj['text-align'] = attrs.align;attrs.align = void 0;}if (attrs.width) {styleObj.width = parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px');attrs.width = void 0;}if (attrs.height) {styleObj.height = parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px');attrs.height = void 0;}// 压缩 stylevar styles = style.replace(/&quot;/g, '"').replace(/&amp;/g, '&').split(';');style = '';for (var i = 0, len = styles.length; i < len; i++) {var info = styles[i].split(':');if (info.length < 2) continue;let key = info[0].trim().toLowerCase(),value = info.slice(1).join(':').trim();if (value.includes('-webkit') || value.includes('-moz') || value.includes('-ms') || value.includes('-o') || value.includes('safe'))style += `;${key}:${value}`;else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import'))styleObj[key] = value;}if (node.name == 'img' || node.name == 'picture') {if (attrs['data-src']) {attrs.src = attrs.src || attrs['data-src'];attrs['data-src'] = void 0;}if ((attrs.src || node.name == 'picture') && !attrs.ignore) {if (this.bubble()) {if ((attrs.src || '').includes('.webp')) node.webp = 1;attrs.i = (this.imgNum++).toString();} else attrs.ignore = 'T';}if (attrs.ignore) styleObj['max-width'] = '100%';var check = item => styleObj[item] && !styleObj[item].includes('auto');if (check('width')) {var parent = this.parent();if (styleObj.width.includes('%') && parent && (parent.attrs.style || '').includes('inline')) {parent.attrs.style += ';max-width:100%'node.auto = 1;}if (parseInt(styleObj.width) > screenWidth)styleObj.height = 'auto';if (check('height')) node.mode = 'scaleToFill';} else if (check('height')) node.mode = 'heightFix';else node.auto = 1;}for (var key in styleObj) {var value = styleObj[key];if (key.includes('flex') || key == 'order' || key == 'self-align') node.c = 1;// 填充链接if (value.includes('url')) {var j = value.indexOf('(');if (j++ != -1) {while (value[j] == '"' || value[j] == "'" || blankChar[value[j]]) j++;value = value.substr(0, j) + this.getUrl(value.substr(j));}}// 转换 rpxelse if (value.includes('rpx'))value = value.replace(/[0-9.\s]*rpx/g, $ => parseFloat($) * screenWidth / 750 + 'px');else if (key == 'white-space' && value.includes('pre'))this.pre = node.pre = true;style += `;${key}:${value}`;}style = style.substr(1);if (style) attrs.style = style;}// 节点出栈处理popNode(node) {// 空白符处理if (node.pre) {node.pre = this.pre = void 0;for (let i = this.STACK.length; i--;)if (this.STACK[i].pre)this.pre = true;}if (node.name == 'head' || (cfg.filter && cfg.filter(node, this) == false))return this.siblings().pop();var attrs = node.attrs;// 替换一些标签名if (node.name == 'picture') {node.name = 'img';if (!attrs.src && (node.children[0] || '').name == 'img')attrs.src = node.children[0].attrs.src;return node.children = void 0;}if (cfg.blockTags[node.name]) node.name = 'div';else if (!cfg.trustTags[node.name]) node.name = 'span';// 处理列表if (node.c) {if (node.name == 'ul') {var floor = 1;for (let i = this.STACK.length; i--;)if (this.STACK[i].name == 'ul') floor++;if (floor != 1)for (let i = node.children.length; i--;)node.children[i].floor = floor;} else if (node.name == 'ol') {for (let i = 0, num = 1, child; child = node.children[i++];)if (child.name == 'li') {child.type = 'ol';child.num = ((num, type) => {if (type == 'a') return String.fromCharCode(97 + (num - 1) % 26);if (type == 'A') return String.fromCharCode(65 + (num - 1) % 26);if (type == 'i' || type == 'I') {num = (num - 1) % 99 + 1;var one = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'],ten = ['X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'],res = (ten[Math.floor(num / 10) - 1] || '') + (one[num % 10 - 1] || '');if (type == 'i') return res.toLowerCase();return res;}return num;})(num++, attrs.type) + '.';}}}// 处理表格的边框if (node.name == 'table') {var padding = attrs.cellpadding,spacing = attrs.cellspacing,border = attrs.border;if (node.c) {this.bubble();attrs.style = (attrs.style || '') + ';display:table';if (!padding) padding = 2;if (!spacing) spacing = 2;}if (border) attrs.style = `border:${border}px solid gray;${attrs.style || ''}`;if (spacing) attrs.style = `border-spacing:${spacing}px;${attrs.style || ''}`;if (border || padding || node.c)(function f(ns) {for (var i = 0, n; n = ns[i]; i++) {var style = n.attrs.style || '';if (node.c && n.name[0] == 't') {n.c = 1;style += ';display:table-' + (n.name == 'th' || n.name == 'td' ? 'cell' : (n.name == 'tr' ? 'row' : 'row-group'));}if (n.name == 'th' || n.name == 'td') {if (border) style = `border:${border}px solid gray;${style}`;if (padding) style = `padding:${padding}px;${style}`;} else f(n.children || []);if (style) n.attrs.style = style;}})(node.children)}this.CssHandler.pop && this.CssHandler.pop(node);// 自动压缩if (node.name == 'div' && !Object.keys(attrs).length) {var siblings = this.siblings();if (!(node.children || []).length) siblings.pop();else if (node.children.length == 1 && node.children[0].name == 'div')siblings[siblings.length - 1] = node.children[0];}}// 工具函数bubble() {for (var i = this.STACK.length, item; item = this.STACK[--i];) {if (cfg.richOnlyTags[item.name]) {if (item.name == 'table' && !Object.hasOwnProperty.call(item, 'c')) item.c = 1;return false;}item.c = 1;}return true;}getUrl(url) {if (url[0] == '/') {if (url[1] == '/') url = this.protocol + ':' + url;else if (this.domain) url = this.domain + url;} else if (this.domain && url.indexOf('data:') != 0 && !url.includes('://'))url = this.domain + '/' + url;return url;}getName = val => this.xml ? val : val.toLowerCase();isClose = () => this.data[this.i] == '>' || (this.data[this.i] == '/' && this.data[this.i + 1] == '>');section = () => this.data.substring(this.start, this.i);parent = () => this.STACK[this.STACK.length - 1];siblings = () => this.STACK.length ? this.parent().children : this.DOM;// 状态机  //+ 为了提取tagText(c) {if (c == '<') {var next = this.data[this.i + 1],isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');if (isLetter(next)) {this.setText();this.start = this.i + 1;this.state = this.TagName;} else if (next == '/') {this.setText();if (isLetter(this.data[++this.i + 1])) {this.start = this.i + 1;this.state = this.EndTag;} else this.Comment();} else if (next == '!') {this.setText();this.Comment();}}}Comment() { //+ 为了处理注释var key;if (this.data.substring(this.i + 2, this.i + 4) == '--') key = '-->';else if (this.data.substring(this.i + 2, this.i + 9) == '[CDATA[') key = ']]>';else key = '>';if ((this.i = this.data.indexOf(key, this.i + 2)) == -1) this.i = this.data.length;else this.i += key.length - 1;this.start = this.i + 1;this.state = this.Text;}TagName(c) {if (blankChar[c]) {this.tagName = this.section();while (blankChar[this.data[this.i]]) this.i++;if (this.isClose()) this.setNode();else {this.start = this.i;this.state = this.AttrName;}} else if (this.isClose()) {this.tagName = this.section();this.setNode();}}AttrName(c) {var blank = blankChar[c];if (blank) {this.attrName = this.section();c = this.data[this.i];}if (c == '=') {if (!blank) this.attrName = this.section();while (blankChar[this.data[++this.i]]);this.start = this.i--;this.state = this.AttrValue;} else if (blank) this.setAttr();else if (this.isClose()) {this.attrName = this.section();this.setAttr();}}AttrValue(c) {if (c == '"' || c == "'") {this.start++;if ((this.i = this.data.indexOf(c, this.i + 1)) == -1) return this.i = this.data.length;this.attrVal = this.section();this.i++;} else {for (; !blankChar[this.data[this.i]] && !this.isClose(); this.i++);this.attrVal = this.section();}this.setAttr();}EndTag(c) {if (blankChar[c] || c == '>' || c == '/') {var name = this.getName(this.section());for (var i = this.STACK.length; i--;)if (this.STACK[i].name == name) break;if (i != -1) {var node;while ((node = this.STACK.pop()).name != name);this.popNode(node);} else if (name == 'p' || name == 'br')this.siblings().push({name,attrs: {}});this.i = this.data.indexOf('>', this.i);this.start = this.i + 1;if (this.i == -1) this.i = this.data.length;else this.state = this.Text;}}
}
module.exports = MpHtmlParser;

/components/parser/trees/trees.wxml

<!--trees 递归子组件-->
<wxs module="handler" src="./handler.wxs" />
<block wx:for="{{nodes}}" wx:key="index" wx:for-item="n"><rich-text wx:if="{{n.en||n.svg||n.err}}" class="_svg" nodes="{{[n]}}" /><!--图片-->
<image wx:elif="{{n.name=='img'}}" class="_img" style="{{n.attrs.style}}" src="{{n.attrs.src}}" lazy-load="{{lazyLoad}}" mode="{{n.mode||'widthFix'}}" show-menu-by-longpress="{{!n.attrs.ignore}}" webp="{{n.webp}}" data-attrs="{{n.attrs}}" data-i="{{index}}" data-auto="{{n.auto}}" data-source="img" bindtap="imgtap" bindload="{{canIUse?handler.load:'loadImg'}}" binderror="error" /><!--文本--><text wx:elif="{{n.type=='text'}}" decode>{{n.text}}</text><text wx:elif="{{n.name=='br'}}">\n</text><!--链接--><view wx:elif="{{n.name=='a'}}" class="_a {{n.attrs.class}}" hover-class="_hover" style="{{n.attrs.style}}" data-attrs="{{n.attrs}}" bindtap="{{canIUse?handler.visited:'linkpress'}}"><trees nodes="{{n.children}}" /></view><!--视频--><block wx:elif="{{n.name=='video'}}"><view wx:if="{{n.lazyLoad}}" id="{{n.attrs.id}}" class="_video {{n.attrs.class}}" style="{{n.attrs.style}}" data-i="{{index}}" bindtap="loadVideo" /><video wx:else 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}}" poster="{{n.attrs.poster}}" src="{{n.attrs.source[n.i||0]}}" unit-id="{{n.attrs['unit-id']}}" data-i="{{index}}" data-source="video" binderror="error" bindplay="play" /></block><!--音频--><audio wx:elif="{{n.name=='audio'}}" id="{{n.attrs.id}}" class="{{n.attrs.class}}" style="{{n.attrs.style}}" author="{{n.attrs.author}}" autoplay="{{n.attrs.autoplay}}" controls="{{n.attrs.controls}}" loop="{{n.attrs.loop}}" name="{{n.attrs.name}}" poster="{{n.attrs.poster}}" src="{{n.attrs.source[n.i||0]}}" data-i="{{index}}" data-source="audio" binderror="error" bindplay="play" /><!--广告--><ad wx:elif="{{n.name=='ad'}}" class="{{n.attrs.class}}" style="{{n.attrs.style}}" unit-id="{{n.attrs['unit-id']}}" data-source="ad" binderror="error" /><!--列表--><view wx:elif="{{n.name=='li'}}" class="{{n.attrs.class}}" style="{{n.attrs.style}};display:flex"><view wx:if="{{n.type=='ol'}}" class="_ol-bef">{{n.num}}</view><view wx:else class="_ul-bef"><view wx:if="{{n.floor%3==0}}" class="_ul-p1"></view><view wx:elif="{{n.floor%3==2}}" class="_ul-p2" /><view wx:else class="_ul-p1" style="border-radius:50%"></view></view><trees class="_node _li" lazy-load="{{lazyLoad}}" nodes="{{n.children}}" /></view><!--富文本--><rich-text wx:elif="{{handler.useRichText(n)}}" id="{{n.attrs.id}}" class="_p __{{n.name}}" nodes="{{[n]}}" /><!--继续递归--><trees wx:else id="{{n.attrs.id}}" class="_node _{{n.name}} {{n.attrs.class}}" style="{{n.attrs.style}}" lazy-load="{{lazyLoad}}" nodes="{{n.children}}" />
</block>

/components/parser/trees/trees.wxss

/* 在这里引入自定义样式 *//* 链接和图片效果 */
._a {color: #366092;display: inline;padding: 1.5px 0 1.5px 0;word-break: break-all;
}
._hover {opacity: 0.7;text-decoration: underline;
}
._visited {color: #551a8b;
}
._img {height: 50px;max-width: 100%;
}
/* 内部样式 */
:host {display: inline;
}
._blockquote, ._div, ._p, ._ul, ._ol, ._li {display: block;
}
._b, ._strong {font-weight: bold;
}
._code {font-family: monospace;
}
._del {text-decoration: line-through;
}
._em, ._i {font-style: italic;
}
._h1 {font-size: 2em;
}
._h2 {font-size: 1.5em;
}
._h3 {font-size: 1.17em;
}
._h5 {font-size: 0.83em;
}
._h6 {font-size: 0.67em;
}
._h1, ._h2, ._h3, ._h4, ._h5, ._h6 {display: block;font-weight: bold;
}
._ins {text-decoration: underline;
}
._li {flex: 1;width: 0;
}
._ol-bef {margin-right: 5px;text-align: right;width: 36px;
}
._ul-bef {line-height: normal;margin: 0 12px 0 23px;
}
._ol-bef, ._ul_bef {flex: none;user-select: none;
}
._ul-p1 {display: inline-block;height: 0.3em;line-height: 0.3em;overflow: hidden;width: 0.3em;
}
._ul-p2 {border: 0.05em solid black;border-radius: 50%;display: inline-block;height: 0.23em;width: 0.23em;
}
._q::before {content: '"';
}
._q::after {content: '"';
}
._sub {font-size: smaller;vertical-align: sub;
}
._sup {font-size: smaller;vertical-align: super;
}
.__bdi, .__bdo, .__ruby, .__rt, ._svg {display: inline-block;
}
._video {background-color: black;display: inline-block;height: 225px;position: relative;width: 300px;
}
._video::after {border-left-color: white;border-style: solid;border-width: 15px 0 15px 30px;content: '';left: 50%;margin: -15px 0 0 -15px;position: absolute;top: 50%;
}

/components/parser/trees/trees.js

/*trees 递归子组件github:https://github.com/jin-yufeng/Parserdocs:https://jin-yufeng.github.io/Parserauthor:JinYufengupdate:2020/03/23
*/
Component({data: {canIUse: !!wx.chooseMessageFile},properties: {nodes: Array,lazyLoad: Boolean},methods: {// 视频播放事件play(e) {this.top.group && this.top.group.pause(this.top.i);if (this.top.videoContexts.length > 1 && this.top.data.autopause)for (var i = this.top.videoContexts.length; i--;)if (this.top.videoContexts[i].id != e.currentTarget.id)this.top.videoContexts[i].pause();},
// 图片点击事件
imgtap(e) {var attrs = e.target.dataset.attrs;if (!attrs.ignore) {var preview = true;this.top.triggerEvent('imgtap', {id: e.target.id,src: attrs.src,ignore: () => preview = false})if (preview) {if (this.top.group) return this.top.group.preview(this.top.i, attrs.i);var urls = this.top.imgList,current = urls[attrs.i] ? urls[attrs.i] : (urls = [attrs.src], attrs.src);wx.previewImage({current,urls})}}
},// 链接点击事件linkpress(e) {var jump = true,attrs = e.currentTarget.dataset.attrs;attrs.ignore = () => jump = false;this.top.triggerEvent('linkpress', attrs);if (jump) {if (attrs['app-id'])wx.navigateToMiniProgram({appId: attrs['app-id'],path: attrs.path})else if (attrs.href) {if (attrs.href[0] == '#')this.top.navigateTo({id: attrs.href.substring(1)})else if (attrs.href.indexOf('http') == 0 || attrs.href.indexOf('//') == 0)wx.setClipboardData({data: attrs.href,success: () =>wx.showToast({title: '链接已复制'})})elsewx.navigateTo({url: attrs.href,})}}},// 错误事件error(e) {var context, src = '',source = e.target.dataset.source,i = e.target.dataset.i,node = this.data.nodes[i];if (source == 'video' || source == 'audio') {// 加载其他 sourcevar index = (node.i || 0) + 1;if (index < node.attrs.source.length)return this.setData({[`nodes[${i}].i`]: index})if (this.top) context = this.top.getVideoContext(e.target.id);} else if (source == 'img')context = {setSrc: (newSrc) => src = newSrc}this.top && this.top.triggerEvent('error', {source,target: e.target,context,...e.detail})if (source == 'img') {var data = {[`nodes[${i}].attrs.src`]: src}if (!src) data[`nodes[${i}].err`] = 1;this.setData(data);}},// 加载视频loadVideo(e) {var i = e.target.dataset.i;this.setData({[`nodes[${i}].lazyLoad`]: false,[`nodes[${i}].attrs.autoplay`]: true})},// 加载图片loadImg(e) {var data = e.target.dataset;if (data.auto)this.setData({[`nodes[${data.i}].attrs.style`]: `${this.data.nodes[data.i].attrs.style};width:${e.detail.width}px`})}}
})

/components/parser/trees/trees.json

{"component": true,"usingComponents": {"trees": "./trees"}
}

/components/parser/trees/handler.wxss

var inlineTags = {abbr: 1,b: 1,big: 1,code: 1,del: 1,em: 1,i: 1,ins: 1,label: 1,q: 1,small: 1,span: 1,strong: 1
}
module.exports = {// 获取图片大小load: function (e) {if (e.target.dataset.auto)e.instance.setStyle({width: e.detail.width + 'px'})},// 链接点击态visited: function (e, owner) {if (!e.instance.hasClass('_visited'))e.instance.addClass('_visited')owner.callMethod('linkpress', e);},// 是否通过 rich-text 显示useRichText: function (item) {return !item.c && !inlineTags[item.name] && (item.attrs.style || '').indexOf('display:inline') == -1}
}

parse组件的使用

index.wxml

 <parser bindimgtap="onTapImage" html="{{html}}" tag-style="{{tagStyle}}" />

index.js

Page({data: {tagStyle: {img: 'font-size:0;display:block;',},html: "<div>小程序实践<span>message</span><img src='https://p.qqan.com/up/2021-6/16232893157513212.jpg' /><img src='https://p.qqan.com/up/2021-6/16232893724729414.jpg' /></div>"},onTapImage(e) {console.log('iamge url', e.detail.src)}
})

index.json

{"usingComponents": {"parser":"../../components/parser/parser",}
}

2.4 在rich-text组件中,如果节点有不支持的HTML属性,节点及子节点都会被移除,那么相同的nodes节点数据,如果改用parser组件渲染可以渲染出来吗?

答案基本上也是不行的,我们从源码中可以看到,parser自定义组件的功能也是有限的,对于它本身不能渲染的标签,在内部也是交给rich-text小程序原生的组件,去渲染的。
所以说有rich-text本身不支持的节点,那么你交给parser也是一样的,也是不行。

微信小程序全栈开发实践 第二章 微信小程序组件介绍及使用 -- 2.3 rich-text 组件,以及如何单击预览它的节点图片并保存相关推荐

  1. 微信小程序全栈开发实践 第二章 微信小程序组件介绍及使用 -- 2.10 image组件,如何实现图片懒加载?

    一.与image组件有关的技术问题 1.1 什么是WebP? webp是image组件的一个boolean属性,开启这个属性之后,代表url可以设置webp这种格式的图片.webP是一种同时提供了有损 ...

  2. 微信小程序全栈开发实践 第二章 微信小程序组件介绍及使用 -- 2.6 scroll-view组件,在小程序中如何实现滚动锚定,如何渲染一个滚动的长列表?

    scroll-view 是一个可以滚动的视图区域的容器组件. 一.重要属性 scroll-view 的滚动属性,实现了两套功能 左右或上下滚动 下拉更新 1.1 与滚动有关的属性: scroll-x ...

  3. 微信小程序全栈开发实践 第二章 微信小程序组件介绍及使用 -- 2.5 可移动容器及可移动区域,以及如何实现侧滑删除功能

    一.学习使用moveable-view与movable-area组件 1.1 关于元素的定位 static 静态定位 元素在页面流动的当前位置定位,这个时候它的top.left.right.botto ...

  4. 微信小程序全栈开发实践 第二章 微信小程序组件介绍及使用 -- 2.9 页面链接组件,如何自定义一个导航栏?

    一.小程序中的导航组件 functional-page-navigator 仅在插件中有效,用于跳转到插件功能页. navigator 小程序标准的导航组件 小程序插件是对一些js接口.自定义组件或页 ...

  5. 微信小程序全栈开发实践 第三章 微信小程序开发常用的API介绍及使用 -- 3.7 网络接口简介(七)学习EventChannel对象

    零.回顾 在之前我们自定义实现picker-view组件的时候, 曾经使用过一个pop-up的自定义组件, 这个组件可以在底部滑入一个面板, 现在我们把登录按钮放在底部滑入的面板之上, 然后在完成登录 ...

  6. 微信小程序全栈开发实践 第三章 微信小程序开发常用的API介绍及使用 -- 3.5 网络接口简介(五)基于Promise+await、async关键字改写登录模块

    零.回顾 在上节课我们主要实践练习了Promise的三个方法,包括any.all.race. 现在我们对Promise变成已经有了一个大致的了解. 这节课我们尝试将登录模块使用Promise编程方式进 ...

  7. 微信小程序全栈开发实践 第三章 微信小程序开发常用的API介绍及使用 -- 3.6 网络接口简介(六)关于Page页面隐藏代码执行及Promise对象catch处理的补充

    零.回顾 在上节课我们主要是基于 Promise 加 await.async关键字改写了登录模块代码, 但是我们在自动登录这一块的代码仍然有问题,这节课我们看一下, 如何在接口调用中实现微信用户的自动 ...

  8. 微信小程序全栈开发实践 第三章 微信小程序开发常用的API介绍及使用 -- 3.9 网络接口简介(九)扩展wxp模块的request3方法,实现用户登录的自动融合

    零.回顾 在上节课我们主要介绍了观察者模式, 并据此模式实现了一个event模块, 这节课我们基础用户登录的自动整合, 尝试在wxp模块当中扩展出一个request3这样的一个接口. 一.在wxp组件 ...

  9. spring boot @value_spring+vue全栈开发实战-第二章Spring Boot 基础配置-笔记0302-2020

    Spring Boot 基础配置 1. Web 容器配置 2.Properties 配置 3.类型安全配置属性 1. Web 容器配置 a.常规配置 在 Spring Boot 项 目 中,可以内置 ...

最新文章

  1. awk5.0 — awk模式之一
  2. ccc计算机比赛如何报名,整理:加拿大的CCC是什么,怎么报名?
  3. 有道编程的界面做的也太粗燥了吧!
  4. 行号 设置vim_在VSCode里面配置Vim正确姿势(细节解析)
  5. Why React?
  6. ORACLE 10g EXPDP,IMPDP使用方法
  7. android bitmap 加边框,Android 给圆角的Bitmap加边框
  8. c#和c++互操作(平台调用相关)
  9. App测试实战:测试内容、测试工具、测试效果
  10. Linux下如何使用Vi编辑器
  11. 孝感网站建设多少钱,孝感做企业网站多少钱
  12. Primo Ramdisk配置教程
  13. 搭建 vue项目(Windows + 命令行 + vsCode)
  14. 人生最难的事情是什么?
  15. 自动化知识图谱表示:从三元组到子图
  16. 函数的右导数与导函数的右极限的关系
  17. 《红楼梦》各版本总结
  18. java图书销售系统_基于springboot的小型图书销售系统 源码下载
  19. 什么是React DOM?
  20. spring-boot创建项目出现spring-boot-starter-parent版本报红问题

热门文章

  1. JavaScript 有趣的冷知识:tagged template literals
  2. ​2020年,又一波技术类书籍推荐,不能错过哟~
  3. GAN(生成对抗网络)和IQA(图像质量评价能擦出什么样的火花呢?)简单聊一些近来published的论文
  4. VM中调节系统窗口大小
  5. ACM 130. [USACO Mar08] 游荡的奶牛(dp+BFS)
  6. 《挑战程序设计竞赛(疑惑)》19.2九宫格拼图
  7. 正点原子潘多拉上STlinkV2.1固件遇到的坑
  8. 青龙BOT机器人交互
  9. 东南大学研究生毕业论文LaTeX模板seuthesix的使用技巧【Mac版】
  10. GSM/GPRS+GPS模块SIM808