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. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-06-15 21:04:04       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-06-15 21:04:04       100 阅读
  3. 在Django里面运行非项目文件

    2024-06-15 21:04:04       82 阅读
  4. Python语言-面向对象

    2024-06-15 21:04:04       91 阅读

热门阅读

  1. C++day4

    C++day4

    2024-06-15 21:04:04      27 阅读
  2. elasticsearch结构化搜索

    2024-06-15 21:04:04       36 阅读
  3. Windows环境下JDK安装及环境变量配置指南

    2024-06-15 21:04:04       30 阅读
  4. LeetCode //MySQL - 177. Nth Highest Salary

    2024-06-15 21:04:04       32 阅读
  5. 【什么是几度cms,主要功能有什么】

    2024-06-15 21:04:04       27 阅读
  6. php中配置variables_order详解

    2024-06-15 21:04:04       33 阅读
  7. React中Hooks--useEffect | useState | useCallback | useMemo

    2024-06-15 21:04:04       21 阅读
  8. 八股系列 Flink

    2024-06-15 21:04:04       22 阅读