解析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格式的图片再存储,这样取 ...
随机推荐
- [转载]Docker的安装配置及使用详解
简介 官网:http://www.docker.com/,点击get started进入下载,目前三个系统的docker容器都有,Windows版需要win10系统,我的是win7系统一开始用的 ...
- Yii2 中日志的记录
Yii2自带日志记录,但用起来感觉比较不是很顺手,故自己封装了个方法,如下: /** * 记录日志 * * @param type $msg * @time 2015年8月31日17:46:20 * ...
- 淌水 UE4的shootergame 案例 准备
从毕业到现在,从GIS到游戏. 先记录一下cesium源码研究停止了一个多月了,还是有点放不下,等有机会一定研究透彻.感谢一下法克鸡丝博主. 好,研究了近两个月的游戏整体制作,熟悉了maya\unfl ...
- eclipse中安装adt插件
对于程序开发的学者来说,eclipse并不陌生,它为我们提供了一个非常广阔的平台来开发程序.同样我们也可以用它来开发android程序.但是在eclipse中并不能直接开发android程序,需要我们 ...
- TCP/IP协议工作原理简述
TCP/IP协议工作原理简述 // */ // ]]> TCP/IP协议工作原理简述 Table of Contents 1 概要 2 应用层 3 传输层 4 网络层 5 链路层 1 概要 ...
- 20151210study
-----------------------------------------------------The election officials were not neutral.选举官员并不是 ...
- [LeetCode][Java]Triangle@LeetCode
Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent n ...
- 我利用网上特效开发的Jquery插件
我利用网上特效开发的Jquery插件 代码如下 (function($){ $.fn.Dialogx = function(options) { var defaults={ Width:" ...
- MySQL 5.6 & 5.7最优配置模板
摘自:http://mp.weixin.qq.com/s?__biz=MjM5MjIxNDA4NA==&mid=207854835&idx=1&sn=c998031ae6816 ...
- java面试中问题
HashMap数据结构 http://blog.csdn.net/weiyouyin/article/details/5693496 HashMap冲突 http://www.blogjava.net ...