天天看点

canvas换图时候会闪烁_详解Canvas动画部分

作者:Army-海军

https://blog.csdn.net/weixin_41229588/article/details/107016224

状态的保存和恢复

save()

保存画布(canvas)的所有状态

restore()

    save 和 restore 方法是用来保存和恢复 canvas 状态的,都没有参数。Canvas 的状态就是当前画面应用的所有样式和变形的一个快照。

    Canvas状态存储在栈中,每当save()方法被调用后,当前的状态就被推送到栈中保存。一个绘画状态包括:

  • 当前应用的变形(即移动,旋转和缩放,见下)。
  • 以及下面这些属性:strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, lineDashOffset, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled
  • 当前的裁切路径(clipping path)

    你可以调用任意多次 save方法。每一次调用 restore 方法,上一个保存的状态就从栈中弹出,所有设定都恢复。

save 和 restore 的应用例子

function draw() {  var ctx = document.getElementById('canvas').getContext('2d');  ctx.fillRect(0,0,150,150);   // 使用默认设置绘制一个矩形  ctx.save();                  // 保存默认状态  ctx.fillStyle = '#09F'       // 在原有配置基础上对颜色做改变  ctx.fillRect(15,15,120,120); // 使用新的设置绘制一个矩形  ctx.save();                  // 保存当前状态  ctx.fillStyle = '#FFF'       // 再次改变颜色配置  ctx.globalAlpha = 0.5;      ctx.fillRect(30,30,90,90);   // 使用新的配置绘制一个矩形  ctx.restore();               // 重新加载之前的颜色状态  ctx.fillRect(45,45,60,60);   // 使用上一次的配置绘制一个矩形  ctx.restore();               // 加载默认颜色配置  ctx.fillRect(60,60,30,30);   // 使用加载的配置绘制一个矩形}
           
canvas换图时候会闪烁_详解Canvas动画部分

    第一步是用默认设置画一个大四方形,然后保存一下状态。改变填充颜色画第二个小一点的蓝色四方形,然后再保存一下状态。再次改变填充颜色绘制更小一点的半透明的白色四方形。

    一旦我们调用 restore,状态栈中最后的状态会弹出,并恢复所有设置。如果不是之前用 save 保存了状态,那么我们就需要手动改变设置来回到前一个状态,这个对于两三个属性的时候还是适用的,一旦多了,我们的代码将会猛涨。

    当第二次调用 restore 时,已经恢复到最初的状态,因此最后是再一次绘制出一个黑色的四方形。

translate移动

translate 方法,它用来移动 canvas 和它的原点到一个不同的位置。

translate(x, y)

translate 方法接受两个参数。x 是左右偏移量,y 是上下偏移量

canvas换图时候会闪烁_详解Canvas动画部分

    例子 平移之后画个圆,然后恢复状态,再平移再画个圆,再恢复状态,每次都画在原点上,每次原点都不在一个位置上

function draw3() {  var ctx = document.getElementById('canvas').getContext('2d');  for (var i = 0; i < 3; i++) {    for (var j = 0; j < 3; j++) {      ctx.save();      ctx.fillStyle = 'rgb(' + (51 * i) + ', ' + (255 - 51 * i) + ', 255)';      ctx.translate(10 + j * 50, 10 + i * 50);      ctx.beginPath();      ctx.arc(0, 0, 10, 0, Math.PI*2, true);      ctx.fill();      ctx.restore();    }  }}
           
canvas换图时候会闪烁_详解Canvas动画部分

旋转Rotating

rotate 方法,它用于以原点为中心旋转 canvas

rotate(angle)

    这个方法只接受一个参数:旋转的角度(angle),它是顺时针方向的,以弧度为单位的值。

    旋转的中心点始终是 canvas 的原点,如果要改变它,我们需要用到 translate 方法。

canvas换图时候会闪烁_详解Canvas动画部分

    来个例子,记住是坐标系旋转,想问题的时候要想清楚  此案例先把中心移到了圆的中心

function draw() {  var ctx = document.getElementById('canvas').getContext('2d');  ctx.translate(75,75);  for (var i=1;i<6;i++){ // Loop through rings (from inside to out)    ctx.save();    ctx.fillStyle = 'rgb('+(51*i)+','+(255-51*i)+',255)';    for (var j=0;j6;j++){       ctx.rotate(Math.PI*2/(i*6));      ctx.beginPath();      ctx.arc(0,i*12.5,5,0,Math.PI*2,true);      ctx.fill();    }    ctx.restore();  }}
           
canvas换图时候会闪烁_详解Canvas动画部分
canvas换图时候会闪烁_详解Canvas动画部分

缩放Scaling

    我们用它来增减图形在 canvas 中的像素数目,对形状,位图进行缩小或者放大

    scale(x, y)

scale  方法可以缩放画布的水平和垂直的单位。两个参数都是实数,可以为负数,x 为水平缩放因子,y 为垂直缩放因子,如果比1小,会比缩放图形, 如果比1大会放大图形。默认值为1, 为实际大小。

    画布初始情况下, 是以左上角坐标为原点的第一象限。如果参数为负实数, 相当于以x 或 y轴作为对称轴镜像反转(例如, 使用translate(0,canvas.height); scale(1,-1); 以y轴作为对称轴镜像反转, 就可得到著名的笛卡尔坐标系,左下角为原点)。

    默认情况下,canvas 的 1 个单位为 1 个像素。举例说,如果我们设置缩放因子是 0.5,1 个单位就变成对应 0.5 个像素,这样绘制出来的形状就会是原先的一半。同理,设置为 2.0 时,1 个单位就对应变成了 2 像素,绘制的结果就是图形放大了 2 倍。

scale 的例子

function draw6() {  var ctx = document.getElementById('canvas').getContext('2d');  ctx.save();  ctx.scale(10, 3);  ctx.fillRect(1, 10, 10, 10);  ctx.restore();  // 镜像x轴  ctx.scale(-1, 1);  ctx.font = '48px serif';  ctx.fillText('海军', -135, 120);}
           
canvas换图时候会闪烁_详解Canvas动画部分

图形相互交叉显示规则

ctx.globalCompositeOperation = type
           

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

source-over

这是默认设置,并在现有画布上下文之上绘制新图形。

canvas换图时候会闪烁_详解Canvas动画部分

source-in

新图形只在新图形和目标画布重叠的地方绘制。其他的都是透明的。

canvas换图时候会闪烁_详解Canvas动画部分

source-out

在不与现有画布内容重叠的地方绘制新图形。

canvas换图时候会闪烁_详解Canvas动画部分

source-atop

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

canvas换图时候会闪烁_详解Canvas动画部分

destination-over

在现有的画布内容后面绘制新的图形。

canvas换图时候会闪烁_详解Canvas动画部分

destination-in

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

canvas换图时候会闪烁_详解Canvas动画部分

destination-out

现有内容保持在新图形不重叠的地方。

canvas换图时候会闪烁_详解Canvas动画部分

destination-atop

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

canvas换图时候会闪烁_详解Canvas动画部分

lighter

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

canvas换图时候会闪烁_详解Canvas动画部分

copy

只显示新图形。

canvas换图时候会闪烁_详解Canvas动画部分

xor

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

canvas换图时候会闪烁_详解Canvas动画部分

multiply

将顶层像素与底层相应像素相乘,结果是一幅更黑暗的图片。

canvas换图时候会闪烁_详解Canvas动画部分

screen

像素被倒转,相乘,再倒转,结果是一幅更明亮的图片。

canvas换图时候会闪烁_详解Canvas动画部分

overlay

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

canvas换图时候会闪烁_详解Canvas动画部分

darken

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

canvas换图时候会闪烁_详解Canvas动画部分

lighten

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

canvas换图时候会闪烁_详解Canvas动画部分

color-dodge

将底层除以顶层的反置。

canvas换图时候会闪烁_详解Canvas动画部分

color-burn

将反置的底层除以顶层,然后将结果反过来。

canvas换图时候会闪烁_详解Canvas动画部分

hard-light

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

canvas换图时候会闪烁_详解Canvas动画部分

soft-light

用顶层减去底层或者相反来得到一个正值。

canvas换图时候会闪烁_详解Canvas动画部分

difference

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

canvas换图时候会闪烁_详解Canvas动画部分

exclusion

和difference相似,但对比度较低。

canvas换图时候会闪烁_详解Canvas动画部分

hue

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

canvas换图时候会闪烁_详解Canvas动画部分

saturation

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

canvas换图时候会闪烁_详解Canvas动画部分

color

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

canvas换图时候会闪烁_详解Canvas动画部分

luminosity

保持底层的色调(hue)和色度(chroma),同时采用顶层的亮度(luma)。

canvas换图时候会闪烁_详解Canvas动画部分

裁切路径

canvas换图时候会闪烁_详解Canvas动画部分

    裁切路径和普通的 canvas 图形差不多,不同的是它的作用是遮罩,用来隐藏不需要的部分。如上图所示。红边五角星就是裁切路径,所有在路径以外的部分都不会在 canvas 上绘制出来。

    与globalCompositeOperation 属性作一比较,它可以实现与 source-in 和 source-atop差不多的效果。最重要的区别是裁切路径不会在 canvas 上绘制东西,而且它永远不受新图形的影响。这些特性使得它在特定区域里绘制图形时相当好用。

     clip()方法

    将当前正在构建的路径转换为当前的裁剪路径。

    看案例

function draw() {  var ctx = document.getElementById('canvas').getContext('2d');  ctx.fillRect(0,0,150,150);  ctx.translate(75,75);  // Create a circular clipping path  ctx.beginPath();  ctx.arc(0,0,60,0,Math.PI*2,true);  ctx.clip();  // draw background  var lingrad = ctx.createLinearGradient(0,-75,0,75);  lingrad.addColorStop(0, '#232256');  lingrad.addColorStop(1, '#143778');  ctx.fillStyle = lingrad;  ctx.fillRect(-75,-75,150,150);  // draw stars  for (var j=1;j<50;j++){    ctx.save();    ctx.fillStyle = '#fff';    ctx.translate(75-Math.floor(Math.random()*150),                  75-Math.floor(Math.random()*150));    drawStar(ctx,Math.floor(Math.random()*4)+2);    ctx.restore();  }}function drawStar(ctx,r){  ctx.save();  ctx.beginPath()  ctx.moveTo(r,0);  for (var i=0;i<9;i++){    ctx.rotate(Math.PI/5);    if(i%2 == 0) {      ctx.lineTo((r/0.525731)*0.200811,0);    } else {      ctx.lineTo(r,0);    }  }  ctx.closePath();  ctx.fill();  ctx.restore();}
           

    用了一个圆形的裁切路径来限制随机星星的绘制区域。

    首先,画了一个与 canvas 一样大小的黑色方形作为背景,然后移动原点至中心点。然后用 clip 方法创建一个弧形的裁切路径。裁切路径也属于 canvas 状态的一部分,可以被保存起来。如果我们在创建新裁切路径时想保留原来的裁切路径,我们需要做的就是保存一下 canvas 的状态。

    裁切路径创建之后所有出现在它里面的东西才会画出来。在画线性渐变时我们就会注意到这点。然后会绘制出50 颗随机位置分布(经过缩放)的星星,当然也只有在裁切路径里面的星星才会绘制出来。

canvas换图时候会闪烁_详解Canvas动画部分

动画基本步骤

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

  1. 清空 canvas

    除非接下来要画的内容会完全充满 canvas (例如背景图),否则你需要清空所有。

    最简单的做法就是用 clearRect 方法。

  2. 保存 canvas 状态

    如果你要改变一些会改变 canvas 状态的设置(样式,变形之类的),又要在每画一帧之时都是原始状态的话,你需要先保存一下。

  3. 绘制动画图形(animated shapes)

    这一步才是重绘动画帧。(重绘是相当费时的,而且性能很依赖于电脑的速度。)

  4. 恢复 canvas 状态

    如果已经保存了 canvas 的状态,可以先恢复它,然后重绘下一帧。

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

requestAnimationFrame(draw)

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

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

    简答的案例

function draw9() {  var ctx = document.getElementById('canvas').getContext('2d');         ctx.globalCompositeOperation = 'destination-over';  ctx.clearRect(0,0,500,500); // clear canvas  ctx.fillStyle = 'green';  ctx.strokeStyle = 'blueviolet';  ctx.save();  ctx.translate(180,180);  ctx.beginPath();  ctx.arc(0,0,150,0,Math.PI*2,false);  ctx.stroke();  var time = new Date();  ctx.rotate( ((2*Math.PI)/60)*time.getSeconds() + ((2*Math.PI)/60000)*time.getMilliseconds() )  ctx.beginPath();  ctx.arc(0,150,20,0,Math.PI*2,false);  ctx.fill();  ctx.closePath();  ctx.restore();  window.requestAnimationFrame(draw9)}window.requestAnimationFrame(draw9);
           
canvas换图时候会闪烁_详解Canvas动画部分

案例2模拟太阳系

var sun = new Image();var moon = new Image();var earth = new Image();function init(){  sun.src = 'https://mdn.mozillademos.org/files/1456/Canvas_sun.png';  moon.src = 'https://mdn.mozillademos.org/files/1443/Canvas_moon.png';  earth.src = 'https://mdn.mozillademos.org/files/1429/Canvas_earth.png';  window.requestAnimationFrame(draw);}function draw() {  var ctx = document.getElementById('canvas').getContext('2d');  ctx.globalCompositeOperation = 'destination-over';  ctx.clearRect(0,0,300,300); // clear canvas  ctx.fillStyle = 'rgba(0,0,0,0.4)';  ctx.strokeStyle = 'rgba(0,153,255,0.4)';  ctx.save();  ctx.translate(150,150);  // Earth  var 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,50,24); // Shadow  ctx.drawImage(earth,-12,-12);  // Moon  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); // Earth orbit  ctx.stroke();  ctx.drawImage(sun,0,0,300,300);  window.requestAnimationFrame(draw);}init();
           
canvas换图时候会闪烁_详解Canvas动画部分

案例3动画时钟

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";  // Hour marks  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();  // Minute marks  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";  // write Hours  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();  // write Minutes  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();  // Write seconds  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);}window.requestAnimationFrame(clock);
           
canvas换图时候会闪烁_详解Canvas动画部分

    关于动画要了解的可不止这些,包括速率,加速度,边界,拖尾效果,鼠标控制等等

举个栗子

var canvas = document.getElementById('canvas');var ctx = canvas.getContext('2d');var raf;var running = false;var ball = {  x: 100,  y: 100,  vx: 5,  vy: 1,  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();  }};function clear() {  ctx.fillStyle = 'rgba(255,255,255,0.3)';  ctx.fillRect(0,0,canvas.width,canvas.height);}function draw() {  clear();  ball.draw();  ball.x += ball.vx;  ball.y += ball.vy;  if (ball.y + ball.vy > canvas.height || ball.y + ball.vy < 0) {    ball.vy = -ball.vy;  }  if (ball.x + ball.vx > canvas.width || ball.x + ball.vx < 0) {    ball.vx = -ball.vx;  }  raf = window.requestAnimationFrame(draw);}canvas.addEventListener('mousemove', function(e){  if (!running) {    clear();    ball.x = e.offsetX;    ball.y = e.offsetY;    ball.draw();  }});canvas.addEventListener('click',function(e){  if (!running) {    raf = window.requestAnimationFrame(draw);    running = true;  }});canvas.addEventListener('mouseout', function(e){  window.cancelAnimationFrame(raf);  running = false;});ball.draw();
           

用你的鼠标移动小球,点击可以释放。

canvas换图时候会闪烁_详解Canvas动画部分

canvas相关的动画js框架

 Three.js

    这个流行的库提供了非常多的3D显示功能,以一种直观的方式使用 WebGL。这个库提供了、 、CSS3D 和 WebGL渲染器,让我们在设备和浏览器之间创建丰富的交互体验。该库于2010年4月首次推出

pixi.js

    虽然只是个渲染框架而非游戏引擎,但是pixi.js挺适合写游戏的,你可以看下netent上的博彩游戏,上万款游戏全是pixi.js写的。pixi.js刚接触的时候觉得很简陋,文档都是英文的,有时候还需要看源码(源码写的非常的优秀)才能理解怎么回事,这对很多人是个很高的门槛,但是如果你能克服这些,会发现用着真的很舒服。

    周边插件也挺丰富的,动画(spine/dragonbones),粒子系统,物理引擎等等,想用什么就引入什么,和unix设计哲学一样,每个工具只干一件事并把这件事做好,你可以组合很多工具完成复杂的功能。

canvas换图时候会闪烁_详解Canvas动画部分

更多前端分享,请关注:

前端路人甲

继续阅读