canvas简介以及常用性能优化

1、Canvas和SVG

1)Canvas

HTML5 的 Canvas 元素使用 JavaScript 在网页上绘制图像。
画布是一个矩形区域,您可以控制其每一像素。
Canvas 拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。
Canvas 是逐像素进行渲染的。
在 Canvas 中,一旦图形被绘制完成,它就不会继续得到浏览器的关注。如果其位置发生变化,那么整个场景也需要重新绘制,包括任何或许已被图形覆盖的对象。

2)SVG

SVG 基于 XML,这意味着 SVG DOM 中的每个元素都是可用的。您可以为某个元素附加 JavaScript 事件处理器。
在 SVG 中,每个被绘制的图形均被视为对象。如果 SVG 对象的属性发生变化,那么浏览器能够自动重现图形。

3)Canvas和SVG的优缺点

Canvas

-依赖分辨率
-不支持事件处理器
-弱的文本渲染能力
-能够以 .png 或 .jpg 格式保存结果图像
-最适合图像密集型的游戏,其中的许多对象会被频繁重绘

SVG

-不依赖分辨率
-支持事件处理器
-最适合带有大型渲染区域的应用程序(比如谷歌地图)
-复杂度高会减慢渲染速度(任何过度使用 DOM 的应用都不快)
-不适合游戏应用

2、创建 Canvas 元素

<canvas id="myCanvas" width="200" height="100"></canvas>

3、基本的canvas用法

当我们在绘制的一个元素(文字、图形)的时候,首先要对这个元素规定它的颜色,文字字体,然后在进行绘制,不然不会生效。

<script type="text/javascript">
var canvasEle = document.getElementById("myCanvas");
var cxt = canvasEle.getContext("2d");
cxt.fillStyle = "#FF0000";
cxt.fillRect(0,0,150,75);
</script>

4、Canvas常用优化点

1)使用多层画布去画一个复杂的场景

场景:假设您有一个游戏,其UI位于顶部,中间是游戏性动作,底部是静态背景。

方法:可以将游戏分成三个<canvas>层。 UI将仅在用户输入时发生变化,游戏层随每个新框架发生变化,并且背景通常保持不变

<div id="stage">
  <canvas id="ui-layer" width="480" height="320"></canvas>
  <canvas id="game-layer" width="480" height="320"></canvas>
  <canvas id="background-layer" width="480" height="320"></canvas>
</div>

<style>
  #stage {
    width: 480px;
    height: 320px;
    position: relative;
    border: 2px solid black
  }
  canvas { position: absolute; }
  #ui-layer { z-index: 3 }
  #game-layer { z-index: 2 }
  #background-layer { z-index: 1 }
</style>

好处:避免了固定组件的重复渲染。

2)使用window.devicePixelRatio对画布进行高清处理

设备屏幕上的像素(逻辑像素),我们可以当做是正常的像素(css中设置的像素),你可以正常使用它。如果你画一个100px的东西,他也就是一个100px的东西。但是,在出现了一些高分辨率屏幕的手机之后,一个属性devicePixelRatio就一起出现了。它允许我们去查询设备像素比。在这里我们需要抛出一个名词逻辑像素,也就是在css设置的100px时,在iphone6/7/8(devicePixelRatio为3)上,实际渲染的是300px的物理像素。比例为3:1

但是这对于我们开发者的影响是什么呢?早些时候,我们注意到当我们向这种高分辨率的屏幕添加img的时候,我们的图形受到devicePixelRatio的影响变得非常模糊。
如何解决这个问题呢?如果我把img的宽和高分别与devicePixelRatio相乘,得到的大小画进屏幕中,在对齐进行缩放devicePixelRatio的大小。Img就会以一种高清的方式呈现。

 createHiDPICanvas (w, h, ratio) {
      const PIXEL_RATIO = (function () {
        const c = document.getElementById('posterCanvas')
        const ctx = c.getContext('2d')
        const dpr = window.devicePixelRatio || 1
        // backingStorePixelRatio,该属性的值决定了浏览器在渲染canvas之前会用几个像素来来存储画布信息 (仅适用于chrome和safari),已经废弃。
        const bsr =
          ctx.webkitBackingStorePixelRatio ||
          ctx.mozBackingStorePixelRatio ||
          ctx.msBackingStorePixelRatio ||
          ctx.oBackingStorePixelRatio ||
          ctx.backingStorePixelRatio ||
          1

        return dpr / bsr
      })()

      if (!ratio) {
        ratio = PIXEL_RATIO
      }
      const can = document.getElementById('posterCanvas')
      can.width = w * ratio
      can.height = h * ratio
      can.style.width = w + 'px'
      can.style.height = h + 'px'
      can.getContext('2d').setTransform(ratio, 0, 0, ratio, 0, 0)
      return can
    },

但是此方法有个问题就是既然同比将画布和内容进行了放大,然后在进行缩放,那么绘制出来的图片大小就会相应的增大。建议根据需求来判断是否需要进行高清操作。

3)使用requestAnimationFrame执行动画

canvas动画的本质是不断地擦除和重绘,再结合一些时间控制的方法达到动画的目的
显示器刷新频率是60Hz,最平滑动画的最佳循环间隔是1000ms/60,约等于16.6ms
requestAnimationFrame就是根据显示器刷新频率来的,这是浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,节省系统资源,提高系统性能,如果页面不是激活状态下的话,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命。

window.requestAnimationFrame(callback)

执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,返回一个 requestID,该回调会在浏览器下一次重绘之前执行

window. cancelAnimationFrame(requestID)

取消一个先前通过调用window. cancelAnimationFrame()方法添加到计划中的动画帧请求,接受一个 requestID
浏览器兼容


浏览器兼容.png

4)1px像素模糊情况

canvas每条线都有一条无限细的中线,线由中线两个伸展。


1px像素模糊.png

解决问题的根源起点应该在0.5的地方,这也是为什么x,y需要+0.5。当x,y做过计算不一定是整数的时候可能+0.5又出现模糊的情况。所以做一个取整可以保证不会出现模糊的情况

cxt.moveTo(parseInt(x)+0.5, parseInt(y)+0.5)
cxt.lineTo(parseInt(x)+0.5, parseInt(y)+0.5)

5)离屏绘制图像

1.绘制图像
context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
image.png
2.绘制性能比较

由于我们具备「把图片中的某一部分绘制到 Canvas 上」的能力。
在平常的html中,我们会把多个对象放在一张图片里面,以减少请求数量。这通常被称为「精灵图」。然而,这实际上存在着一些潜在的性能问题。使用 drawImage 绘制同样大小的区域,数据源是一张和绘制区域尺寸相仿的图片的情形,和比起数据源是一张较大图片(我们只是把数据扣下来了而已)的情形,前者的开销要小一些。可以认为,两者相差的开销正是「裁剪」这一个操作的开销。
虽然看上去开销相差并不多,但是 drawImage 是最常用的 API 之一,我认为还是有必要进行优化的。优化的思路是,将「裁剪」这一步骤事先做好,保存起来,每一帧中仅绘制不裁剪。

3.离屏绘制

我们可以先把待绘制的区域裁剪好,保存起来,这样每次绘制时就能轻松很多
drawImage 方法的第一个参数不仅可以接收 Image 对象,也可以接收另一个 Canvas 对象。而且,使用 Canvas 对象绘制的开销与使用 Image 对象的开销几乎完全一致。我们只需要实现将对象绘制在一个未插入页面的 Canvas 中,然后每一帧使用这个 Canvas 来绘制。

// 在离屏 canvas 上绘制
var canvasOffscreen = document.createElement('canvas');
canvasOffscreen.width = dw;
canvasOffscreen.height = dh;
canvasOffscreen.getContext('2d').drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);

// 在绘制每一帧的时候,绘制这个图形
context.drawImage(canvasOffscreen, x, y);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 一、简介 是一个可以使用脚本(通常为JavaScript)来绘制图形的 HTML 元素.例如,它可以用于绘制图表、...
    Adoins阅读 2,246评论 0 2
  • 一:canvas简介 1.1什么是canvas? ①:canvas是HTML5提供的一种新标签 ②:HTML5 ...
    GreenHand1阅读 4,707评论 2 32
  • 1.绘图 fillRect(x, y, width, height)[https://developer.mozi...
    SnuggleE阅读 647评论 0 0
  • @(HTML5)[canvas与SVG] [TOC] 十 、canvas canvas的基本用法 canvas是H...
    踏浪free阅读 770评论 0 0
  • 一、图形的组合方式 globalAlpha是一个介于0和1之间的值(包括0和1),用于指定所有绘制的透明度。默认值...
    空谷悠阅读 1,307评论 0 0