解析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中,后面的循环要根据这两个属性来确定其作用域的文本格式。

 1 while(text[index]!=WHITESPACE&&text[index]!=RIGHTSYN){
 2                         tagname.push(text[index++]);
 3                         if(index==len)break;
 4                     }
 5                     if(index==len)return;
 6                     while(text[index]!=RIGHTSYN){
 7                         if(index==len){
 8                             break;
 9                         }
10                     }
11                     var tag=tagname.join(‘‘).toLowerCase();
12                     tagname=[];
13                     if(tag==TAGB){
14                         isbold=true;
15                     }
16                     else if(tag==TAGI){
17                         isitalic=true;
18                     }
19                     else if(tag==TAGLI){
20                         isicon=true;
21                     }
22                     else if(tag==TAGU){
23                         isunderline=true;
24                     }
25                     if(tag==TAGP||tag==TAGLI||tag==TAGUL){
26                         nowrap=false;
27                     }
28                     else{
29                         nowrap=true;
30                     }
31                     if(tag==TAGUL){
32                         uloffset+=ULOFFSET;
33                     }
34
35                     if(isitalic==true&&isbold==true){
36                         font=ITALICBOLD;
37                     }
38                     else if(isitalic==false&&isbold==true){
39                         font=BOLD;
40                     }
41                     else if(isitalic==true&&isbold==false){
42                         font=ITALIC;
43                     }
44                     else{
45                         font=NORMAL;
46                     }
47                     if(VALIDTAGS.contains(tag)){
48                         tagnames.push(tag);
49                         fontsarr.push(font);
50                     }

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

三、有效闭标签的处理

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

 1 if(text[index]=="/"){
 2                     var arr=[];
 3                     while(++index<len&&text[index]!=RIGHTSYN&&text[index]!=LEFTSYN){
 4                         arr.push(text[index]);
 5                     }
 6                     if(index==len)return;
 7                     if(text[index]==LEFTSYN)break;
 8                     var tag=arr.join(‘‘).trim().toLowerCase();
 9                     if(tag==tagnames[tagnames.length-1]){
10                         font=fontsarr.pop();
11                         tagnames.pop();
12                         if(tag==TAGUL){
13                             uloffset -=ULOFFSET;
14                             uloffset =(uloffset>0)?uloffset:0;
15                         }
16                         if(!tagnames.contains(tag)){
17                             if(tag==TAGI){
18                                 font=font.replace("italic",‘normal‘);
19                                 isitalic=false;
20                             }
21                             else if(tag==TAGB){
22                                 font=font.replace("bold",‘normal‘);
23                                 isbold=false;
24                             }
25                             else if(tag==TAGU){
26                                 isunderline=false;
27                             }
28                         }
29                     }
30                 }

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

四、canvas渲染

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

 1 var  drawtext=function(data){
 2                 data=data.trim();
 3                 var len=data.length;
 4                 if(len==0){
 5                     return;
 6                 }
 7                 if(!nowrap&&xoffset>MARGIN){
 8                     xoffset = MARGIN+uloffset;
 9                     yoffset += LINEHEIGHT;
10                 }
11
12                 if(isicon){
13                     ctx.beginPath();
14                     ctx.arc(MARGIN+uloffset+MARGIN,yoffset-MARGIN,MARGIN,0,Math.PI*2,true);
15                     ctx.closePath();
16                     ctx.fill();
17                     xoffset +=30;
18                 }
19
20
21                 var index=0;
22                 var renderindex=0;
23                 ctx.font=font;
24                 while(index<len){
25                     while(canvaswidth-xoffset>ctx.measureText(data.substring(renderindex,++index)).width){
26                         if(index===len){
27                             break;
28                         }
29                     }
30
31                     if(index==len){
32                         ctx.fillText(data.substring(renderindex,index),xoffset,yoffset);
33                         if(isunderline){
34                             canvas.strokeStyle = "red";
35                             canvas.lineWidth = 5;
36                             ctx.beginPath();
37                             ctx.moveTo(xoffset, yoffset);
38                             ctx.lineTo(xoffset+ctx.measureText(data.substring(renderindex,index)).width, yoffset);
39                             ctx.closePath();
40                             ctx.stroke();
41                         }
42                         xoffset+=ctx.measureText(data.substring(renderindex,index)).width;
43                         break;
44                     }
45                     ctx.fillText(data.substring(renderindex,--index),xoffset,yoffset);
46                     if(isunderline){
47                         canvas.strokeStyle = "red";
48                         canvas.lineWidth = 5;
49                         ctx.beginPath();
50                         ctx.moveTo(xoffset, yoffset);
51                         ctx.lineTo(canvaswidth, yoffset);
52                         ctx.closePath();
53                         ctx.stroke();
54                     }
55
56
57                     renderindex=index;
58                     xoffset = MARGIN;
59                     yoffset += LINEHEIGHT;
60                 }
61                 return;
62             };

结束语

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

Array.prototype.contains=function(item){
            return new RegExp("^" + this.join("|")+ "$","i").test(item.toString());
        }
时间: 2024-10-29 19:07:36

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

UGUI多个Canvas的渲染先后层次关系设置

这几天在做游戏的WindowManager,一开始只是想到打开单一窗口和设置窗口并存的问题,一切运行良好. 但是昨天加了一个等待窗口(沙漏加菊花)之后就出现了问题. 首先说明我的Window的结构: 一个空的GameObject Canvas 所有的UI控件 举例说: 在登录界面,点击登录按钮后向服务器发送登录请求,这个时候会显示转菊花的界面,一切OK. 窗口打开顺序为 登录窗口  --- 等待窗口 下面的大图就是登录窗口,上面的就是沙漏菊花等待窗口. 这个时候如果再弹出一个其它的界面,比如弹出

解析json结构绘制canvas

在工作中偶尔会遇到绘制转发卡/邀请卡的业务,且这个转发卡/邀请卡的风格会有很多,要求最后生成图片.这时候如果使用一张图片绘制一个canvas,这个工作量会相当大.分析一下转发邀请的内容,会发现所有的里面的元素都是一样的,只是风格不一致,所以我使用了解析json结构来绘制canvas,如果后期需要增加风格,只要增加json就可以了. demo图大概这样: 点击下方的不同风格的图片就会生成不一样的图片. 下面我们要实现代码: style样式: *{padding: 0;margin: 0;} bod

Android中自定义常用的三个对象解析(Paint,Color,Canvas)

Paint,Color,Canvas Paint:画笔对象,画图用的"笔" Color:颜色,相当于调料 Canvas:画布,现实中的纸板 Paint 画笔 常用的方法就是设置和获取到画笔的样式: paint.setStyle(); 设置画笔的风格,空心的或者是实心的 paint.setColor(); 设置画笔的颜色 paint.setStrokeWidth(); 设置边框线的宽度 paint.setAlpha(); 设置画笔的Alpha值 paint.setAntiAlias();

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

本篇文章主要介绍了详解Nuxt.js Vue服务端渲染摸索,写的十分的全面细致,具有一定的参考价值,对此有需要的朋友可以参考学习下.如有不足之处,欢迎批评指正. Nuxt.js 十分简单易用.一个简单的项目只需将 nuxt 添加为依赖组件即可.Vue因其简单易懂的API.高效的数据绑定和灵活的组件系统,受到很多前端开发人员的青睐.国内很多公司都在使用vue进行项目开发,我们正在使用的简书,便是基于Vue来构建的.我们知道,SPA前端渲染存在两大痛点:(1)SEO.搜索引擎爬虫难以抓取客户端渲染的

渲染引擎,HTML解析

这是how browser to work 的翻译 转自:携程设计委员会 渲染引擎 渲染引擎的职责是……渲染,也就是把请求的内容显示到浏览器屏幕上. 默认情况下渲染引擎可以显示HTML,XML文档以及图片. 通过插件(浏览器扩展)它可以显示其它类型文档.比如使用PDF viewer插件显示PDF文件.我们会在一个专门的章节讨论插件与扩展.在这一节我们将专注渲染引擎的主要用途——显示用CSS格式化的HTML与图片. 各种渲染引擎 我们提到的Firefox, Safari两种浏览器构建于两种渲染引擎

页面渲染深入解析

基本渲染过程 用户请求的资源通过浏览器的网络层到达渲染引擎后,渲染工作开始.每次渲染文档通常不会超过8K的数据块,其中基础的渲染过程如下图所示: 第一步:渲染引擎首先解析HTML文档,转换为一棵DOM树: 第二步:接下来不管是内联式,外联式还是嵌入式引入的CSS样式也会被解析,渲染出另 外一棵用于渲染DOM树的树-渲染树(render tree) ,渲染树包含带有颜色,尺寸等显示属性的矩形,这些矩形的顺序与显示顺序一致: 第三步:然后就是对渲染树的每个节点进行布局处理,确定其在屏幕上的显示位置:

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

hello~各位亲爱的看官老爷们大家好.估计大家都听过,尽量将CSS放头部,JS放底部,这样可以提高页面的性能.然而,为什么呢?大家有考虑过么?很长一段时间,我都是知其然而不知其所以然,强行背下来应付考核当然可以,但实际应用中必然一塌糊涂.因此洗(wang)心(yang)革(bu)面(lao),小结一下最近玩出来的成果. 友情提示,本文也是小白向为主,如果直接想看结论可以拉到最下面看的~ 由于关系到文件的读取,那是肯定需要服务器的,我会把全部的文件放在github上,给我点个 star 我会开心

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

渲染引擎 渲染引擎的职责是……渲染,也就是把请求的内容显示到浏览器屏幕上. 默认情况下渲染引擎可以显示HTML,XML文档以及图片. 通过插件(浏览器扩展)它可以显示其它类型文档.比如使用PDF viewer插件显示PDF文件.我们会在一个专门的章节讨论插件与扩展.在这一节我们将专注渲染引擎的主要用途——显示用CSS格式化的HTML与图片. 各种渲染引擎 我们提到的Firefox, Safari两种浏览器构建于两种渲染引擎之上:Firefox使用Gecko —— Mozilla自家的渲染引擎:S

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

背景: 在地图上绘制大量的circleMarker,leaflet能选择使用canvas来渲染,比起默认的svg渲染来说在大量绘制的情况下会更加流畅.但当触发其中某一个circleMarker的tooltip或popup时,浏览器报错"Uncaught RangeError: Maximum call stack size exceeded": 解决过程: 1.写了个测试代码来复现问题: 1 <!DOCTYPE html> 2 <html> 3 <head