Movatterモバイル変換


[0]ホーム

URL:


MDN Web Docs

此页面由社区从英文翻译而来。了解更多并加入 MDN Web Docs 社区。

基本的动画

由于我们是用 JavaScript 去操控<canvas> 对象,这样要实现一些交互动画也是相当容易的。在本章中,我们将看看如何做一些基本的动画。

可能最大的限制就是图像一旦绘制出来,它就是一直保持那样了。如果需要移动它,我们不得不对所有东西(包括之前的)进行重绘。重绘是相当费时的,而且性能很依赖于电脑的速度。

动画的基本步骤

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

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

操控动画

在 canvas 上绘制内容是用 canvas 提供的或者自定义的方法,而通常,我们仅仅在脚本执行结束后才能看见结果,比如说,在 for 循环里面做完成动画是不太可能的。

因此,为了实现动画,我们需要一些可以定时执行重绘的方法。有两种方法可以实现这样的动画操控。首先可以通过setIntervalsetTimeout 方法来控制在设定的时间点上执行重绘。

有安排地更新画布

首先,可以用setInterval()setTimeout()requestAnimationFrame() 来设定定期执行一个指定函数。

setInterval()

当设定好间隔时间后,function 会定期执行。

setTimeout()

在设定好的时间之后执行函数

requestAnimationFrame()

告诉浏览器你希望执行一个动画,并在重绘之前,请求浏览器执行一个特定的函数来更新动画。

如果你并不需要与用户互动,你可以使用setInterval() 方法,它就可以定期执行指定代码。如果我们需要做一个游戏,我们可以使用键盘或者鼠标事件配合上setTimeout() 方法来实现。通过设置事件监听,我们可以捕捉用户的交互,并执行相应的动作。

备注:下面的例子,采用window.requestAnimationFrame()实现动画效果。这个方法提供了更加平缓并更加有效率的方式来执行动画,当系统准备好了重绘条件的时候,才调用绘制动画帧。一般每秒钟回调函数执行 60 次,也有可能会被降低。想要了解更多关于动画循环的信息,尤其是游戏,可以在Game development zone 参考这篇文章Anatomy of a video game

太阳系的动画

该示例展示了一个小型太阳系模型的动画。

HTML

html
<canvas width="300" height="300"></canvas>

JavaScript

js
const sun = new Image();const moon = new Image();const earth = new Image();function init() {  sun.src = "canvas_sun.png";  moon.src = "canvas_moon.png";  earth.src = "canvas_earth.png";  window.requestAnimationFrame(draw);}function draw() {  const ctx = document.getElementById("canvas").getContext("2d");  ctx.globalCompositeOperation = "destination-over";  ctx.clearRect(0, 0, 300, 300); // 清除画布  ctx.fillStyle = "rgb(0 0 0 / 40%)";  ctx.strokeStyle = "rgb(0 153 255 / 40%)";  ctx.save();  ctx.translate(150, 150);  // 地球  const time = new Date();  ctx.rotate(    ((2 * Math.PI) / 60) * time.getSeconds() +      ((2 * Math.PI) / 60000) * time.getMilliseconds(),  );  ctx.translate(105, 0);  ctx.fillRect(0, -12, 40, 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();

结果

动画时钟

该示例绘制一个可以显示当前时间的动画时钟。

HTML

html
<canvas width="150" height="150">当前时间</canvas>

JavaScript

js
function clock() {  const now = new Date();  const canvas = document.getElementById("canvas");  const ctx = canvas.getContext("2d");  ctx.save();  ctx.clearRect(0, 0, 150, 150);  ctx.translate(75, 75);  ctx.scale(0.4, 0.4);  ctx.rotate(-Math.PI / 2);  ctx.strokeStyle = "black";  ctx.fillStyle = "white";  ctx.lineWidth = 8;  ctx.lineCap = "round";  // 小时刻度  ctx.save();  for (let i = 0; i < 12; i++) {    ctx.beginPath();    ctx.rotate(Math.PI / 6);    ctx.moveTo(100, 0);    ctx.lineTo(120, 0);    ctx.stroke();  }  ctx.restore();  // 分钟刻度  ctx.save();  ctx.lineWidth = 5;  for (let i = 0; i < 60; i++) {    if (i % 5 !== 0) {      ctx.beginPath();      ctx.moveTo(117, 0);      ctx.lineTo(120, 0);      ctx.stroke();    }    ctx.rotate(Math.PI / 30);  }  ctx.restore();  const sec = now.getSeconds();  // 要显示扫秒式的时钟,请使用:  // const sec = now.getSeconds() + now.getMilliseconds() / 1000;  const min = now.getMinutes();  const hr = now.getHours() % 12;  ctx.fillStyle = "black";  // 显示图像描述  canvas.innerText = `当前时间:${hr}:${min}`;  // 时针  ctx.save();  ctx.rotate(    (Math.PI / 6) * hr + (Math.PI / 360) * min + (Math.PI / 21600) * sec,  );  ctx.lineWidth = 14;  ctx.beginPath();  ctx.moveTo(-20, 0);  ctx.lineTo(80, 0);  ctx.stroke();  ctx.restore();  // 分针  ctx.save();  ctx.rotate((Math.PI / 30) * min + (Math.PI / 1800) * sec);  ctx.lineWidth = 10;  ctx.beginPath();  ctx.moveTo(-28, 0);  ctx.lineTo(112, 0);  ctx.stroke();  ctx.restore();  // 秒针  ctx.save();  ctx.rotate((sec * Math.PI) / 30);  ctx.strokeStyle = "#D40000";  ctx.fillStyle = "#D40000";  ctx.lineWidth = 6;  ctx.beginPath();  ctx.moveTo(-30, 0);  ctx.lineTo(83, 0);  ctx.stroke();  ctx.beginPath();  ctx.arc(0, 0, 10, 0, Math.PI * 2, true);  ctx.fill();  ctx.beginPath();  ctx.arc(95, 0, 10, 0, Math.PI * 2, true);  ctx.stroke();  ctx.fillStyle = "rgb(0 0 0 / 0%)";  ctx.arc(0, 0, 3, 0, Math.PI * 2, true);  ctx.fill();  ctx.restore();  ctx.beginPath();  ctx.lineWidth = 14;  ctx.strokeStyle = "#325FA2";  ctx.arc(0, 0, 142, 0, Math.PI * 2, true);  ctx.stroke();  ctx.restore();  window.requestAnimationFrame(clock);}window.requestAnimationFrame(clock);

结果

备注:尽管时钟每秒只更新一次,但动画图像每秒更新 60 次(或者以你的 Web 浏览器的显示刷新率)。要使用扫描式的时钟,请将上面的const sec 定义替换为已注释的版本。

循环全景照片

在这个例子中,会有一个自左向右滑动的全景图。我们使用了在维基百科中找到的尤塞米提国家公园的图片,当然你可以随意找一张任何尺寸大于 canvas 的图片。

js
var img = new Image();// User Variables - customize these to change the image being scrolled, its// direction, and the speed.img.src = "capitan_meadows_yosemite_national_park.jpg";var CanvasXSize = 800;var CanvasYSize = 200;var speed = 30; // lower is fastervar scale = 1.05;var y = -4.5; // vertical offset// Main programvar dx = 0.75;var imgW;var imgH;var x = 0;var clearX;var clearY;var ctx;img.onload = function () {  imgW = img.width * scale;  imgH = img.height * scale;  if (imgW > CanvasXSize) {    // image larger than canvas    x = CanvasXSize - imgW;  }  if (imgW > CanvasXSize) {    // image width larger than canvas    clearX = imgW;  } else {    clearX = CanvasXSize;  }  if (imgH > CanvasYSize) {    // image height larger than canvas    clearY = imgH;  } else {    clearY = CanvasYSize;  }  // get canvas context  ctx = document.getElementById("canvas").getContext("2d");  // set refresh rate  return setInterval(draw, speed);};function draw() {  ctx.clearRect(0, 0, clearX, clearY); // clear the canvas  // if image is <= Canvas Size  if (imgW <= CanvasXSize) {    // reset, start from beginning    if (x > CanvasXSize) {      x = -imgW + x;    }    // draw additional image1    if (x > 0) {      ctx.drawImage(img, -imgW + x, y, imgW, imgH);    }    // draw additional image2    if (x - imgW > 0) {      ctx.drawImage(img, -imgW * 2 + x, y, imgW, imgH);    }  }  // image is > Canvas Size  else {    // reset, start from beginning    if (x > CanvasXSize) {      x = CanvasXSize - imgW;    }    // draw aditional image    if (x > CanvasXSize - imgW) {      ctx.drawImage(img, x - imgW + 1, y, imgW, imgH);    }  }  // draw image  ctx.drawImage(img, x, y, imgW, imgH);  // amount to move  x += dx;}
<canvas width="800" height="200"></canvas>

下方就是是图片在其中滑动的<canvas>。需要注意的是这里定义的 width 和 height 必须与 JavaScript 代码中的变量值CanvasXZSizeCanvasYSize保持一致。

html
<canvas width="800" height="200"></canvas>

鼠标追踪动画

html
<!doctype html><html lang="en">  <head>    <meta charset="UTF-8" />    <meta name="viewport" content="width=device-width, initial-scale=1.0" />    <meta http-equiv="X-UA-Compatible" content="ie=edge" />    <title>Document</title>    <script>      var cn;      //= document.getElementById('cw');      var c;      var u = 10;      const m = {        x: innerWidth / 2,        y: innerHeight / 2,      };      window.onmousemove = function (e) {        m.x = e.clientX;        m.y = e.clientY;      };      function gc() {        var s = "0123456789ABCDEF";        var c = "#";        for (var i = 0; i < 6; i++) {          c += s[Math.ceil(Math.random() * 15)];        }        return c;      }      var a = [];      window.onload = function myfunction() {        cn = document.getElementById("cw");        c = cn.getContext("2d");        for (var i = 0; i < 10; i++) {          var r = 30;          var x = Math.random() * (innerWidth - 2 * r) + r;          var y = Math.random() * (innerHeight - 2 * r) + r;          var t = new ob(            innerWidth / 2,            innerHeight / 2,            5,            "red",            Math.random() * 200 + 20,            2,          );          a.push(t);        }        //cn.style.backgroundColor = "#700bc8";        c.lineWidth = "2";        c.globalAlpha = 0.5;        resize();        anim();      };      window.onresize = function () {        resize();      };      function resize() {        cn.height = innerHeight;        cn.width = innerWidth;        for (var i = 0; i < 101; i++) {          var r = 30;          var x = Math.random() * (innerWidth - 2 * r) + r;          var y = Math.random() * (innerHeight - 2 * r) + r;          a[i] = new ob(            innerWidth / 2,            innerHeight / 2,            4,            gc(),            Math.random() * 200 + 20,            0.02,          );        }        //  a[0] = new ob(innerWidth / 2, innerHeight / 2, 40, "red", 0.05, 0.05);        //a[0].dr();      }      function ob(x, y, r, cc, o, s) {        this.x = x;        this.y = y;        this.r = r;        this.cc = cc;        this.theta = Math.random() * Math.PI * 2;        this.s = s;        this.o = o;        this.t = Math.random() * 150;        this.o = o;        this.dr = function () {          const ls = {            x: this.x,            y: this.y,          };          this.theta += this.s;          this.x = m.x + Math.cos(this.theta) * this.t;          this.y = m.y + Math.sin(this.theta) * this.t;          c.beginPath();          c.lineWidth = this.r;          c.strokeStyle = this.cc;          c.moveTo(ls.x, ls.y);          c.lineTo(this.x, this.y);          c.stroke();          c.closePath();        };      }      function anim() {        requestAnimationFrame(anim);        c.fillStyle = "rgba(0,0,0,0.05)";        c.fillRect(0, 0, cn.width, cn.height);        a.forEach(function (e, i) {          e.dr();        });      }    </script>    <style>      #cw {        position: fixed;        z-index: -1;      }      body {        margin: 0;        padding: 0;        background-color: rgba(0, 0, 0, 0.05);      }    </style>  </head>  <body>    <canvas></canvas>    qwerewr  </body></html>

Output

其他例子

Advanced animations

我们将在下一章看到一些先进的动画技术和物理现象。

Help improve MDN

Learn how to contribute.

This page was last modified on byMDN contributors.


[8]ページ先頭

©2009-2025 Movatter.jp