片 1
在富文本组件 rich-text 中,节点的事件是被屏蔽的,例如节点里面的图片,它的单击事件,我们是不能监听的。那么,在这种情况下,我们如何实现点击预览节点图片,并保存它们呢?
片 2
我们先看一下 rich-text 这个组件。
上面是它的示例代码。它只有两个属性:nodes 与 space。space 代表空格策略,控制中文空格显示的大小,有三种值,在中文环境直接取 emsp 就好。另一个属性 nodes 节点,可以取字符串,也可以取数组,但如果是字符串的话会影响性能,所以一般情况下我们都使用数组。
在 nodes 属性中,有这样一些子属性。name 表示节点名称,例如 p、div、span、img 等,大部分 HTML 标签都受支持,就连 HTML5 不太常用的 ruby 标签也支持。ruby 是一个在字符上方,显示东亚字符拼音文本的标签。attrs 表示节点的属性,是定义在 HTML 标签上的属性,例如 img 标签的 src、width、height 属性等等这些都是。children 代表子节点列表,它是一个数组。type 代表节点类型,共有两种,node 与 text。默认是 node,可以不写。当类型是 node 时,有 children 属性;如果是 text,则只有一个 text 属性,text 节点只能包括纯文本。
片 3
使用 rich-text 组件,关键在于 nodes 的编写。
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。
下面我们看开发中可能遇到的相关技术问题。
片 4
如果可以拿到单击事件,以事件的 currentTarget 取到目标组件,再判断目标组件是不是 image,如果是,取其 src 属性拿到图片链接,就可以预览、下载图片。
但是,rich-text 中的所有节点,都是屏蔽单击事件的,所以通过添加网页的 click 事件,再对事件对象进行处理,这个方法是行不通的。
但我们仍然可以在 rich-text 组件上添加 tap 事件,在事件函数中,使用 wx.previewImage 这个接口预览图片,然后选择需要的图片下载。在预览之前,我们需要遍历 rich-text 的 nodes 数据,将所有图片地址预先取出来(见上面代码)。
当单击 rich-text 富文本组件时,触发预览(见上面代码)。在 tap 事件句柄中,事件对象 e 是 TouchEvent 对象,使用它的 pageX、pageY 属性,还可以取到用户大概单击了什么位置。如果以位置可以判断出图片是哪一张,就可以在调用 wx.previewImage 预览图片时,作为第一个参数传进去,这样就能实现指定图片的预览与下载了;如果不能确定,取第一张就可以了。
可以从源码中看一下实际的运行效果。
片 5
如果 rich-text 中有多张图片,上下图片间会有间隔。
这是样式问题。在 HTML5 开发中,我们可以通过查找 img 标签,然后修改其样式,但是在小程序中不可以。小程序的 rich-text 是通过 Web Componnet 实现的,不允许在外部修改内部的 img 元素的样式。
我们可以直接修改 nodes 数据中的 img 样式,给它添加两个内联样式(见上面代码)。这个缝隙是行内容引起的,通过设置元素为块元素,并设置字体大小为 0,图片间的缝隙就没有了。
如果每个 img 节点都添加内嵌样式比较麻烦,还可以在 wxss 文件中声明一个通用的类样式,然后在每个 img 节点上添加这个类样式名称(见上面代码)。
可以从源码中看一下实际的运行效果。
片 6
有人说,nodes 数据格式,需要手动创建与维护比较麻烦,有没有办法直接将 HTML 源码解析并呈现呢?
答案是有的。有一个开源组件(见上面地址),可以解析 HTML 源码并在小程序中直接呈现。
使用方法也很简单。首先下载源码,将 parser 文件夹至项目中适当位置,接着在 json 配置文件中引用组件,再接着准备好数据,最后在 WXML 中使用,这样就可以了(见上方源码)。
这个组件支持给每个特定的 HTML 标签,定义自己的样式,这个功能是通过 tag-style 属性完成的。我们定义了 img 的无间隙样式(“font-size:0;display:block;”,见上面代码),所以在运行效果中,上下图片之间已经没有间隙了。这也算是问题二的另一个解决方案。
相关代码可以在源码中看到。
现在我们简单看一下这个 parser 组件它是怎么实现的?
片 7
通过查看 parser 的源码可以看到,它对 HTML 源码文本的解析,是通过一个一个的词法状态机,一个字符一个字符去解析的。(见上面代码)例如 Comment 是用于解析注释的,以这种解析确定 tag。
在拿到 tag 单词以后,再通过有限的 switch 遍历,生成对应的 nodes 数据。parser 组件支持的标签列表,是在源码内部写死的(见上面代码)。它不能解析的 tag,它一定不能呈现,这一点也说明软件都是有边界的。
片 8
在这个组件内部,有一个名为 trees 的自定义组件,在这个组件中对上一步生成的 nodes 数据作了绑定渲染(见上面代码)。parser 的 nodes 数据,与 rich-text 的 nodes 并不是对等的,它是大于后者的。parser 不仅支持 rich-text,还支持 audio、video 等标签,并且从源码中可以看到,它还支持递归,也就是说它支持 div 容器的嵌套。
在源码中我们还看到了 ad 的影子,这说明它支持广告(见上面代码)。将 unit-ad 传递给它,它就能解析并呈现出一个 Banner 广告。该功能具体是通过 ad 这件小程序组件实现的。
相关代码都可以在源码中看到。
还有,问题一关于单击图片放大、下载的问题,通过这个组件也能完美得到解决,并且不需要预先从 nodes 数据中解析 urls。因为 parser 这个组件,它对 image 的解析和呈现,是通过小程序的 image 组件实现的。所以它原生支持单击放大预览与下载。
片 9
从源码可以看出,parser 原生支持图片的放大预览,并且单击时还向外层派发了一个 imgtap 事件(见上面代码)。
片 10
我们在使用这个组件时,可以添加一个对 imgtap 事件的监听,在运行时就能拿到被单击图片的网址了(见上面)。
相关代码都可以在源码中看到。
parser 这个开源组件,不依赖于第三方库,自实现了词法解析和 DOM 重建,代码量也不大,很值得研究学习。有了这个技巧,我们甚至可以设计一门编程语言,自定义语法、写一个编译器,解析、编译自己语言写的程序。
与 parser 组件类似的开源项目,还有一个 wxParse 开源项目(见上面网址),它不仅支持解析 HTML,还支持解析 Markdown 内容。有人说,parser 是基于 wxParse 二次开发的。wxParse 这个开源项目有也值得研究一下。
最后我们看一下作业。
上节课留了一个作业给大家:在上节课的问题三中,为什么 setTimeout 设置的延时定时器,要使用 17 毫秒?
这是因为目前小程序 1 秒内最大渲染的帧数是 60 帧,每帧渲染约平均花费 16.66 毫秒,这是一个渲染周期最小的时间单位,17 毫秒约相当于这个数字。
今天最后也留个作业:在官方的 rich-text 组件中,如果节点有不支持的 HTML 属性,节点及子节点都会被移除。那么相同的 nodes 节点,如果使用 parser 组件渲染,可以渲染出来吗?
好,这节课我们就讲到这里。这节课我们主要介绍了 rich-text 组件,及如何单击预览并下载组件中的图片。下节课我们介绍通用的容器组件 view,及如何使用 view 进行 Flex 布局。基本我们看到的所有 UI 布局,都可以基于 view 容器,用 flex 布局方法实现。
2020 年 5 月 15 日