大多数Web应用的富文本内容都是以HTML字符串的形式存储的,所以通过HTML文档显示HTML内容自然是没有问题的。但是,在微信小程序(以下简称“小程序”)中,我们应该如何渲染这部分内容呢?
解决办法
wxParse
小程序启动时,无法直接呈现HTML内容,于是一个名为“wxParse”的库诞生了。其原理是将HTML代码解析成树形结构的数据,然后通过小程序的模板来呈现数据。
富文本
后来,小程序添加了一个“富文本”组件来显示富文本内容。然而,这个组件有一个很大的限制:所有节点的事件都被屏蔽在组件中。也就是说,在这个组件中,即使是“预览图片”这样的简单功能也无法实现。
网络视图
后来,小程序允许通过“网页视图”组件嵌套网页,通过网页显示HTML内容是最好的兼容解决方案。但是,因为需要加载一个页面,所以性能很差。
当“WePY”遇到“wxParse”时
考虑到用户体验和功能交互,我们放弃了两个原生组件“富文本”和“web-view”,选择了“wxParse”。然而,当我使用它时,我发现“wxParse”不能很好地满足需求:
我们的小程序是基于“WePY”框架开发的,而“wxParse”是基于原生小程序编写的。为了使它们兼容,必须修改“wxParse”的源代码。“wxParse”只是通过图像组件显示和预览原始img元素的图片。在实际使用中,可以利用云存储界面对图像进行缩小,从而达到“小图显示、原图预览”的目的。“wxParse”直接使用小程序的视频组件来显示视频,但是视频组件的层次问题往往会导致UI异常(例如,阻塞了一个固定位置的元素)。另外,如果你环顾一下“wxParse”的代码仓库,你会发现它已经两年没有迭代了。因此,基于“WePY”组件模式重写富文本组件的想法应运而生,其结果就是“WePY HTML”项目。
实施程序
解析HTML
首先还是要把HTML字符串解析成树形结构的数据,我用的是“特殊字符分离法”。HTML中的特殊字符是“”和“”,前者是起始符,后者是终止符。
如果要解析的内容以starter开始,那么starter和terminator之间的内容将被截取作为解析的节点。如果要解析的内容不是以起始符开始的,则从起始符开始到开始符之前的内容(如果起始符不存在,则为结尾)将被截取并解析为纯文本。剩余的内容进入下一轮解析,直到没有剩余的内容。如下图所示:
为了形成树结构,应该在解析期间维护上下文节点(默认情况下是根节点):
如果截取的内容是开始标记,则根据匹配的标记名称和属性在当前上下文节点下创建子节点。如果标签不是自结束标签(br、img等)。),上下文节点被设置为新节点。如果截取的内容是结束标签,则根据标签名称关闭当前上下文节点(将上下文节点设置为其父节点)。如果是纯文本,则在当前上下文节点下创建文本节点,并且上下文节点保持不变。该过程如下表所示:
在上述过程之后,HTML字符串被解析成一个节点树。
对比
将上述算法与其他类似的解析算法进行比较(性能通过“解析长度为10000的HTML代码”来衡量):
可以看出,在不考虑容错(产生错误结果而不是抛出异常)的情况下,这个组件的算法相比其他两个有着压倒性的优势,满足了小程序的需求。一般来说,富文本编辑器生成的代码不会有语法错误。因此,即使容错性差,问题也不大(但需要改进)。
模板渲染
树结构的绘制不可避免地涉及到子节点的递归处理。但是小程序的模板不支持递归,似乎陷入了一个大坑。
看一下“wxParse”模板的实现,它以一种简单粗暴的方式解决了这个问题:嵌套调用是通过13个几乎完全相同的模板进行的(1调用2,2调用3,…,12调用13),这意味着它最多可以支持12次嵌套。一般来说,这个深度就够了。
因为“WePY”框架本身有构造机制,所以不需要手工编写十几个几乎相同的模板,通过一个内置的插件来生成。
以下模板需要重复嵌套(简化),通过在代码开始前后插入特殊注释进行标记,并在需要嵌入下一个模板的地方用另一个特殊注释("")进行标记:
!-wepyhtml-repeat start-template name=' wepyhtml-0 ' block wx : if=' { { content } } ' wx : for=' { { content } } ' block wx : if=' { { item . type==' node ' } ' view class=' wepyhtml-tag-{ { item . name } } '!-下一个模板-/查看/阻止块wx : else { { item . text } }/阻止/阻止/模板!- wepyhtml-repeat end -以下是相应的构建代码(“wepy-plugin-replace”需要安装):
//wepy . config . js { plugins : { replace : { filter :/。wxml$/,config: { find: /!-wepyhtml-重复开始- ([Ww]?)!- wepyhtml-repeat end - /,replace(match,TPL){ let result=' ';//反正不用付钱,就写一个20层嵌套的for(让I=0;i=20i ) {结果='n' tpl。替换(' wepyhtml-0 ',' wepyhtml-I ')。替换(/!-下一个模板- /g,()={ return i===20?' : ` template is=' wepyhtml-$ { I 1 } ' wx : if=' { { item . children } } ' data=' { { content : item . children '/template `;});}返回结果;}}}}}但是运行后发现第二层和更深层的节点没有渲染,说明嵌套失败。查看dist目录中生成的wxml文件,我们可以发现变量名与组件的源代码不同:
在生成组件代码时,如果=' { $ html content $ wepyhtml $ content } } ' wx: for=' { $ html content $ wepyhtml $ content } } ' ' wepy '则阻塞wx :为了避免组件数据的变量名与页面数据的变量名发生冲突,请按照一定的规则为组件的变量名加前缀(如上面的代码所示)
wepyHTMl $().因此,在生成嵌套模板时,还必须使用带前缀的变量名。
在组件代码中添加变量“thisIsMe”来标识前缀:
!-wepyhtml-repeat start-template name=' wepyhtml-0 ' { ThisMe } }块wx : if=' { { content } } ' wx : for=' { { content } } '块wx : if=' { { item . type==' node ' } ' view class=' wepyhtml-tag-{ { item . name } } '!-下一个模板-/查看/阻止块wx : else { { item . text } }/阻止/阻止/模板!-wepyhtml-重复结束-然后修改构建代码:
replace(match,TPL){ let result=' ';让前缀=' ';//匹配前缀TPL=TPL . replace(/ { { s } *( $)。*? $)this Me s * } }/,(match,p)={ prefix=p;返回“”;});for(设I=0;i=20i ) {结果='n' tpl。替换(' wepyhtml-0 ',' wepyhtml-I ')。替换(/!-下一个模板- /g,()={ return i===20?' : ' template is=' wepyhtml-$ { I 1 } ' wx : if=' { { item . children } } ' data=' { $ { prefix } content : item . children } } '/template `;});}返回结果;}至此,渲染问题解决。
画
为了节省流量,提高加载速度,在显示富文本内容时,通常会根据需要的大小缩小里面的图片,点击小图片进行预览后才会显示原始图片。这主要涉及节点属性的修改:
把图片原路径(src属性值)存到自定义属性(例如数据-src)中,并将其添加到预览图数组。把图片的科学研究委员会属性值修改为缩小后的图片网址(一般云服务商都有提供此类统一资源定位器规则)。点击图片时,使用自定义属性的值进行预览。为了实现这个需求,本组件在解析节点时提供了一个钩子(无创建):
onNodeCreate(name,attrs){ if(name==' img '){ attrs[' data-src ']=attrs。src//预览图数组这个。预览。推送(attrs。src);//缩图attrs.src=resizeImg(attrs.src,640);}}对应的模板和事件处理逻辑如下:
模板名称=' wepyhtml-img ' image class=' wepyhtml-tag-img ' mode=' width fix ' src=' http : { { elem。attrs。' src } } ' data-src=' http : { { elem。attrs[' data-src ']| | elem。attrs。src } } ' @ tap=' IMgtap '/image/template//点击小图看大图imgTap(e){ wepy。previewimage({当前目标。数据集。src,网址:这个。previewimgs });}视频
在小程序中视频组件的层级是较高的(且无法降低)。如果页面设计上存在着可能挡住视频的元素,处理起来就需要一些技巧了:
隐藏录像组件,用图像组件(视频封面)占位;点击图片时,让视频全屏播放;如果退出了全屏,则暂停播放。相关代码如下:
模板名=' wepyhtml-video ' view class=' wepyhtml-tag-video ' @ tap=' video AP ' data-nodeid=' { { elem。nodeid } } '!-视频封面-image class=' wepyhtml-tag-img wepyhtml-tag-video _ poster ' mode=' width fix ' src=' http : { { elem }。attrs。海报} } '/图片!-播放图标-image class=' wepyhtml-tag-img wepyhtml-tag-video _ _ play ' src=' http :/imgs/icon-play.png'/image!-视频组件-视频样式=' display : none ' src=' http : { { elem。attrs。src } } ' id=' wepyhtml-video-{ { elem。nodeid } } ' @ full screenchange=' video full screenchange ' @ play=' video play '/video/view/template/pretyprint e class=' pretyprint hljs clo jure ' style=' word-wrap : break-word;余量: 0px 0px 1.5 empadding : 0.5 em文本装饰:无;字体样式:正常;font-family: Menlo,摩纳哥,Consolas,' Courier New ',monospacecolor: rgb(68,68,68);边界半径: 4px线高: 1.5 em断字:断字;背景-color: rgb(246,246,246);border:无;' overflow-x : auto;'{ //点击封面图,播放视频可视图文(e){ const nodeId=e . CurrentTarget。数据集。nodeIdconst context=wepy。create video context(' wepyhtml-video-' nodeId);语境。play();//在安卓微信下,如果视频不可见,则调用播放()也无法播放//需要再调用全屏方法if (wepy.getSystemInfoSync().平台===' Android '){上下文。请求全屏();} }, //视频层级较高,为防止遮挡其他特殊定位元素,造成界面异常, //strong制全屏播放视频播放(e){ wepy。创建视频上下文。id ).请求全屏();}, //退出全屏则暂停videoFullscreenChange(e) { if(!细节。全屏){ wepy。create video context(e . CurrentTarget。id ).pause();} }}开源
最后贴一下「我们复制网页」的项目仓库:https://github.com/beiliao-web-frontend/wepy-html,具体使用方法见项目内的自述文件。如果你在使用过程中遇到了问题,或者是有好的建议和意见,都可以在问题中提出。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。