HTML Canvas 动画

动画的基本步骤

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

  1. 清空 canvas
  2. 保存 canvas 状态
  3. 绘制动画图形
  4. 恢复 canvas 状态

操控动画

我们使用 canvas 提供的或自定义的方法来绘制内容,通常我们是在脚本执行结束后才能看到结果。因此,我们在 for 循环不太可能完成动画,我们只能看到动画的最后一帧。剪藏

为了实现动画,我们通常会使用 setInterval​ 和 setTimeout​ 方法来控制间隔多久绘制一次内容。

这个例子实现了一个动态时钟:

function clock(){
  var now = new Date();
  var ctx = document.getElementById('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 (var 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 (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();

  var sec = now.getSeconds();
  var min = now.getMinutes();
  var hr  = now.getHours();
  hr = hr>=12 ? hr-12 : hr;

  ctx.fillStyle = "black";

  // 绘制时针
  ctx.save();
  ctx.rotate( hr*(Math.PI/6) + (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 = "rgba(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);
}
//告诉浏览器使用 clock 方法绘制动画
window.requestAnimationFrame(clock);

执行结果:

image

高级动画

添加更加符合物理的运动让动画看起来更高级

绘制小球

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

var ball = {
  x: 100,
  y: 100,
  radius: 25,
  color: 'blue',
  draw: function() {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();
  }
};

ball.draw();

添加速率


function draw() {
  ctx.clearRect(0,0, canvas.width, canvas.height);
  ball.draw();
  //改变下一次绘制的小球坐标
  ball.x += ball.vx;
  ball.y += ball.vy;
  raf = window.requestAnimationFrame(draw);
}

边界

//添加边界
if (ball.y + ball.vy + ball.radius > canvas.height 
|| ball.y + ball.vy - ball.radius < 0) {
  ball.vy = -ball.vy;
}
if (ball.x + ball.vx + ball.radius > canvas.width 
|| ball.x + ball.vx - ball.radius < 0) {
  ball.vx = -ball.vx;
}

完整代码

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
let raf;

const ball = {
  x: 100,
  y: 100,
  vx: 5,
  vy: 2,
  radius: 25,
  color: "blue",
  draw() {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();
  },
};

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ball.draw();
  ball.x += ball.vx;
  ball.y += ball.vy;
  ball.vy *= 0.99;
  ball.vy += 0.25;

  if (ball.y + ball.vy + ball.radius > canvas.height 
  || ball.y + ball.vy - ball.radius < 0) {
    ball.vy = -ball.vy;
  }
  if (ball.x + ball.vx + ball.radius > canvas.width 
  || ball.x + ball.vx - ball.radius < 0) {
    ball.vx = -ball.vx;
  }

  raf = window.requestAnimationFrame(draw);
}

canvas.addEventListener("mouseover", (e) => {
  raf = window.requestAnimationFrame(draw);
});

canvas.addEventListener("mouseout", (e) => {
  window.cancelAnimationFrame(raf);
});

ball.draw();

像素操作

ImageData 对象

ImageData ​对象中存储这 canvas 对象真实的像素数据,包含以下几个只读属性

  • width​:图片宽度,单位是像素
  • height​:图片高度,单位是像素
  • data​:Unit8ClampedArray ​类型的一维数组,包含 RGBA 格式的整型数据,范围在 0 到 255 之间

data​ 属性返回的数组,它存储了初始像素数据。每个像素使用 4 个 1 bytes 的值(分别是红、绿、蓝和透明)来表示。每个部分的值用 0 到 255 表示。数据第 0 项到第 3 项存图片左上角第一个像素点的 RGBA 数据。然后依次从左往右存每一行的像素数据。

创建 ImageData 对象

我们使用 createImageData ​方法创建 ImageData ​对象,它有两个重载

  1. 传入具体的宽高创建对象,所有像素被预设为透明黑

    var myImageData = ctx.createImageData(width, height);
    
  2. 传入另一个 ImageData ​对象,同样所有像素被与设定为透明黑。这个重载并非复制图片数据

    var myImageData = ctx.createImageData(anotherImageData);
    

获取像素数据

我们可以使用 getImageData() ​方法获取一个矩形区域的 ImageData ​对象:

var myImageData = ctx.getImageData(left, top, width, height);

(left​,top​)是矩形左上角的坐标

如果我们选取的区域在画布之外,那么会返回一个透明黑色的 ImageData ​对象

写入像素数据

我们可以使用 putImageData() ​方法将像素数据写入到画布中:

ctx.putImageData(myImageData, dx, dy);

dx ​和 dy ​参数表示写入的画布坐标

缩放和反锯齿

我们使用drawImage方法缩放图片,imageSmoothingEnabled ​属性控制是否开启反锯齿(默认开启)。如果我们关闭反锯齿,将图形放大时,图片会出现锯齿(图 1)。开启反锯齿,图像会平滑缩放(图 2)

​​image

保存图片

我们可以使用以下方法保存图片:

canvas.toDataURL(type, encoderOptions);
  • type​(可选):图片格式,默认为 image/png
  • encoderOptions​(可选):在指定图片格式为 image/jpeg​ 或 image/webp​ 的情况下,可以从 0 到 1 的区间内选择图片的质量。

它返回一个包含图片展示的 Data URL

参考链接

基本的动画 - Web API 接口参考 | MDN (mozilla.org)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值