悟夕导航

第五集:交互!让鼠标成为上帝的指挥棒

96 0 0
“老板说要‘沉浸式体验’,我把画布点出了烟花,他以为我偷偷学了 Unity。”
—— 某位刚被加薪的前端小可爱

大家好,欢迎来到《Canvas 翻车指南》第五集!
前面我们让火柴人狂奔到冒烟,今天把鼠标变成魔法棒:

  • 点击 → 粒子烟花
  • 拖拽 → 橡皮擦
  • 防抖 & 节流 → 鼠标疯移也不卡成狗

准备好冰可乐,咱们让用户体验升维打击


一、监听三剑客:down、move、up

canvas.addEventListener('mousedown', start);
canvas.addEventListener('mousemove', move);
canvas.addEventListener('mouseup', end);

手机端再补个 touchstart / touchmove / touchend一套代码两端通吃
坐标转换别忘了:

function getPos(e) {
  const rect = canvas.getBoundingClientRect();
  return {
    x: (e.clientX || e.touches[0].clientX) - rect.left,
    y: (e.clientY || e.touches[0].clientY) - rect.top
  };
}

口诀:

“拿坐标先 getBounding,不然全屏乱飞像鬼魂。”

二、点击放烟花:粒子池 + radial 爆炸

目标:点哪炸哪, 0.5 秒后消失帧率稳如狗

1. 对象池提前 new 好

const MAX = 300;
const pool = [];
for (let i = 0; i < MAX; i++) pool.push(new Particle());

粒子类:

class Particle {
  init(x, y, color) {
    this.x = x; this.y = y;
    this.vx = (Math.random() - 0.5) * 6;
    this.vy = (Math.random() - 0.5) * 6;
    this.life = 1;
    this.decay = 0.02;
    this.color = color;
  }
  update() {
    this.x += this.vx;
    this.y += this.vy;
    this.vy += 0.1; // 重力
    this.life -= this.decay;
  }
  draw(ctx) {
    ctx.save();
    ctx.globalAlpha = this.life;
    ctx.fillStyle = this.color;
    ctx.beginPath();
    ctx.arc(this.x, this.y, 3, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
  }
}

2. 点击事件

let tail = 0;
function boom(x, y) {
  const color = `hsl(${Math.random()*360},100%,60%)`;
  for (let i = 0; i < 50; i++) {
    pool[tail].init(x, y, color);
    tail = (tail + 1) % MAX;
  }
}

canvas.addEventListener('click', e => {
  const p = getPos(e);
  boom(p.x, p.y);
});

3. RAF 里更新 & 绘制

function loop() {
  requestAnimationFrame(loop);
  ctx.fillStyle = 'rgba(0,0,0,0.08)'; // 拖尾
  ctx.fillRect(0, 0, w, h);
  pool.forEach(p => {
    if (p.life <= 0) return;
    p.update();
    p.draw(ctx);
  });
}
loop();

刷新:
点哪炸哪夜空拖尾像 Winamp 可视化
globalAlpha 拖尾改成 clearRect就是硬核 Starfield


三、拖拽橡皮擦:把画布当沙盘

目标:按住鼠标擦除像素松手即停

1. 状态机

let isDrawing = false;
function start(e) { isDrawing = true; move(e); }
function end() { isDrawing = false; }
function move(e) {
  if (!isDrawing) return;
  const p = getPos(e);
  ctx.save();
  ctx.globalCompositeOperation = 'destination-out'; // 橡皮擦模式
  ctx.beginPath();
  ctx.arc(p.x, p.y, 20, 0, Math.PI * 2);
  ctx.fill();
  ctx.restore();
}

口诀:

destination-out 就是橡皮擦,画哪哪消失,爽到飞起。”

2. 手机兼容

canvas.addEventListener('touchstart', e => start(e.touches[0]));
canvas.addEventListener('touchmove', e => {
  e.preventDefault(); // 防止页面滚动
  move(e.touches[0]);
});
canvas.addEventListener('touchend', end);

现在:
鼠标 / 手指双通像 Procreate 橡皮老板看完默默给你买 iPad


四、防抖 & 节流:鼠标疯移也不卡

1. 节流(throttle)

function throttle(fn, wait) {
  let last = 0;
  return function (...args) {
    const now = Date.now();
    if (now - last > wait) {
      last = now;
      fn.apply(this, args);
    }
  };
}
canvas.addEventListener('mousemove', throttle(move, 16)); // 60Hz

2. 防抖(debounce)

适合“鼠标停止后保存”:

function debounce(fn, delay) {
  let timer = null;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}
window.addEventListener('resize', debounce(saveCanvas, 500));

口诀:

“节流像水龙头,防抖像电梯门。”

五、合体技:烟花 + 橡皮擦,双模式切换

加按钮:

<button id="mode">切换到橡皮擦</button>
let mode = 'firework';
document.getElementById('mode').onclick = () => {
  mode = mode === 'firework' ? 'eraser' : 'firework';
  this.textContent = mode === 'eraser' ? '切换到烟花' : '切换到橡皮擦';
};

move 里分流:

function move(e) {
  if (mode === 'firework') return; // 烟花只点
  if (!isDrawing) return;
  // 橡皮擦逻辑
}

瞬间:一个页面两种玩法像 Switch 卡带


六、性能锦囊:离屏擦除 & 分层 Canvas

  • 背景静态?bgCanvas主画布只清局部
  • 橡皮擦太大?globalCompositeOperation = 'destination-out' + drawImageGPU 帮你算
  • 手机老机型?touchmove 节流 24Hz肉眼无感,帧率翻倍

七、本集知识点一口闷

技能口诀
getBoundingClientRect拿坐标忘记转手机坐标
destination-out橡皮擦用完不回 source-over 全消失
对象池提前 new爆炸时 malloc 会卡
throttle水龙头16ms 是 60Hz 门槛

八、课后作业(翻车 field)

  1. 五彩画板

    • 调色盘选色 → 鼠标拖拽画笔globalCompositeOperation = 'source-over'
    • 橡皮擦按钮切换,一键擦除
  2. 撤销(用数组保存每一步 ImageData50 步上限
  3. Bonus双指缩放 & 移动像地图一样拖适配触摸板

九、下集预告

第六集《性能翻车现场:十万颗粒子如何不卡成 PPT》
带你上:

  • WebWorker 算物理,主线程只画画
  • 顶点着色器 trick,GPU 批渲染
  • OffscreenCanvas + transferControlToOffscreen帧率锁 144Hz

“人生就像交互事件,
你不监听 move,
就永远不知道用户下一秒想擦除谁。”

—— 我说的。

第五集结束,点赞、转发、晒烟花
下集一起把粒子干到十万级,回见!

0
快来点个赞吧

发表评论

隐私评论

评论列表

来写一个评论吧