www.bifa88.com 41

利用分支优化,3个大妈娘心满满的例证带您入门

利用分层优化 HTML5 画布渲染

2015/02/02 · HTML5 ·
HTML5

原文出处: IBM
developerworks   

使用分层画布来优化HTML5渲染的教程,画布html5

这篇文章主要介绍了使用分层画布来优化HTML5渲染的教程,来自于IBM官方网站开发者技术文档,需要的朋友可以参考下

简介

通常情况下,在玩 2D 游戏或渲染 HTML5
画布时,需要执行优化,以便使用多个层来构建一个合成的场景。在 OpenGL 或
WebGL
等低级别渲染中,通过逐帧地清理和绘制场景来执行渲染。实现渲染之后,需要优化游戏,以减少渲染的量,所需成本因情况而异。因为画布是一个
DOM 元素,它使您能够对多个画布进行分层,以此作为一种优化方法。
常用的缩写

  •     CSS: Cascading Style Sheets(级联样式表)
        DOM: Document Object Model(文档对象模型)
        HTML: HyperText Markup Language(超文本标记语言)

本文将探讨对画布进行分层的合理性。了解 DOM
设置,从而实现分层的画布。使用分层进行优化需要各种实践。本文还将探讨一些优化策略的概念和技术,它们扩展了分层方法。

您可以下载在本文中使用的示例的源代码。
选择优化策略

选择最佳优化策略可能很难。在选择分层的场景时,需要考虑场景是如何组成的。大屏幕上固定物的渲染经常需要重用若干个组件,它们是进行研究的极佳候选人。视差或动画实体等效果往往需要大量的变化的屏幕空间。在探索您的最佳优化策略时,最好注意这些情况。虽然画布的分层优化需要采用几种不同的技术,但在正确应用这些技术后,往往会大幅提升性能。
设置层

在使用分层的方法时,第一步是在 DOM
上设置画布。通常情况下,这很简单,只需定义画布元素,将其放入 DOM
中即可,但画布层可能需要一些额外的样式。在使用 CSS
时,成功地实现画布分层有两个要求:

    各画布元素必须共存于视区 (viewport) 的同一位置上。
    每个画布在另一个画布下面必须是可见的。

图 1显示了层设置背后的通用重叠概念。
图 1. 层示例
www.bifa88.com 1
设置层的步骤如下:

  •     将画布元素添加到 DOM。
        添加画布元素定位样式,以便支持分层。
        样式化画布元素,以便生成一个透明的背景。

设置画布重叠堆栈

在 CSS 中创建一个重叠堆栈 (overlay stack) 可能需要少量的样式。使用 HTML
和 CSS
有许多方法进行重叠。本文中的示例使用一个<div>标签来包含画布。<div>标签指定了一个惟一
ID,它将样式应用于其子 HTML5 画布元素,如清单 1所示。
清单 1. 画布定位样式
 

CSS Code复制内容到剪贴板

  1. #viewport {   
  2.     /**  
  3.      * Position relative so that canvas elements
     
  4.      * inside of it will be relative to the parent
     
  5.      */  
  6.     position: relative;   
  7. }   
  8.     
  9. #viewport canvas {   
  10.     /**  
  11.      * Position absolute provides canvases to be able
     
  12.      * to be layered on top of each other
     
  13.      * Be sure to remember a z-index!
     
  14.      */  
  15.     position: absolute;   
  16. }   

容器<div>通过将所有子画布元素样式化为使用绝对定位来完成重叠要求。通过选择让#viewport使用相对定位,您可以适应未来的发展,因此,应用于子样式的绝对布局样式将会是相对于#viewport容器的样式。

这些 HTML5 画布元素的顺序也很重要。可以按元素出现在 DOM
上的顺序进行顺序管理,也可以按照画布应该显示的顺序来样式化 z-index
样式,从而管理顺序。虽然并非总是如此,但其他样式可能也会影响渲染;在引入额外的样式(比如任何一种
CSS 转换)时要小心。
透明的背景

通过使用重叠可见性来实现层技术的第二个样式要求。该示例使用这个选项来设置
DOM 元素背景颜色,如清单 2所示。
清单 2. 设置透明背景的样式表规则
 

XML/HTML Code复制内容到剪贴板

  1. canvas {   
  2.     /**   
  3.      * Set transparent to let any other canvases render through   
  4.      */   
  5.     background-color: transparent;   
  6. }  

将画布样式化为拥有一个透明背景,这可以实现第二个要求,即拥有可见的重叠画布。现在,您已经构造了标记和样式来满足分层的需要,所以您可以设置一个分层的场景。
分层方面的考虑因素

在选择优化策略时,应该注意使用该策略时的所有权衡。对 HTML5
画布场景进行分层是一个侧重于运行时内存的策略,用于获得运行时速度方面的优势。您可以在页面的浏览器中增加更多的权重,以获得更快的帧速率。一般来说,画布被视为是浏览器上的一个图形平面,其中包括一个图形
API。

通过在 Google Chrome 19
进行测试,并记录浏览器的选项卡内存使用情况,您可以看到内存使用的明显趋势。该测试使用了已经样式化的<div>(正如上一节中讨论的那样),并生成了放置在<div>上的用单一颜色填充的画布元素。画布的大小被设定为
1600 x 900 像素,并从 Chrome1 的任务管理器实用程序收集数据。表
1显示了一个示例。

在 Google Chrome 的 Task Manager
中,您可以看到某个页面所使用的内存量(也称为 RAM)。Chrome 也提供 GPU
内存,或者是 GPU
正在使用的内存。这是常见信息,如几何形状、纹理或计算机将您的画布数据推送到屏幕可能需要的任何形式的缓存数据。内存越低,放在计算机上的权重就会越少。虽然目前还没有任何确切的数字作为依据,但应始终对此进行测试,确保您的程序不会超出极限,并使用了过多的内存。如果使用了过多的内存,浏览器或页面就会因为缺乏内存资源而崩溃。GPU
处理是一个远大的编程追求,已超出本文的讨论范围。您可以从学习 OpenGL
或查阅 Chrome 的文档(请参阅参考资料)开始。
表 1. 画布层的内存开销
www.bifa88.com 2

在表 1中,随着在页面上引入和使用了更多的 HTML5
画布元素,使用的内存也越多。一般的内存也存在线性相关,但每增加一层,内存的增长就会明显减少。虽然这个测试并没有详细说明这些层对性能带来的影响,但它确实表明,画布会严重影响
GPU
内存。一定要记得在您的目标平台上执行压力测试,以确保平台的限制不会导致您的应用程序无法执行。

当选择更改某个分层解决方案的单一画布渲染周期时,需考虑有关内存开销的性能增益。尽管存在内存成本,但这项技术可以通过减小每一帧上修改的像素数量来完成其工作。

下一节将说明如何使用分层来组织一个场景。
对场景进行分层:游戏

在本节中,我们将通过重构一个滚动平台跑步风格的游戏上的视差效果的单画布实现,了解一个多层解决方案。图
2显示了游戏视图的组成,其中包括云、小山、地面、背景和一些交互实体。
图 2. 合成游戏视图
www.bifa88.com 3

在游戏中,云、小山、地面和背景都以不同的速度移动。本质上,背景中较远的元素移动得比在前面的元素慢,因此形成了视差效果。为了让情况变得更为复杂,背景的移动速度会足够慢,它每半秒钟才重新渲染一次。

通常情况下,好的解决方案会将所有帧都清除并重新渲染屏幕,因为背景是一个图像并且在不断变化。在本例中,由于背景每秒只需变化两次,所以您不需要重新渲染每一帧。

目前,您已经定义了工作区,所以可以决定场景的哪些部分应该在同一个层上。组织好各个层之后,我们将探讨用于分层的各种渲染策略。首先,需要考虑如何使用单个画布来实现该解决方案,如清单
3所示。
清单 3. 单画布渲染循环的伪代码
 

XML/HTML Code复制内容到剪贴板

  1. /**   
  2.  * Render call   
  3.  *   
  4.  * @param {CanvasRenderingContext2D} context Canvas context   
  5.  */   
  6. function renderLoop(context)   
  7. {   
  8.     context.clearRect(0, 0, width, height);   
  9.     background.render(context);   
  10.     ground.render(context);   
  11.     hills.render(context);   
  12.     cloud.render(context);   
  13.     player.render(context);   
  14. }  

像清单
3中的代码一样,该解决方案会有一个render函数,每个游戏循环调用或每个更新间隔都会调用它。在本例中,渲染是从主循环调用和更新每个元素的位置的更新调用中抽象出来。

遵循 “清除到渲染”
解决方案,render会调用清除上下文,并通过调用屏幕上的实体各自的render函数来跟踪它。清单
3遵循一个程序化的路径,将元素放置到画布上。虽然该解决方案对于渲染屏幕上的实体是有效的,但它既没有描述所使用的所有渲染方法,也不支持任何形式的渲染优化。

为了更好地详细说明实体的渲染方法,需要使用两种类型的实体对象。清单
4显示了您将使用和细化的两个实体。
清单 4. 可渲染的Entity伪代码
 

XML/HTML Code复制内容到剪贴板

  1. var Entity = function() {   
  2.     /**   
  3.      Initialization and other methods   
  4.      **/   
  5.     
  6.     /**   
  7.       * Render call to draw the entity   
  8.       *   
  9.       * @param {CanvasRenderingContext2D} context   
  10.       */   
  11.     this.render = function(context) {   
  12.         context.drawImage(this.image, this.x, this.y);   
  13.     }   
  14. };  

 

XML/HTML Code复制内容到剪贴板

  1. var PanningEntity = function() {   
  2.     /**   
  3.      Initialization and other methods   
  4.      **/   
  5.     
  6.     /**   
  7.       * Render call to draw the panned entity   
  8.       *   
  9.       * @param {CanvasRenderingContext2D} context   
  10.      */   
  11.     this.render = function(context) {   
  12.         context.drawImage(   
  13.             this.image,   
  14.             this.x – this.width,   
  15.             this.y – this.height);   
  16.         context.drawImage(   
  17.             this.image,   
  18.             this.x,   
  19.             this.y);   
  20.         context.drawImage(   
  21.             this.image,   
  22.             this.x + this.width,   
  23.             this.y + this.height);   
  24.     }   
  25. };  

清单 4中的对象存储实体的图像、x、y、宽度和高度的实例变量。这些对象遵循
JavaScript
语法,但为了简洁起见,仅提供了目标对象的不完整的伪代码。目前,渲染算法非常贪婪地在画布上渲染出它们的图像,完全不考虑游戏循环的其他任何要求。

为了提高性能,需要重点注意的是,panning渲染调用输出了一个比所需图像更大的图像。本文忽略这个特定的优化,但是,如果使用的空间比您的图像提供的空间小,那么请确保只渲染必要的补丁。
确定分层

现在您知道如何使用单一画布实现该示例,让我们看看有什么办法可以完善这种类型的场景,并加快渲染循环。要使用分层技术,则必须通过找出实体的渲染重叠,识别分层所需的
HTML5 画布元素。
重绘区域

为了确定是否存在重叠,要考虑一些被称为重绘区域的不可见区域。重绘区域是在绘制实体的图像时需要画布清除的区域。重绘区域对于渲染分析很重要,因为它们使您能够找到完善渲染场景的优化技术,如图
3所示。
图 3. 合成游戏视图与重绘区域
www.bifa88.com 4

为了可视化图
3中的效果,在场景中的每个实体都有一个表示重绘区域的重叠,它跨越了视区宽度和实体的图像高度。场景可分为三组:背景、前景和交互。场景中的重绘区域有一个彩色的重叠,以区分不同的区域:

  •     背景 – 黑色
        云 – 红色
        小山 – 绿色
        地面 – 蓝色
        红球 – 蓝色
        黄色障碍物 – 蓝色

对于除了球和障碍物以外的所有重叠,重绘区域都会横跨视区宽度。这些实体的图像几乎填满整个屏幕。由于它们的平移要求,它们将渲染整个视区宽度,如图
4所示。预计球和障碍物会穿过该视区,并且可能拥有通过实体位置定义的各自的区域。如果您删除渲染到场景的图像,只留下重绘区域,就可以很容易地看到单独的图层。
图 4. 重绘区域
www.bifa88.com 5

初始层是显而易见的,因为您可以注意到互相重叠的各个区域。由于球和障碍物区域覆盖了小山和地面,所以可将这些实体分组为一层,该层被称为交互层。根据游戏实体的渲染顺序,交互层是顶层。

找到附加层的另一种方法是收集没有重叠的所有区域。占据视区的红色、绿色和蓝色区域并没有重叠,并且它们组成了第二层——前景。云和交互实体的区域没有重叠,但因为球有可能跳跃到红色区域,所以您应该考虑将该实体作为一个单独的层。

对于黑色区域,可以很容易地推断出,背景实体将会组成最后一层。填充整个视区的任何区域(如背景实体)都应视为填充整个层中的该区域,虽然这对本场景并不适用。在定义了我们的三个层次之后,我们就可以开始将这层分配给画布,如图
5所示。
图 5. 分层的游戏视图
www.bifa88.com 6

现在已经为每个分组的实体定义了层,现在就可以开始优化画布清除。此优化的目标是为了节省处理时间,可以通过减少每一步渲染的屏幕上的固定物数量来实现。需要重点注意的是,使用不同的策略可能会使图像获得更好的优化。下一节将探讨各种实体或层的优化方法。
渲染优化

优化实体是分层策略的核心。对实体进行分层,使得渲染策略可以被采用。通常,优化技术会试图消除开销。正如表
1所述,由于引入了层,您已经增加了内存开销。这里讨论的优化技术将减少处理器为了加快游戏而必须执行的大量工作。我们的目标是寻找一种减少要渲染的空间量的方法,并尽可能多地删除每一步中出现的渲染和清除调用。
单一实体清除

第一个优化方法针对的是清除空间,通过只清除组成该实体的屏幕子集来加快处理。首先减少与区域的各实体周围的透明像素重叠的重绘区域量。使用此技术的包括相对较小的实体,它们填充了视区的小区域。

第一个目标是球和障碍物实体。单一实体清除技术涉及到在将实体渲染到新位置之前清除前一帧渲染该实体的位置。我们会引入一个清除步骤到每个实体的渲染,并存储实体的图像的边界框。添加该步骤会修改实体对象,以包括清除步骤,如清单
5所示。
清单 5. 包含单框清除的实体
 

XML/HTML Code复制内容到剪贴板

  1. var Entity = function() {   
  2.     /**   
  3.      Initialization and other methods   
  4.      **/   
  5.     
  6.     /**   
  7.      * Render call to draw the entity   
  8.      *   
  9.      * @param {CanvasRenderingContext2D} context   
  10.      */   
  11.     this.render = function(context) {   
  12.         context.clearRect(   
  13.             this.prevX,   
  14.             this.prevY,   
  15.             this.width,   
  16.             this.height);   
  17.         context.drawImage(this.image, this.x, this.y);   
  18.         thisthis.prevX = this.x;   
  19.         thisthis.prevY = this.y;   
  20.     }   
  21. };     

render函数的更新引入了一个常规drawImage之前发生的clearRect调用。对于该步骤,对象需要存储前一个位置。图
6显示了对象针对前一个位置所采取的步骤。
图 6. 清除矩形
www.bifa88.com 7

您可以为每个实体创建一个在更新步骤前被调用的clear方法,实现此渲染解决方案(但本文将不会使用clear方法)。您还可以将这个清除策略引入到PanningEntity,在地面和云实体上添加清除,如清单
6所示。
清单 6. 包含单框清除的PanningEntity
 

XML/HTML Code复制内容到剪贴板

  1. var PanningEntity = function() {   
  2.     /**   
  3.      Initialization and other methods   
  4.      **/   
  5.     
  6.     /**   
  7.      * Render call to draw the panned entity   
  8.      *   
  9.      * @param {CanvasRenderingContext2D} context   
  10.      */   
  11.     this.render = function(context) {   
  12.         context.clearRect(   
  13.             this.x,   
  14.             this.y,   
  15.             context.canvas.width,   
  16.             this.height);   
  17.         context.drawImage(   
  18.             this.image,   
  19.             this.x – this.width,   
  20.             this.y – this.height);   
  21.         context.drawImage(   
  22.             this.image,   
  23.             this.x,   
  24.             this.y);   
  25.         context.drawImage(   
  26.             this.image,   
  27.             this.x + this.width,   
  28.             this.y + this.height);   
  29.     }   
  30. };  

因为PanningEntity横跨了整个视区,所以您可以使用画布宽度作为清除矩形的大小。如果使用此清除策略,则会为您提供已为云、小山和地面实体定义的重绘区域。

为了进一步优化云实体,可以将云分离为单独的实体,使用它们自己的重绘区域。这样做会大幅减少在云重绘区域内要清除的屏幕空间量。图
7显示了新的重绘区域。
图 7. 具有单独重绘区域的云
www.bifa88.com 8

单一实体清除策略产生的解决方案可以解决像本例这样的分层画布游戏上的大多数问题,但仍然可以对它进行优化。为了寻找针对该渲染策略的极端情况,我们假设球会与三角形碰撞。如果两个实体碰撞,实体的重绘区域就有可能发生重叠,并创建一个不想要的渲染构件。另一个清除优化,更适合于可能会碰撞的实体,它也将有益于分层。
脏矩形清除

若没有单一清除策略,脏矩形清除策略可以是一个功能强大的替代品。您可以对有重绘区域的大量实体使用这种清除策略,这种实体包括密集的粒子系统,或有小行星的空间游戏。

从概念上讲,该算法会收集由算法管理的所有实体的重绘区域,并在一个清除调用中清除整个区域。为了增加优化,此清除策略还会删除每个独立实体产生的重复清除调用,如清单
7所示。
清单 7.DirtyRectManager
 

XML/HTML Code复制内容到剪贴板

  1. var DirtyRectManager = function() {   
  2.     // Set the left and top edge to the max possible   
  3.     // (the canvas width) amd right and bottom to least-most   
  4.     
  5.     // Left and top will shrink as more entities are added   
  6.     this.left   = canvas.width;   
  7.     this.top    = canvas.height;   
  8.     
  9.     // Right and bottom will grow as more entities are added   
  10.     this.right  = 0;   
  11.     this.bottom = 0;   
  12.     
  13.     // Dirty check to avoid clearing if no entities were added   
  14.     this.isDirty = false;   
  15.     
  16.     // Other Initialization Code   
  17.     
  18.     /**   
  19.      * Other utility methods   
  20.      */   
  21.     
  22.     /**   
  23.      * Adds the dirty rect parameters and marks the area as dirty
      
  24.      *    
  25.      * @param {number} x   
  26.      * @param {number} y   
  27.      * @param {number} width   
  28.      * @param {number} height   
  29.      */   
  30.     this.addDirtyRect = function(x, y, width, height) {   
  31.         // Calculate out the rectangle edges   
  32.         var left   = x;   
  33.         var right  = x + width;   
  34.         var top    = y;   
  35.         var bottom = y + height;   
  36.     
  37.         // Min of left and entity left   
  38.         this.left   = left < this.left      left   : this.left;   
  39.         // Max of right and entity right   
  40.         this.right  = right > this.right    right  : this.right;   
  41.         // Min of top and entity top   
  42.         this.top    = top < this.top        top    : this.top;   
  43.         // Max of bottom and entity bottom   
  44.         this.bottom = bottom > this.bottom  bottom : this.bottom;   
  45.     
  46.         this.isDirty = true;   
  47.     };   
  48.     
  49.     /**   
  50.      * Clears the rectangle area if the manager is dirty   
  51.      *   
  52.      * @param {CanvasRenderingContext2D} context   
  53.      */   
  54.     this.clearRect = function(context) {   
  55.         if (!this.isDirty) {   
  56.             return;   
  57.         }   
  58.     
  59.         // Clear the calculated rectangle   
  60.         context.clearRect(   
  61.             this.left,   
  62.             this.top,   
  63.             this.right – this.left,   
  64.             this.bottom – this.top);   
  65.     
  66.         // Reset base values   
  67.         this.left   = canvas.width;   
  68.         this.top    = canvas.height;   
  69.         this.right  = 0;   
  70.         this.bottom = 0;   
  71.         this.isDirty = false;   
  72.     }   
  73. };  

将脏矩形算法集成到渲染循环,这要求在进行渲染调用之前调用清单
7中的管理器。将实体添加到管理器,使管理器可以在清除时计算清除矩形的维度。虽然管理器会产生预期的优化,但根据游戏循环,管理器能够针对游戏循环进行优化,如图
8所示。
图 8. 交互层的重绘区域
www.bifa88.com 9

  1.     帧 1 – 实体在碰撞,几乎重叠。
        帧 2 – 实体重绘区域是重叠的。
        帧 3 – 重绘区域重叠,并被收集到一个脏矩形中。
        帧 4 – 脏矩形被清除。


8显示了由针对在交互层的实体的算法计算出的重绘区域。因为游戏在这一层上包含交互,所以脏矩形策略足以解决交互和重叠的重绘区域问题。
作为清除的重写

对于在恒定重绘区域中动画的完全不透明实体,可以使用重写作为一项优化技术。将不透明的位图渲染为一个区域(默认的合成操作),这会将像素放在该区域中,不需要考虑该区域中的原始渲染。这个优化消除了渲染调用之前所需的清除调用,因为渲染会覆盖原来的区域。

通过在之前的渲染的上方重新渲染图像,重写可以加快地面实体。也可以通过相同的方式加快最大的层,比如背景。

通过减少每一层的重绘区域,您已经有效地为层和它们所包含的实体找到优化策略。
结束语

对画布进行分层是一个可以应用于所有交互式实时场景的优化策略。如果想利用分层实现优化,您需要通过分析场景的重绘区域来考虑场景如何重叠这些区域。一些场景是具有重叠的重绘区域的集合,可以定义层,因此它们是渲染分层画布的良好候选。如果您需要粒子系统或大量物理对象碰撞在一起,对画布进行分层可能是一个很好的优化选择。

这篇文章主要介绍了使用分层画布来优化HTML5渲染的教程,来自于IBM官方网站开发者技术文档…

www.bifa88.com 10

简介

通常情况下,在玩 2D 游戏或渲染 HTML5
画布时,需要执行优化,以便使用多个层来构建一个合成的场景。在 OpenGL 或
WebGL
等低级别渲染中,通过逐帧地清理和绘制场景来执行渲染。实现渲染之后,需要优化游戏,以减少渲染的量,所需成本因情况而异。因为画布是一个
DOM 元素,它使您能够对多个画布进行分层,以此作为一种优化方法。

canvas入门

常用的缩写

  • CSS: Cascading Style Sheets(级联样式表)
  • DOM: Document Object Model(文档对象模型)
  • HTML: HyperText Markup Language(超文本标记语言)

本文将探讨对画布进行分层的合理性。了解 DOM
设置,从而实现分层的画布。使用分层进行优化需要各种实践。本文还将探讨一些优化策略的概念和技术,它们扩展了分层方法。

您可以下载在本文中使用的示例的源代码。

本文首发于我的个人博客:http://cherryblog.site/
github项目地址:https://github.com/sunshine940326/canvasStar
项目演示地址:https://sunshine940326.github.io/canvasStar/

选择优化策略

选择最佳优化策略可能很难。在选择分层的场景时,需要考虑场景是如何组成的。大屏幕上固定物的渲染经常需要重用若干个组件,它们是进行研究的极佳候选人。视差或动画实体等效果往往需要大量的变化的屏幕空间。在探索您的最佳优化策略时,最好注意这些情况。虽然画布的分层优化需要采用几种不同的技术,但在正确应用这些技术后,往往会大幅提升性能。

[toc]

设置层

在使用分层的方法时,第一步是在 DOM
上设置画布。通常情况下,这很简单,只需定义画布元素,将其放入 DOM
中即可,但画布层可能需要一些额外的样式。在使用 CSS
时,成功地实现画布分层有两个要求:

  • 各画布元素必须共存于视区 (viewport) 的同一位置上。
  • 每个画布在另一个画布下面必须是可见的。

图 1显示了层设置背后的通用重叠概念。

之前看到了一个很好看的canvas效果,然后拿来做我的博客背景,不少童鞋留言说求教程,并且反应说太耗内存,于是前一段我就重写了一遍,并且使用离屏渲染进行优化,效果还是挺显著的。但是因为毕竟是canvas,需要一直进行重绘,所以还是比较耗内存的,但是比优化之前已经好很多了。并且最近准备自己写插件,于是就拿这个练手了,

图 1. 层示例

www.bifa88.com 11

设置层的步骤如下:

  1. 将画布元素添加到 DOM。
  2. 添加画布元素定位样式,以便支持分层。
  3. 样式化画布元素,以便生成一个透明的背景。

github地址:https://github.com/sunshine940326/canvasStar

设置画布重叠堆栈

在 CSS 中创建一个重叠堆栈 (overlay stack) 可能需要少量的样式。使用 HTML
和 CSS
有许多方法进行重叠。本文中的示例使用一个<div>标签来包含画布。<div>标签指定了一个惟一 ID,它将样式应用于其子 HTML5 画布元素,如清单 1所示。

代码还有很多的不足,求大神 review (づ。◕‿‿◕。)づ~

清单 1. 画布定位样式

CSS

#viewport { /** * Position relative so that canvas elements *
inside of it will be relative to the parent */ position: relative; }
#viewport canvas { /** * Position absolute provides canvases to be
able * to be layered on top of each other * Be sure to remember a
z-index! */ position: absolute; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#viewport {
    /**
     * Position relative so that canvas elements
     * inside of it will be relative to the parent
     */
    position: relative;
}
 
#viewport canvas {
    /**
     * Position absolute provides canvases to be able
     * to be layered on top of each other
     * Be sure to remember a z-index!
     */
    position: absolute;
}

容器<div>通过将所有子画布元素样式化为使用绝对定位来完成重叠要求。通过选择让#viewport使用相对定位,您可以适应未来的发展,因此,应用于子样式的绝对布局样式将会是相对于#viewport容器的样式。

这些 HTML5 画布元素的顺序也很重要。可以按元素出现在 DOM
上的顺序进行顺序管理,也可以按照画布应该显示的顺序来样式化 z-index
样式,从而管理顺序。虽然并非总是如此,但其他样式可能也会影响渲染;在引入额外的样式(比如任何一种
CSS 转换)时要小心。

canvas 基本知识

透明的背景

通过使用重叠可见性来实现层技术的第二个样式要求。该示例使用这个选项来设置
DOM 元素背景颜色,如清单
2所示。

什么是 canvas

canvas 是 HTML5 新定义的标签,通过使用脚本(通常是
JavaScript)绘制图形。
<canvas> 标签只是图形容器,相当于一个画布,canvas
元素本身是没有绘图能力的。所有的绘制工作必须在 JavaScript
内部完成,相当于使用画笔在画布上画画。

默认情况下,<canvas> 没有边框和内容。默认是一个 300150
的画布,所以我们创建了 <canvas> 之后要对其设置宽高。
**我们可以通过html属性‘width’,‘height’来设置canvas的宽高,不可以通过
css 属性来设置宽高。因为通过 css 属性设置的宽高会使 canvas 内的图像按照
300
150 时的比例放大或缩小**

清单 2. 设置透明背景的样式表规则

JavaScript

canvas { /** * Set transparent to let any other canvases render
through */ background-color: transparent; }

1
2
3
4
5
6
canvas {
    /**
     * Set transparent to let any other canvases render through
     */
    background-color: transparent;
}

将画布样式化为拥有一个透明背景,这可以实现第二个要求,即拥有可见的重叠画布。现在,您已经构造了标记和样式来满足分层的需要,所以您可以设置一个分层的场景。

getContext()

context 是一个封装了很多绘图功能的对象,我们在页面中创建一个 canvas
标签之后,首先要使用 getContext() 获取 canvas 的上下文环境,目前
getContext() 的参数只有 2d,暂时还不支持 3d

getContext("2d") 对象是内建的 HTML5
对象,拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。

分层方面的考虑因素

在选择优化策略时,应该注意使用该策略时的所有权衡。对 HTML5
画布场景进行分层是一个侧重于运行时内存的策略,用于获得运行时速度方面的优势。您可以在页面的浏览器中增加更多的权重,以获得更快的帧速率。一般来说,画布被视为是浏览器上的一个图形平面,其中包括一个图形
API。

通过在 Google Chrome 19
进行测试,并记录浏览器的选项卡内存使用情况,您可以看到内存使用的明显趋势。该测试使用了已经样式化的<div>(正如上一节中讨论的那样),并生成了放置在<div>上的用单一颜色填充的画布元素。画布的大小被设定为
1600 x 900 像素,并从 Chrome1 的任务管理器实用程序收集数据。表
1显示了一个示例。

在 Google Chrome 的 Task Manager
中,您可以看到某个页面所使用的内存量(也称为 RAM)。Chrome 也提供 GPU
内存,或者是 GPU
正在使用的内存。这是常见信息,如几何形状、纹理或计算机将您的画布数据推送到屏幕可能需要的任何形式的缓存数据。内存越低,放在计算机上的权重就会越少。虽然目前还没有任何确切的数字作为依据,但应始终对此进行测试,确保您的程序不会超出极限,并使用了过多的内存。如果使用了过多的内存,浏览器或页面就会因为缺乏内存资源而崩溃。GPU
处理是一个远大的编程追求,已超出本文的讨论范围。您可以从学习 OpenGL
或查阅 Chrome
的文档(请参阅参考资料)开始。

canvas 元素绘制图像

canvas 创建图形有两种方式

表 1. 画布层的内存开销
层数 内存 GPU 内存
0 30.0 11.9
1 37.6 28.9
1 37.6 28.9
2 49.0 46.6
3 52.2 59.6
8 58.4 98.0
16 65.0 130
32 107 187

在表 1中,随着在页面上引入和使用了更多的 HTML5
画布元素,使用的内存也越多。一般的内存也存在线性相关,但每增加一层,内存的增长就会明显减少。虽然这个测试并没有详细说明这些层对性能带来的影响,但它确实表明,画布会严重影响
GPU
内存。一定要记得在您的目标平台上执行压力测试,以确保平台的限制不会导致您的应用程序无法执行。

当选择更改某个分层解决方案的单一画布渲染周期时,需考虑有关内存开销的性能增益。尽管存在内存成本,但这项技术可以通过减小每一帧上修改的像素数量来完成其工作。

下一节将说明如何使用分层来组织一个场景。

context.fill()

fill() 方法填充当前的图像(路径)。默认颜色是黑色。在填充前要先使用
fillStyle 设置填充的颜色或者渐变,并且如果路径未关闭,那么 fill()
方法会从路径结束点到开始点之间添加一条线,以关闭该路径(正如
closePath() 一样),然后填充该路径。

对场景进行分层:游戏

在本节中,我们将通过重构一个滚动平台跑步风格的游戏上的视差效果的单画布实现,了解一个多层解决方案。图
2显示了游戏视图的组成,其中包括云、小山、地面、背景和一些交互实体。

context.stroke()

stroke() 方法会实际地绘制出通过 moveTo()lineTo()
方法定义的路径。默认颜色是黑色。在进行图形绘制前,要设置好绘图的样式

fillStyle()//填充的样式
strokeStyle()//边框样式
context.lineWidth()//图形边框宽度
图 2. 合成游戏视图

www.bifa88.com 12

在游戏中,云、小山、地面和背景都以不同的速度移动。本质上,背景中较远的元素移动得比在前面的元素慢,因此形成了视差效果。为了让情况变得更为复杂,背景的移动速度会足够慢,它每半秒钟才重新渲染一次。

通常情况下,好的解决方案会将所有帧都清除并重新渲染屏幕,因为背景是一个图像并且在不断变化。在本例中,由于背景每秒只需变化两次,所以您不需要重新渲染每一帧。

目前,您已经定义了工作区,所以可以决定场景的哪些部分应该在同一个层上。组织好各个层之后,我们将探讨用于分层的各种渲染策略。首先,需要考虑如何使用单个画布来实现该解决方案,如清单
3所示。

绘制矩形

用 canvas 绘制一个矩形很简单

fillRect(x,y,width,height)  // 实心矩形 
strokeRect(x,y,width,height)        // 空心矩形
  • x :起始点的 x 坐标
  • y :起始点的 y 坐标
  • width : 矩形的宽
  • height : 矩形的高

//html代码
<canvas id="canvas"></canvas>
//script代码
   var canvas = document.getElementById('canvas');
    var context = canvas.getContext('2d');
    context.fillRect(0, 0, 100, 100);
    context.strokeRect(120, 0, 100, 100);

显示如下:

www.bifa88.com 13

canvas绘制矩形有填充颜色

我们可以看出,在没有设置颜色的情况下,默认是黑色的。

我们还可以通过设置 fillStyle 或者 fillStyle 改变其填充颜色。

context.fillStyle = "pink";
context.strokeStyle = "darkred";
context.fillRect(0, 0, 100, 100);
context.strokeRect(120, 0, 100, 100);

效果如下

www.bifa88.com 14

canvas绘制矩形有填充颜色

清单 3. 单画布渲染循环的伪代码

JavaScript

/** * Render call * * @param {CanvasRenderingContext2D} context
Canvas context */ function renderLoop(context) { context.clearRect(0,
0, width, height); background.render(context); ground.render(context);
hills.render(context); cloud.render(context); player.render(context); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Render call
*
* @param {CanvasRenderingContext2D} context Canvas context
*/
function renderLoop(context)
{
    context.clearRect(0, 0, width, height);
    background.render(context);
    ground.render(context);
    hills.render(context);
    cloud.render(context);
    player.render(context);
}

像清单
3中的代码一样,该解决方案会有一个render函数,每个游戏循环调用或每个更新间隔都会调用它。在本例中,渲染是从主循环调用和更新每个元素的位置的更新调用中抽象出来。

遵循 “清除到渲染”
解决方案,render会调用清除上下文,并通过调用屏幕上的实体各自的render函数来跟踪它。清单 3遵循一个程序化的路径,将元素放置到画布上。虽然该解决方案对于渲染屏幕上的实体是有效的,但它既没有描述所使用的所有渲染方法,也不支持任何形式的渲染优化。

为了更好地详细说明实体的渲染方法,需要使用两种类型的实体对象。清单
4显示了您将使用和细化的两个实体。

清除矩形区域

clearRect(x,y,width,height)

 - x :清除矩形起始点的 x 坐标
 - y :清除矩形起始点的 y 坐标
 - width : 清除矩形矩形的宽
 - height : 清除矩形矩形的高

var canvas = document.getElementById(‘canvas’);
var context = canvas.getContext(“2d”);
context.fillRect(0, 0, 100, 100);
context.strokeRect(120, 0, 100, 100);
context.fillStyle = “pink”;
context.strokeStyle = “darkred”;
context.fillRect(0, 120, 100, 100);
context.strokeRect(120, 120, 100, 100);
context.clearRect( 50,50,120,120)

效果如下:

www.bifa88.com 15

清除矩形

清单 4. 可渲染的Entity伪代码

JavaScript

var Entity = function() { /** Initialization and other methods **/
/** * Render call to draw the entity * * @param
{CanvasRenderingContext2D} context */ this.render = function(context) {
context.drawImage(this.image, this.x, this.y); } }; var PanningEntity =
function() { /** Initialization and other methods **/ /** *
Render call to draw the panned entity * * @param
{CanvasRenderingContext2D} context */ this.render = function(context) {
context.drawImage( this.image, this.x – this.width, this.y –
this.height); context.drawImage( this.image, this.x, this.y);
context.drawImage( this.image, this.x + this.width, this.y +
this.height); } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
var Entity = function() {
    /**
     Initialization and other methods
     **/
 
    /**
      * Render call to draw the entity
      *
      * @param {CanvasRenderingContext2D} context
      */
    this.render = function(context) {
        context.drawImage(this.image, this.x, this.y);
    }
};
 
var PanningEntity = function() {
    /**
     Initialization and other methods
     **/
 
    /**
      * Render call to draw the panned entity
      *
      * @param {CanvasRenderingContext2D} context
     */
    this.render = function(context) {
        context.drawImage(
            this.image,
            this.x – this.width,
            this.y – this.height);
        context.drawImage(
            this.image,
            this.x,
            this.y);
        context.drawImage(
            this.image,
            this.x + this.width,
            this.y + this.height);
    }
};

清单 4中的对象存储实体的图像、x、y、宽度和高度的实例变量。这些对象遵循
JavaScript
语法,但为了简洁起见,仅提供了目标对象的不完整的伪代码。目前,渲染算法非常贪婪地在画布上渲染出它们的图像,完全不考虑游戏循环的其他任何要求。

为了提高性能,需要重点注意的是,panning渲染调用输出了一个比所需图像更大的图像。本文忽略这个特定的优化,但是,如果使用的空间比您的图像提供的空间小,那么请确保只渲染必要的补丁。

实心圆

context.arc(x, y, radius, starAngle,endAngle, anticlockwise)

  • x : 圆心的 x 坐标
  • y:圆心的 y 坐标
  • radius : 半径
  • starAngle :开始角度
  • endAngle:结束角度
  • anticlockwise :是否逆时针(true)为逆时针,(false)为顺时针

context.beginPath();
context.arc(300, 350, 100, 0, Math.PI * 2, true);
//不关闭路径路径会一直保留下去
context.closePath();
context.fillStyle = 'rgba(0,255,0,0.25)';
context.fill();

效果如下:

www.bifa88.com 16

canvas绘制圆弧

确定分层

现在您知道如何使用单一画布实现该示例,让我们看看有什么办法可以完善这种类型的场景,并加快渲染循环。要使用分层技术,则必须通过找出实体的渲染重叠,识别分层所需的
HTML5 画布元素。

圆弧

如果不填充颜色,实心圆就是圆弧

    context.beginPath();
    context.arc(600, 350, 100, 0, Math.PI , true);
    context.strokeStyle = 'pink';
    context.closePath();
    context.stroke();

    context.beginPath();
    context.arc(300, 350, 100, 0, Math.PI , true);
    context.strokeStyle = 'red';
    //没有closePath
    context.stroke();

效果如图:

www.bifa88.com 17

canvas绘制圆弧

  • 系统默认在绘制第一个路径的开始点为beginPath
  • 如果画完前面的路径没有重新指定beginPath,那么画第其他路径的时候会将前面最近指定的beginPath后的全部路径重新绘制
  • 每次调用context.fill()的时候会自动把当次绘制的路径的开始点和结束点相连,接着填充封闭的部分

所以说,如果第一个圆弧没有 closePath() 并且第二个圆弧没有
beginPath() 的话就是这样的效果:

www.bifa88.com 18

canvas绘制矩形

重绘区域

为了确定是否存在重叠,要考虑一些被称为重绘区域的不可见区域。重绘区域是在绘制实体的图像时需要画布清除的区域。重绘区域对于渲染分析很重要,因为它们使您能够找到完善渲染场景的优化技术,如图
3所示。

绘制线段

  • moveTo(x,y):把路径移动到画布中的指定点,不创建线条
  • lineTo(x,y):添加一个新点,然后在画布中创建从该点到最后指定点的线条
  • 每次画线都从 moveTo 的点到 lineTo 的点,

    context.strokeStyle = 'pink';
    context.moveTo(0, 0);
    context.lineTo(100, 100);
    context.stroke();*/

效果如下:

www.bifa88.com 19

canvas绘制片段

如果没有 moveTo 那么第一次 lineTo 的效果和 moveTo 一样,
例如:

    context.strokeStyle = 'pink';
    context.lineTo(100, 100);
    context.lineTo(200, 200);
    context.stroke();*/

效果如下:

www.bifa88.com 20

canvas绘制线段

每次lineTo后如果没有moveTo,那么下次lineTo的开始点为前一次lineTo的结束点
例如:

// 绘制片段
    context.strokeStyle = 'pink';
    context.lineTo(200, 200);
    context.lineTo(200, 100);
    context.lineTo(100,50);
    context.stroke();

效果如下:

www.bifa88.com 21

canvas绘制线段

我们可以使用 canvas 的线段绘制各种各样的图形,比如绘制一个六边形

var n = 0;
    var dx = 150;
    var dy = 150;
    var s = 100;
    context.beginPath();
    context.fillStyle = 'pink';
    context.strokeStyle = 'rgb(0,0,100)';
    var x = Math.sin(0);
    var y = Math.cos(0);
    var dig = Math.PI / 15 * 5;
    for (var i = 0; i < 6; i++) {
        var x = Math.sin(i * dig);
        var y = Math.cos(i * dig);
        context.lineTo(dx + x * s, dy + y * s);
        console.log( x ,y )
    }
    context.closePath();
    context.fill();
    context.stroke();

www.bifa88.com 22

使用canvas绘制六边形

绘制 30 角形:

var n = 0;
    var dx = 150;
    var dy = 150;
    var s = 100;
    context.beginPath();
    context.fillStyle = 'pink';
    context.strokeStyle = 'rgb(0,0,100)';
    var x = Math.sin(0);
    var y = Math.cos(0);
    var dig = Math.PI / 15 * 7;
    for (var i = 0; i < 30; i++) {
        var x = Math.sin(i * dig);
        var y = Math.cos(i * dig);
        context.lineTo(dx + x * s, dy + y * s);
        console.log( x ,y )
    }
    context.closePath();
    context.fill();
    context.stroke();

效果如下:

![canvas绘制 30
脚形](http://img.blog.csdn
.net/20170804152344651?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3Vuc2hpbmU5NDAzMjY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

图 3. 合成游戏视图与重绘区域

www.bifa88.com 23

为了可视化图
3中的效果,在场景中的每个实体都有一个表示重绘区域的重叠,它跨越了视区宽度和实体的图像高度。场景可分为三组:背景、前景和交互。场景中的重绘区域有一个彩色的重叠,以区分不同的区域:

  • 背景 – 黑色
  • 云 – 红色
  • 小山 – 绿色
  • 地面 – 蓝色
  • 红球 – 蓝色
  • 黄色障碍物 – 蓝色

对于除了球和障碍物以外的所有重叠,重绘区域都会横跨视区宽度。这些实体的图像几乎填满整个屏幕。由于它们的平移要求,它们将渲染整个视区宽度,如图
4所示。预计球和障碍物会穿过该视区,并且可能拥有通过实体位置定义的各自的区域。如果您删除渲染到场景的图像,只留下重绘区域,就可以很容易地看到单独的图层。

线性渐变

var lg= context.createLinearGradient(xStart,yStart,xEnd,yEnd)
lg.addColorStop(offset,color)

  • xstart:渐变开始点x坐标
  • ystart:渐变开始点y坐标
  • xEnd:渐变结束点x坐标
  • yEnd:渐变结束点y坐标
  • offset:设定的颜色离渐变结束点的偏移量(0~1)
  • color:绘制时要使用的颜色

例如:

    var g1 = context.createLinearGradient(0, 0, 0, 300);
    g1.addColorStop(0, '#E55D87'); 
    g1.addColorStop(1, '#5FC3E4');
    context.fillStyle = g1;
    context.fillRect(0, 0, 400, 300);

效果如下:

www.bifa88.com 24

canvas绘制渐变

图 4. 重绘区域

www.bifa88.com 25

初始层是显而易见的,因为您可以注意到互相重叠的各个区域。由于球和障碍物区域覆盖了小山和地面,所以可将这些实体分组为一层,该层被称为交互层。根据游戏实体的渲染顺序,交互层是顶层。

找到附加层的另一种方法是收集没有重叠的所有区域。占据视区的红色、绿色和蓝色区域并没有重叠,并且它们组成了第二层——前景。云和交互实体的区域没有重叠,但因为球有可能跳跃到红色区域,所以您应该考虑将该实体作为一个单独的层。

对于黑色区域,可以很容易地推断出,背景实体将会组成最后一层。填充整个视区的任何区域(如背景实体)都应视为填充整个层中的该区域,虽然这对本场景并不适用。在定义了我们的三个层次之后,我们就可以开始将这层分配给画布,如图
5所示。

径向渐变

var rg=context.createRadialGradient(xStart,yStart,radiusStart,xEnd,yEnd,radiusEnd)
rg.addColorStop(offset,color)

  • xStart:发散开始圆心x坐标
  • yStart:发散开始圆心y坐标
  • radiusStart:发散开始圆的半径
  • xEnd:发散结束圆心的x坐标
  • yEnd:发散结束圆心的y坐标
  • radiusEnd:发散结束圆的半径
  • offset:设定的颜色离渐变结束点的偏移量(0~1)
  • color:绘制时要使用的颜色

www.bifa88.com 26

径向渐变原理

例如:

// 同心圆径向渐变
    var g1 = context.createRadialGradient(200, 150, 0, 200, 150, 200);
    g1.addColorStop(0.1, '#F09819');
    g1.addColorStop(1, '#EDDE5D');
    context.fillStyle = g1;
    context.beginPath();
    context.arc(200, 150, 100, 0, Math.PI * 2, true);
    context.closePath();
    context.fill();

www.bifa88.com 27

canvas绘制同心圆径向渐变

//不同圆心的径向渐变模型
    var g1 = context.createRadialGradient(100, 150, 10, 300, 150, 80);
    g1.addColorStop(0.1, '#F09819');
    g1.addColorStop(0.8, 'red');
    g1.addColorStop(1, '#EDDE5D');

    context.fillStyle = g1;
    context.fillRect(0, 0, 300, 500);

效果图:

www.bifa88.com 28

不同圆心径向渐变

图 5. 分层的游戏视图

www.bifa88.com 29

现在已经为每个分组的实体定义了层,现在就可以开始优化画布清除。此优化的目标是为了节省处理时间,可以通过减少每一步渲染的屏幕上的固定物数量来实现。需要重点注意的是,使用不同的策略可能会使图像获得更好的优化。下一节将探讨各种实体或层的优化方法。

图形变形

渲染优化

优化实体是分层策略的核心。对实体进行分层,使得渲染策略可以被采用。通常,优化技术会试图消除开销。正如表
1所述,由于引入了层,您已经增加了内存开销。这里讨论的优化技术将减少处理器为了加快游戏而必须执行的大量工作。我们的目标是寻找一种减少要渲染的空间量的方法,并尽可能多地删除每一步中出现的渲染和清除调用。

缩放

scale(x,y)

  • x :x坐标轴按 x 比例缩放
  • y :x坐标轴按 y 比例缩放

单一实体清除

第一个优化方法针对的是清除空间,通过只清除组成该实体的屏幕子集来加快处理。首先减少与区域的各实体周围的透明像素重叠的重绘区域量。使用此技术的包括相对较小的实体,它们填充了视区的小区域。

第一个目标是球和障碍物实体。单一实体清除技术涉及到在将实体渲染到新位置之前清除前一帧渲染该实体的位置。我们会引入一个清除步骤到每个实体的渲染,并存储实体的图像的边界框。添加该步骤会修改实体对象,以包括清除步骤,如清单
5所示。

旋转

rotate(angle)

  • angle :坐标轴旋转x角度(角度变化模型和画圆的模型一样)
清单 5. 包含单框清除的实体

www.bifa88.com,JavaScript

var Entity = function() { /** Initialization and other methods **/
/** * Render call to draw the entity * * @param
{CanvasRenderingContext2D} context */ this.render = function(context) {
context.clearRect( this.prevX, this.prevY, this.width, this.height);
context.drawImage(this.image, this.x, this.y); this.prevX = this.x;
this.prevY = this.y; } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var Entity = function() {
    /**
     Initialization and other methods
     **/
 
    /**
     * Render call to draw the entity
     *
     * @param {CanvasRenderingContext2D} context
     */
    this.render = function(context) {
        context.clearRect(
            this.prevX,
            this.prevY,
            this.width,
            this.height);
        context.drawImage(this.image, this.x, this.y);
        this.prevX = this.x;
        this.prevY = this.y;
    }
};

render函数的更新引入了一个常规drawImage之前发生的clearRect调用。对于该步骤,对象需要存储前一个位置。图 6显示了对象针对前一个位置所采取的步骤。

平移

translate(x,y)

  • x :坐标原点向x轴方向平移x
  • y :坐标原点向y轴方向平移y

平移,缩放,旋转先后顺序不同,坐标轴的变化图,图片来源于网络:

www.bifa88.com 30

平移缩放旋转先后顺序不同坐标轴的变化图

图 6. 清除矩形

www.bifa88.com 31

您可以为每个实体创建一个在更新步骤前被调用的clear方法,实现此渲染解决方案(但本文将不会使用clear方法)。您还可以将这个清除策略引入到PanningEntity,在地面和云实体上添加清除,如清单
6所示。

图形组合

globalCompositeOperation=type
设置或返回新图像如何绘制到已有的图像上。最后的效果取决于 type 的值
type:

  • source-over(默认值):在原有图形上绘制新图形
  • destination-over:在原有图形下绘制新图形
  • source-in:显示原有图形和新图形的交集,新图形在上,所以颜色为新图形的颜色
  • destination-in:显示原有图形和新图形的交集,原有图形在上,所以颜色为原有图形的颜色
  • source-out:只显示新图形非交集部分
  • destination-out:只显示原有图形非交集部分
  • source-atop:显示原有图形和交集部分,新图形在上,所以交集部分的颜色为新图形的颜色
  • destination-atop:显示新图形和交集部分,新图形在下,所以交集部分的颜色为原有图形的颜色
  • lighter:原有图形和新图形都显示,交集部分做颜色叠加
  • xor:重叠飞部分不现实
  • copy:只显示新图形
    效果图如下,图片来源于网络

www.bifa88.com 32

效果图

清单 6. 包含单框清除的PanningEntity

JavaScript

var PanningEntity = function() { /** Initialization and other methods
**/ /** * Render call to draw the panned entity * * @param
{CanvasRenderingContext2D} context */ this.render = function(context) {
context.clearRect( this.x, this.y, context.canvas.width, this.height);
context.drawImage( this.image, this.x – this.width, this.y –
this.height); context.drawImage( this.image, this.x, this.y);
context.drawImage( this.image, this.x + this.width, this.y +
this.height); } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var PanningEntity = function() {
    /**
     Initialization and other methods
     **/
 
    /**
     * Render call to draw the panned entity
     *
     * @param {CanvasRenderingContext2D} context
     */
    this.render = function(context) {
        context.clearRect(
            this.x,
            this.y,
            context.canvas.width,
            this.height);
        context.drawImage(
            this.image,
            this.x – this.width,
            this.y – this.height);
        context.drawImage(
            this.image,
            this.x,
            this.y);
        context.drawImage(
            this.image,
            this.x + this.width,
            this.y + this.height);
    }
};

因为PanningEntity横跨了整个视区,所以您可以使用画布宽度作为清除矩形的大小。如果使用此清除策略,则会为您提供已为云、小山和地面实体定义的重绘区域。

为了进一步优化云实体,可以将云分离为单独的实体,使用它们自己的重绘区域。这样做会大幅减少在云重绘区域内要清除的屏幕空间量。图
7显示了新的重绘区域。

阴影

shadowOffsetX:设置或返回阴影距形状的水平距离(默认值为 0)
shadowOffsetY:设置或返回阴影距形状的垂直距离(默认值为 0)
shadowColor:设置或返回用于阴影的颜色
shadowBlur:设置或返回用于阴影的模糊级别(值越大越模糊)

例如:

    context.fillStyle = 'white';
    context.beginPath();
    context.arc(100,100,10,0,2 * Math.PI);
    context.shadowColor = 'white';
    context.shadowBlur = 10;
    context.fill();
    context.closePath();

我们看到的效果就是我们在开头提起的例子中的 star
粒子的效果,因为其有白色阴影的效果,所以看起来像是发光一样,效果如下图:

www.bifa88.com 33

带阴影效果的圆形

图 7. 具有单独重绘区域的云

www.bifa88.com 34

单一实体清除策略产生的解决方案可以解决像本例这样的分层画布游戏上的大多数问题,但仍然可以对它进行优化。为了寻找针对该渲染策略的极端情况,我们假设球会与三角形碰撞。如果两个实体碰撞,实体的重绘区域就有可能发生重叠,并创建一个不想要的渲染构件。另一个清除优化,更适合于可能会碰撞的实体,它也将有益于分层。

图像绘制

drawImage()
向画布上绘制图像、画布或视频

  • 在画布上定位图像:context.drawImage(img,x,y);
  • 在画布上定位图像,并规定图像的宽度和高度:context.drawImage(img,x,y,width,height);
  • 剪切图像,并在画布上定位被剪切的部分:context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
  • img:规定要使用的图像、画布或视频。
  • sx:可选。开始剪切的 x 坐标位置。
  • sy:可选。开始剪切的 y 坐标位置。
  • swidth:可选。被剪切图像的宽度。
  • sheight:可选。被剪切图像的高度。
  • x:在画布上放置图像的 x 坐标位置。
  • y:在画布上放置图像的 y 坐标位置。
  • width:可选。要使用的图像的宽度。(伸展或缩小图像)
  • height:可选。要使用的图像的高度。(伸展或缩小图像)

www.bifa88.com 35

canvas绘制图形例子

脏矩形清除

若没有单一清除策略,脏矩形清除策略可以是一个功能强大的替代品。您可以对有重绘区域的大量实体使用这种清除策略,这种实体包括密集的粒子系统,或有小行星的空间游戏。

从概念上讲,该算法会收集由算法管理的所有实体的重绘区域,并在一个清除调用中清除整个区域。为了增加优化,此清除策略还会删除每个独立实体产生的重复清除调用,如清单
7所示。

图像平铺

createPattern(image,type)
type:

  • no-repeat:不平铺
  • repeat-x:横方向平铺
  • repeat-y:纵方向平铺
  • repeat:全方向平铺
清单 7.DirtyRectManager

JavaScript

var DirtyRectManager = function() { // Set the left and top edge to the
max possible // (the canvas width) amd right and bottom to least-most //
Left and top will shrink as more entities are added this.left =
canvas.width; this.top = canvas.height; // Right and bottom will grow as
more entities are added this.right = 0; this.bottom = 0; // Dirty check
to avoid clearing if no entities were added this.isDirty = false; //
Other Initialization Code /** * Other utility methods */ /** *
Adds the dirty rect parameters and marks the area as dirty * * @param
{number} x * @param {number} y * @param {number} width * @param
{number} height */ this.addDirtyRect = function(x, y, width, height) {
// Calculate out the rectangle edges var left = x; var right = x +
width; var top = y; var bottom = y + height; // Min of left and entity
left this.left = left < this.left left : this.left; // Max of right
and entity right this.right = right > this.right right : this.right;
// Min of top and entity top this.top = top < this.top top :
this.top; // Max of bottom and entity bottom this.bottom = bottom >
this.bottom bottom : this.bottom; this.isDirty = true; }; /** *
Clears the rectangle area if the manager is dirty * * @param
{CanvasRenderingContext2D} context */ this.clearRect =
function(context) { if (!this.isDirty) { return; } // Clear the
calculated rectangle context.clearRect( this.left, this.top, this.right

  • this.left, this.bottom – this.top); // Reset base values this.left =
    canvas.width; this.top = canvas.height; this.right = 0; this.bottom = 0;
    this.isDirty = false; } };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
var DirtyRectManager = function() {
    // Set the left and top edge to the max possible
    // (the canvas width) amd right and bottom to least-most
 
    // Left and top will shrink as more entities are added
    this.left   = canvas.width;
    this.top    = canvas.height;
 
    // Right and bottom will grow as more entities are added
    this.right  = 0;
    this.bottom = 0;
 
    // Dirty check to avoid clearing if no entities were added
    this.isDirty = false;
 
    // Other Initialization Code
 
    /**
     * Other utility methods
     */
 
    /**
     * Adds the dirty rect parameters and marks the area as dirty
     *
     * @param {number} x
     * @param {number} y
     * @param {number} width
     * @param {number} height
     */
    this.addDirtyRect = function(x, y, width, height) {
        // Calculate out the rectangle edges
        var left   = x;
        var right  = x + width;
        var top    = y;
        var bottom = y + height;
 
        // Min of left and entity left
        this.left   = left < this.left      left   : this.left;
        // Max of right and entity right
        this.right  = right > this.right    right  : this.right;
        // Min of top and entity top
        this.top    = top < this.top        top    : this.top;
        // Max of bottom and entity bottom
        this.bottom = bottom > this.bottom  bottom : this.bottom;
 
        this.isDirty = true;
    };
 
    /**
     * Clears the rectangle area if the manager is dirty
     *
     * @param {CanvasRenderingContext2D} context
     */
    this.clearRect = function(context) {
        if (!this.isDirty) {
            return;
        }
 
        // Clear the calculated rectangle
        context.clearRect(
            this.left,
            this.top,
            this.right – this.left,
            this.bottom – this.top);
 
        // Reset base values
        this.left   = canvas.width;
        this.top    = canvas.height;
        this.right  = 0;
        this.bottom = 0;
        this.isDirty = false;
    }
};

将脏矩形算法集成到渲染循环,这要求在进行渲染调用之前调用清单
7中的管理器。将实体添加到管理器,使管理器可以在清除时计算清除矩形的维度。虽然管理器会产生预期的优化,但根据游戏循环,管理器能够针对游戏循环进行优化,如图
8所示。

图像裁剪

clip()从原始画布剪切任意形状和尺寸的区域,需要先创建裁剪区域,再绘制图像;一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内(不能访问画布上的其他区域)。您也可以在使用
clip() 方法前通过使用 save()
方法对当前画布区域进行保存,并在以后的任意时间对其进行恢复(通过
restore() 方法)。
例如:

    // 设置剪切区域(粉色矩形)
    context.rect(0,0,500,400);
    context.fillStyle = "pink";
    context.fill();
    context.clip();

    // 在剪切区域中绘制图形(白色矩形)
    context.fillStyle = "white";
    context.fillRect(10,10,100,100);

    // 之后绘制的图形只能显示在剪切区域之内(红色矩形)
    context.fillStyle = "red";
    context.fillRect(100,100,600,600)

效果如下:可以看到我们设置的红色矩形是一个 600600
的矩形,但是显然是没有显示完的,一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内(不能访问画布上的其他区域)。
*

www.bifa88.com 36

canvas进行图像剪切

所以说我们可以在使用 clip() 方法前通过使用 save()
方法对当前画布区域进行保存,并在以后的任意时间对其进行恢复(通过
restore() 方法)。
代码如下:

context.save();
    // 设置剪切区域
    context.rect(0,0,500,400);
    context.fillStyle = "pink";
    context.fill();
    context.clip();

    // 在剪切区域中绘制图形
    context.fillStyle = "white";
    context.fillRect(10,10,100,100);

    context.restore();
    // 之后绘制的图形只能显示在剪切区域之内
    context.fillStyle = "red";
    context.fillRect(100,100,600,600)

这样就可以正常显示了:

www.bifa88.com 37

canvas进行图像裁剪

图 8. 交互层的重绘区域

www.bifa88.com 38

  • 帧 1 – 实体在碰撞,几乎重叠。
  • 帧 2 – 实体重绘区域是重叠的。
  • 帧 3 – 重绘区域重叠,并被收集到一个脏矩形中。
  • 帧 4 – 脏矩形被清除。


8显示了由针对在交互层的实体的算法计算出的重绘区域。因为游戏在这一层上包含交互,所以脏矩形策略足以解决交互和重叠的重绘区域问题。

绘制文字

fillText(text,x,y):绘制实心文字
strokeText():绘制文字描边(空心)
textAlign:设置或返回文本内容的当前对齐方式
textBaseline:设置或返回在绘制文本时使用的当前文本基线
font:设置或返回文本内容的当前字体属性

例如:

    context.font="40px Arial";
    context.fillText("Hello world",200,200);
    context.strokeText("Hello world",200,300)

效果如下:

www.bifa88.com 39

canvas绘制文字

作为清除的重写

对于在恒定重绘区域中动画的完全不透明实体,可以使用重写作为一项优化技术。将不透明的位图渲染为一个区域(默认的合成操作),这会将像素放在该区域中,不需要考虑该区域中的原始渲染。这个优化消除了渲染调用之前所需的清除调用,因为渲染会覆盖原来的区域。

通过在之前的渲染的上方重新渲染图像,重写可以加快地面实体。也可以通过相同的方式加快最大的层,比如背景。

通过减少每一层的重绘区域,您已经有效地为层和它们所包含的实体找到优化策略。

准备工作

好的开始是成功的一半

简单介绍了下 canvas 的常用
api,大家发现是不是也没有那么难呢( ̄▽ ̄)*,那么让我们回到标题,一起来看一下这个少女心满满的例子是怎样实现的~

canvas 其实写一个炫酷的特效在技术上并不难,难的是你的创意,因为 canvas
实现粒子的效果还是比较惊艳的,但其实代码都是比较简单的,无非就是随机的创建图形或者路径,当然图形也是闭合的路径。在加上一定的位移就可以了。但是你要设计出一个好的特效是非常不容易的。

所以我们就先来分析一下这个效果由那几部分构成,将其拆分开来。

特效pc端演示地址:https://sunshine940326.github.io/canvasStar/
(当然,可以直接查看我的博客,背景暂时就是这个,不知道什么时候会变,捂脸ing:http://cherryblog.site/)

结束语

对画布进行分层是一个可以应用于所有交互式实时场景的优化策略。如果想利用分层实现优化,您需要通过分析场景的重绘区域来考虑场景如何重叠这些区域。一些场景是具有重叠的重绘区域的集合,可以定义层,因此它们是渲染分层画布的良好候选。如果您需要粒子系统或大量物理对象碰撞在一起,对画布进行分层可能是一个很好的优化选择。

分析 star 的表现和行为

我们可以将其一直位移向上的粒子称为 star,我们观察 star 的特点:

  • 开始创建时位置随机(坐标随机)
  • 透明度随机
  • 创建时的大小在一定范围内(半径在一定范围内)
  • 匀速上升
  • 总数不变

所以我们就可以总结出 star
的特点就是总数固定,创建时坐标和半径还有透明度随机,匀速上升。是不是很简单了呢\[\]( ̄▽ ̄)~*

下载

描述 名字 大小
文章源代码 HTML5CanvasRenderingSource.zip 3KB

分析 dot 的表现和行为

再让我们来看一下随着鼠标移入产生的粒子,我们称为 dot,同理,我们观察得到
dot 的特点

  • 列表内容
  • 鼠标移动时产生
  • 新产生的 dot 和之前的 3 个 dot 产生连线
  • 向四周移动
  • 达到一定条件消失

这样,我们就完成了一半了呢~将事件屡清楚之后我们就可以开始着手撸代码了!

参考资料

背景的 HTML 和 CSS

其实需要的 HTML 代码和 CSS 代码很简答的,HTML
只需要一行就可以了呢,设置一个渐变的背景蒙层和一个 canvas 标签。

HTML 和 CSS 如下:

<div class="filter"></div>
<canvas id="canvas"></canvas>

html, body {
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
            overflow: hidden;
            background: black;
            background: linear-gradient(to bottom, #dcdcdc 0%, palevioletred 100%);
        }

        #main-canvas {
            width: 100%;
            height: 100%;
        }

        .filter {
            width: 100%;
            height: 100%;
            position: absolute;
            top: 0;
            left: 0;
            background: #fe5757;
            animation: colorChange 30s ease-in-out infinite;
            animation-fill-mode: both;
            mix-blend-mode: overlay;

        }

        @keyframes colorChange {
            0%, 100% {
                opacity: 0;
            }
            50% {
                opacity: .7;
            }
        }

是的,我使用的是一个渐变的背景,不仅是从上到下的渐变,并且颜色也是会渐变的,效果如下:

www.bifa88.com 40

渐变背景

学习

  • Google Chrome Task
    Manager:了解如何使用
    Task Manager(任务管理器)获得在 Google Chrome
    中运行的特定进程的详细信息,或者强制关闭行为异常的选项卡或应用程序。
  • GPU Accelerated Compositing in
    Chrome:获得有关在
    Chrome 中的硬件加速合成的实现背景和细节。
  • Parallax:在 Wikipedia
    上阅读有关视差效果的更多资料。
  • “使用 HTML5 canvas
    绘制精美的图形“(developerWorks,2011
    年 2 月):了解如何使用一个简单而强大的 HTML 元素 Canvas 来增强您的
    Web 页面。
  • HTML5
    Canvas:观看此演示,它侧重于画布
    API 的使用,并向您演示如何绘制一个很简单的动画。
  • “HTML5 基础知识,第 4 部分:最后的完善:Canvas
    元素“(developerWorks,2011
    年 7 月):阅读这个对 HTML5 canvas
    元素的介绍。其中的几个示例可以说明所包括的函数。
  • Canvas Pixel
    Manipulation:观看此演示,它是管理画布以开发有效的视觉资产的一个很好的示例,它来自
    Safari Dev Center。
  • WHATWG:探索该社区,它由使用 W3C 来调优
    HTML5 的开发人员组成。
  • Canvas
    教程:查看这个来自
    Mozilla 开发人员的教程和演示。
  • HTML5 Canvas
    参考:浏览
    W3Schools.com 有用的练习,帮助您掌握画布知识。
  • jQuery Events
    API:了解用于注册行为的方法,使其在用户与浏览器交互时生效。
  • developerWorks Web
    开发专区:查找涵盖多个基于
    Web 的解决方案的文章。参阅Web
    开发技术库,获得各种技术文章和技巧、教程、标准以及
    IBM Redbooks。
  • developerWorks
    技术活动和网络广播:随时关注这些领域中的技术。
  • developerWorks
    点播演示:那里提供了面向初学者的产品安装和设置演示,以及面向经验丰富的开发人员的高级功能演示。
  • Twitter 上的
    developerWorks:立即加入,关注
    developerWorks tweet。

设置参数以及获取 dom 对象

    /*
     * @var star_r:star半径系数,系数越大,半径越大
     * @var star_alpha:生成star的透明度,star_alpha越大,透明度越低
     * @var initStarsPopulation:初始化stars的个数
     * @var move_distance:star位移的距离,数值越大,位移越大
     * @var dot_r : dot半径系数,系数越大,半径越大
     * @var dot_speeds : dots运动的速度
     * @var dot_alpha : dots的透明度
     * @var aReduction:dot消失条件,透明度小于aReduction时消失
     * @var dotsMinDist:dot最小距离
     * @var maxDistFromCursor:dot最大距离
     * */
    var config = {
        star_r : 3,
        star_alpha : 5,
        initStarsPopulation : 150,
        move_distance : 0.25,
        dot_r : 5,
        dot_speeds : 0.5,
        dot_alpha : 0.5,
        dot_aReduction : 0.01,
        dotsMinDist : 5,
        maxDistFromCursor : 50,
    };
    var stars = [],
        dots = [],
        canvas = document.getElementById('canvas'),
        ctx = canvas.getContext('2d'),
        WIDTH,
        HEIGHT,
        mouseMoving = false,
        mouseMoveChecker,
        mouseX,
        mouseY;

获得产品和技术

  • OpenGL:获得最新的驱动程序。
  • jQuery:下载流行的 JavaScript
    Library,可以简化 HTML 文档遍历、事件处理、动画和 Ajax
    交互并实现快速 Web 开发。
  • Modernizr:获得此开源 JavaScript
    库,帮助您构建下一代的 HTML5 和 CSS3 驱动的 Web 站点。
  • Kibo:下载流行的专门为快速跨浏览器的键盘事件处理编写的库。
  • IBM
    产品评估版本:下载或在
    IBM SOA Sandbox
    中探索在线试用版本,并动手使用来自
    DB2、Lotus、Rational、Tivoli 和 WebSphere
    的应用程序开发工具和中间件产品。

绘制单个 star

    /* 设置单个 star
     * @param id:id
     * @param x:x坐标
     * @param y:y坐标
     * @param useCache:是否使用缓存
     * */
    function Star(id, x, y) {
        this.id = id;
        this.x = x;
        this.y = y;
        this.cacheCanvas = document.createElement("canvas");
        this.cacheCtx = this.cacheCanvas.getContext("2d");
        this.r = Math.floor(Math.random() * star_r) + 1;
        this.cacheCtx.width = 6 * this.r;
        this.cacheCtx.height = 6 * this.r;
        var alpha = ( Math.floor(Math.random() * 10) + 1) / star_alpha;
        this.color = "rgba(255,255,255," + alpha + ")";
        if (useCache) {
            this.cache()
        }
    }

讨论

  • developerWorks
    社区:探索由开发人员推动的博客、论坛、组和维基,并与其他
    developerWorks 用户进行交流。

    赞 收藏
    评论

www.bifa88.com 41

让每一个 star 动起来

这里我使用的是原型的方式,将 drawcachemovedie
方法都设置在 Star 的原型上,这样在使用 new 创建对象的时候,每一个
star 都可以继承这些方法。

Star.prototype = {
        draw : function () {
            if (!this.useCacha) {
                ctx.save();
                ctx.fillStyle = this.color;
                ctx.shadowBlur = this.r * 2;
                ctx.beginPath();
                ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
                ctx.closePath();
                ctx.fill();
                ctx.restore();
            } else {
                ctx.drawImage(this.cacheCanvas, this.x - this.r, this.y - this.r);
            }
        },

        cache : function () {
            this.cacheCtx.save();
            this.cacheCtx.fillStyle = this.color;
            this.cacheCtx.shadowColor = "white";
            this.cacheCtx.shadowBlur = this.r * 2;
            this.cacheCtx.beginPath();
            this.cacheCtx.arc(this.r * 3, this.r * 3, this.r, 0, 2 * Math.PI);
            this.cacheCtx.closePath();
            this.cacheCtx.fill();
            this.cacheCtx.restore();
        },

        move : function () {
            this.y -= move_distance;
            if (this.y <= -10) {
                this.y += HEIGHT + 10;
            }
            this.draw();
        },

        die : function () {
            stars[this.id] = null;
            delete stars[this.id]
        }
    };

绘制 dot

function Dot(id, x, y, useCache) {
        this.id = id;
        this.x = x;
        this.y = y;
        this.r = Math.floor(Math.random() * dot_r)+1;
        this.speed = dot_speeds;
        this.a = dot_alpha;
        this.aReduction = dot_aReduction;
        this.useCache = useCache;
        this.dotCanvas = document.createElement("canvas");
        this.dotCtx = this.dotCanvas.getContext("2d");
        this.dotCtx.width = 6 * this.r;
        this.dotCtx.height = 6 * this.r;
        this.dotCtx.a = 0.5;
        this.color = "rgba(255,255,255," + this.a +")";
        this.dotCtx.color = "rgba(255,255,255," + this.dotCtx.a + ")";
        this.linkColor = "rgba(255,255,255," + this.a/4 + ")";
        this.dir = Math.floor(Math.random()*140)+200;

        if( useCache){
            this.cache()
        }
    }

让每一个 dot 动起来

Dot.prototype = {
        draw : function () {
            if( !this.useCache){
                ctx.save();
                ctx.fillStyle = this.color;
                ctx.shadowColor = "white";
                ctx.shadowBlur = this.r * 2;
                ctx.beginPath();
                ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
                ctx.closePath();
                ctx.fill();
                ctx.restore();
            }else{
                ctx.drawImage(this.dotCanvas, this.x - this.r * 3, this.y - this.r *3);

            }
        },

        cache : function () {
            this.dotCtx.save();
            this.dotCtx.a  -= this.aReduction;
            this.dotCtx.color = "rgba(255,255,255," + this.dotCtx.a + ")";
            this.dotCtx.fillStyle = this.dotCtx.color;
            this.dotCtx.shadowColor = "white";
            this.dotCtx.shadowBlur = this.r * 2;
            this.dotCtx.beginPath();
            this.dotCtx.arc(this.r * 3, this.r * 3, this.r, 0, 2 * Math.PI, false);
            this.dotCtx.closePath();
            this.dotCtx.fill();
            this.dotCtx.restore();
        },
        link : function () {
            if (this.id == 0) return;
            var previousDot1 = getPreviousDot(this.id, 1);
            var previousDot2 = getPreviousDot(this.id, 2);
            var previousDot3 = getPreviousDot(this.id, 3);
            var previousDot4 = getPreviousDot(this.id, 4);


            if (!previousDot1) return;
            ctx.strokeStyle = this.linkColor;
            ctx.moveTo(previousDot1.x, previousDot1.y);
            ctx.beginPath();
            ctx.lineTo(this.x, this.y);
            if (previousDot2 != false) ctx.lineTo(previousDot2.x, previousDot2.y);
            if (previousDot3 != false) ctx.lineTo(previousDot3.x, previousDot3.y);
            if (previousDot4 != false) ctx.lineTo(previousDot4.x, previousDot4.y);

            ctx.stroke();
            ctx.closePath();
        },

        move : function () {


            this.a -= this.aReduction;
            if(this.a <= 0 ){
                this.die();
                return
            }
            this.dotCtx.a  -= this.aReduction;
            this.dotCtx.color = "rgba(255,255,255," + this.dotCtx.a + ")";
            this.color = "rgba(255,255,255," + this.a + ")";
            this.linkColor = "rgba(255,255,255," + this.a/4 + ")";
            this.x = this.x + Math.cos(degToRad(this.dir)) * this.speed;
            this.y = this.y + Math.sin(degToRad(this.dir)) * this.speed;

            this.draw();
            this.link();

        },

        die : function () {
            dots[this.id] = null;
            delete dots[this.id];
        }
    };

鼠标移入事件监听

此外,我们还需要设置一些其他的函数和对鼠标移入事件的监听,这里就不再赘述了,感兴趣的同学可以直接到
github 下载源码。

canvas 离屏渲染优化

我所使用的离屏优化是基于此文,原文写的很好,大家感兴趣的话可以去看一下:http://www.cnblogs.com/axes/p/3567364.html?utm\_source=tuicool&utm\_medium=referral。
因为这个效果之前我也在博客用当做背景过,不少同学都反应很卡,所以我就找了下优化的教程做了下优化,我发现对性能影响最大的可能就是
canvas 的离屏渲染优化了,这也是 canvas 的最常见优化之一。

名字听起来很复杂,什么离屏渲染,其实就是设置缓存,绘制图像的时候在屏幕之外的地方绘制好,然后再直接拿过来用,这不就是缓存的概念吗?!︿( ̄︶ ̄)︿.

建立两个 canvas
标签,大小一致,一个正常显示,一个隐藏(缓存用的,不插入dom中),先将结果draw缓存用的canvas上下文中,因为游离canvas不会造成ui的渲染,所以它不会展现出来,再把缓存的内容整个裁剪再
draw 到正常显示用的 canvas 上,这样能优化不少。

其实已经体现在上述的代码中的,比如,创建 star 的代码中:

 /* 设置单个star
     * @param id:id
     * @param x:x坐标
     * @param y:y坐标
     * @param useCache:是否使用缓存
     * */
    function Star(id, x, y, useCache) {
        this.id = id;
        this.x = x;
        this.y = y;
        this.useCacha = useCache;
        this.cacheCanvas = document.createElement("canvas");
        this.cacheCtx = this.cacheCanvas.getContext("2d");
        this.r = Math.floor(Math.random() * star_r) + 1;
        this.cacheCtx.width = 6 * this.r;
        this.cacheCtx.height = 6 * this.r;
        var alpha = ( Math.floor(Math.random() * 10) + 1) / star_alpha;
        this.color = "rgba(255,255,255," + alpha + ")";
        if (useCache) {
            this.cache()
        }
    }

细心的同学可能就会发现

        this.cacheCanvas = document.createElement("canvas");
        this.cacheCtx = this.cacheCanvas.getContext("2d");

这段代码就是又创建了一个 canvas 标签,然后再 star 的原型中有一个 cache
方法,这个 cache 方法就是在刚刚创建的 canvas 中绘制
star,而不是直接在原来的 canvas 画布中绘制的。

        cache : function () {
            this.cacheCtx.save();
            this.cacheCtx.fillStyle = this.color;
            this.cacheCtx.shadowColor = "white";
            this.cacheCtx.shadowBlur = this.r * 2;
            this.cacheCtx.beginPath();
            this.cacheCtx.arc(this.r * 3, this.r * 3, this.r, 0, 2 * Math.PI);
            this.cacheCtx.closePath();
            this.cacheCtx.fill();
            this.cacheCtx.restore();
        },

之后我们需要将我们绘制的离屏 canvas 使用 drawImage
方法插入到我们最先开始创建的 canvas 画布中。

这里要注意的是,创建的离屏 canvas
的大小,因为太大的话同样会浪费性能,所以我们可以创建和我们每一个 star
粒子相同的 canvas ,但是这个例子中不适用,要将离屏的 canvas
设置的稍微大一些,因为我们还需要设置发光的效果(也就是设置阴影)。

发福利

发福利的时间到了~╰( ̄▽ ̄)╭,很多小伙伴对 canvas
不是很感兴趣,但是想直接使用这个效果,于是我就将其封装起来,你只需要引入这个
JS,在 HTML 中添加一个 id 为 canvas 的标签,然后设置相应的 CSS 就可以~

github
下载地址:https://github.com/sunshine940326/canvasStar

在 README
中有使用方法因为是第一次自己封装函数,自己一个人在不停的摸索中前进,所以还有很多的不足,希望有大神可以指点一二