SVG 与 Canvas:如何选择
来源是微软的官方文档:
http://msdn.microsoft.com/zh-cn/library/gg193983
Canvas
另一种向用户提供更丰富的图形体验的方法,通过 <canvas> 标记提供,该标记由 Apple for Safari 在 HTML5 中或在其他图形小工具中引入。它在绘制即时模式图形(包括矩形、路径和图像)方面公开更具编程性的体验,与 SVG 类似。即时模式图形呈现是一个“触发即忘”模型,该模型将图形直接呈现到屏幕上,但随后对所完成的操作不保留任何上下文。与保留模式相反,不保存呈现的图形;要在每次需要新框架时描述整个场景,开发人员需要重新调用所有必需的绘图命令,而不考虑实际更改(SVG 已知拥有“场景图”)。
元素
为了使用画布 (Canvas) 功能,Web 开发人员直接引入了一个 Canvas 元素:
<canvas id="myCanvas" width="1200px" height="1200px"></canvas>
然后使用 <canvas> 传统的2D基础库 API 来绘制图像和矢量。
使用 JavaScript 代码执行对画布上图形的操作,通过添加对图形的支持可以获得 Web 开发人员熟悉使用的优势。
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d");
如前面提到的,存在与 SVG 中相似的形状和对象,举例来说,开发人员可以使用下面的代码来绘制矩形:
ctx.fillStyle = "rgb(0,0,255)"; ctx.fillRect(10, 10, 100, 100);
稍后将讨论这些方法的优缺点以及适当的方案。
最终结果与在 SVG 中相同。http://samples.msdn.microsoft.com/workshop/samples/graphicsInHTML5/canvasintro.htm
但正如 SVG 一样,Canvas 具有更复杂的几何基元,区别在于这些基元采用函数形式。
事件的可编程性
为了能够绘制比矩形更复杂的图形(如前面显示的夏威夷地图),canvas
API 提供了一个路径 API,该路径 API 支持与 SVG 中的 <path>
元素相似的命令,只是你要对每一行代码段调用相应的 API,而不是在单个属性中列出它们:
ctx.beginPath(); ctx.moveTo(233.08751,519.30948); ctx.lineTo(235.02744,515.75293); ctx.lineTo(237.29070000000002,515.42961); ctx.lineTo(237.61402,516.23791); ctx.lineTo(235.51242000000002,519.30948); ctx.lineTo(233.08751,519.30948); ctx.closePath();
有关夏威夷地图,请参阅 http://samples.msdn.microsoft.com/workshop/samples/graphicsInHTML5/canvasmap.htm。
路径 API 并不局限于 moveTo
和 arc
,它包含相同的 SVG 方向相位(包括二次曲线和贝塞尔曲线)。
通过有限的事件和功能可以捕获鼠标在图像上的位置。因为不存在图形保留知识,所以编程人员必须转换 mouseX
标记的单个元素上的 mouseY
、<canvas>
坐标,然后将该命令相应地传送到位于内存中结构中的形状。第三方库为支持更复杂的路径而存在,这包括内置 isPointInPath API,但后者被限制为最后路径绘制。因而与 SVG 不同,既没有任何样式也不支持多个几何图上的命中检测。另外,因为 Canvas 不支持可伸缩性,所以缩放时夏威夷地图将很快失真:
Canvas 是一个功能强大的低级别 API,开发人员利用它可以提供新的图形体验。
Canvas 与 SVG 的简要摘要
下面是简要摘要,可帮助你确定何时使用 Canvas 而不使用 SVG 或者何时使用 SVG 而不使用 Canvas 来创建矢量图形。
Canvas | SVG |
---|---|
基于像素(动态 .png) | 基于形状 |
单个 HTML 元素 | 多个图形元素,这些元素成为 DOM 的一部分 |
仅通过脚本修改 | 通过脚本和 CSS 修改 |
事件模型/用户交互颗粒化 (x,y) | 事件模型/用户交互抽象化 (rect, path) |
图面较小时、对象数量较大 (>10k)(或同时满足这二者)时性能更佳 | 对象数量较小 (<10k)、图面更大(或同时满足这二者)时性能更佳 |
在上表中,考虑这二者在现有软件方面的意象模型。Canvas 类似于 MSPaint,在其中,你可以使用形状和其他工具来绘制和创建图像。SVG 类似于 Office PowerPoint 幻灯片,它具有可编程的支持并且能够添加主题。
何时使用 <canvas>
、何时使用 SVG:方案
这一节介绍这两种技术的技术优势和限制,包括用于确定一种技术何时相对于另一种技术更适合的常用方法。应该注意的是,SVG 和 <canvas>
都能实现几乎相同的结果,功能也完全重复。介绍以下特定情况非常重要:<canvas>
明显好于 SVG,或相反,二者组合更加合适,或可以使用和考虑任一技术。
这些方案比较清晰地描述了更适合使用 SVG 的情况、更适合使用 Canvas 的情况,以及其他介于这之间的情况。它们描述了每种技术的优缺点,以便开发人员可以了解相应技术的行为并针对其应用程序做出好的选择。
从性能方面选择
有时存在一些外部影响,要求独立于(或几乎独立于)功能选择技术。有关使用 Canvas 或 SVG 的问题,存在两个主要区别。
有时开发人员的知识、技能组合和现有资产会对技术的选择起到重大作用。如果开发人员具备低级别图形 API 方面的深层知识,但在 Web 技术方面知识有限,则很可能会选择 Canvas 技术。
另外,性能对于高流量的网站来说是绝对关键的。可以对这两种技术的性能特征加以比较。这可能会要求开发 Canvas 没有附带的辅助功能、自定义样式以及更粒度化的用户交互功能。虽然 Canvas 通常被视为具有高性能,但是并不意味着它就是明显的选择。下图显示了 SVG 对象和 Canvas 对象之间在呈现时间上的差异。
一般情况下,随着屏幕大小的增大,画布将开始降级,因为需要绘制更多的像素。随着屏幕上的对象数目增多,SVG 将开始降级,因为我们正不断将这些对象添加到 DOM 中。这些度量不一定准确,以下方面的不同一定会引起变化:实现和平台、是否使用完全硬件加速的图形,以及 JavaScript 引擎的速度。
高保真度的复杂矢量文档
高保真度的复杂矢量文档已经成为并将继续成为 SVG 的最有效点,原因主要有两个。存在足够多的极为详细的文档,包括由 CAD 程序生成的那些文档,针对这些文档,SVG 的 scalable 部分提供了独立文档形式或嵌入网页中的文档形式的详细视图。通过该技术还可以进行高保真打印。SVG 的声明性性质向工具、客户端或服务器端提供从数据库生成形状的能力。 最后,我们看到了政府机构的发展,因工程图(为了专利)或工业图(为了城市规划目的)缘故从建议支持转变为对 SVG 的必需支持。这种转变还将继续,因为对于公众使用的电子文档(如下),政府部门越来越不是只喜欢一家供应商:
- 建筑图、工程图和楼层图
- 电子图、航空图和示意图
- 组织结构图
- 地图
- 生物图
以下各图显示了前一方案中可以保留的详细信息示例。第一个图像显示可以在测试驱动网站上找到的网页快照。它包含呼吸系统图和元素周期表。
http://ie.microsoft.com/testdrive/Graphics/RealWorldDataAndDiagrams/Default.xhtml
第二个图像显示同一张图放大 1000% 后的效果
尽管考虑到观察大的示意图的有用性,但在需要细化到细节处时或者出于工程目的需要打印整个示意图时,具有可缩放性的 S 将变得足够清晰和有价值。出于这些原因,我们将高保真度的复杂矢量文档放在谱表的远端,接近 SVG,如下图中所示。
这些文档也可以受益于交互性,这是 SVG 使这些方案最适合于保留图形模式的第二方面。
增强的 Web 图形
SVG 作为图像格式
SVG 另外还常用于简单图像,无论是应用程序还是网页中的图像,大图像还是小图像。由于 SVG 要加载到 DOM 中,或者创建图像前至少要进行解析,所以性能会稍微有所下降,但相比于呈现网页的成本(大约几毫秒),这种下降是极其微小。
在文件大小方面(为了评估网络流量的目的),下面演示的两个图像是一样的,只差了 1K(SVG 稍微大点,没有压缩)。
与以前一样,因为 SVG 作为图像格式是可缩放的,所以如果开发人员想要以更大的比例使用该图像,或者用户使用高 DPI的屏幕,则可移植网络图形 (PNG) 要么会变得异常,要么需要更大形式的文件来实现保真。
SVG 因此可以充当非常好的图像替换格式,甚至对网页上最简单的图像也是如此。静态 WebApp/网页图像因此落在谱表的 SVG 端。
像素操作
在谱表的另一端,当使用 Canvas 时可以获得快速的绘图速度,且不需要保留相应信息。存在若干种最适合于 <canvas> 的实时数据方案。使用光线跟踪在像平面上通过像素跟踪光线路径并模拟其与虚拟对象相遇的效果,可以水化图像。 下图显示了这种模拟。
需要很多计算,因此速度依赖于浏览器中的 JavaScript 引擎速度。然而尽管大多数情况下本地代码无可置疑要更快一些,但是随着 JavaScript 引擎逐渐成熟,我们开始看到在像程序集和 C++ 这样的时期内这种差距在缩小。
光线跟踪(通常在 Web 上的背景中完成)获得的效果甚为广泛。范围从创建许多不同的视觉效果(包括根据其他简单矢量图形创建逼真的图像)到应用照片过滤器以去除红眼。
因为 Canvas API 允许开发人员读写像素,所以这里唯一的限制是速度和想象力。上一个示例由 Adam Burmister 提供并且位于 http://labs.flog.co.nz/raytracer/ 上。 在此示例中,可通过大量的库来实现创建最终图像所需的计算,但主结束函数是 fillRect
。
setPixel: function(x, y, color){ var pxW, pxH; pxW = this.options.pixelWidth; pxH = this.options.pixelHeight; this.canvas.fillStyle = color.toString(); this.canvas.fillRect (x * pxW, y * pxH, pxW, pxH); },
出于此原因,高性能图形(如光线跟踪器)作为 Canvas 方案落在谱表的最左端,如下图所示。
注意生成上述光线跟踪器的作者 注意 因为该方案将产生静态图像,所以桌面软件经过了很好的调整以适合光线跟踪器所需的大量浮动操作。
下至金属像素操作的有趣实现是图像的过滤器应用。过滤器已存在于 Web 上,且需要显著的处理速度(受益于其对图形管道中更深层的硬件加速图形的应用),因此开发人员可以试用边缘检测或其他数学表达式等算法。
实时数据
对于更常见的方案,Canvas 非常适合输出实时数据。请注意如何简便地确定这些方案,因为已经表明使用 Canvas 难以进行用户交互。因此,下面将讨论非交互的实时数据可视化。
今天的天气数据可以通过特定间隔内服务器上生成的图像非常静态地呈现,也可以尽可能快地通过客户端第三方插件呈现。尽管 ECWMF 已经研究过使用 SVG 而不使用服务器生成的图像如何节省成本,但是 Canvas 在天气模式(以及其他快速的实时数据)的大多数图形表示形式方面,明显是赢家。下图显示以图形方式显示在地图上的天气模式。
如你可以从上图中看到的,没有必要存在大的绘图图面,而且屏幕上的对象数量是相当高的。通过使用 Canvas API,可以在不影响 DOM 的情况下快速绘制(和擦除)这些对象。尽管可以使用 SVG Ellipse 完成此操作,但是将它们加载到 DOM 中以及通过动画修改它们的成本是非常高的。事实上,无论你是在图像中还是在数据动画中看到大量的形状(尤其是不相似的形状)要进行分析,通常都会指出 Canvas 是要使用的技术。这里的实际限制是受 CPU 速度和 JavaScript 引擎速度的控制,能多快可视化显示数据。但除了占用 CPU 的光线跟踪方案之外,仍然可以实现合理的动画。Reasonable 描述客户端可以使用 JavaScript 执行的操作与服务器可以通过电线计算和封送的操作之间的相对动画。
此方案似乎是 <canvas>
的关键用例。
像素替换(绿屏)
另一个使用 Canvas 的可能情况是,在视频上进行颜色检测以便用其他场景或图像替换背景色。像光线跟踪器或过滤器一样,因 JavaScript 中当前性能速度限制的缘故,很可能会使用桌面软件预处理任何需要高的最终产品质量的现实方案。但是,由于 <canvas> 是为低级别像素读取和写入设计的,因此诸如 greenscreen 替换之类的方案甚至无法使用 SVG 完成。
从两个视频中读写像素到另一个视频中所需的代码要求使用两个视频、两个画布和一个最终画布。一次捕捉视频上的一帧,然后绘制到两个单独的画布上。这样允许读回数据。
ctxSource1.drawImage(video1, 0, 0, videoWidth, videoHeight); ctxSource2.drawImage(video2, 0, 0, videoWidth, videoHeight);
因此,下一步是检索每个绘制图像的句柄,以便你可以检查每个单独的像素。
currentFrameSource1 = ctxSource1.getImageData(0, 0, videoWidth, videoHeight); currentFrameSource2 = ctxSource2.getImageData(0, 0, videoWidth, videoHeight);
获取后,代码将浏览绿屏的像素数组,搜索绿色像素,如果找到,代码将用背景场景中的像素替换所有绿色像素。
for (var i = 0; i < n; i++) { // Grab the RBG for each pixel: r = currentFrameSource1.data[i * 4 + 0]; g = currentFrameSource1.data[i * 4 + 1]; b = currentFrameSource1.data[i * 4 + 2]; // If this seems like a green pixel replace it: if ( (r >= 0 && r <= 59) && (g >= 74 && g <= 144) && (b >= 0 && b <= 56) ) // Target green is (24, 109, 21), so look around those values. { pixelIndex = i * 4; currentFrameSource1.data[pixelIndex] = currentFrameSource2.data[pixelIndex]; currentFrameSource1.data[pixelIndex + 1] = currentFrameSource2.data[pixelIndex + 1]; currentFrameSource1.data[pixelIndex + 2] = currentFrameSource2.data[pixelIndex + 2]; currentFrameSource1.data[pixelIndex + 3] = currentFrameSource2.data[pixelIndex + 3]; } }
最后,像素数组将写入到目标画布中。
ctxDest.putImageData(currentFrameSource1, 0, 0);
http://samples.msdn.microsoft.com/workshop/samples/graphicsInHTML5/canvasgreenscreen.htm(若要完整地查看 greenscreen 代码,请查看该页的源代码。)
组合/交叉方案
以下方案可以在 SVG 或 Canvas 中完成,都可以获得适当的结果,但你可能会更喜欢一项技术胜过另一项技术。
图表和图形
需要矢量图形的图表和图形的谱表很宽广。大部分这些图形最好使用 SVG 进行创建,因为它们具有下列三个特征之一:
- 图形是从可以轻松转换为 XML (SVG) 的现有数据生成的
- 它们需要用户交互
- 优点是可在网页上设置样式
我们使用可显著增加方案范围的交互功能来扩展高保真文档方案。 其中包括:
- 交互的组织结构图和流程图
- 数据图(环形图、条形图、散点图)
- 交互式地图 - 路径查找
- 建筑图、工程图和楼层图
- 航班或音乐厅的座位图
已经确定快速的实时数据处理已针对 Canvas 进行了更好地优化(主要取决于速度)。
二维游戏
制作休闲游戏(此处定义为 Web 上的简单二维游戏)时,开发人员需要在 canvas 和 svg 之间做出选择。因为历史上游戏库一直利用较低级别的图形 API,所以将倾向于选择 <canvas>
。
当库的其他组成部分(如受欢迎的物理引擎)比图形层要明显深时,图形将变成实现细节。边框、速度、大小和位置等图形几何将传递给引擎,而引擎随后将用速度、碰撞和位置进行响应。图形位于堆栈中的最高层。
通过由同一作者开发的两个游戏来演示独立于游戏逻辑的图形概念,旨在说明 <svg>
和 <canvas>
:SVG-oids 和 canvas-pinball。一个出色的、独立于游戏引擎的图形层示例是将 canvas-pinball 与 SVG-Dice 进行比较(当两者都使用相同的物理引擎时)。
尽管游戏和演示逻辑是不同的,但是这两个游戏的物理引擎都会跟踪位置、碰撞、速度以及游戏组成部分的其他物理方面。
对于 canvas-pinball,自定义的更高级别的动画管理器通过使用一系列 Canvas API 重新绘制场景。
if (animationsInProgress) { ctx.save(); ctx.lineWidth = 2.0; ctx.beginPath(); ctx.moveTo(89, 322); ctx.lineTo(101, 295); . . . ctx.stroke(); ctx.restore(); ctx.moveTo(tVp.x, tVp.y); }
对于 SVG Dice,自定义的更高级别的动画管理器使用组转换通过 DOM 在屏幕上重新定位现有图形。
if (animationsInProgress) { this.rotation += (this.circleBody.m_linearVelocity.x/20); var transFormString = "translate(" + Math.round(this.circleBody.m_position.x) + "," + Math.round(this.circleBody.m_position.y) + ") scale (" + this.scale.toString() + ") rotate(" + Math.round(this.rotation).toString() + "," + Math.round(this.xTrans).toString() + "," + Math.round(this.yTrans).toString() + ")"; this.die2.setAttribute("transform", transFormString); }
一种技术要重新绘制和重新定位形状,而另一种技术只需重新定位,但需要在内存中维护形状,这会带来成本。此成本对于大多数休闲游戏来说都是相当低的,但预期是使用较低级别的 API 实现即时模式图形的游戏更加让人熟悉。
高级方案
可能大部分功能强大的方案都会涉及组合整个图形、样式和文档技术。
用户界面设计
几年前可能就在争论 SVG 是否是适合用于用户界面设计的技术。这些要求与 SVG 一致。事实上,Linux 操作系统的至少一个前端完全建立在 SVG 之上。滑块、复选框、圆形按钮等控件以及其他标准固有控件集中的非框控件都很适合在矢量图形中使用。但是,随着 CSS 的最近和未来开发(包括圆角、渐变、过滤器和打印机事件),可以通过标准的框模型 HTML 文档中心构造来开发大多数这些控件。其他控件(尤其是使用最新 CSS Grid 和 Flexbox 模型的控件)都更好地面向 HTML 元素,至少作为容器。
此处提供了一个丰富的数据驱动图表的示例。 尽管该示例的输出没有架构好,但显示的最终结果正确。 图形和图表控件是众所周知的难开发,但第三方以及 Microsoft 已经成功了。 通过在客户端或服务器端提供最新的数据绑定抽象,客户端的 Web 呈现主要保持静态或者需要插件,这样可减轻开发人员的负担。 下面我们利用了 SVG 的丰富性来提供增强的用户体验。 无论代码如何(将传递给客户端,又或许在将来更多的声明性交互中使用),图表都用两个关键组件来呈现。 工具和数据。图形工具或背景是基本的静态 SVG:
<rect id="tipsh" x="20" y="100" width="194" height="34" rx="5" ry="5" /> <rect id="tip" x="20" y="100" width="190" height="30" rx="5" ry="5" /> <text id="tiptxt" x="40" y="120" font-size="12" font-family="Arial" fill="#ffffff" visibility="hidden">milliseconds</text> <polygon id="arrow" points="10,110 20,105 20,115" style="fill:#ffffff" /> <line x1="3" x2="460" y1="359" y2="359" style="stroke:#cccccc;stroke-width:1"/>
然后每个单独的数据点要么传递到客户端并进行操作,要么在服务器上生成:
<text x="10" y="348" font-size="12" font-family="Arial">{Page}.svg</text> <rect x="115" y="350" width="86" height="8" style="fill:url(#inverseblue);filter:url(#Gaussian_Blur);" rx="12" ry="12"/> <rect x="115" y="333" width="86" height="17" rx="12" ry="12" onmouseover="changeColor(evt)" onmousemove="changeText(evt,'2 milliseconds')" onmouseout="changeTextNotOver(evt)" /> <text x="171" y="345" font-size="11" font-family="Arial" fill="#ffffff">6.1%</text>
http://samples.msdn.microsoft.com/workshop/samples/graphicsInHTML5/svgchart.htm
结论
通过最新浏览器中提供的现有矢量图形技术的分析功能,可以交互方式使用标准 Web 技术来满足现有的和新的方案。 从今以后,我们将拥有巨大的机会可以让声明性动画支持广告版位。 通过使用方案驱动的功能开发,我们可以在竞争中处于领先地位,并在 Web 应用程序和网页中提供基于标准的图形丰富的体验。