解析html并使用canvas进行渲染
在学习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进行渲染的更多相关文章
- 解析json结构绘制canvas
在工作中偶尔会遇到绘制转发卡/邀请卡的业务,且这个转发卡/邀请卡的风格会有很多,要求最后生成图片.这时候如果使用一张图片绘制一个canvas,这个工作量会相当大.分析一下转发邀请的内容,会发现所有的里 ...
- 从解析HTML开始,破解页面渲染时间长难题
摘要:在本文中,将重点关注网页的初始渲染,即它从解析 HTML 开始. 我将探索可能导致高渲染时间的问题,以及如何解决它们. 本文分享自华为云社区<页面首屏渲染性能指南>,作者:Ocean ...
- 解析Nuxt.js Vue服务端渲染摸索
本篇文章主要介绍了详解Nuxt.js Vue服务端渲染摸索,写的十分的全面细致,具有一定的参考价值,对此有需要的朋友可以参考学习下.如有不足之处,欢迎批评指正. Nuxt.js 十分简单易用.一个简单 ...
- Canvas 渲染模式
1. Canvas Canvas Component 是UI布局和渲染的抽象空間,所有的UI都必須在此元素之下(子物件),简单来说 Canvas 就是渲染 UI 的組件. 2. Render Mode ...
- 浏览器是怎样工作的:渲染引擎,HTML解析
渲染引擎 渲染引擎的职责是……渲染,也就是把请求的内容显示到浏览器屏幕上. 默认情况下渲染引擎可以显示HTML,XML文档以及图片. 通过插件(浏览器扩展)它可以显示其它类型文档.比如使用PDF vi ...
- 渲染引擎,HTML解析
这是how browser to work 的翻译 转自:携程设计委员会 渲染引擎 渲染引擎的职责是……渲染,也就是把请求的内容显示到浏览器屏幕上. 默认情况下渲染引擎可以显示HTML,XML文档以及 ...
- 原来 CSS 与 JS 是这样阻塞 DOM 解析和渲染的
hello~各位亲爱的看官老爷们大家好.估计大家都听过,尽量将CSS放头部,JS放底部,这样可以提高页面的性能.然而,为什么呢?大家有考虑过么?很长一段时间,我都是知其然而不知其所以然,强行背下来应付 ...
- 记vue+leaflet的一次canvas渲染爆栈
背景: 在地图上绘制大量的circleMarker,leaflet能选择使用canvas来渲染,比起默认的svg渲染来说在大量绘制的情况下会更加流畅.但当触发其中某一个circleMarker的too ...
- html中canvas渲染图片,并转化成base64格式保存
最近在做一个上传头像然后保存显示的功能,因为涉及到裁剪大小和尺寸比例,所以直接上传图片再展示的话,就会出现问题,所以就想用canvas来渲染裁剪后的图片,然后转化成base64格式的图片再存储,这样取 ...
随机推荐
- sublime text3的安装与汉化
sublime text3 3126 64位的下载地址: https://download.sublimetext.com/Sublime%20Text%20Build%203126%20x64%20 ...
- Bootstrap插件系列——Bootstrap-table初始化、分页、客户端搜索、服务端搜索
又好久不写博客,最近项目都是用的bootstrap的样式,不出意外,应该是要在bootstrap的道路上越走越远了,所以下定决心,把bootstrap的插件都好好学学. 昨天写了boostrap-ta ...
- iOS 适配https
1.准备证书 首先找后台要一个证书(SSL证书,一般你跟后台说要弄https,然后让他给你个证书,他就知道了),我们需要的是.cer的证书.但是后台可能给我们的是.crt的证书. 我们需要转换一下: ...
- ext.ajax.request请求时带有遮罩效果
ajax请求时有时需要操作大量的数据,反应有时会很慢,这时我们想要来一个遮罩效果,具体步骤如下 1.定义一个遮罩 var myMask = new Ext.LoadMask(Ext.getBody() ...
- 进阶版css的点滴
-moz-:firefox: -webkit- Safari 和 Chrome
- iOS动画实现改变frme和contenOffset
[UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:0.4]; [UIView setAnimat ...
- Beyond Compare 2
Beyond Compare 2 确实很好用,差异行不交叉,自动留出空白,比windiff要清楚.
- CSS 浮动副作用 ,清除浮动
参考:http://www.divcss5.com/jiqiao/j406.shtml 副作用:一般是一个盒子里使用了CSS float浮动属性,导致父级对象盒子不能被撑开,背景色不显示(如果父级不设 ...
- jenkins---配置邮件
如果出现以下错误: 553 mail from must equal authorized user 看看管理员邮箱是否配置: 再测试,就能够正常收到邮件了!
- Java和Ibatis调用存储过程并取得返回值详解
Java和Ibatis调用存储过程并取得返回值详解 2011-07-19 17:33 jiandanfeng2 CSDN博客 字号:T | T 本文主要介绍了Java和Ibatis调用存储过程的方法, ...