New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Canvas 与 SVG的主要区别 #13

Open
abcrun opened this Issue May 18, 2014 · 0 comments

Comments

Projects
None yet
1 participant
@abcrun
Owner

abcrun commented May 18, 2014

HTML绘制图形功能,如今比较流行的可能就是HTML5中的Canvas了,但是并不是任何场合都适合用Canvas,HTML5草案推出之前,对于页面的图像动画,虽然各家都推出自己支持方案,但是一直以来都是由Flash统治着。IE通过调用Silverlight插件或者VML(CSS Filter)实现图形功能的,其他浏览器则是通过SVG来支持图形功能的。当然这篇文章主要是总结Canvas和SVG的区别,然后从具体实现上区分这两种HTML图形的差别。

Canvas与SVG的主要区别

  1. 从图像类别区分,Canvas是基于像素的位图,而SVG却是基于矢量图形。可以简单的把两者的区别看成photoshop与illustrator的区别。
  2. 从渲染模式上来说,Canvas属于 即时模式,而SVG则是 保留模式 ,这两种模式的区别可以参见 cshao 的博文: http://www.lifelaf.com/blog/?p=354。
  3. 从结构上说,Canvas没有图层的概念,所有的修改整个画布都要重新渲染,而SVG则可以对单独的标签进行修改。
  4. 从操作对象上说,Canvas是基于HTML canvas标签,通过宿主提供的Javascript API对整个画布进行操作的,而SVG则是基于XML元素的。
  5. 从功能上讲,SVG发布日期较早,所以功能相对Canvas比较完善。
  6. 关于动画,Canvas更适合做基于位图的动画,而SVG则适合图表的展示。关于SVG和Canvas的运行场景可参考MSCN关于 如何为您的网站在Canvas和SVG之间做出选择
    如何为您的网站在Canvas和SVG之间做出选择
  7. 从搜索引擎角度分析,由于svg是有大量标签组成,所以可以通过给标签添加属性,便于爬虫搜索。

根据上面所述,下面总结一下Canvas与SVG在应用中的主要区别:

基本图形

SVG是基于XML的矢量图形,而Canvas则是由脚本绘制出来的。尽管两者图形的绘制形式不同,但是不管是Canvas还是SVG,都是通过定义的基本形状组合其他图形的。HTML5中将Canvas的基本图形简单的分为:line,rect,path(包括arc,curve)三部分,而SVG则相对复杂一点包括:line,rect,circle,ellipse,polyline(polygon),path。SVG将circle,ellipse,polyline(polygon)给单独罗列出来,Canvas则在Path的基础上分离出来了arc,curve形状的接口,其他的两者基本相同。需要说明的是SVG中path 可以用来画任何形状(包括曲线等),而Canvas则只能通过arc,curve来绘制圆弧,而且对于Canvas在绘制path前后需要通过context.beginPath(),contex.closePath()标识。

SVG是有XML节点组成的,相对canvas脚本会稍微简单点,但是同样需要一堆数据用来表示各种参数,未免容易记混,不过还好有些规律:

  • x,y表示起点坐标
  • x1,y1 x2,y2 xn,yn表示控制或者连接点坐标
  • c,r开头肯定是和圆弧有关了,r表示 半径 ,cx,cy表示圆心,rx,ry分别表示x轴y轴相对弧线的半径
  • points,d很明显表示多个点的组合

SVG中规定的基本形状:

<line x1=0 y1=0 x2=200 y2=200 stroke=green stroke-width=4 stroke-dasharray=10,6></line>
<rect x=0 y=0 rx=50 ry=100 width=300 height=150 stroke=orange stroke-width=8 stroke-dasharray=10,6 fill=yellow fill-opacity=0.8></rect>
<circle cx=50 cy=50 r=50 fill=gray fill-opacity=0.6></circle>
<ellipse cx=100 cy=100 rx=100 ry=80 fill=blue fill-opacity=0.4></ellipse>
<polyline points="0,0 15,30 24,200 100,50" fill="none" stroke=red stroke-width=4></polyline>

不管是SVG还是Canvas,绘制曲线相对都是比较复杂,所以我将他们放在一起总结一下,不管是对比还是学习备忘还是很有必要的,先从复杂的说起 - SVG,先通过代码看一下SVG中path节点构成:

<path fill="#c53" fill-rule="evenodd" opacity=".5" d="M 70 140 C 17.5 ,140 150,0 200,100 C 220, 140 40,100 100,0 C 127,-47 170,140 70 140"/>

通过path绘制图形时往往会被里面类似于M,C,Q等的参数给搞糊涂,如下是从《Building Web Application with SVG》一书中摘抄下来的命令表:

Commands Parameters Instruction
M, m x, y Move to a new point (x,y).
L, l x, y Draw a line from the current point to a new point (x,y).
H, h x Draw a horizontal line from the current point to a new point (x,current-point-y).
V, v y Draw a vertical line from the current point to a new point (current-point-x,y).
A, a rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y Draw an elliptical arc from the current point to a new point (x,y). The arc belongs to an ellipse that has radii rx and ry and a rotation with respect to the positive x-axis of x-axis-rotation (in degrees) - 需要说明一下:表示X轴与椭圆横轴的角度. If large-arc-flag is 0 (zero), then the small arc (less than 180 degrees) is drawn. A value of 1 results in the large arc (greater than 180 degrees) being drawn. If sweep-flag is 0, then the arc is drawn in a negative angular direction (counterclockwise); if it is 1, then the arc is drawn in a positive angular direction (clockwise).
Q, q x1, y1 x, y Draw a quadratic Bézier curve from the current point to a new point (x,y) using (x1,y1) as the control point.
T, t x, y Draw a smooth quadratic Bézier curve segment from the current point to a new point (x,y). The control point is computed automatically as the reflection of the control point on the previous command relative to the current point. If there is no previous command or if the previous command was not a Q, q, T, or t, the control point is coincident with the current point.
C, c x1, y1 x2, y2 x, y Draw a cubic Bézier curve from the current point to a new point (x,y) using (x1,y1) and (x2,y2) as control points.
S, s x2, y2 x, y Draw a smooth cubic Bézier curve segment from the current point to a new point (x,y). The first control point is computed automatically as the reflection of the control point on the previous command relative to the current point. If there is no previous command or if the previous command was not a C, c, S, or s, the first control point is coincident with the current point. (x2,y2) is the second control point.

而相对Canvas绘制弧线的API:

  • context.arc(x, y, radius, startAngle, endAngle, anticlockwise)//圆弧
  • context.arcTo(x1, y1, x2, y2, radius)//圆弧 - 依赖控制点-:当前点,x1,y1与x2,y2形成的切线
  • context.quadraticCurveTo(cpx, cpy, x, y)//二次方程曲线,当前点是起始点,xy表示结束点,而cpx,cpy表示控制点
  • context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)//贝塞尔曲线。三个控制点 - 当前点,cp1x/cp1y,cp2x/cp2y

具体看下面的例子:

var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');

context.stokeStyle = 'black';
context.beginPath();
context.moveTo(0,0);
context.lineTo(100,200);
context.arcTo(350,350,100,100,20)
context.stroke();
context.closePath();
context.strokeStyle = 'green';
context.beginPath();
context.moveTo(100,100);
context.lineTo(350,350);
context.stroke();
context.closePath();
context.strokeStyle = 'green';
context.beginPath();
context.moveTo(100,200);
context.lineTo(350,350);
context.stroke();
context.closePath();

context.strokeStyle = 'blue';
context.beginPath();
context.moveTo(0,0);
context.quadraticCurveTo(100,25,0,50);
context.stroke();
context.closePath();
context.strokeStyle = 'purple';
context.beginPath();
context.moveTo(0,50);
context.lineTo(100,25);
context.stroke();
context.closePath();

context.strokeStyle = 'orange';
context.beginPath();
context.moveTo(150,0);
context.bezierCurveTo(0,125,300,175,150,300);
context.stroke();
context.closePath();
context.strokeStyle = 'red';
context.beginPath();
context.moveTo(0,125);
context.lineTo(150,0);
context.stroke();
context.closePath();
context.strokeStyle = 'red';
context.beginPath();
context.moveTo(300,175);
context.lineTo(150,300);
context.stroke();
context.closePath();

画布,群组和复用(pattern/gradient)

Canvas是通过Javascript绘制在canvas DOM区域的,而svg则是有不同的图形元素节点组合在SVG元素中的。这个很好理解,DOM中的canvas和svg都是对应的画布,然而如果希望图像仅显示在画布部分区域或者说只有部分区域的图像可以显示,该如何处理呢?当然可以通过坐标位置和图像大小来设置,然而canvas和SVG定义了clip来设置画布上的显示内容,除了clip,svg还可以通过设置mask来设置图形的显示,如果熟悉Photoshop或者illustrator的话对于这些概念一定该不会陌生。需要说明的是canvas中可以通过context.save(),context.restore()改变上下文状态来改变显示图像的作用范围,而svg则是通过预先定义clipPath或mask(里面是显示图形的组合),然后对图形设置clip-path或mask属性来设置显示的作用域。除此之外Canvas针对所有的图形,可以通过设置globalCompositeOperation来显示或隐藏不同形状重叠部分,有点类似于PS中的选取合并或蒙版。

globalCompositeOperation:copy|destination-atop|destination-in|destination-out|destination-over|lighter|source-atop|source-in|source-out|source-overxor

Canvas不像Photoshop,它没有图层的概念,更别提 群组 了,当图形部分区域改变时,canvas需要重新绘制整个图形;同理Canvas也不能复用已有的图形。然而SVG是由不同的xml节点组成的,可以认为有不同的图层构成,因此SVG可以对定义的图形进行群组和复用。SVG通过(<g />)包含需要的图形节点,对整个组进行处理;通过<use xlink:href="#id" />对定义好的节点复用,例如:

<rect fill="#ada1d9" fill-opacity="1" fill-rule="nonzero" stroke="#32287d" stroke-width="10" stroke-linecap="butt" stroke-linejoin="bevel" stroke-miterlimit="4" stroke-opacity="0.4" id="rectangle" width="20" height="20" x="90" y="-10" />
<use x="" y="" xlink:href="#rectangle" />

另外SVG还可以预先定义pattern,渐变(linearGradient/radialGradient)甚至包括剪切区/蒙版(上面已提过),通过设置图形的属性对预先定义好的如pattern和渐变重复利用(如fill="url(#patternId)")。相应的Canvas是通过context.createLinearGradient(),context.createLinearGradient()创建渐变作用于接下来创建的图形的,同时canvas也有pattern(context.createPattern()),只不过是针对位图的,用于设置图片的重复状态。

动画与交互

Canvas和SVG都可以通过Javascript和客户端进行交互或创建动画;SVG还可以通过声明的方式,在图形中声明动画标签,通过设置属性来进行交互和创建动画,这个有点类似于CSS3的动画属性。

SVG中动画的declare标签包括:

  • (属性:attributeName,dur,values,repeatCount)
  • (属性:type,dur,from,to,repeatCount)
  • (animateMotion用于设置图形按照设置好的路径运动)

如果在图形中声明了动画标签,可以给声明的动画标签或者通过set标签给begin,end属性绑定触发行为(如#id.click+1、indefinite等),当这些行为在触发时改变图形的样式。这很像给SVG绑定Javascript事件,但是这两者是完全不同的。当然这些声明动画标签可以绑定 onend事件 从而在动画结束后调用这个事件绑定的函数,同时也可以通过调用Javascript函数来触发动画的开始:node.beginElement();

通过JS操作或执行动画时没有特殊之处,只不过Canvas是针对整个画布的所有元素,通过设置和判断每个元素的位置坐标重新渲染;而SVG可以对选取的节点进行操作。不过对于SVG,由于SVG中元素都是XML,所以在DOM节点操作时多出个命名空间的标识:

  • setAttributeNS()
  • getAttributeNS()
  • getElementById()
  • createElementNS()
  • appendChild()
  • cloneNode()
  • firstChild
  • previousSibling() and nextSibling()
  • getElementsByTagNameNS()
  • parentNode()
  • removeChild()
  • createTextNode()

其他

  • SVG中对文字功能相对更多,如可以预先定义路径,然后通过设置textPath的xlink:href="#id"属性,使文字按照路径的方向排列:
<defs>
<path id="curve" d="M 10 100 C 200 30 300 250 350 50" />
</defs>
<text font-family="arial" font-size="16" fill="black">
<textPath xlink:href="#curve">Hello, here is some text lying along a Bézier curve.</textPath>
</text>
  • Canvas是基于位图的,可以利用图片做很多事情,甚至可以利用drawImage或者tile位置来模拟audio,video的功能,而SVG则相对比较简单。
  • SVG比较成熟,有很多滤镜可以使用,而Canvas并不完善。
  • Canvas可以将图形保存转换成64位data编码的位图,可以用于保存或其他操作。

参考文档:

@abcrun abcrun added the blog label May 18, 2014

@abcrun abcrun added the 未完成 label May 31, 2014

@abcrun abcrun removed the 未完成 label Mar 11, 2015

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment