在学习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. 第四十三章 微服务CICD(5)- gitlab + jenkins + docker + dockerregsitry

    一.总体流程 部署: 开发机(mac) ip:11.11.11.11 docker:1.12.1 部署机(centos7) ip:10.211.55.4 docker:1.12.3 生产机(cento ...

  2. Linux下调试程序方法

    您可以用各种方法来监控运行着的用户空间程序:可以为其运行调试器并单步调试该程序,添加打印语句,或者添加工具来分析程序.本文描述了几种可以用来调试在 Linux 上运行的程序的方法.我们将回顾四种调试问 ...

  3. 关于monkeyrunner的一些初步理解性的题目

    1.Monkeyrunner中包含几个基本类?分别大概的作用是什么? Monkeyrunner中基本包含了MonkeyRunner,MonkeyDevice,MonkeyImage MonkeyRun ...

  4. Oracle 11G在用EXP 导出时,空表不能导出解决

    Oracle 11G在用EXP 导出时,空表不能导出解决 (转)(.http://wanwentao.blog.51cto.com/2406488/545154 11G中有个新特性,当表无数据时,不分 ...

  5. 插入排序 - C语言

    插入排序的思想: 以现有的已排序元素为基础,下一个元素添加到正确的位置,则最终会完成排序. 第一个元素本身是已经排序好的.从第二个开始排. void insertSort(int arr[], int ...

  6. maven下读取资源文件的问题(转)

    原文链接:http://shenchao.me/2016/04/20/maven%E4%B8%8B%E8%AF%BB%E5%8F%96%E8%B5%84%E6%BA%90%E6%96%87%E4%BB ...

  7. 用Backbone.js创建一个联系人管理系统(三)

    原文: Build a Contacts Manager Using Backbone.js: Part 3 欢迎回到这系列的教程,关注使用Backbone.js构建应用程序. 如果你还没看过第一,二 ...

  8. linux环境下部署tomcat

    服务器环境:Red Hat Enterprise Linux Server release 6.5 安装部署包:apache-tomcat-8.0.30.tar.gz.jdk-8u66-linux-x ...

  9. hasOwnProperty和in

    返回一个布尔值,指出一个对象是否具有指定名称的属性. hasOwnProperty 此方法无法检查该对象的原型链中是否具有该属in 可以检查原型链中是否具有该属

  10. hdoj 2022 海选女主角

    Problem Description potato老师虽然很喜欢教书,但是迫于生活压力,不得不想办法在业余时间挣点外快以养家糊口.“做什么比较挣钱呢?筛沙子没力气,看大门又不够帅...”potato ...