Canvas如何做出你瞅啥效果【密集恐惧症慎入】

1,695 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

前言

眼睛如果还没有变得象太阳,它就看不见太阳;

心灵也是如此,本身如果不美也就看不见美。

——普洛丁

介绍

本期我们将利用canvas做一个有趣的眼球注视鼠标位置的效果,就是不管你划到哪儿,眼球就注视到哪儿,是不是让你酥酥麻麻。。先看下效果:

VID_20210924_093746.gif

效果特别实现起来特别简单,我们会从基本结构,眼球类,眼珠移动来讲起~

开始

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;

眼球类就非常简单了,我们只要将黑白两个圆套在一起就行了眼球的绘制。当然眼珠要动起来,所以小黑球我们做了单独偏移量的叠加。

现在我们的界面是这样的:

微信截图_20210924100826.png

呆呆的,像极了一群波波先生,因为他们并没什么交互反馈~

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);
     })
 }

我们拿到了当前鼠标的位置,遍历每一个眼球实例,通过他们的坐标与鼠标位置的差值,计算出应该转向的角度,给其增加偏移值。

这样我们的效果就实现了,在线演示

思考

其实,这个效果类似于之前写的一篇虚拟摇杆的文章,如果想看的话在专栏里自行翻阅。

我们还可以实现很多这类的创意效果,可以配上咧嘴表情或者直接一些动植物的转向,还是那句话,没有做不到只有想不到。一起加油吧~