Canvas绘制老友记时钟

news2024/11/26 15:46:30

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1826065.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ 30 之 new 和 delete 关键字

#include <iostream> #include <string.h> using namespace std;class Students08{ public:Students08(){cout << "students08的默认构造函数"<< endl;}Students08(int a){cout << "students08的有参构造函数"<< endl…

永磁同步直线电机(PMLSM)控制与仿真1-永磁同步直线电机数学模型

文章目录 1、引言2、永磁同步直线电机数学模型2.1 直线电机的结构和工作原理2.2 永磁同步直线电机系统干扰分析2.2.1 齿槽效应2.2.2 端部效应 2.3 永磁同步直线电机的结构2.4 永磁同步直线电机的数学模型2.4.1 ABC坐标系下 PMLSM 的数学模型2.4.2 dq坐标系下 PMLSM 的数学模型2…

集成学习 Ensemble Learning

目录 一、集成学习概览1、介绍2、学习器3、boosting和bagging比较1、样本选择2、样例权重3、预测函数4、计算5、其他 4、结合策略 二、Adaboost1、介绍2、运行过程3、特点4、代码示例 三、随机森林1、介绍2、随机森林生成3、特点4、优缺点5、代码示例6、参数介绍 四、GBDT1、介…

使用python绘制三维散点图

使用python绘制三维散点图 三维散点图三维散点图的用途效果代码 三维散点图 三维散点图&#xff08;3D Scatter Plot&#xff09;是一种用于展示三维数据的图表。与二维散点图类似&#xff0c;三维散点图通过点在三维空间中的位置来表示数据点的三个特征。每个点在 x、y 和 z …

RTA_OS基础功能讲解 2.9-警报器

RTA_OS基础功能讲解 2.9-警报器 文章目录 RTA_OS基础功能讲解 2.9-警报器一、警报器简介二、警报器配置2.1 激活一个任务2.2 设置一个事件2.3 执行回调函数2.4 递增一个(软件)计数器三、警报器设置3.1 绝对警报3.1.1 单次触发3.1.2 周期触发3.1.3 在过去设置警报3.1.4 将绝对…

微信小程序04: 获取openId和unionId

全文目录,一步到位 1.前言简介1.1 专栏传送门1.1.1 上文小总结1.1.2 上文传送门 2. 获取openId和unionId操作2.1 准备工作2.1.1 请先复制00篇的统一封装代码2.1.2 微信登录请求dto 2.2 具体代码使用与注释如下2.2.1 业务代码2.2.2 代码解释(一)[无需复制]2.2.3 获取的map使用方…

【Python推导式秘籍】:一行代码的艺术,高效数据处理之道

文章目录 &#x1f68b;Python推导式&#x1f680;一、列表推导式&#x1f308;1. 了解推导式❤️2. 实践&#x1f4a5;3. 总结 &#x1f680;二、字典推导式&#x1f308;1. 了解字典推导式❤️2. 实践&#x1f4a5;3. 总结 &#x1f680;三、集合推导式&#x1f308;1. 了解集…

Qwen2的各模型性能、占用显存和推理速度比较(摘自官方文档)

Qwen2的各模型性能、占用显存和推理速度比较&#xff08;摘自官方文档&#xff09; 性能 推理速度&#xff08;从大到小&#xff09; 72B 57B-A14B 7B 1.5B 0.5B

目标检测数据集 - PCB板表面缺陷检测数据集下载「包含VOC、COCO、YOLO三种格式」

数据集介绍&#xff1a;PCB 板表面缺陷检测数据集&#xff0c;真实采集高质量 PCB 板表面含缺陷图片数据&#xff0c;数据集含多款不同 PCB 板高清表面图片数据&#xff0c;包括俯拍正拍、旋转拍摄姿态。数据标注标签包括 missing_hole、mouse_bite、open_circuit、short、spur…

【ARM-Linux篇】阿里云人脸识别方案

一、接入阿里云 https://vision.aliyun.com/ 点击“人脸搜索1:N” 点击"立即开通"&#xff1a; 使用阿里云APP/支付宝/钉钉扫码登录&#xff1a; 购买“人脸搜索1:N”能力&#xff0c;第一次购买&#xff0c;可以有5000次的免费使用&#xff1a; 开通完后&#xff…

C++ bfS

岛屿的最大面积 . - 力扣&#xff08;LeetCode&#xff09; 1.刚开始mn又加了int 2.bfs里符合条件了&#xff0c;不push&#xff0c;&#xff0c;&#xff0c;在写什么几把 class Solution { public:int dx[4] {0, 0, 1, -1};int dy[4] {1, -1, 0, 0};bool vis[50][50];int…

学习笔记——网络管理与运维——SNMP(SNMP版本)

二、SNMP版本 1、SNMP版本 SNMP共有三个版本&#xff1a;SNMPv1、SNMPv2c和SNMPv3。 (1)SNMPv1 1990年5月&#xff0c;RFC1157定义了SNMP的第一个版本SNMPv1。RFC1157提供了一种监口控和管理计算机网络的系统方法。SNMPv1基于团体名认证&#xff0c;安全性较差&#xff0c;…

宠物健康顾问系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;顾问管理&#xff0c;用户管理&#xff0c;健康知识管理&#xff0c;管理员管理&#xff0c;论坛管理&#xff0c;公告管理 顾问账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;顾…

长亭网络通信基础

长亭笔试之前就已经学过一遍了 这算温故而知新吧 TCP/IP 首先我在这里默写一下之前的7层和4层 应用层 应 【表示层 数据格式转换 传 【会话层 …

Docker部署常见应用之SFTP服务器

文章目录 简介Dockers部署单用户多用户用户信息加密使用SSH密钥认证 参考文章 简介 atmoz/sftp 是一个基于 Docker 的 SFTP 服务镜像&#xff0c;它使用 OpenSSH 来提供 SFTP 服务。这个镜像支持创建单个或多个用户的 SFTP 访问&#xff0c;并允许用户通过 SFTP 协议安全地共享…

通信协议—Modbus

1、modbus简介 Modbus服务器&#xff1a;接收处理来自客户端的请求&#xff0c;并返回相应的响应&#xff1b; Modbus客户端&#xff1a;向Modbus服务器发送请求&#xff0c;并接收服务器返回的响应的设备或程序&#xff1b; 2、modbus poll调试工具下载 modbus poll用于测…

2024 JavaScript笔记(精简版)

系列文章目录 文章目录 系列文章目录第一章 JavaScript简介&#xff1a;1.1 特点&#xff1a;1.2 JavaScript与Java的区别1.3 JavaScript不能做什么1.4 JavaScript组成 第二章 JavaScript必备基础知识JavaScript代码调试方式 一、变量2.1.1 变量的命名规则&#xff1a;2.1.2 变…

03-appium环境配置和启动参数设置

参考文章&#xff1a;https://blog.csdn.net/lovedingd/article/details/110949993 一、appium介绍 Appium是一个开源、跨平台的自动化测试框架&#xff0c;支持Android、IOS等平台&#xff0c;同时也支持多语言&#xff0c;比如&#xff1a;Java、Python等。 Appiumu通过扩展…

【内存管理之C语言数组】

1.栈空间上的C数组 糟糕的可用性&#xff0c;但是你将在遗留代码中见到它们 相同类型的对象的内存块 大小必须是常量表达式 第一个元素索引为0 2.指针和C数组 更奇怪的是&#xff1a;数组标识符退化为指向第一个元素的指针 3.访问数组 4.堆空间上的C数组 相同类型的对象的内…

transformer模型首次体验代码

前言 首先是安装python&#xff0c;更新pip源到清华源。安装transformer pip install transformer安装jupyter lab&#xff0c;也简单一行 pip install jupyterlab现在不想用anaconda了&#xff0c;因为国内没有源了&#xff0c;国外的又慢。直接用pip吧。 然后开始体验之旅…