点击文档时重置跟随鼠标的Ball Div至初始状态并锁定的问题
解决点击文档后Ball仍跟随鼠标移动的问题
问题根源
- 重置函数
resetBallToInitialState未移除window上的mousemove事件监听,导致鼠标移动时仍会触发跟随逻辑 mouseOverListener和mouseOutListener在循环中被反复覆盖,最终只能移除最后一个元素的监听,其他元素的监听残留- 初始状态通过
element.style获取,只能拿到行内样式,无法获取CSS类设置的实际样式 - 点击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); });
关键修改说明
- 存储激活的Ball和初始状态:用
activeCursor记录当前激活的Ball,用Map存储每个Ball的初始样式(通过getComputedStyle获取实际生效的样式,而非仅行内样式) - 统一管理事件监听:将所有需要移除的监听函数存储在
eventListeners对象中,确保重置时能彻底移除所有监听,包括mousemove以及所有元素的mouseover/mouseout - 修复变量覆盖问题:移除点击Ball时内部的
cursor变量声明,改用activeCursor跟踪当前操作的元素 - 修正HTML语法错误:清理原HTML中多余的引号和无效字符,避免DOM解析问题
- 优化事件绑定逻辑:点击Ball前先移除之前的监听,防止重复绑定;重置时彻底清空所有相关监听
内容的提问来源于stack exchange,提问作者Romany Refaat
相关产品推荐
相关产品推荐

