这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战
前言
世上有两样东西不可直视,一是太阳,二是人心。 ——东野圭吾《白夜行》
介绍
七夕将至,今天就介绍给大家如何绘制一个心形动画!!保证你的女神看完神魂颠倒!!!
如何绘制心型
1.笛卡尔心形
这里先给大家讲一个故事,很久很久以前,数学家笛卡儿曾流落到瑞典,邂逅了美丽的瑞典公主克里斯蒂娜。笛卡儿发现克里斯蒂娜公主聪明伶俐,便做起了公主的数学老师,于是二人完全沉浸在了数学的世界中。国王知道后,认为笛卡儿配不上自己的女儿,不但强行拆散了他们,还没收了之后笛卡儿写给公主的所有信件。后来,笛卡儿染上黑死病,在临死前给公主寄去了最后一封信,信中只有一行字:舔狗舔狗一无所有!哦哦,不是这句,其实信中只有一句公式:r=a(1-sinθ)。最后,这句公式落到公主手上,公主在纸上建立了极坐标系,用笔在上面描下方程的点,终于解开了这行字的秘密——这就是美丽的心形线。
const canvas = document.createElement("canvas");
let w = canvas.width = 800;
let h = canvas.height = 600;
let ctx = canvas.getContext("2d");
let a = 72;
ctx.save();
ctx.fillStyle = "#f23232";
ctx.translate(w/2, h/2);
ctx.scale(1, -1);
ctx.beginPath();
ctx.moveTo(0, 0);
let r = 0, x = 0, y = 0;
for (let i = -5; i < 360; i++) {
r = a * (1 - Math.sin(i * Math.PI / 180));
x = r * Math.cos(i * Math.PI / 180);
y = r * Math.sin(i * Math.PI / 180);
ctx.lineTo(x, y);
}
ctx.fill();
ctx.closePath();
ctx.restore();
document.body.appendChild(canvas);
2.双椭圆心形
虽然笛卡尔的故事很浪漫,它也是标准的心形曲线,但是确实不符合审美标准。所以有了另外一种心形曲线。
const canvas = document.createElement("canvas");
let w = canvas.width = 800;
let h = canvas.height = 600;
let ctx = canvas.getContext("2d");
ctx.save();
ctx.beginPath();
ctx.fillStyle = "#f23232";
ctx.translate(w/2, h/2);
ctx.rotate(Math.PI)
let r = 0, a = 20, start = 0, end = 0;
for (let i = 0; i < 1000; i++) {
start += Math.PI * 2 / 1000;
end = start + Math.PI * 2 / 1000;
r = a * Math.sqrt(225 / (17 - 16 * Math.sin(start) * Math.sqrt(Math.cos(start) ** 2)));
ctx.arc(0, 0, r, start, end, false);
}
ctx.fill();
ctx.closePath();
ctx.restore();
document.body.appendChild(canvas);
3.完美心形
双椭圆心形是在转折的地方还不够平滑,依然缺乏部分美感,出现一直完美的心形曲线。
const canvas = document.createElement("canvas");
let w = canvas.width = 800;
let h = canvas.height = 600;
let ctx = canvas.getContext("2d");
ctx.save();
ctx.beginPath();
ctx.fillStyle = "#f23232";
ctx.translate(w/2, h/2);
ctx.scale(1,-1);
ctx.moveTo(0,0);
let angle=0,x=0,y=0,a=6;
for (let i = 0; i < 30; i+=0.2) {
angle = i / Math.PI;
x = a * (16*Math.sin(angle)**3);
y = a * (13*Math.cos(angle)-5*Math.cos(2*angle)-2*Math.cos(3*angle)-Math.cos(4*angle));
ctx.lineTo(x,y);
}
ctx.fill();
ctx.closePath();
ctx.restore();
document.body.appendChild(canvas);
心形动画
1.基础结构
<style>
*{
padding: 0;
margin: 0;
}
html,body{
width: 100%;
height: 100vh;
overflow: hidden;
}
#canvas{
width: 100%;
height: 100%;
}
</style>
<canvas id="canvas"></canvas>
<script type="module" src="./app.js"></script>
/*app.js*/
import Heart from "./js/heart.js"
window.onLoad = new Heart().init();
/*heart.js*/
class Heart {
constructor(optoins) {}
init() {}
render() {}
reset() {}
}
export default Heart;
这里我们把基础结构先写出来,利用module模式来导入模块,接下来来我们集中精力写heart.js就行了。
2.定义传参
我们首先要分析都需要什么参数,什么参数是可以从外界传递过来的,什么是自己本身的。
我们着手写constructor,不多说先看代码:
constructor(optoins) {
this.backgroundColor = "black"; // 背景色
this.strokeColor = "white"; // 前景色
this.lineWidth = 2; // 线宽
this.step = 20; // 线段间隔
Object.assign(this, optoins);
this.canvas = null; // 画布
this.ctx = null; // 环境
this.w = 0; // 画布宽度
this.h = 0; // 画布高度
this.offset = 0; // 频率修正值
this.imageBuffer = []; // 图像buffer信息
}
首先,我们定义背景色backgroundColor,前景色strokeColor,线宽lineWidth,线段间隔step这四个属性可以通过外界传入进行浅拷贝从而改变其参数值。经过分析我们要画心形肯定要有写出一套心形数据存储起来,这里imageBuffer就是来收集生成的心形数据的容器。
3.初始化
init() {
this.canvas = document.getElementById("canvas");
this.ctx = this.canvas.getContext("2d");
window.addEventListener("resize", this.reset.bind(this));
this.reset();
this.render();
}
reset() {
const {strokeColor, lineWidth} = this;
this.w = this.canvas.width = window.innerWidth;
this.h = this.canvas.height = window.innerHeight;
this.ctx.strokeStyle = strokeColor;
this.ctx.lineWidth = lineWidth;
this._storeHeartInBuffer();
}
这里,初始化先获取到画布容器定义环境,监听屏幕改变事件。宽高等一些参数可能随着屏幕变化而变化,我就直接放到reset方法里面,初始化直接调用他首次获取到。另外,我也要获取屏幕改变后的imageBuffer信息,同样,我们要再获取一遍。
4.绘制心形
我这里要绘制的肯定是一个符合美感的完美心形,就利用刚刚的心形曲线公式来绘制。
_storeHeartInBuffer() {
const {ctx, w, h} = this;
ctx.beginPath();
for (let angle = 0; angle < Math.PI * 2; angle += 0.01) {
let r = Math.min(w, h) * 0.025;
let x = r * 16 * Math.pow(Math.sin(angle), 3);
let y = -r * (13 * Math.cos(angle) - 5 * Math.cos(2 * angle) - 2 * Math.cos(3 * angle) - Math.cos(4 * angle));
ctx.lineTo(w / 2 + x, h * 0.45 + y);
}
ctx.fill();
let image = ctx.getImageData(0, 0, w, h);
this.imageBuffer = new Uint32Array(image.data.buffer);
}
不过,这次我们绘制不是让他展示出来而是通过getImageData拿到他信息从而转化成数组方便提取信。
5.渲染背景与线
render(delta) {
const {w, h, render} = this;
requestAnimationFrame(render.bind(this));
this.ctx.fillRect(0, 0, w, h);
this.offset = delta / 25;
this._drawBackground();
this._drawHeartLines();
}
requestAnimationFrame去做实时的渲染更新。这里定义好执行绘制背景与线段的方法。接下来,就终于到了主绘制逻辑了!
_drawBackground() {
const {ctx, w, h, backgroundColor} = this;
ctx.save();
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, w, h);
ctx.restore();
}
_drawHeartLines() {
const {h, step} = this;
for (let y = 0; y < h; y += step) {
this._drawHeartLine(y + this.offset % step);
}
}
_drawHeartLine(y) {
const {ctx, w, imageBuffer, step} = this;
ctx.beginPath();
let _y;
for (let x = 0; x < w; x += 1) {
if (imageBuffer[Math.floor(y) * w + x]) {
_y = y + step / 2;
} else {
_y = y;
}
ctx.lineTo(x, _y);
}
ctx.stroke();
}
这里绘制背景很简单不做赘述。绘制线段每隔step绘制一条。绘制的时候就需要用到我们的主角了imageBuffer里面的心形信息了。如果判断有当前点对应了心形所在的点,那么给他的y轴加一个偏移量再进行绘制。
这里我就完成了,是不是很容易,演示地址
拓展与延伸
当我们学会绘制一个心形的同时其实可以有更多的动画形式去展现,比如很多粒子效果换成不同颜色的爱心,再或者心中间镂空,其的边线换成粒子效果。所以只有你想不到没有你做不到。
七夕要来了,祝天下有情人终成眷属!不多说了,身为单身汪的我先躲起来了~~~
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!