『 canvas 动画』为了让老婆彻底理解正弦和余弦,我连夜制作了这个效果

news2025/4/19 21:45:29

前言

最近在做 canvas 相关的效果时,经常用到三角函数以及正/余弦相关的数字知识,这些知识点都是我们在初中的时候就学过的,但是这么多年基本已经忘的差不多了,刚好最近又学到了一个用 canvas 来实现的正/余弦动画效果,今天就分享给大家。

我们还是先来看一下最终的实现效果,如下图所示:

通过上图可以看到,随着圆圈的转动,正弦余弦 不断的绘制,通过这个动画就能够很好的理解 正弦余弦 之间的区别,下面我们就一起来看一下这个效果是如何实现的吧!

旋转的圆

根据上面的动画可以明显的看出,正弦余弦 的绘制是跟随左侧的圆来的,因此咱们可以先将圆绘制出来,这样就能够获取到 正弦余弦 的所有点了。

首先我们还是先来编写相关的基础代码,依旧是采用 TS + ES6 来进行编写,当然也需要添加 canvas 标签和简单的 css 代码,这里对这两块内容不做编写,在文章的最后会放出完整的代码和实现效果。

下面我们就先一起来编写基础的代码,如下所示:

class Triangulation {canvas: HTMLCanvasElement;ctx: CanvasRenderingContext2D;arraySin: number[];arrayCos: number[];constructor() {this.canvas = document.getElementById('canvas') as HTMLCanvasElement;this.ctx = this.canvas.getContext('2d');this.canvas.width = 600;this.canvas.height = 400;this.arraySin = [];this.arrayCos = [];this.animate();}draw() {this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);}animate() {requestAnimationFrame(() => this.animate());this.draw();}
} 

还是老规矩,这里依旧是先准备一个 Triangulation 类,并添加相关的 drawanimate 方法,在 constructor 中我们定义了两个数组,分别是 arraySinarrayCos,它们是用来保存随着圆旋转而获得的 正弦余弦 的值。下面我们需要先绘制左边的圆圈,并且通过圆获取到对应的 正弦余弦

我们依旧是定义一个 Circle 类,并且在这个类中添加相关的方法,最后在 Triangulation 中进行调用来执行,下面我们一起来看 Circle 类是如何实现的,代码如下:

class Circle {deg: number;ctx: CanvasRenderingContext2D;arraySin: number[];arrayCos: number[];w: number;h: number;constructor(ctx: CanvasRenderingContext2D, arraySin: number[], arrayCos: number[]) {this.x = 100;this.y = 100;this.deg = 0;this.ctx = ctx;this.arraySin = arraySin;this.arrayCos = arrayCos;}update() {this.deg += 0.01;}draw() {let x = 80 * Math.cos(this.deg) + this.x;let y = 80 * Math.sin(this.deg) + this.y;// 绘制旋转的圆点this.ctx.beginPath();this.ctx.arc(x, y, 3, 0, Math.PI * 2);this.ctx.fillStyle = 'green';this.ctx.fill();// 绘制外层的圆环this.ctx.beginPath();this.ctx.arc(this.x, this.y, 80, 0, Math.PI * 2);this.ctx.strokeStyle = `green`;this.ctx.stroke();// 将绘制的点添加到数组中this.arraySin.push(y);this.arrayCos.push(x);}
} 

在上述的代码中,我们通过三角函数 Math.sin()Math.cos() 来获取圆弧的 x轴y轴,并通过 ctx.arc() 来绘制圆,最后把获取到的每一个 x轴y轴 的值添加到前面的 arraySinarrayCos 数组中,这样当圆圈不断转动时,这两个数组中就会包含所有的 正弦余弦 的值;当然,我们还需要在 Triangulation 类的 draw 方法中去执行 Circle 类的 updatedraw 方法。

我们一起来看一下 Triangulationdraw 方法发修改,代码如下:

class Triangulation {...other codecircle: Circle;constructor() {...other codethis.circle = new Circle(this.ctx, this.arraySin, this.arrayCos);this.animate();}draw() {this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);this.circle.update();this.circle.draw();}...other code
} 

可以看到在 Triangulation 类的 constructor 中,我们实例化了 Circle,然后在 draw 方法中调用了实例化 Circle 提供的相关方法,最后绘制出一个旋转的圆圈,如下所示:

到这里为止,我们已经实现了圆圈的旋转,并且在圆圈的旋转中,不断的获取到当前的 正弦余弦 的值,下面我们就需要通过获取到的 正弦余弦 的值将它们绘制出来,让我们继续来学习~

舞动的正弦与余弦

在前面我们已经在绘制圆的过程中,将 正弦余弦 的值添加到了 arraySinarrayCos 这两个数组中,接下来我们就需要使用这两个数组中的值来绘制相应的效果了,让我们一起来看代码,如下:

class Triangulation {...other codedraw() {this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);this.circle.update();this.circle.draw();// 根据数组中的数值绘制对应的曲线if (this.arraySin.length > 314 * 2 * 2) {this.arraySin.splice(0, 1);this.arrayCos.splice(0, 1);}// 遍历数组,根据值绘制相应的点for (let i in this.arraySin) {// 绘制正弦曲线this.ctx.beginPath();this.ctx.arc(200 + (+i) / 1.57, this.arraySin[this.arraySin.length - (+i)], 0.5, 0, Math.PI * 2);this.ctx.fillStyle = `hsl(${(+i) * 1.4}, 80%, 60%)`;this.ctx.fill();// 绘制余弦函数this.ctx.beginPath();this.ctx.arc(200 + (+i) / 1.57, 400 - this.arrayCos[this.arraySin.length - (+i)], 0.5, 0, Math.PI * 2);this.ctx.fillStyle = `hsl(${(+i) * .9}, 50%, 30%)`;this.ctx.fill();}}
} 

我们在 Triangulation 类的 draw 方法中,通过循环遍历 arraySin 中的值,从而绘制出每一个点,连起来后就成了一条完整的线,并且随着圆圈的转动线段也会跟着舞动起来,我们一起来看一下实现的效果,如图:

通过上图可以很好的看出,当圆旋转时,我们不断的将当前旋转的点添加到 arraySinarrayCos 数组中,然后通过循环这两个数组中的值,将它们绘制成线段,最后就实现了上述的效果。

做到这里,正弦余弦 已经实现了,但是一般人看到这里可能会一脸懵逼,确实光看这个效果,都不知道到底是在讲什么,因此我们还需要添加相关的辅助线,通过辅助线的帮助,最终就可以看出完整的效果。

辅助线

在前面也说,光看这两条线段在这里跳动,其实看不出这是 正弦余弦 ,因此我们需要添加相关的辅助线来帮助我们理解 正弦余弦,那么该在哪里添加呢?还记得我们的 正弦余弦 是怎么获得到对应的点的吗?其实就是在 Circle 类中通过不断旋转圆得到的,因此我们就需要在 Circle 类的 draw 方法中去添加相关的辅助线了,我们一起来看代码,如下:

class Circle {...other codew: number;h: number;constructor(ctx: CanvasRenderingContext2D, arraySin: number[], arrayCos: number[]) {...other codethis.w = 600;this.h = 400;}update() {this.deg += 0.01;}draw() {...other code// 添加辅助线this.ctx.moveTo(this.x, this.y);this.ctx.lineTo(x, y);this.ctx.stroke();this.ctx.strokeStyle = `hsl(${(x - 0.2) * 1.4}, 80%, 60%)`;this.ctx.beginPath();this.ctx.moveTo(x, y);this.ctx.lineTo(x, 200);// 添加正弦辅助线this.ctx.moveTo(x, y);this.ctx.lineTo(200, y);this.ctx.stroke();}
} 

首先我们添加 正弦 的辅助线,让 正弦 在绘制的过程中随着圆的旋转得到相应的点,实现的效果如下所示:

通过上图可以看到在圆的旋转过程中,正弦 的值跟着圆在不断的变化,当然现在这条辅助线还不完整,因此我们需要继续完善辅助线相关的代码,如下所示:

class Circle {...other codew: number;h: number;constructor(ctx: CanvasRenderingContext2D, arraySin: number[], arrayCos: number[]) {...other codethis.w = 600;this.h = 400;}update() {this.deg += 0.01;}draw() {...other code// 添加余弦辅助线this.ctx.moveTo(200, 300);this.ctx.lineTo(this.w, 300);this.ctx.moveTo(0, 200);this.ctx.lineTo(this.w, 200);this.ctx.moveTo(100, 100);this.ctx.lineTo(this.w, 100);this.ctx.moveTo(200, 0);this.ctx.lineTo(200, this.h);this.ctx.moveTo(100, 100);this.ctx.lineTo(100, 200);this.ctx.stroke();}
} 

通过添加更多的辅助线,我们可以得到更完整的实现效果,如下所示:

写到这里,正弦的绘制轨迹已经出来了,但是余弦的绘制却没有头,因此我们还需要添加最后的圆弧,用于余弦绘制时的跟随效果,代码如下:

class Circle {...other codew: number;h: number;constructor(ctx: CanvasRenderingContext2D, arraySin: number[], arrayCos: number[]) {...other codethis.w = 600;this.h = 400;}update() {this.deg += 0.01;}draw() {...other codethis.ctx.beginPath();this.ctx.arc(200, 200, 200 - x, Math.PI / 2, Math.PI);this.ctx.stroke();this.ctx.beginPath();this.ctx.arc(200, 200, 100, Math.PI / 2, Math.PI);this.ctx.stroke();}
} 

至此,我们通过简单的三角函数绘制了一个圆,并且通过获取到圆中的每一个点得出正弦余弦,并将它们绘制出来,最终的完整代码和实现效果可以在这里进行查看:

总结

通过三角函数相关的知识,从而获取到圆中的每一个点,而正弦余弦正是由圆圈中的每一个点组成的。当然,我老婆学会没有我不清楚,大家学会没就只有大家自己清楚了~

最后

为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

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

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

相关文章

成功转行Python工程师,年薪30W+,经验总结都在这

这是给转行做Python的小白的参考,无论是从零开始,或者是转行的朋友来说,这都是值得一看的,也是可以作为一种借鉴吧。 而且我决定转行IT(互联网)行业,其实理由也很简单,不用动体力&a…

循环冗余编码(CRC编码)与海明码(考研前突击一下QAQ)

循环冗余编码(CRC编码)与海明码 一.环冗余编码 1.循环冗余编码的形成 生成多项式:G1011 表示成生成多项式为G(x)X3X1X^3X1X3X1 示例: 假设信息字节为:F1001010 选取生成多项式(默认)G1011 将…

2022年下半年部分团队的总结

这是 2021 年年底的汇报。 这是 2022 年上半年的汇报。 踏石留印 抓铁有痕 CSDN 是中国 IT 人士学习,成长,成功的平台。除了一些创新的探索之外, 20 多年来,CSDN 团队为这个平台开发和维护着各种基本功能和服务,还进…

自动化测试技术笔记(一):前期调研怎么做

昨天下午在家整理书架,把很多看完的书清理打包好,预约了公益捐赠机构上门回收。 整理的过程中无意翻出了几年前的工作记事本,里面记录了很多我刚开始做自动化和性能测试时的笔记。虽然站在现在的角度来看,那个时候无论是技术细节…

“ 这片绿茵从不缺乏天才,努力才是最终的入场券——梅西 ”

前言 想了又想还是忍不住想发布一篇文章来纪念一下2022年的卡塔尔世界杯,这伟大的诸神黄昏之战。4年一届的世界杯像是一把衡量时间的坐标,正所谓青春不过几届世界杯!2014巴西世界杯在上初一,2018俄罗斯世界杯在上高二,…

如何成为一名合格的互联网大厂Python工程师?

Python开发工程师,是一个在IT行业圈子里一直都很热门的话题,无论是像腾讯、百度这样的大型公司,还是刚刚起步的初创公司,都会招python开发工程师。 python已成为越来越多开发者的开发语言选择, 而python开发工程师工资…

[附源码]计算机毕业设计Python架构的博客平台设计(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置: Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术: django python Vue 等等组成,B/S模式 pychram管理等…

【Python计算几何】德劳内三角剖分算法 | scatter 绘制散点图 | Dealunay 函数

猛戳!跟哥们一起玩蛇啊 👉 《一起玩蛇》🐍 💭 写在前面: 本章我们将介绍的是计算机和领域的 Delaunay 三角剖分算法(即德劳内三角剖分),它是一种用于将点集划分成三角形网格的算法。…

如何同时启动Android平台GB28181设备接入模块和轻量级RTSP服务模块?

技术背景 在介绍GB28181设备接入模块和轻量级RTSP服务之前,我们需要先搞清楚,二者的使用场景和技术设计的差别: 首先是GB28181设备接入模块: 为什么要设计GB28181设备接入模块?GB28181接入SDK,实现不具备…

软件:分享六款实用的软件,每一款值得收藏

❤️作者主页:IT技术分享社区 ❤️作者简介:大家好,我是IT技术分享社区的博主,从事C#、Java开发九年,对数据库、C#、Java、前端、运维、电脑技巧等经验丰富。 ❤️个人荣誉: 数据库领域优质创作者🏆&#x…

[XCTF]funny_video(难度2)

目录 一、题目重述 二、解题思路 1.分解音视频 2.处理音频 总结 前言 视频夹杂着一段音频,怎么提取?遇到一款新的工具!MKVToolNix 特此记录! 一、题目重述 一段视频,观看之后发现有一段还夹杂着音频。 XCTF-fu…

避坑指南!Python里面的这九个坑,坑的就是你

Python里面有一些坑,让你防不胜防,菜鸟经常会弄晕,而学习多年的Python老鸟也会时不时中招。小编整理了9个坑,都是会经常碰到的坑,让你大呼我曾经也碰到过! 虽然是小的问题,但是在实际的项目中,哪…

测出让人血压升高的页面崩溃,我是如何排查的

前情回顾 前几天在一次web应用测试过程中,前端发起了向后端接口的查询请求,由于后端响应较慢,前端一直处于等待响应返回状态。在几分钟后,突然页面出现让人惊悚的“噢噢,页面崩溃了”几个大字。 看到这几个字的一瞬间…

用于销售、报告等的 LearnDash Group Management LMS分组管理插件

目录 获取强大、直观的LearnDash LMS组管理和报告 使用 LearnDash Groups LMS分组管理插件进行 B2B 销售 节省设置分组的时间或让客户自己构建和购买! 获取强大、直观的LearnDash LMS组管理和报告 LearnDash分组是将学生组织成逻辑单元以进行报告和课程访问的绝…

java回顾:Maven高级

目录 一、私服搭建 二、Maven高级 2.1、依赖范围 2.2、依赖传递 2.3、依赖可选 2.4、依赖排除 2.5、依赖冲突 三、ssm工程改造成分层构建 3.1、maven的继承 3.2、继承的一些应用 3.3、maven的聚合(多模块开发) 一、私服搭建 https://blog.…

rust语句,表达式以及函数

语句和表达式 在rust里,语句和表达式的区别是非常重要的。语句没有返回值,表达式有返回值。例如: fn main() {let y {let x 3; // 赋值语句x 1 // 表达式};println!("The value of y is: {}", y); }上面使用一…

SQL | 自联接 Self Join

有时你可能需要获取位于同一个表中的相关数据。为此,你可以使用一种特殊的联接,称为自联接(Self Join)。在今天的文章中,我们将学习如何使用 Navicat Premium 作为数据库客户端编写包含自联接的查询。如果你没未使用过…

报表工具软件-FineReport JS实现下拉框自动展开

1. 概述 1.1 预期效果 在使用下拉框做筛选查询时,需要点击下拉框下拉三角才会展开所有选项,有些使用场景下,用户希望自动展开选项列表,尤其是在多个控件联动场景下。如下图所示: 地区控件选择地区后,销售…

新一代最强开源UI自动化测试神器Playwright(Java版)页面元素交互

Playwright 可以与 HTML 输入元素交互,例如文本输入、复选框、单选按钮、下拉选择框、鼠标点击、字符输入、模拟键盘事件以及上传文件和焦点元素。 Playwright 操作文本框 使用Locator.fill()是填写表单字段的最简单方法。input它聚焦元素并使用输入的文本触发事件…

JVM面试题详解系列——垃圾收集算法详解

垃圾收集算法 标记 - 清除算法 首先标记出所有需要被回收的对象,标记完后统一回收所有被标记的对象。 后续的收集算法都是基于这种思路并对其不足进行改进而得到的。 这种方法主要有两个缺点: 一个是效率问题,标记和清除两个过程的效率都…