Canvas绘制老友记时钟

Canvas绘制老友记时钟

前言

一直做3D/2D可视化,Canvas API和三角函数,空间几何是基础。在官网上看了一遍Canvas API之后,决定绘制一个老友记时钟来巩固知识点,本文用实际代码讲解绘制过程。

在这里插入图片描述

代码

HTML

<canvas id="myCanvas" width="300" height="300"></canvas>

Javascript

const canvas = document.getElementById("myCanvas");
const bgImage = new Image();
const ctx = canvas.getContext("2d");

bgImage.src = "https://thumbnail1.baidupcs.com/thumbnail/cc3e81310m71ea6cdb2c5755bda0dc0c?fid=1099650259173-250528-406088947420032&rt=pr&sign=FDTAER-DCb740ccc5511e5e8fedcff06b081203-VVyEw%2bgld39Yr5Tjp%2f1KbwKqa4M%3d&expires=8h&chkbd=0&chkv=0&dp-logid=409019635711974061&dp-callid=0&time=1718445600&size=c1512_u982&quality=90&vuk=1099650259173&ft=image&autopolicy=1";

// 时钟半径
const r = 100

bgImage.onload = function () {
  render();
  setInterval(function(){
    render();
  }, 1000);
  
  // 每一帧都先用canvas.clearRect(x,y,w,h)擦掉画布上的像素,否则会造成当前像素和之前的像素叠加的问题。将画布的原点移到画布的中心,有助于绘制刻度和以中心为基点旋转的指针,在之前得保存平移之前的环境状态。
  function render() {
    drawClockBackGround();
    drawHourTicks();
    drawTime();
    ctx.restore();
  }
  
  // 时针、分针、秒针的做法是一致的,使用canvas.rotate()绕原点旋转,旋转之前都要canvas.save()保存当前状态(指针的每一帧动作都是让画布旋转特定的角度,所以画完一次要摆正一次画布,否则秒针旋转一次,分针会在此基础上旋转)
  function drawTime(){
    var now = new Date();
    h = now.getHours();
    m = now.getMinutes();
    s = now.getSeconds();
    
    ctx.strokeStyle = 'black';
    
    drawHour(h,m);
    drawMinute(m,s);
    drawSecond(s);
  }
  
  function drawHour(h, m) {
    const hour = h + m/60;
    ctx.save();
    ctx.beginPath();
    ctx.rotate(hour * 2 * Math.PI / 12); // 时针旋转一周是12小时
    ctx.lineWidth = 4;
    ctx.moveTo(0, 0.2 * 0.4 * r);
    ctx.lineTo(0, -0.8 * 0.4 * r);
    ctx.stroke();
    ctx.closePath();
    ctx.restore();
  }
  
  function drawMinute(m, s) {
    const minute = m + s/60;
    ctx.save();
    ctx.beginPath();
    ctx.rotate(minute * 2 * Math.PI / 60); // 分针旋转一周是60分钟
    ctx.lineWidth = 2;
    ctx.moveTo(0, 0.2 * 0.6 * r);
    ctx.lineTo(0, -0.8 * 0.6 * r);
    ctx.stroke();
    ctx.closePath();
    ctx.restore();
  }
  
  function drawSecond(s) {
    ctx.save();
    ctx.beginPath();
    ctx.rotate(s * 2 * Math.PI / 60); // 秒针旋转一周是60秒
    ctx.lineWidth = 2;
    ctx.moveTo(0, 0.2 * 0.8 * r);
    ctx.lineTo(0, -0.8 * 0.8 * r);
    ctx.stroke();
    ctx.closePath();
    ctx.restore();
  }
  
  function drawHourTicks() {
    const hourTickLength = 5; // 刻度的长度
    const hourTickColor = "yellow";
    const gap = 10; // 刻度起始位置距离表盘边缘的间隔
    for (let i = 0; i * Math.PI / 6 < 2 * Math.PI; i ++) {
      const angle = i * Math.PI / 6;
      ctx.beginPath();
      ctx.moveTo((r - gap) * Math.cos(angle), (r - gap) * Math.sin(angle));
      ctx.lineTo((r - gap + hourTickLength) * Math.cos(angle), (r - gap + hourTickLength) * Math.sin(angle));
      ctx.strokeStyle = hourTickColor;
      ctx.stroke();
      ctx.closePath();
    }
  }
  
  function drawClockBackGround() {
    // 清除canvas  
    ctx.clearRect(0, 0, canvas.width, canvas.height);  
    ctx.save();
    
    // 将坐标系原点平移到画布中心位置
    ctx.translate(canvas.width / 2, canvas.height / 2);

    // 绘制圆形遮罩(实际上绘制一个圆形,并用白色填充)  
    ctx.beginPath();
    ctx.arc(0, 0, r, 0, Math.PI * 2);
    ctx.closePath();
    
    // 表盘背景图片
    drawBGImage();
    
    // 指针交汇处的圆形
    ctx.beginPath();
    ctx.fillStyle = 'black';
    ctx.arc(0, 0, 5, 0, Math.PI * 2, false);
    ctx.fill();
    ctx.closePath();
    
    // 描边表盘轮廓
    ctx.beginPath();
    ctx.lineWidth = 2;
    ctx.arc(0, 0, r, 0, Math.PI * 2);
    ctx.strokeStyle = 'black';  
    ctx.stroke();
    ctx.closePath();
    
  }
  
  function drawBGImage() {
    ctx.fillStyle = 'white'; // 遮罩颜色,通常与背景色相同  
    ctx.fill();  

    // 设置globalCompositeOperation为'source-in',这样接下来的绘制只会在遮罩区域内显示  
    ctx.globalCompositeOperation = 'source-in';  

    // 绘制背景图片,它现在只会在圆形区域内显示
    const scale = 1;  
    const scaledWidth = bgImage.width * scale;  
    const scaledHeight = bgImage.height * scale;  
    ctx.drawImage(bgImage, 0, 0, scaledWidth, scaledHeight, - canvas.width / 2, - canvas.height / 2, canvas.width, canvas.height);
    // 重置globalCompositeOperation以便后续绘制不受影响  
    ctx.globalCompositeOperation = 'source-over';
  }  
};


效果

在这里插入图片描述

链接

在线演练请参考: CodePen

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值