You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

网站addEventListener事件选择疑问:仅mousepad聚焦时控制星空背景移动

解决方案

问题根源

  1. <div>元素默认不支持聚焦,所以focus事件无法触发,需要给.mousepad添加tabindex属性使其可聚焦。
  2. 你的思路偏差:不是要在聚焦时调用render,而是需要控制鼠标移动事件是否能影响星空的运动状态——只有当.mousepad聚焦时,才让鼠标移动驱动星空;失焦时停止响应。

具体修改步骤

1. 修改HTML,让mousepad可聚焦

给.mousepad添加tabindex属性,值设为0(保证它能被键盘或鼠标聚焦):

<div class="mousepad" tabindex="0"></div>

2. 修改JavaScript逻辑

  • 添加状态变量控制鼠标追踪开关
  • 绑定focus/blur事件切换状态
  • 限制鼠标移动事件的执行条件

在JS开头添加:

const mousepad = document.querySelector('.mousepad');
let trackingMouse = false; // 控制是否响应鼠标移动

// 绑定聚焦/失焦事件
mousepad.addEventListener('focus', () => {
  trackingMouse = true;
});
mousepad.addEventListener('blur', () => {
  trackingMouse = false;
  // 失焦后重置指针,避免星空继续滑动
  pointerX = null;
  pointerY = null;
});

然后修改onMouseMove和onTouchMove函数,只有trackingMouse为true时才执行逻辑:

function onMouseMove(event) {
  if (!trackingMouse) return;
  touchInput = false;
  movePointer(event.clientX, event.clientY);
}

function onTouchMove(event) {
  if (!trackingMouse) return;
  touchInput = true;
  movePointer(event.touches[0].clientX, event.touches[0].clientY, true);
  event.preventDefault();
}

修改后的完整代码

HTML

<canvas></canvas>

<div class="mousepad" tabindex="0"></div>

CSS

body {
  width: 100%;
  height: 100vh;
  background-color: #000;
  background-image: radial-gradient(circle at top right, rgba(84, 187, 115, 0.13), transparent), radial-gradient(circle at 20% 80%, rgba(41, 196, 255, 0.13), transparent)
}

canvas {
  position: fixed;
  width: 100%;
  height: 100%;
}

.mousepad {
  width: 700px;
  height: 100px;
  background-color: rgba(41, 196, 255, 0.13);
  border-radius: 10px;
  margin: auto;
  outline: none; /* 去掉默认聚焦边框,可选 */
}

/* Hide Scrollbar for aesthetic looks*/

::-webkit-scrollbar {
  overflow: auto;
  display: none;
}

JavaScript

const STAR_COLOR = '#fff';
const STAR_SIZE = 3;
const STAR_MIN_SCALE = 0.2;
const OVERFLOW_THRESHOLD = 50;
const STAR_COUNT = (window.innerWidth + window.innerHeight) / 20;

const canvas = document.querySelector('canvas'),
  context = canvas.getContext('2d');
const mousepad = document.querySelector('.mousepad');
let trackingMouse = false;

let scale = 1, // device pixel ratio
  width,
  height;

let stars = [];

let pointerX,
  pointerY;

let velocity = {
  x: 0,
  y: 0,
  tx: 0,
  ty: 0,
  z: 0.0005
};

let touchInput = false;

generate();
resize();
step();

window.onresize = resize;
canvas.onmousemove = onMouseMove;
canvas.ontouchmove = onTouchMove;
canvas.ontouchend = onMouseLeave;
document.onmouseleave = onMouseLeave;

// 绑定聚焦失焦事件
mousepad.addEventListener('focus', () => {
  trackingMouse = true;
});
mousepad.addEventListener('blur', () => {
  trackingMouse = false;
  pointerX = null;
  pointerY = null;
});

function generate() {
  for (let i = 0; i < STAR_COUNT; i++) {
    stars.push({
      x: 0,
      y: 0,
      z: STAR_MIN_SCALE + Math.random() * (1 - STAR_MIN_SCALE)
    });
  }
}

function placeStar(star) {
  star.x = Math.random() * width;
  star.y = Math.random() * height;
}

function recycleStar(star) {
  let direction = 'z';

  let vx = Math.abs(velocity.x),
    vy = Math.abs(velocity.y);

  if (vx > 1 || vy > 1) {
    let axis;

    if (vx > vy) {
      axis = Math.random() < vx / (vx + vy) ? 'h' : 'v';
    } else {
      axis = Math.random() < vy / (vx + vy) ? 'v' : 'h';
    }

    if (axis === 'h') {
      direction = velocity.x > 0 ? 'l' : 'r';
    } else {
      direction = velocity.y > 0 ? 't' : 'b';
    }
  }

  star.z = STAR_MIN_SCALE + Math.random() * (1 - STAR_MIN_SCALE);

  if (direction === 'z') {
    star.z = 0.1;
    star.x = Math.random() * width;
    star.y = Math.random() * height;
  } else if (direction === 'l') {
    star.x = -OVERFLOW_THRESHOLD;
    star.y = height * Math.random();
  } else if (direction === 'r') {
    star.x = width + OVERFLOW_THRESHOLD;
    star.y = height * Math.random();
  } else if (direction === 't') {
    star.x = width * Math.random();
    star.y = -OVERFLOW_THRESHOLD;
  } else if (direction === 'b') {
    star.x = width * Math.random();
    star.y = height + OVERFLOW_THRESHOLD;
  }
}

function resize() {
  scale = window.devicePixelRatio || 1;

  width = window.innerWidth * scale;
  height = window.innerHeight * scale;

  canvas.width = width;
  canvas.height = height;

  stars.forEach(placeStar);
}

function step() {
  context.clearRect(0, 0, width, height);

  update();
  render();

  requestAnimationFrame(step);
}

function update() {
  velocity.tx *= 0.96;
  velocity.ty *= 0.96;

  velocity.x += (velocity.tx - velocity.x) * 0.8;
  velocity.y += (velocity.ty - velocity.y) * 0.8;

  stars.forEach((star) => {
    star.x += velocity.x * star.z;
    star.y += velocity.y * star.z;

    star.x += (star.x - width / 2) * velocity.z * star.z;
    star.y += (star.y - height / 2) * velocity.z * star.z;
    star.z += velocity.z;

    // recycle when out of bounds
    if (star.x < -OVERFLOW_THRESHOLD || star.x > width + OVERFLOW_THRESHOLD || star.y < -OVERFLOW_THRESHOLD || star.y > height + OVERFLOW_THRESHOLD) {
      recycleStar(star);
    }
  });
}

function render() {
  stars.forEach((star) => {
    context.beginPath();
    context.lineCap = 'round';
    context.lineWidth = STAR_SIZE * star.z * scale;
    context.globalAlpha = 0.5 + 0.5 * Math.random();
    context.strokeStyle = STAR_COLOR;

    context.beginPath();
    context.moveTo(star.x, star.y);

    var tailX = velocity.x * 2,
      tailY = velocity.y * 2;

    // stroke() wont work on an invisible line
    if (Math.abs(tailX) < 0.1) tailX = 0.5;
    if (Math.abs(tailY) < 0.1) tailY = 0.5;

    context.lineTo(star.x + tailX, star.y + tailY);

    context.stroke();
  });
}

function movePointer(x, y) {
  if (typeof pointerX === 'number' && typeof pointerY === 'number') {
    let ox = x - pointerX,
      oy = y - pointerY;

    velocity.tx = velocity.tx + (ox / 8 * scale) * (touchInput ? 1 : -1);
    velocity.ty = velocity.ty + (oy / 8 * scale) * (touchInput ? 1 : -1);
  }

  pointerX = x;
  pointerY = y;
}

function onMouseMove(event) {
  if (!trackingMouse) return;
  touchInput = false;
  movePointer(event.clientX, event.clientY);
}

function onTouchMove(event) {
  if (!trackingMouse) return;
  touchInput = true;
  movePointer(event.touches[0].clientX, event.touches[0].clientY, true);
  event.preventDefault();
}

function onMouseLeave() {
  pointerX = null;
  pointerY = null;
}

内容的提问来源于stack exchange,提问作者user13608015

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 07:20:39