canvas变换

322 阅读3分钟

前言

课件地址

github.com/buglas/canv…

课堂目标

1.掌握上下文对象的状态的管理方法。

2.可以灵活使用变换操控图形。

知识点

1.上下文对象的状态管理

2.canvas 变换

1-状态管理

状态管理,管理的是上下文对象的状态。

上下文对象的状态就是上下文对象的属性。比如描边颜色,填充颜色,投影,线条样式,变换信息…

管理上下文状态的方法有两个:

  • 保存当前状态:save()
  • 恢复上一次保存的状态:restore()

一般在我们绘制具备同一种样式的图形时,都会用save() restore() 将其包裹起来。这是为了避免当前的图形样式影响以后所要绘制的的图形样式。

状态是可以嵌套的:

a - save()
    b - save()
    restore() – b
restore() – a

代码示例:

ctx.save();
ctx.fillStyle='green';
ctx.fillRect(50,50,400,200);
ctx.restore();

ctx.save();
ctx.fillStyle='red';
ctx.fillRect(50,300,400,200);
ctx.restore();

2-变换

变换的本质是对canvas 坐标系的操作。

变换有3个特性:

  • 移动: translate(x,y)

image-20220427215700783

代码示例:

ctx.fillStyle='green';
ctx.translate(100,100);
ctx.fillRect(100,100,400,200);
  • 旋转: rotate(angle)

image-20220427215719582

代码示例:

ctx.fillStyle='green';
ctx.rotate(Math.PI/24);
ctx.fillRect(100,100,400,200);
  • 缩放: scale(x,y)

image-20220427215734530

代码示例:

ctx.fillStyle='green';
ctx.scale(0.5,0.5);
ctx.fillRect(100,100,400,200);

3-矩阵变换

我们之前所说的translate(x,y)、rotate(angle)和 scale(x,y) 方法都是属于矩阵变换的。

除此之外canvas 还提供了两个完整的矩阵变换方法:

  • 相对变换矩阵:transform(a, b, c, d, e, f)
  • 绝对变换矩阵:setTransform(a, b, c, d, e, f)

image-20220428112633077

解释一下上面的参数:

  • a,d :x,y 轴向的缩放,默认为1
  • c,b :x,y 轴向的倾斜,默认为0
  • e,f :x,y 轴向的位移,默认为0

关于矩阵变换的算法,我们这里先不做详解,其中涉及的知识量有点大,之后咱们会再单独拿出一篇文章来说矩阵。

案例-钟表

image-20220428164535280

当前案例会用到两种变换:

  • 位移,将坐标原点移至canvas 画布中心。
  • 旋转,将x轴逆时针旋转90°,与钟表的零点对齐。

整体代码:

const canvas=document.getElementById('canvas');
canvas.width=window.innerWidth;
canvas.height=window.innerHeight;

const ctx=canvas.getContext('2d');

//色系
const [redA,redB,yellow]=['#db655c','#d63d46','#9f8d7d'];

//一圈的弧度
const c=Math.PI*2;

//canvas 宽高
const {width,height}=canvas;

//请求动画帧
!(function render(){
  ctx.clearRect(0,0,width,height);
  draw();
  requestAnimationFrame(render);
})()

function draw(){
  //保存状态
  ctx.save();

  //整体偏移
  ctx.translate(width/2,height/2);

  //整体旋转
  ctx.rotate(-Math.PI/2);

  // 钟表的表框-圆弧路径
  //内框 - 145,20
  //外框 - 155,9
  ctx.save();
  ctx.beginPath();
  ctx.arc(
    0,0,
    145,
    0,c
  );
  ctx.strokeStyle=redA;
  ctx.lineWidth=20;
  ctx.stroke();

  ctx.beginPath();
  ctx.arc(
    0,0,
    155,
    0,c
  );
  ctx.strokeStyle=redB;
  ctx.lineWidth=9;
  ctx.stroke();
  ctx.restore();

  //15分钟一个的刻度 - 直线 - 90,120,4,15
  ctx.save();
  ctx.beginPath();
  ctx.lineWidth=15;
  ctx.strokeStyle=redB;
  for(let i=0;i<4;i++){
    ctx.moveTo(90,0);
    ctx.lineTo(120,0);
    ctx.rotate(c/4);
  }
  ctx.stroke();
  ctx.restore();

  //5分钟一个的刻度 - 直线 - 90,120,12,6
  ctx.save();
  ctx.beginPath();
  ctx.lineWidth=6;
  ctx.strokeStyle=yellow;
  for(let i=0;i<12;i++){
    if(i%3){
      ctx.moveTo(90,0);
      ctx.lineTo(120,0);
    }
    ctx.rotate(c/12);
  }
  ctx.stroke();
  ctx.restore();

  //1分钟一个的刻度 - 直线 - 118,120,60,3
  ctx.save();
  ctx.beginPath();
  ctx.lineWidth=3;
  ctx.strokeStyle=yellow;
  for(let i=0;i<60;i++){
    if(i%5){
      ctx.moveTo(118,0);
      ctx.lineTo(120,0);
    }
    ctx.rotate(c/60);
  }
  ctx.stroke();
  ctx.restore();



  //基于当前时间获取时、分、秒针的弧度
  const {rh,rm,rs}=getRadian();

  // 时针 - 直线 - -20,65,9
  ctx.save();
  ctx.beginPath();
  ctx.lineWidth=9;
  ctx.strokeStyle=yellow;
  ctx.rotate(rh);
  ctx.moveTo(-20,0);
  ctx.lineTo(65,0);
  ctx.stroke();
  ctx.restore();

  // 分针 - 直线 - -28,80,4
  ctx.save();
  ctx.beginPath();
  ctx.lineWidth=4;
  ctx.strokeStyle=yellow;
  ctx.rotate(rm);
  ctx.moveTo(-28,0);
  ctx.lineTo(80,0);
  ctx.stroke();
  ctx.restore();

  // 秒针 - 直线 - -30,88,2
  ctx.save();
  ctx.beginPath();
  ctx.lineWidth=2;
  ctx.strokeStyle=redB;
  ctx.rotate(rs);
  ctx.moveTo(-30,0);
  ctx.lineTo(88,0);
  ctx.stroke();
  ctx.restore();

  //圆弧 10
  ctx.save();
  ctx.fillStyle=redB;
  ctx.beginPath();
  ctx.arc(0,0,10,0,c);
  ctx.fill();
  ctx.restore();

  //还原上一次save 的状态
  ctx.restore();
}

//基于当前时间获取时、分、秒针的弧度
function getRadian(){
  /*获取当前时间的时分秒*/
  //获取当前时间
  const date=new Date();
  //当前小时 getHours
  let h=date.getHours();
  if(h>12){h-=12}
  //当前分钟 getMinutes
  let m=date.getMinutes();
  //当前秒 getSeconds
  let s=date.getSeconds();

  /*基于时间比,计算指针在圆周中的弧度*/
  //时针旋转弧度
  const rh=c*h/12+c*m/12/60+c*s/12/60/60;
  //分针旋转弧度
  const rm=c*m/60+c*s/60/60;
  //秒针旋转弧度
  const rs=c*s/60;

  //返回三个弧度
  return {rh,rm,rs};
}