在学习html5的时候,使用canvas实现了对html文本的解析和渲染,支持的tag有<p>、<i>、<b>、<u>、<ul>、<li>,并参考chrome对不规则html进行了解析。代码扔在了我的github上(https://github.com/myhonor2013/gadgets,里面的html-render-with-canvas目录里面)。

程序的主函数是个循环,对html文本从左往右进行解析,wangy每个循环开始处指向有效的html '<'位置,例如'<p'、'<\'、'<f'、'<\x'等都是有效的,而'< p'、'< \'、'< \x'等都是无效的,总之'<'后面必须紧跟一个非空字符,这是参考了chrome的解析得出的结论。这也就意味着每个循环的末尾必须找到第一个类似的位置才能结束循环。在每次循环末尾调用渲染函数在canvas上进行渲染。在循环的过程中要时刻注意html字符串指针是否越界,如果越界则结束循环进行渲染。

一、预处理

预处理简单地对html文本中连续的空字符(回车、tab、缩进)用单个的空格进行了替换:

var text=data.text.replace(/[\r\n\t]/g,WHITESPACE).replace(/\s+/g,WHITESPACE).trim();

然后从html文本开始位置寻找第一个所谓的有效tag位置,并对此位置之前的文本进行渲染。以下则每次循环都以有效的'<'开始。这又分两种情况:有效开标签和有效闭标签。

二、有效开标签的处理

有效开标签即'<'后面不是'\'的标签,用正则表达式就是^<[^\/]+.*。寻找和'<'匹配的'>'标签并将标签名称push到tagname中。接下来根据tagname确定其后面的文本应该采用的格式,亦即isbold、isitalic、isicon(<li>标签)、isunderline、nowrap、uloffset等属性,并进而根据isbold和isitalic确定绘制canvas需要的font属性值。font和isicon、isunderline、nowrap、uloffset便是canvas渲染真正需要的属性。如果是支持的tag同时将标签名称push到tagnames,将font 入栈到fontsarr中,后面的循环要根据这两个属性来确定其作用域的文本格式。

 while(text[index]!=WHITESPACE&&text[index]!=RIGHTSYN){
tagname.push(text[index++]);
if(index==len)break;
}
if(index==len)return;
while(text[index]!=RIGHTSYN){
if(index==len){
break;
}
}
var tag=tagname.join('').toLowerCase();
tagname=[];
if(tag==TAGB){
isbold=true;
}
else if(tag==TAGI){
isitalic=true;
}
else if(tag==TAGLI){
isicon=true;
}
else if(tag==TAGU){
isunderline=true;
}
if(tag==TAGP||tag==TAGLI||tag==TAGUL){
nowrap=false;
}
else{
nowrap=true;
}
if(tag==TAGUL){
uloffset+=ULOFFSET;
} if(isitalic==true&&isbold==true){
font=ITALICBOLD;
}
else if(isitalic==false&&isbold==true){
font=BOLD;
}
else if(isitalic==true&&isbold==false){
font=ITALIC;
}
else{
font=NORMAL;
}
if(VALIDTAGS.contains(tag)){
tagnames.push(tag);
fontsarr.push(font);
}

后面部分就是本次循环的作用域文本,文本被放在texttodraw中并在结束前进行canvas渲染。在结束前还要将texttodraw清空,并将isicon置为false。

三、有效闭标签的处理

有效闭标签即'<'后面紧跟'\'的标签,用正则表达式就是^<\/.*。同样往前找出其匹配的闭合'<'。如果闭合标签名和tagnames(其中依次保存了有效开标签处理时的标签名称,还记得吗)中的最后一个相同,则将tagnames的最后一个元素出栈。如果标签名称是ul则对uloffset往前缩进;如果tagnames中不再包含当前标签名称,则根据标签语义对字体进行相应处理,这是考虑了多层嵌套的情况。

 if(text[index]=="/"){
var arr=[];
while(++index<len&&text[index]!=RIGHTSYN&&text[index]!=LEFTSYN){
arr.push(text[index]);
}
if(index==len)return;
if(text[index]==LEFTSYN)break;
var tag=arr.join('').trim().toLowerCase();
if(tag==tagnames[tagnames.length-1]){
font=fontsarr.pop();
tagnames.pop();
if(tag==TAGUL){
uloffset -=ULOFFSET;
uloffset =(uloffset>0)?uloffset:0;
}
if(!tagnames.contains(tag)){
if(tag==TAGI){
font=font.replace("italic",'normal');
isitalic=false;
}
else if(tag==TAGB){
font=font.replace("bold",'normal');
isbold=false;
}
else if(tag==TAGU){
isunderline=false;
}
}
}
}

接下来同样是本次循环的作用域文本,对其进行获取并根据前面确定的属性值对其进行渲染。和开标签的处理一致,不再赘述。

四、canvas渲染

两个全局变量xoffset和yoffset用以标识上次渲染结束后的位置。在渲染开始时首先对这两个属性需要根据uloffset、nowrap等属性进行调整。然后如果具有isicon属性,则绘制出<li>标签对应的前面的实心圆。接着就是对文本进行渲染了,设定font后逐字符取出并使用measureText测量是否满行,如果是则绘制后需要换行。在渲染过程中如果需要绘制下划线则一并进行绘制。如此反复,直到所有字符绘制完毕。完整的渲染函数如下:

 var  drawtext=function(data){
data=data.trim();
var len=data.length;
if(len==0){
return;
}
if(!nowrap&&xoffset>MARGIN){
xoffset = MARGIN+uloffset;
yoffset += LINEHEIGHT;
} if(isicon){
ctx.beginPath();
ctx.arc(MARGIN+uloffset+MARGIN,yoffset-MARGIN,MARGIN,0,Math.PI*2,true);
ctx.closePath();
ctx.fill();
xoffset +=30;
} var index=0;
var renderindex=0;
ctx.font=font;
while(index<len){
while(canvaswidth-xoffset>ctx.measureText(data.substring(renderindex,++index)).width){
if(index===len){
break;
}
} if(index==len){
ctx.fillText(data.substring(renderindex,index),xoffset,yoffset);
if(isunderline){
canvas.strokeStyle = "red";
canvas.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(xoffset, yoffset);
ctx.lineTo(xoffset+ctx.measureText(data.substring(renderindex,index)).width, yoffset);
ctx.closePath();
ctx.stroke();
}
xoffset+=ctx.measureText(data.substring(renderindex,index)).width;
break;
}
ctx.fillText(data.substring(renderindex,--index),xoffset,yoffset);
if(isunderline){
canvas.strokeStyle = "red";
canvas.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(xoffset, yoffset);
ctx.lineTo(canvaswidth, yoffset);
ctx.closePath();
ctx.stroke();
} renderindex=index;
xoffset = MARGIN;
yoffset += LINEHEIGHT;
}
return;
};

结束语

使用js解析html时切忌使用递归,这样处理很容易造成堆栈溢出和性能问题。另代码中出现的Array的contains方法是在Array的prototype上添加的用以判断是否包含字符串的方法:

Array.prototype.contains=function(item){
return new RegExp("^" + this.join("|")+ "$","i").test(item.toString());
}

解析html并使用canvas进行渲染的更多相关文章

  1. 解析json结构绘制canvas

    在工作中偶尔会遇到绘制转发卡/邀请卡的业务,且这个转发卡/邀请卡的风格会有很多,要求最后生成图片.这时候如果使用一张图片绘制一个canvas,这个工作量会相当大.分析一下转发邀请的内容,会发现所有的里 ...

  2. 从解析HTML开始,破解页面渲染时间长难题

    摘要:在本文中,将重点关注网页的初始渲染,即它从解析 HTML 开始. 我将探索可能导致高渲染时间的问题,以及如何解决它们. 本文分享自华为云社区<页面首屏渲染性能指南>,作者:Ocean ...

  3. 解析Nuxt.js Vue服务端渲染摸索

    本篇文章主要介绍了详解Nuxt.js Vue服务端渲染摸索,写的十分的全面细致,具有一定的参考价值,对此有需要的朋友可以参考学习下.如有不足之处,欢迎批评指正. Nuxt.js 十分简单易用.一个简单 ...

  4. Canvas 渲染模式

    1. Canvas Canvas Component 是UI布局和渲染的抽象空間,所有的UI都必須在此元素之下(子物件),简单来说 Canvas 就是渲染 UI 的組件. 2. Render Mode ...

  5. 浏览器是怎样工作的:渲染引擎,HTML解析

    渲染引擎 渲染引擎的职责是……渲染,也就是把请求的内容显示到浏览器屏幕上. 默认情况下渲染引擎可以显示HTML,XML文档以及图片. 通过插件(浏览器扩展)它可以显示其它类型文档.比如使用PDF vi ...

  6. 渲染引擎,HTML解析

    这是how browser to work 的翻译 转自:携程设计委员会 渲染引擎 渲染引擎的职责是……渲染,也就是把请求的内容显示到浏览器屏幕上. 默认情况下渲染引擎可以显示HTML,XML文档以及 ...

  7. 原来 CSS 与 JS 是这样阻塞 DOM 解析和渲染的

    hello~各位亲爱的看官老爷们大家好.估计大家都听过,尽量将CSS放头部,JS放底部,这样可以提高页面的性能.然而,为什么呢?大家有考虑过么?很长一段时间,我都是知其然而不知其所以然,强行背下来应付 ...

  8. 记vue+leaflet的一次canvas渲染爆栈

    背景: 在地图上绘制大量的circleMarker,leaflet能选择使用canvas来渲染,比起默认的svg渲染来说在大量绘制的情况下会更加流畅.但当触发其中某一个circleMarker的too ...

  9. html中canvas渲染图片,并转化成base64格式保存

    最近在做一个上传头像然后保存显示的功能,因为涉及到裁剪大小和尺寸比例,所以直接上传图片再展示的话,就会出现问题,所以就想用canvas来渲染裁剪后的图片,然后转化成base64格式的图片再存储,这样取 ...

随机推荐

  1. 初步涉及JDBC

    一.为什么要使用JDBC: 1.在之前的学习中,我们都是通过控制台来输入信息,创建对象,然后再输出信息,但是这样无法保存数据,每次程序运行都需要重新输入,很麻烦. 2. 在这样的情况下,我们就需要利用 ...

  2. SVN服务器与测试服务器代码同步

    在本地做测试项目的时候,想svn提交和服务器上的代码一步到位,不想再手动更新一次了,所以就研究了下同步, 要实现svn提交后自动更新到测试服务器,在你的版本库下的hooks文件夹下添加post-com ...

  3. sql server 执行计划(execution plan)介绍

    大纲:目的介绍sql server 中执行计划的大致使用,当遇到查询性能瓶颈时,可以发挥用处,而且带有比较详细的学习文档和计划,阅读者可以按照我计划进行,从而达到对执行计划一个比较系统的学习. 什么是 ...

  4. transform-style: preserve-3d在iphone下的bug

    经测,当元素设置transform-style: preserve-3d;后,其实现rotateY时的动画效果会穿透上层的覆盖图层. 马克一下

  5. QT toLocal8Bit奇怪的问题

    #include "mainwindow.h" #include "ui_mainwindow.h" #include<QDebug> MainWi ...

  6. MVC中的常见问题

    1.  The model backing the 'MusicStoreDBContext' context has changed since the database was created. ...

  7. .Net WinForm下配置Log4Net(总结不输出原因)

    最近做一个winform项目,配置了Log4net 但是总是不能输出,搜索了很多文章加上自己的探索发现自己在项目中添加的 Log4Net.config 生成时没有被复制到Debug文件夹下, 所以程序 ...

  8. uploadify 后台动态传参数

    最近项目中用到上传控件,参数需要动态传参,经过查询总结了一下Uploadify 动态传参 jQuery(document).ready(function () { var ctrlid = getQu ...

  9. Log

    Public Shared Sub WriteLog(ByVal FormID As String, ByVal ex1 As Exception) 'log信息 Dim loginfo As Str ...

  10. wpf 后台绘制圆弧

    wpf 前台绘制圆弧很简单,如:<Path x:Name="path_data" Stroke="#FFE23838" StrokeThickness=& ...