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

点击文档时重置跟随鼠标的Ball Div至初始状态并锁定的问题

解决点击文档后Ball仍跟随鼠标移动的问题

问题根源

  1. 重置函数resetBallToInitialState未移除window上的mousemove事件监听,导致鼠标移动时仍会触发跟随逻辑
  2. mouseOverListener和mouseOutListener在循环中被反复覆盖,最终只能移除最后一个元素的监听,其他元素的监听残留
  3. 初始状态通过element.style获取,只能拿到行内样式,无法获取CSS类设置的实际样式
  4. 点击Ball时内部重新声明cursor变量,覆盖全局变量,导致重置时操作的可能不是当前激活的Ball

修复后的完整代码

HTML(修正了原代码中的语法错误,比如多余的引号)

<div class="cursor-explaining-div">SO MUCH OF A TEXT</div>
<nav id="nav" class="flex">
  <div class="business-name">
    <h2>
      Fashion
    </h2>
  </div>
  <div class="links flex">
    <a href="#">Services</a>
    <a href="#">About</a>
    <a href="#">Contact</a>
  </div>
</nav>

<section class="landing flex">
  <div class="ball ball1" id="ball1"></div>
  <div class="ball ball2" id="ball2"></div>
  <div class="ball ball3" id="ball3"></div>
  <div class="ball ball4" id="ball4"></div>
  <div class="left-landing">
      <h1>Photoghraphy 📷</h1>
      <p class="shady-cl">
          Photography is the art 🎨 of creating durable images by recording light, either electronically by means of an image sensor 😀, or chemically by means of a light-sensitive material such as photographic film.
      </p>
      <button id="contact-me">Contact Me</button>
  </div>        
  <div class="right-landing">
      <img src="https://placehold.co/300x200.png?text=WebDesign.jpg" class="landing-image" alt="">
  </div>        
</section>

JavaScript

let ballIsOn = false;
let activeCursor = null; // 存储当前激活的ball元素
let initialStates = new Map(); // 存储每个ball的初始状态

const cursorExplainingDiv = document.querySelector(".cursor-explaining-div");

// 存储所有需要移除的事件监听
let eventListeners = {
  mousemove: null,
  mouseover: [],
  mouseout: []
};

// 提前获取所有ball的初始状态
document.querySelectorAll(".ball").forEach(ball => {
  const computedStyle = getComputedStyle(ball);
  initialStates.set(ball, {
    width: computedStyle.width,
    height: computedStyle.height,
    zIndex: computedStyle.zIndex,
    opacity: computedStyle.opacity,
    left: computedStyle.left,
    top: computedStyle.top,
    backgroundColor: computedStyle.backgroundColor,
    pointerEvents: computedStyle.pointerEvents
  });
});

const landingBallVar = function landingBalls(e) {
  if (ballIsOn) return;
  
  ballIsOn = true;
  activeCursor = e.target;
  const cursor = activeCursor;

  // 应用激活样式
  cursor.style.width = "30px";
  cursor.style.height = "30px";
  cursor.style.zIndex = "99";
  cursor.style.opacity = "1";
  cursor.style.transition = ".1s ease";
  cursor.style.pointerEvents = "none";

  // 定位到点击位置
  const x = e.clientX;
  const y = e.clientY;
  cursor.style.left = `${x - 15}px`;
  cursor.style.top = `${y - 15}px`;

  // 先移除之前的mousemove监听(如果有)
  if (eventListeners.mousemove) {
    window.removeEventListener("mousemove", eventListeners.mousemove);
  }

  // 绑定新的mousemove监听并存储
  eventListeners.mousemove = function(e) {
    setTimeout(() => {
      const newX = e.clientX;
      const newY = e.clientY;
      cursor.style.left = `${newX - 15}px`;
      cursor.style.top = `${newY - 15}px`;
    }, 100);
  };
  window.addEventListener("mousemove", eventListeners.mousemove);

  // 绑定元素的mouseover/mouseout监听并存储
  const allBodyEl = {
    h1: document.querySelectorAll("h1"),
    h2: document.querySelectorAll("h2"),
    p: document.querySelectorAll("p"),
    button: document.querySelectorAll("button"),
    img: document.querySelectorAll("img"),
  };

  // 清空之前的监听列表
  eventListeners.mouseover = [];
  eventListeners.mouseout = [];

  for (const key in allBodyEl) {
    allBodyEl[key].forEach(element => {
      const mouseOverHandler = function(e) {
        cursor.style.width = "40px";
        cursor.style.height = "40px";
        cursor.style.backgroundColor = "#bc7025";
        const newX = e.clientX;
        const newY = e.clientY;
        cursor.style.left = `${newX - 10}px`;
        cursor.style.top = `${newY - 20}px`;
      };

      const mouseOutHandler = function() {
        setTimeout(() => {
          cursor.style.width = "30px";
          cursor.style.height = "30px";
          cursor.style.transition = ".1s ease";
          cursor.style.backgroundColor = initialStates.get(activeCursor).backgroundColor;
          cursor.innerHTML = "";
        }, 100);
      };

      element.addEventListener("mouseover", mouseOverHandler);
      element.addEventListener("mouseout", mouseOutHandler);
      
      // 存储监听函数以便后续移除
      eventListeners.mouseover.push({element, handler: mouseOverHandler});
      eventListeners.mouseout.push({element, handler: mouseOutHandler});
    });
  }
};

function resetBallToInitialState() {
  if (!activeCursor) return;

  // 恢复初始状态
  const initialState = initialStates.get(activeCursor);
  activeCursor.style.width = initialState.width;
  activeCursor.style.height = initialState.height;
  activeCursor.style.zIndex = initialState.zIndex;
  activeCursor.style.opacity = initialState.opacity;
  activeCursor.style.left = initialState.left;
  activeCursor.style.top = initialState.top;
  activeCursor.style.backgroundColor = initialState.backgroundColor;
  activeCursor.style.pointerEvents = initialState.pointerEvents;

  // 移除mousemove监听
  if (eventListeners.mousemove) {
    window.removeEventListener("mousemove", eventListeners.mousemove);
    eventListeners.mousemove = null;
  }

  // 移除所有mouseover/mouseout监听
  eventListeners.mouseover.forEach(({element, handler}) => {
    element.removeEventListener("mouseover", handler);
  });
  eventListeners.mouseout.forEach(({element, handler}) => {
    element.removeEventListener("mouseout", handler);
  });

  // 重置状态
  ballIsOn = false;
  activeCursor = null;
}

document.addEventListener("click", (e) => {
  if (ballIsOn && !e.target.closest(".ball")) {
    resetBallToInitialState();
  }
});

// 给所有ball绑定点击事件
document.querySelectorAll(".ball").forEach(ball => {
  ball.addEventListener("click", landingBallVar);
});

关键修改说明

  1. 存储激活的Ball和初始状态:用activeCursor记录当前激活的Ball,用Map存储每个Ball的初始样式(通过getComputedStyle获取实际生效的样式,而非仅行内样式)
  2. 统一管理事件监听:将所有需要移除的监听函数存储在eventListeners对象中,确保重置时能彻底移除所有监听,包括mousemove以及所有元素的mouseover/mouseout
  3. 修复变量覆盖问题:移除点击Ball时内部的cursor变量声明,改用activeCursor跟踪当前操作的元素
  4. 修正HTML语法错误:清理原HTML中多余的引号和无效字符,避免DOM解析问题
  5. 优化事件绑定逻辑:点击Ball前先移除之前的监听,防止重复绑定;重置时彻底清空所有相关监听

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 04:42:33