使用requestAnimationFrame

1、早期定时动画

  • 早期使用setInterval()控制动画的执行。
  • 存在的问题:
    • 无法准确知晓循环之间的延时。
    • 不能保证时间精度。
1
2
3
4
5
6
7
8
(function() { 
function updateAnimations() {
doAnimation1();
doAnimation2();
// 其他任务
}
setInterval(updateAnimations, 100);
})();

2、requestAnimationFrame

  • requestAnimationFrame()

    • 方法接收一个参数:要在重绘屏幕前调用的函数。这个函数就是修改DOM样式以反映下一次重绘有什么变化的地方。
    • 为了实现动画循环,可以串联调用。
      1
      2
      3
      4
      5
      6
      7
      8
      function updateProgress() { 
      var div = document.getElementById("status");
      div.style.width = (parseInt(div.style.width, 10) + 5) + "%";
      if (div.style.left != "100%") {
      requestAnimationFrame(updateProgress);
      }
      }
      requestAnimationFrame(updateProgress);
  • 因为requestAnimationFrame()只会调用一次传入的函数,所以每次更新用户界面时需要再手动调用它一次。同样,也需要控制动画何时停止。

  • 传给requestAnimationFrame()的函数实际上可以接收一个参数,此参数是一个 DOMHighResTimestamp 的实例(比如performance.now()返回的值),表示下次重绘的时间。

3、cancelAnimationFrame

  • 与setTimeout类似,requestAnimationFrame()返回一个请求ID;
  • cancelAnimationFrame()来取消重绘任务。
1
2
3
4
let requestID = window.requestAnimationFrame(() => { 
console.log('Repaint!');
});
window.cancelAnimationFrame(requestID);

4、通过requestAnimationFrame节流

  • 支持该方法的浏览器会暴露作为钩子的回调队列。
    • 钩子就是浏览器在执行下一次重绘前的一个点。
    • 通过requestAnimationFrame ()递归地向队列中加入回调函数,可以保证每次重绘最多只调用一次回调函数。

基本的画布功能

  • 创建<canvas>元素时至少需要width和height属性。
  • getContext()方法可以获取对绘图上下文的引用。
    • 对于平面图形,需要传入参数:2d。
  • toDataURL()方法可以导出<canvas>元素上的图像。
    • 方法接收一个参数:要生成图像的MIME类型。
1
2
3
4
5
6
let drawing = document.getElementById("drawing"); 
// 确保浏览器支持<canvas>
if (drawing.getContext) {
let context = drawing.getContext("2d");
// 其他代码
}

2D绘图上下文

1、填充和描边

  • 两个属性:fillStyle和strokeStyle。
    • 可以是字符串】渐变对象或图案对象;
    • 默认值为”#000000”;
    • 字符串表示颜色值,可以是CSS支持的任意格式:名称、十六进制代码、rgb、rgba、hsl或hsla。

2、绘制矩形

  • fillRect()、strokeRect()、clearRect():
    • 方法都接收4个参数:矩形x坐标、矩形y坐标、矩形宽度和矩形高度(单位均为像素)。
    • fillRect()方法用于以指定颜色在画布上绘制并填充矩形;填充颜色使用fillStyle属性指定(实心矩形)。
    • strokeRect()方法可以通过strokeStyle属性指定的颜色绘制举行轮廓(绘制边框,内部空心)。
    • clearRect()方法可以擦除画布中某个区域
      • 方法用于把绘图上下文种的某个区域变透明。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let drawing = document.getElementById("drawing"); 
// 确保浏览器支持<canvas>
if (drawing.getContext) {
let context = drawing.getContext("2d");
/*
* 引自 MDN 文档
*/
// 绘制红色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 绘制半透明蓝色矩形
context.fillStyle = "rgba(0,0,255,0.5)";
context.fillRect(30, 30, 50, 50);
// 在前两个矩形重叠的区域擦除一个矩形区域
context.clearRect(40, 40, 10, 10);
}

3、绘制路径

  • beginPath()方法是开始绘制新路径必须首先调用的方法。
  • 再用以下方法来绘制路径:
  • 创建路径之后,可以使用closePath()方法绘制一条返回起点的线。如果路径已经完成,则既可以指定fillstyle 属性并调用fill ()方法来填充路径,也可以指定strokeStyle 属性并调用stroke ()方法来描画路径,还可以调用clip()方法基于已有路径创建一 个新剪切区域。
  • isPointInPath()方法用于确定指定的点是否在路径上,可以在关闭路径前随时调用
    • 接收x轴和y轴坐标作为参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let drawing = document.getElementById("drawing"); 
// 确保浏览器支持<canvas>
if (drawing.getContext) {
let context = drawing.getContext("2d");
// 创建路径
context.beginPath();
// 绘制外圆
context.arc(100, 100, 99, 0, 2 * Math.PI, false);
// 绘制内圆
context.moveTo(194, 100);
context.arc(100, 100, 94, 0, 2 * Math.PI, false);
// 绘制分针
context.moveTo(100, 100);
context.lineTo(100, 15);
// 绘制时针
context.moveTo(100, 100);
context.lineTo(35, 100);
// 描画路径
context.stroke();
}

4、绘制文本

  • fillText()和strokeText()用于绘制文本。
    • 两个方法接收4个参数:字符串、x坐标、y坐标和可选的最大像素宽度。
    • fillText()方法使用fillStyle属性绘制文本;
    • strokeText()方法使用strokeStyle属性;
    • 两个方法最终绘制的结果都取决于以下3个属性:
  • measureText()方法
    • 接收一个参数:要绘制的文本;
    • 返回一个TextMetrics对象;
    • 使用font、textAlign和textBaseline属性当前的值计算绘制指定文本后的大小。
1
2
3
4
5
6
7
8
let fontSize = 100; 
context.font = fontSize + "px Arial";
while(context.measureText("Hello world!").width > 140) {
fontSize--;
context.font = fontSize + "px Arial";
}
context.fillText("Hello world!", 10, 10);
context.fillText("Font size is " + fontSize + "px", 10, 50);

5、变换

  • 以下方法可用于改变绘制上下文的变换矩阵:
  • 所有这些变换,包括fillstyle和strokestyle属性,会一直保留在上下文中,直到再次修改它们

6、绘制图像

  • drawImage()方法用于把现有图像绘制到画布上。
  • 方法可以接收3组不同的参数:
    1、 传入HTML种img元素/canvas元素、绘制目标的x和y坐标;(直接绘制)
    2、 传入HTML img元素、绘制目标的x、y坐标、目标宽度、目标高度;(可用于改变图像大小)
    3、 要绘制的图像、源图像x坐标、源图像y坐标、源图像宽度、源图像高度、目标区域x坐标、目标区域y坐标、目标区域宽度、目标区域高度。(只把图像绘制到上下文中的一个区域)

7、阴影

  • 2D上下文可以根据以下属性的值自动为已有形状或路径生成阴影:
  • 这些属性都可以通过context对象读写。
1
2
3
4
5
6
7
8
9
10
11
12
let context = drawing.getContext("2d"); 
// 设置阴影
context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.shadowBlur = 4;
context.shadowColor = "rgba(0, 0, 0, 0.5)";
// 绘制红色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 绘制蓝色矩形
context.fillStyle = "rgba(0,0,255,1)";
context.fillRect(30, 30, 50, 50);

8、渐变

  • 渐变通过CanvasGradient的实例表示,在2D上下文中创建和修改都非常简单。
  • createLinearGradient()方法用于创建线性渐变。
    • 接收4个参数:起点x坐标、起点y坐标、终点x坐标和终点y坐标。
    • 调用之后,该方法会以指定大小创建一个新的CanvasGradient对象并返回实例。
  • addColorStop()方法为渐变指定色标。
    • 方法接收两个参数:色标位置和CSS颜色字符串。
    • 色标位置通过0~1范围的值表示,0是第一种颜色,1是最后一种颜色。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      let gradient = context.createLinearGradient(30, 30, 70, 70); 
      gradient.addColorStop(0, "white");
      gradient.addColorStop(1, "black");
      // 绘制红色矩形
      context.fillStyle = "#ff0000";
      context.fillRect(10, 10, 50, 50);
      // 绘制渐变矩形
      context.fillStyle = gradient;
      context.fillRect(30, 30, 50, 50);

9、图案

  • createPattern()方法。
    • 接收两个参数:一个HTML<img>和一个表示该如何重复图像的字符串。
    • 第二个参数的值与CSS的background-repeat属性,包括”repeat”、”repeat-x”、”repeat-y”和”no-repeat”。
    • 第一个参数也可以是<video>元素或者另一个<canvas>元素。
1
2
3
4
5
let image = document.images[0], 
pattern = context.createPattern(image, "repeat");
// 绘制矩形
context.fillStyle = pattern;
context.fillRect(10, 10, 150, 150);

10、图像数据

  • getImageData()方法获取原始图像数据。
    • 方法接收4个参数:要取得数据中第一个像素的左上角坐标和要取得的像素宽度及高度。
    • 返回的对象是一个ImageData的实例。每个对象都包含3个属性:
      • width:图像宽;
      • height:图像高;
      • data:包含图像的原始像素信息的数组。每个data中的元素都由4个值表示,分别代表红、绿、蓝和透明度。

11、合成

  • globalAlpha属性是一个范围在0~1的值(包括0和1),用于指定所有绘制内容的透明度,默认值为0。
  • globalCompositionOperation属性表示新绘制的形状如何与上下文中已有的形状融合。该属性为一个字符串,可以取下列值: