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

React中3D卡片Hover效果与原生JS表现不一致问题排查

React 3D卡片Hover效果与原生JS表现不一致的排查与修复

问题描述

尝试在React中复刻原生JavaScript的3D卡片Hover效果,逻辑完全照搬,仅用useRef替代了querySelector,但最终表现和原生版本不一致,无法定位问题。

原生核心逻辑回顾

原生代码通过mouseenter获取卡片的getBoundingClientRect(),在mousemove时计算鼠标相对卡片中心的位置,动态修改transform和.glow的背景渐变,鼠标离开时重置样式。

常见问题与修复方案

1. getBoundingClientRect()时机错误

React组件渲染是异步的,若在mouseenter时直接获取ref.current.getBoundingClientRect(),可能DOM还未完全更新,导致bounds值不准确。
修复:用requestAnimationFrame延迟获取,确保DOM渲染完成:

const handleMouseEnter = () => {
  requestAnimationFrame(() => {
    if (cardRef.current) {
      bounds.current = cardRef.current.getBoundingClientRect();
      document.addEventListener('mousemove', rotateToMouse);
    }
  });
};

2. 样式重置逻辑不匹配

原生代码中mouseleave时重置$card.style.background = '',但React中卡片背景图是通过CSS类设置的,重置background会覆盖类样式,和原生逻辑冲突。
修复:只清除行内的transform和.glow的backgroundImage:

const handleMouseLeave = () => {
  document.removeEventListener('mousemove', rotateToMouse);
  if (cardRef.current) cardRef.current.style.transform = '';
  if (glowRef.current) glowRef.current.style.backgroundImage = '';
};

3. Ref引用未正确绑定

确保cardRef和glowRef正确绑定到DOM元素,避免组件重渲染时丢失引用:

<div ref={cardRef} className="card">
  3D Card
  <div ref={glowRef} className="glow" />
</div>

4. 事件监听未及时清理

React组件卸载时若未移除mousemove监听,会导致内存泄漏,也可能影响后续组件行为。
修复:用useEffect在组件卸载时清理监听:

useEffect(() => {
  return () => document.removeEventListener('mousemove', rotateToMouse);
}, []);

修复后的完整React代码示例

import { useRef, useEffect } from 'react';

export default function App() {
  const cardRef = useRef(null);
  const glowRef = useRef(null);
  const bounds = useRef(null);

  const rotateToMouse = (e) => {
    if (!cardRef.current || !glowRef.current || !bounds.current) return;
    
    const mouseX = e.clientX;
    const mouseY = e.clientY;
    const leftX = mouseX - bounds.current.x;
    const topY = mouseY - bounds.current.y;
    const center = {
      x: leftX - bounds.current.width / 2,
      y: topY - bounds.current.height / 2
    };
    const distance = Math.sqrt(center.x ** 2 + center.y ** 2);

    cardRef.current.style.transform = `
      scale3d(1.07, 1.07, 1.07)
      rotate3d(
        ${center.y / 100},
        ${-center.x / 100},
        0,
        ${Math.log(distance) * 2}deg
      )
    `;

    glowRef.current.style.backgroundImage = `
      radial-gradient(
        circle at
        ${center.x * 2 + bounds.current.width / 2}px
        ${center.y * 2 + bounds.current.height / 2}px,
        #ffffff55,
        #0000000f
      )
    `;
  };

  const handleMouseEnter = () => {
    requestAnimationFrame(() => {
      if (cardRef.current) {
        bounds.current = cardRef.current.getBoundingClientRect();
        document.addEventListener('mousemove', rotateToMouse);
      }
    });
  };

  const handleMouseLeave = () => {
    document.removeEventListener('mousemove', rotateToMouse);
    if (cardRef.current) cardRef.current.style.transform = '';
    if (glowRef.current) glowRef.current.style.backgroundImage = '';
  };

  useEffect(() => {
    return () => document.removeEventListener('mousemove', rotateToMouse);
  }, []);

  return (
    <div className="App">
      <div 
        ref={cardRef} 
        className="card" 
        onMouseEnter={handleMouseEnter} 
        onMouseLeave={handleMouseLeave}
      >
        3D Card
        <div ref={glowRef} className="glow" />
      </div>
    </div>
  );
}

CSS关键注意事项

确保React父容器(如.App)继承原生body的3D透视样式,否则旋转效果会失效:

.App {
  width: 100vw;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  perspective: 1500px;
  background: linear-gradient(white, #efefef);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 06:10:34