小知识,大挑战!本文正在参与“程序员必备小知识”创作活动
前言
眼睛如果还没有变得象太阳,它就看不见太阳;
心灵也是如此,本身如果不美也就看不见美。
——普洛丁
介绍
本期我们将利用canvas做一个有趣的眼球注视鼠标位置的效果,就是不管你划到哪儿,眼球就注视到哪儿,是不是让你酥酥麻麻。。先看下效果:
效果特别实现起来特别简单,我们会从基本结构,眼球类,眼珠移动来讲起~
开始
1.基本结构
我们本次使用了vite做构建,先放个画布元素,再通过module模式来引入主逻辑,方便后面的模块加载进来。
<canvas id="canvas"></canvas>
<script type="module" src="./app.js"></script>
接下来是主逻辑的结构:
/*app.js*/
import Eye from "./js/eye"
class Application {
constructor() {
this.canvas = null;
this.ctx = null;
this.w = 0;
this.h = 0;
this.eyeList = []
this.init();
}
init() {
this.canvas = document.getElementById("canvas");
this.ctx = this.canvas.getContext("2d");
window.addEventListener("resize", this.reset.bind(this));
this.reset();
this.render();
this.step();
}
reset() {
this.eyeList.length = 0;
this.w = this.canvas.width = this.ctx.width = window.innerWidth;
this.h = this.canvas.height = this.ctx.height = window.innerHeight;
this.render()
}
render() {
const {w, h, ctx, eyeList} = this;
let size = 48;
for (let i = 1; i < Math.ceil(w / 160); i++) {
for (let j = 1; j < Math.ceil(w / 160); j++) {
let eye = new Eye({
x: -size + i * 160,
y: -size + j * 160,
size
}).render(ctx);
eyeList.push(eye);
}
}
}
step() {
requestAnimationFrame(this.step.bind(this));
const {ctx, w, h, eyeList} = this;
ctx.clearRect(0, 0, w, h);
eyeList.forEach(eye => {
eye.draw();
})
}
}
window.onload = new Application();
我们在这里主要完成了这么几件事:
- 获取到画布并在初始化将画布铺满全屏
- 绑定reset事件,每当屏幕改变重置画布
- 在render中遍历实例化眼球类,将其排列在画布中用eyeList保存,并通过step实时绘制
现在界面应该是直接报错的,因为眼球类我们还没有写呢,只是目前期望他需要有size眼球半径大小,x轴坐标,y轴坐标至少保证这些基本参数能传入。
2.眼球类
/*eye.js*/
class Eye {
constructor(options) {
this.x = 0; // x轴坐标
this.y = 0; // y轴坐标
this.size = 48, // 眼球半径
this.scale = 1; // 缩放大小
this.cx = 0; // x轴中心偏移量
this.cy = 0; // y轴中心偏移量
Object.assign(this, options)
return this;
}
render(ctx) {
if (!ctx)
throw new Error("context is undefined.");
this.ctx = ctx;
return this;
}
draw(){
this.drawSphere();
this.drawCenter();
}
drawSphere() {
// 绘制眼球
const {ctx, x, y, scale, size} = this;
ctx.save()
ctx.translate(x, y);
ctx.scale(scale, scale)
ctx.beginPath();
ctx.lineCap = "round";
ctx.lineWidth = 5;
ctx.strokeStyle = "#000000";
ctx.fillStyle = "#FFFFFF";
ctx.arc(0, 0, size, 0, 2 * Math.PI)
ctx.fill();
ctx.stroke()
ctx.restore();
}
drawCenter() {
// 绘制眼珠
const {ctx, x, y, scale, size,cx,cy} = this;
ctx.save()
ctx.translate(x+cx, y+cy);
ctx.scale(scale, scale)
ctx.beginPath();
ctx.fillStyle = "#000000";
ctx.arc(0, 0, size * 0.5, 0, 2 * Math.PI)
ctx.fill();
ctx.stroke()
ctx.restore();
}
}
export default Eye;
眼球类就非常简单了,我们只要将黑白两个圆套在一起就行了眼球的绘制。当然眼珠要动起来,所以小黑球我们做了单独偏移量的叠加。
现在我们的界面是这样的:
呆呆的,像极了一群波波先生,因为他们并没什么交互反馈~
3.眼珠移动
要让眼珠移动我们要做两件事,在画布上注册一个鼠标移动事件。
/*app.js*/
init() {
// ...
this.canvas.addEventListener("mousemove", this.onMouseMove.bind(this), false)
// ...
}
接下来就是最关键的一步了!!
/*app.js*/
onMouseMove(e) {
const {clientX, clientY} = e;
this.eyeList.forEach(eye => {
const {x, y, size} = eye;
let dx = x - clientX;
let dy = y - clientY;
let angle = Math.atan2(dy, dx)+Math.PI;
eye.cx = Math.cos(angle) * (size/2-5);
eye.cy = Math.sin(angle) * (size/2-5);
})
}
我们拿到了当前鼠标的位置,遍历每一个眼球实例,通过他们的坐标与鼠标位置的差值,计算出应该转向的角度,给其增加偏移值。
这样我们的效果就实现了,在线演示
思考
其实,这个效果类似于之前写的一篇虚拟摇杆的文章,如果想看的话在专栏里自行翻阅。
我们还可以实现很多这类的创意效果,可以配上咧嘴表情或者直接一些动植物的转向,还是那句话,没有做不到只有想不到。一起加油吧~