从入门到精通canvas:基础系列(三)

209 阅读4分钟

组合

画图,文字,图形,本章会讲解动画,再canvas上执行动画效果,在动画之前可以先看一下组合,之前画图都总是将一个图形画在另一个之上,对于其他更多的情况,仅仅这样是远远不够的。比如,对合成的图形来说,绘制顺序会有限制。现在可以使用globalCompositeOperation来改变这种情况,还可以使用clip隐藏我们不想看到的地方,不同于之前的clearRect,清除地方仅限制矩形

globalCompositeOperation

这个属性设定了在画新图形时采用的遮盖策略,其值是一种遮盖方式的字符串

  • source-over:这个是默认属性,也就是再现有的图上绘制上下文。

  • source-in:新图形只在新图形和目标画布重叠的地方绘制,其它地方都是透明的

  • source-out:在不与现有画布内容重叠的地方绘制新图形

  • source-atop: 新图形只在与现有画布内容重叠的地方绘制。

  • destination-over:在现有的画布内容后面绘制新的图形。

  • destination-in:现有的画布内容保持在新图形和现有画布内容重叠的位置。其他的都是透明的。

  • destination-out: 现有内容保持在新图形不重叠的地方(重叠的话就会变为透明)。

  • destination-atop:现有的画布只保留与新图形重叠的部分,新的图形是在画布内容后面绘制的。

  • lighter: 两个重叠图形的颜色是通过颜色值相加来确定的。

  • copy: 只显示新图形。

  • xor:图像中,那些重叠和正常绘制之外的其他地方是透明的。

  • multiply: 将顶层像素与底层相应像素相乘。

  • screen:像素被倒转,相乘,再倒转,得到图像

  • overlay:multiply 和 screen 的结合,原本暗的地方更暗,原本亮的地方更亮。

  • darken:保留两个图层中最暗的像素。

  • lighten:保留两个图层中最亮的像素。

  • color-dodge: 将底层除以顶层的反置。

  • color-burn:将反置的底层除以顶层,然后将结果反过来。

  • hard-light:屏幕相乘(A combination of multiply and screen)类似于叠加,但上下图层互换了。

  • soft-light:用顶层减去底层或者相反来得到一个正值。

  • difference:一个柔和版本的强光(hard-light)。纯黑或纯白不会导致纯黑或纯白。

  • exclusion:对比度比较低和difference相比

  • hue:保留了底层的亮度和色度,同时采用了顶层的色调(hue)。

  • saturation:保留底层的亮度和色调,同时采用顶层的色度。

  • color:保留了底层的亮度,同时采用了顶层的色调和色度

  • luminosity:保持底层的色调和色度,同时采用顶层的亮度

clip(裁剪)

我们使用 clip()方法来创建一个新的裁切路径。默认情况下,canvas 有一个与它自身一样大的裁切路径(也就是没有裁切效果)。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<style>
  #canvas {
    border: 1px solid #000;
  }
</style>

<body>
  <canvas id="canvas" width="300" height="300"></canvas>
  <script>
    const canvas = document.getElementById("canvas");
    ctx = canvas.getContext("2d");
    ctx.fillRect(0, 0, 150, 150);
    ctx.translate(75, 75);
    // Create a circular clipping path
    ctx.beginPath();
    ctx.arc(0, 0, 70, 0, Math.PI * 2, true);
    ctx.clip();

    // draw background
    var lingrad = ctx.createLinearGradient(0, -75, 0, 75);
    lingrad.addColorStop(0, "red");
    lingrad.addColorStop(1, "pink");

    ctx.fillStyle = lingrad;
    ctx.fillRect(-75, -75, 150, 150);
    ctx.fillStyle = "#22a4f1";
    ctx.beginPath();
    ctx.moveTo(50, 0);
    ctx.lineTo(50, 50);
    ctx.lineTo(130, 30);
    ctx.fill()
  </script>
</body>

</html>

上面代码中呢我绘制了一个剪裁区域,在剪裁区域呢有个三角形,三角形超出剪裁区域的就会看不见,只有剪裁区域内的才可以被看见

基础动画

你可以通过以下的步骤来画出一帧:

  1. 清空 canvas 除非接下来要画的内容会完全充满 canvas(例如背景图),否则你需要清空所有。最简单的做法就是用 clearRect 方法。
  2. 保存 canvas 状态 如果你要改变一些会改变 canvas 状态的设置(样式,变形之类的),又要在每画一帧之时都是原始状态的话,你需要先保存一下。
  3. 绘制动画图形(animated shapes)  这一步才是重绘动画帧。
  4. 恢复 canvas 状态 如果已经保存了 canvas 的状态,可以先恢复它,然后重绘下一帧。
<!DOCTYPE html>
<html lang="en">

<head>
 <meta charset="UTF-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Document</title>
</head>
<style>
 #canvas {
   border: 1px solid #000;
 }
</style>

<body>
 <canvas id="canvas" width="300" height="300"></canvas>
 <script>
   const canvas = document.getElementById("canvas");
   const ctx = canvas.getContext("2d");
   // 设置三张图片
   var sun = new Image();
   var moon = new Image();
   var earth = new Image();
   function init () {
     sun.src = "https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/640b4df0a5074e9a9bf4777fdf1fd74e~tplv-k3u1fbpfcp-watermark.image";
     moon.src = "https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/05bc3992bd5044448f029b7d68049b38~tplv-k3u1fbpfcp-watermark.image";
     earth.src = " https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f4ad71733e934b818a52bcfea56a683f~tplv-k3u1fbpfcp-watermark.image";
     window.requestAnimationFrame(draw);

   }
   function draw () {
     var ctx = document.getElementById("canvas").getContext("2d");
     // 设置遮盖策略,再现有的画布后面进行绘制
     ctx.globalCompositeOperation = "destination-over";
     ctx.clearRect(0, 0, 300, 300);
     // 初始化样式
     ctx.fillStyle = "rgba(0,0,0,0.4)";
     ctx.strokeStyle = "rgba(0,153,255,0.4)";
     // 记录初始化样式
     ctx.save();
     // 移动到画布中间
     ctx.translate(150, 150);

     var time = new Date();
     // 每秒旋转角度*当前分钟
     // (2 * Math.PI) / 60) * time.getSeconds()
     // 每毫秒旋转角度*当前毫秒
     // ((2 * Math.PI) / 60000) * time.getMilliseconds(),
     ctx.rotate(
       ((2 * Math.PI) / 60) * time.getSeconds() +
       ((2 * Math.PI) / 60000) * time.getMilliseconds(),
     );
     ctx.translate(105, 0);
     ctx.fillRect(0, -12, 50, 24);
     // 绘图图片
     ctx.drawImage(earth, -12, -12);
     // 同上一样
     ctx.save();
     ctx.rotate(
       ((2 * Math.PI) / 6) * time.getSeconds() +
       ((2 * Math.PI) / 6000) * time.getMilliseconds(),
     );
     ctx.translate(0, 28.5);
     ctx.drawImage(moon, -3.5, -3.5);
     ctx.restore();
     ctx.restore();
     ctx.beginPath();

     ctx.arc(150, 150, 105, 0, Math.PI * 2, false);
     ctx.stroke();

     ctx.drawImage(sun, 0, 0, 300, 300);
     // 每帧执行一次
     window.requestAnimationFrame(draw);
   }

   init();
 </script>
</body>

</html>

上面是一个简单的太阳系动画,主要就是先设置起始位置,然后算出来每秒每毫秒的旋转角度,进行移动,然后使用requestAnimationFrame执行连续执行

下面实现一个全景照片的自动展示,使用定时器来展示。注释写的比较清除,这里就不多陈述了,主要也就是一些计算和判断

跳动的小球,是基于刷新率来进行计算的即,代码中会有很清楚的注释,可以根据注释来看,这里就不过多进行陈述了。