【HTML】情人节给npy一颗炫酷的爱心

闲谈

兄弟们,这不情人节快要到了,我该送女朋友什么🎁呢?哦,对了,差点忘了,我好像没有女朋友。
image.pngimage.png
不过这不影响我们要过这个节日,我们可以学习技术。举个简单的🌰: 比如说,今天我们学习了如何画一颗炫酷的💗,以后找到了女朋友忘准备礼物了,是不是可以用这个救救场,🐶。

开干

首先,我们需要画一个💗的形状出来,例如下面这样
image.png
这个简单,我们通过豆包搜一波公式即可。公式如下:

x ( t ) = 16 sin ⁡ 3 ( t ) x(t) = 16\sin^3(t) x(t)=16sin3(t)
y ( t ) = 13 c o s ( t ) − 5 c o s ( 2 t ) − 2 cos ⁡ ( 3 t ) − c o s ( 4 t ) y(t) = 13cos(t) - 5cos(2t) - 2\cos(3t) - cos(4t) y(t)=13cos(t)5cos(2t)2cos(3t)cos(4t)


思路: 利用上面的公式,我们只需要根据许许多多的t去求得x,y的坐标,然后将这些点画出来即可。
使用Canvas时用到的一些函数解释,这里moveTolineTo还是有点上头的:

// 获取到一个绘图环境对象,这个对象提供了丰富的API来执行各种图形绘制和图像处理操作
ctx = canvas.getContext('2d');
/** 
该方法用于在当前路径上从当前点画一条直线到指定的 (x, y) 坐标。
当调用 lineTo 后,路径会自动延伸到新指定的点,并且如果之前已经调用了 beginPath() 或 moveTo(),则这条线段会连接到前一个点。
要看到实际的线条显示在画布上,需要调用 stroke() 方法。
*/
ctx.lineTo(x, y);
/**
此方法用于移动当前路径的起始点到指定的 (x, y) 坐标位置,但不会画出任何可见的线条。
它主要用于开始一个新的子路径或者在现有路径之间创建空隙。当你想要从一个地方不连续地移动到另一个地方绘制时,就需要使用 moveTo。
*/
ctx.moveTo(x, y);

友情提示:上面的函数是个倒的爱心,所以Y轴要取负数。

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>LoveCanvas</title><style>body {background: black;}</style></head><body><canvas id="canvas"></canvas></body><script>const canvas = document.getElementById("canvas");const ctx = canvas.getContext("2d");const themeColor = "#d63e83";// 爱心线的实体let loveLine = null;// 保存爱心方程的坐标let XYPoint = [];// 线条宽度,可自定义修改const lineWidth = 5;/**得到爱心方程的坐标 **/function getXYPoint() {const pointArr = [];const enlargeFactor = 20;for (let t = 0; t < 2 * Math.PI; t += 0.01) {const x = 16 * Math.pow(Math.sin(t), 3) * enlargeFactor;const y =-(13 * Math.cos(t) -5 * Math.cos(2 * t) -2 * Math.cos(3 * t) -Math.cos(4 * t)) * enlargeFactor;// 将爱心的坐标进行居中pointArr.push({ x: canvas.width / 2 + x, y: canvas.height / 2 + y });}return pointArr;}class LoveLine {constructor(pointXY) {this.pointXY = pointXY;}draw() {for (let point of this.pointXY) {ctx.lineTo(point.x, point.y);ctx.moveTo(point.x, point.y);}ctx.strokeStyle = themeColor;ctx.lineWidth = lineWidth;ctx.stroke();ctx.fill();}}function initLoveLine() {XYPoint = getXYPoint();loveLine = new LoveLine(XYPoint);loveLine.draw();}function init() {const width = window.innerWidth;const height = window.innerHeight;canvas.width = width;canvas.height = height;initLoveLine();}// 如果需要保持在窗口大小变化时也实时更新canvas尺寸window.onresize = init;init();</script>
</html>

粒子特效

这么快就做好了,是不是显得不是很够诚意?
image.pngimage.png
我们可以加入一波粒子特效,这里我采用的方案是基于之前的Canvas+requestAnimationFrame来做。
效果如下:
123.gif
首先什么是requestAnimationFrame呢?参见MDN

你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

也就是我们可以使用这个函数达到每10ms刷新一次界面达到动态的效果。
首先我们定义一个粒子

// 粒子点的类
class Dot {constructor(x, y, initX, initY) {// 原始点的坐标,用来圈定范围this.initX = initX;this.initY = initY;this.x = x;this.y = y;this.r = 1;// 粒子移动的速度,也就是下一帧,粒子在哪里出现this.speedX = Math.random() * 2 - 1;this.speedY = Math.random() * 2 - 1;// 这个粒子最远能跑多远this.maxLimit = 15;}// 绘制每一个粒子的方法draw() {ctx.beginPath();ctx.fillStyle = themeColor;ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);ctx.fill();ctx.closePath();}move() {if (Math.abs(this.x - this.initX) >= this.maxLimit)this.speedX = -this.speedX;if (Math.abs(this.x - this.y) >= this.maxLimit)this.speedY = -this.speedY;this.x += this.speedX;this.y += this.speedY;this.draw();}
}

我们在定义两个使用到粒子函数的方法.

  1. initDots函数,该函数主要是将粒子点初始化,并且画出来。
function initDots(x, y) {XYPoint = getXYPoint();dots = [];for (let point of XYPoint) {for (let i = 0; i < SINGLE_DOT_NUM; i++) {const border = Math.random() * 5;const dot = new Dot(border + point.x,border + point.y,point.x,point.y);dot.draw();dots.push(dot);}}
}
  1. moveDots函数,顾名思义,也就是移动粒子点
function moveDots() {ctx.clearRect(0, 0, canvas.width, canvas.height);loveLine.draw();for (const dot of dots) {dot.move();}animationFrame = window.requestAnimationFrame(moveDots);
}

完整代码如下:

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>LoveCanvas</title><style>body {background: black;}</style></head><body><canvas id="canvas"></canvas></body><script>const canvas = document.getElementById("canvas");const ctx = canvas.getContext("2d");const themeColor = "#d63e83";// 爱心线的实体let loveLine = null;// 保存爱心方程的坐标let XYPoint = [];// 线条宽度,可自定义修改const lineWidth = 5;// 每个原来的点对应的粒子数目const SINGLE_DOT_NUM = 15;// 粒子点的集合let dots = [];let animationFrame = null;/**得到爱心方程的坐标 **/function getXYPoint() {const pointArr = [];const enlargeFactor = 20;for (let t = 0; t < 2 * Math.PI; t += 0.01) {const x = 16 * Math.pow(Math.sin(t), 3) * enlargeFactor;const y =-(13 * Math.cos(t) -5 * Math.cos(2 * t) -2 * Math.cos(3 * t) -Math.cos(4 * t)) * enlargeFactor;// 将爱心的坐标进行居中pointArr.push({ x: canvas.width / 2 + x, y: canvas.height / 2 + y });}return pointArr;}class LoveLine {constructor(pointXY) {this.pointXY = pointXY;}draw() {for (let point of this.pointXY) {ctx.lineTo(point.x, point.y);ctx.moveTo(point.x, point.y);}ctx.strokeStyle = themeColor;ctx.lineWidth = lineWidth;ctx.stroke();ctx.fill();}}function initLoveLine() {XYPoint = getXYPoint();loveLine = new LoveLine(XYPoint);loveLine.draw();}// 粒子点的类class Dot {constructor(x, y, initX, initY) {this.initX = initX;this.initY = initY;this.x = x;this.y = y;this.r = 1;this.speedX = Math.random() * 2 - 1;this.speedY = Math.random() * 2 - 1;this.maxLimit = 15;}// 绘制每一个粒子的方法draw() {ctx.beginPath();ctx.fillStyle = themeColor;ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);ctx.fill();ctx.closePath();}move() {if (Math.abs(this.x - this.initX) >= this.maxLimit)this.speedX = -this.speedX;if (Math.abs(this.x - this.y) >= this.maxLimit)this.speedY = -this.speedY;this.x += this.speedX;this.y += this.speedY;this.draw();}}function initLoveLine() {XYPoint = getXYPoint();loveLine = new LoveLine(XYPoint);loveLine.draw();}function initDots(x, y) {XYPoint = getXYPoint();dots = [];for (let point of XYPoint) {for (let i = 0; i < SINGLE_DOT_NUM; i++) {const border = Math.random() * 5;const dot = new Dot(border + point.x,border + point.y,point.x,point.y);dot.draw();dots.push(dot);}}}function moveDots() {ctx.clearRect(0, 0, canvas.width, canvas.height);loveLine.draw();for (const dot of dots) {dot.move();}animationFrame = window.requestAnimationFrame(moveDots);}function init() {const width = window.innerWidth;const height = window.innerHeight;canvas.width = width;canvas.height = height;if (animationFrame) {window.cancelAnimationFrame(animationFrame);ctx.clearRect(0, 0, canvas.width, canvas.height);}initLoveLine();initDots();moveDots();}// 如果需要保持在窗口大小变化时也实时更新canvas尺寸window.onresize = init;init();</script>
</html>

注:大家如果觉得中间那条线不好看,可以去掉initLoveLine()即可。

最后

祝今天有情人终成眷属,无情人早日找到心仪的另一半,哈哈

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

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

相关文章

Linux_进程

进程创建 进程退出码 进程等待 程序替换 Shell作为命令行解释器是一个进程&#xff0c;它也有自己的数据结构task_struct和代码和数据。为了防止用户输入的指令造成Shell崩溃&#xff0c;所以Shell执行用户输入的指令是通过创建一个子进程来执行的。例如lspwd等等。 一.进程…

数据工程工程师学习路线图

数据工程岗位要求 Skill Sets required: - Hands on experience enabling data via Adobe Analytics and/or Google Analytics - Understanding of how customer level data is captured and stitched with behavioural data - Experience working with Testing (QA) and D…

【Java 数据结构】泛型进阶

泛型 1 什么是泛型2 引出泛型2.1 语法 3 泛型类的使用3.1 语法3.2 示例3.3 类型推导(Type Inference) 泛型是如何编译的擦除机制裸类型4 泛型的上界4.1 语法4.2 示例4.3 复杂示例 5 泛型方法5.1 定义语法5.2 示例5.3 使用示例-可以类型推导5.4 使用示例-不使用类型推导 6 通配符…

如何在极低成本硬件上落地人工智能算法 —— 分布式AI

一、背景 分布式AI的发展前景非常广阔&#xff0c;随着5G、6G等高速网络通信技术的普及和边缘计算能力的提升&#xff0c;以及AI算法和硬件的不断优化进步&#xff0c;分布式AI将在多个领域展现出强大的应用潜力和市场价值&#xff1a; 1. **物联网&#xff08;IoT&#xff0…

react【四】css

文章目录 1、css1.1 react和vue css的对比1.2 内联样式1.3 普通的css1.4 css modules1.5 在react中使用less1.6 CSS in JS1.6.1 模板字符串的基本使用1.6.2 styled-components的基本使用1.6.3 接受传参1.6.4 使用变量1.6.5 继承样式 避免代码冗余1.6.6 设置主题色 1.7 React中添…

正月初五迎财神

大家好&#xff0c;我是小悟 正月初五&#xff0c;人们在这一天迎接财神&#xff0c;祈求财运亨通、事业顺利。按照习俗&#xff0c;家家户户都会燃放鞭炮、点灯笼、摆设祭品&#xff0c;以示虔诚。 早晨&#xff0c;太阳刚刚升起&#xff0c;大家便早早起床&#xff0c;开始准…

【数据存储+多任务爬虫】

数据存储 peewee模块 第三方模块&#xff0c;也需要在cmd中安装。 from peewee import *db MySQLDatabase("spider",host"127.0.0.1",port3306,userroot,password123456 )# 类》表 class Person(Model):name CharField(max_length20) # 类型/约束bi…

核心篇-OSPF技术之序(中)

文章目录 一. 实验专题1.1. 实验1&#xff1a;配置多区域OSPF1.1.1. 实验目的1.1.2. 实验拓扑1.1.3. 实验步骤&#xff08;1&#xff09;配置地址&#xff08;2&#xff09;运行OSPF 1.1.4. 实验调试&#xff08;1&#xff09;查看路由器信息&#xff08;2&#xff09;创建环回…

Spring Boot 笔记 010 创建接口_更新用户头像

1.1.1 usercontroller中添加updateAvatar&#xff0c;校验是否为url PatchMapping("updateAvatar")public Result updateAvatar(RequestParam URL String avatarUrl) {userService.updateAvatar(avatarUrl);return Result.success();} 1.1.2 userservice //更新头像…

【UDS】搞懂时间参数

文章目录 背景时间参数的定义应用层相关会话层相关传输层相关网络层相关实际案例分析背景 TBD. 时间参数的定义 注意,这些时间参数都是超时阈值,需要理解为什么要有这些阈值,在哪一端判断这些阈值的,无需“死记硬背”它们的含义。 应用层相关 【P2 Client】 P2 Client 的…

数学建模:K-means聚类手肘法确定k值(含python实现)

原理 当K-means聚类的k值不被指定时&#xff0c;可以通过手肘法来估计聚类数量。   在聚类的过程中&#xff0c;随着聚类数的增大&#xff0c;样本划分会变得更加精细&#xff0c;每个类别的聚合程度更高&#xff0c;那么误差平方和&#xff08;SSE&#xff09;会逐渐变小&am…

YOLOv8改进 | Conv篇 | 利用FasterBlock二次创新C2f提出一种全新的结构(全网独家首发,参数量下降70W)

一、本文介绍 本文给大家带来的改进机制是利用FasterNet的FasterBlock改进特征提取网络,将其用来改进ResNet网络,其旨在提高计算速度而不牺牲准确性,特别是在视觉任务中。它通过一种称为部分卷积(PConv)的新技术来减少冗余计算和内存访问。这种方法使得FasterNet在多种设…

LeetCode、72. 编辑距离【中等,二维DP】

文章目录 前言LeetCode、72. 编辑距离【中等&#xff0c;二维DP】题目链接与分类二维DP 资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里云平台优质作者、专注于Java后端技术领域。 涵盖技术内容…

nvm 安装nodejs教程【详细】

目录 一、安装nvm 二、配置镜像 三、安装nodejs 安装 查看正在用的nodejs版本 切换版本 一、安装nvm 双击安装包&#xff1a; 无脑下一步即可&#xff0c;当然你可以自定义你自己的安装目录。 安装完后&#xff0c;打开环境变量&#xff0c;你会发现nvm为我们自动配置好…

TinUI v5预发布记录

TinUI v5预发布记录 前言新控件滚动选择框菜单按钮 新样式pre1pre2pre3 新功能导入字体文件 前言 TinUI是一个从2021年正式开始并一直维护到现在的小项目&#xff0c;中间经过了四代版本的更新。因为一些原因&#xff0c;2023年&#xff0c;TinUI-4后更新较少。 TinUI发展历程…

【Linux】Kali Linux 系统安装详细教程(虚拟机)

目录 1.1 Kali linux简介 1.2 Kali Linux工具 1.3 VMware workstation和ESXi的区别 二、安装步骤 一、Kali概述 1.1 Kali linux简介 Kali Linux是基于Debian的Linux发行版&#xff0c; 设计用于数字取证操作系统。每一季度更新一次。由Offensive Security Ltd维护和资助。最…

使用一根网线,让Ubuntu和正点原子I.MX6ULL开发板互相ping通

1.硬件准备 准备一根网线即可 2. 让windows和I.MX6ULLping通 2.1 找根网线将I.MX6ULL和电脑连起来 2.2 让I.MX6ULL通电运行起来&#xff0c;我这里使用的是正点原子版本的内核、 2.3 进入电脑的网络连接后&#xff0c;按照如下步骤操作 2.4 将ip地址、子网掩码、默认网关…

【C++初阶】第三站:类和对象(中) -- 类的6个默认成员函数

目录 前言 类的6个默认成员函数 构造函数 概念 特性 析构函数 概念 特性 拷贝构造函数 概念 特征 赋值运算符重载 运算符重载 赋值运算符重载 const成员 const修饰类成员函数 取地址及const取地址操作符重载 本章总结&#xff1a; 前言 有时候我们写好了一个栈&#xff0c;头脑…

FPGA转行ISP的探索之一:行业概览

ISP的行业位置 最近看到一个分析&#xff0c;说FPGA的从业者将来转向ISP&#xff08;Image Signal Process图像信号处理&#xff09;是个不错的选择&#xff0c;可以适应智能汽车、AI等领域。故而我查了一下ISP&#xff0c;对它大致有个概念。 传统的ISP对应的是相机公司&…

使用python绘制可视化的欧拉公式三维曲线图

欧拉公式应用非常广泛&#xff0c;它在研究交流电、信号分析、量子力学、极坐标切换、求反常积分以及研究任何圆周运动等方面都有着重要的应用&#xff0c;它建立了复数、自然对数、圆周率等重要数学常量之间的关系&#xff0c;描述了复数在极坐标和笛卡尔坐标之间的转换。公式…