React组件中鼠标移动事件全局触发问题求助
嗨,我帮你梳理下问题的核心,其实根源特别明显:
你在Component2的useEffect里,把handleMouse直接绑定到了window的mousemove事件上了!这就意味着只要鼠标在整个页面上移动,不管是不是悬停在你的h1元素上,这个事件都会被触发。你之前尝试的stopPropagation、stopImmediatePropagation这些方法,都是用来阻止DOM事件在元素层级间冒泡/捕获的,但window是事件传播链的最顶层,这些方法对全局绑定的事件完全起不到限制作用。
再对比你的Component1:你会发现Component1的useEffect里绑定的是handleMousemove(注意函数名和handleMouse不一样),而动画逻辑的handleMouse只绑定在了h1的onMouseOver上,所以只有鼠标移到h1上才会触发动画,全局的pointermove是另一个无关的监听,所以没问题。
那怎么修复呢?给你两个可行的方案:
方案一:去掉全局监听(推荐,因为你本来想要的就是hover触发)
直接删掉Component2里的useEffect代码块,只保留h1上的onMouseOver={handleMouse}绑定。这样只有当鼠标真正悬停在h1元素上时,才会触发你的动画逻辑,完全不会全局触发。
修改后的Component2代码大概是这样:
import React, { useState } from "react"; const New = () => { const [value, setValue] = useState("Hello"); const handleMouse = (event) => { const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; let iterations = 0; const interval = setInterval(() => { event.target.innerText = event.target.innerText .split("") .map((ele, i, ar) => { if (i < iterations) { return value[i]; } return letters[Math.floor(Math.random() * 26)]; }) .join(""); // 用value的长度代替固定5,适配不同文本内容 if (iterations >= value.length) clearInterval(interval); iterations += 1; }, 60); }; // 删掉原来的useEffect全局监听 return ( <div> <h1 onMouseOver={handleMouse} className="text-white text-6xl hover:text-rose-700 transition-all duration-500 cursor-default" > {value} </h1> </div> ); }; export default New;
方案二:如果必须全局监听,那就加范围判断
如果你确实需要全局监听mousemove,但只想在鼠标在h1范围内时执行逻辑,可以用React的useRef来标记元素,然后在handleMouse里判断当前鼠标是否在元素内:
import React, { useEffect, useRef, useState } from "react"; const New = () => { const [value, setValue] = useState("Hello"); const h1Ref = useRef(null); // 创建ref绑定到h1 const intervalRef = useRef(null); // 存interval ID,防止重复创建导致内存泄漏 const handleMouse = (event) => { // 先判断鼠标是否在h1元素范围内,不在就直接返回 if (!h1Ref.current || !h1Ref.current.contains(event.target)) return; // 先清除之前的interval,避免动画混乱 if (intervalRef.current) clearInterval(intervalRef.current); const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; let iterations = 0; intervalRef.current = setInterval(() => { event.target.innerText = event.target.innerText .split("") .map((ele, i, ar) => { if (i < iterations) { return value[i]; } return letters[Math.floor(Math.random() * 26)]; }) .join(""); if (iterations >= value.length) { clearInterval(intervalRef.current); intervalRef.current = null; } iterations += 1; }, 60); }; useEffect(() => { window.addEventListener("mousemove", handleMouse); return () => { window.removeEventListener("mousemove", handleMouse); // 组件卸载时清除interval,防止内存泄漏 if (intervalRef.current) clearInterval(intervalRef.current); }; }, [value]); // 依赖加上value,避免闭包导致的旧值问题 return ( <div> <h1 ref={h1Ref} // 绑定ref className="text-white text-6xl hover:text-rose-700 transition-all duration-500 cursor-default" > {value} </h1> </div> ); }; export default New;
另外还要提醒你一个小细节:原来的代码里如果鼠标多次快速悬停h1,会创建多个interval,可能导致动画混乱甚至内存泄漏,所以上面的代码里我加了intervalRef来存interval ID,每次触发前先清除之前的,组件卸载时也会清除。
备注:内容来源于stack exchange,提问作者Vaikruta

