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

React Spring卡片悬停效果异常:Blob偏移与卡片平移问题求助

问题描述

我想要实现一个卡片悬停交互效果:卡片随鼠标在卡片内的位置平移,同时背景模糊Blob跟随鼠标位置移动。

当前Blob代码片段:

<div
  className={`absolute w-[500px] border-none  bottom-[50px] h-[1000px] z-[-1] back ${
    hovered ? "opacity-95" : "opacity-0"
  } `}
  style={{
    transform: `translateX(${translateX}px) translateY(${2 * translateY}px)`,
    background: backgroundColor,
    borderRadius: "100%",
    mixBlendMode: "soft-light",
    left: left,
    filter: "blur(180px)",
    willChange: "transform",
    transition: "transform 0.2s cubic-bezier",
  }}
></div>

完整实现代码:

import React, { useState, useEffect } from "react";
import { useSpring, animated } from "@react-spring/web";

export type Props = {
  children: React.ReactNode;
  backgroundColor: string;
  direction: string;
  left: string;
};

const calc = (x: number, y: number): [number, number, number] => [
  -(y - window.innerHeight / 2) / 360,
  (x - window.innerWidth / 2) / 240,
  1.02,
];

const trans = (x: number, y: number, s: number): string =>
  `perspective(900px) rotateX(${-x}deg) rotateY(${-y}deg)`;

const HoverCard: React.FC<Props> = ({
  children,
  backgroundColor,
  direction,
  left,
}) => {
  const [hovered, setIsHovered] = useState(false);
  const [springProps, set] = useSpring(() => ({
    xys: [0, 0, 1],
    config: { mass: 5, tension: 6000, friction: 1000 },
  }));

  const [cursorCoords, setCursorCoords] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const handleMousePosition = (event: MouseEvent) => {
      const { clientX: x, clientY: y } = event;
      setCursorCoords({ x, y });
    };

    window.addEventListener("mousemove", handleMousePosition);

    return () => {
      window.removeEventListener("mousemove", handleMousePosition);
    };
  }, []);

  const calcTranslate = (
    coordinate: number,
    containerSize: number,
    itemSize: number
  ) => (coordinate / containerSize) * (containerSize - itemSize);

  const translateX =
    typeof window !== "undefined"
      ? calcTranslate(cursorCoords.x, window.innerWidth, 600)
      : 0;
  const translateY =
    typeof window !== "undefined"
      ? calcTranslate(cursorCoords.y, window.innerHeight, 500)
      : 0;

  const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {
    const { clientX: x, clientY: y } = event;
    set({ xys: calc(x, y) });
    setIsHovered(true);
  };

  const handleMouseLeave = () => {
    set({ xys: [0, 0, 1] });
    setIsHovered(false);
  };

  return (
    <animated.div
      className="  overflow-hidden  mb-3 md:mb-8 rounded-xl"
      onMouseMove={handleMouseMove}
      onMouseLeave={handleMouseLeave}
      style={{ transform: springProps.xys.to(trans) }}
    >
      <div
        className={`z-[1] relative bg-white dark:bg-[#161b22] h-full border-[#30363d] border-[0.5px] rounded-xl shadow-xl md:flex  ${direction} justify-between`}
      >
        {children}
//this is the blob        
<div
          className={`absolute w-[500px] border-none  bottom-[50px] h-[1000px] z-[-1] back ${
            hovered ? "opacity-95" : "opacity-0"
          } `}
          style={{
            transform: `translateX(${translateX}px) translateY(${2 * translateY}px)`,
            background: backgroundColor,
            borderRadius: "100%",
            mixBlendMode: "soft-light",
            left: left,
            filter: "blur(180px)",
            willChange: "transform",
            transition: "transform 0.2s cubic-bezier",
          }}
        ></div>
      </div>
    </animated.div>
  );
};

export default HoverCard;

当前问题

  • Blob与鼠标在卡片内的实际位置严重偏移
  • 卡片平移效果不符合预期

期望效果

卡片随鼠标位置平滑平移,背景模糊Blob在卡片内跟随光标移动。


修复方案

问题根源

  1. Blob坐标计算错误:使用全局窗口坐标计算偏移,未基于卡片自身位置和尺寸,导致偏移严重
  2. 卡片变换逻辑问题:原calc函数基于窗口中心计算旋转角度,而非卡片自身中心,导致卡片平移/旋转不符合预期

具体修复步骤

1. 修正Blob的坐标计算逻辑

  • 移除全局mousemove监听,改为在卡片的handleMouseMove中获取鼠标在卡片内的相对坐标
  • 基于卡片宽高和鼠标相对位置计算Blob偏移量,确保Blob跟随卡片内光标移动

2. 修正卡片的变换逻辑

  • 修改calc函数,基于卡片自身中心而非窗口中心计算旋转角度,让卡片平移/旋转更贴合鼠标位置

3. 调整Blob的初始定位

  • 移除固定定位样式,改为基于卡片中心的初始位置,避免初始偏移

修复后的完整代码

import React, { useState, useRef } from "react";
import { useSpring, animated } from "@react-spring/web";

export type Props = {
  children: React.ReactNode;
  backgroundColor: string;
  direction: string;
};

// 基于卡片中心计算旋转角度
const calc = (cardRect: DOMRect, x: number, y: number): [number, number, number] => [
  -(y - cardRect.top - cardRect.height / 2) / 40,
  (x - cardRect.left - cardRect.width / 2) / 40,
  1.02,
];

const trans = (x: number, y: number, s: number): string =>
  `perspective(900px) rotateX(${x}deg) rotateY(${y}deg) scale(${s})`;

const HoverCard: React.FC<Props> = ({
  children,
  backgroundColor,
  direction,
}) => {
  const [hovered, setIsHovered] = useState(false);
  const [springProps, set] = useSpring(() => ({
    xys: [0, 0, 1],
    config: { mass: 3, tension: 300, friction: 40 },
  }));
  const [blobOffset, setBlobOffset] = useState({ x: 0, y: 0 });
  const cardRef = useRef<HTMLDivElement>(null);

  const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {
    if (!cardRef.current) return;
    
    const cardRect = cardRef.current.getBoundingClientRect();
    const { clientX: x, clientY: y } = event;
    
    // 更新卡片变换
    set({ xys: calc(cardRect, x, y) });
    
    // 计算鼠标在卡片内的相对位置(0到1范围)
    const relX = (x - cardRect.left) / cardRect.width;
    const relY = (y - cardRect.top) / cardRect.height;
    
    // Blob偏移:基于卡片尺寸调整,让Blob中心跟随鼠标
    const blobWidth = 500;
    const blobHeight = 1000;
    setBlobOffset({
      x: relX * (cardRect.width - blobWidth),
      y: relY * (cardRect.height - blobHeight),
    });
    
    setIsHovered(true);
  };

  const handleMouseLeave = () => {
    set({ xys: [0, 0, 1] });
    setBlobOffset({ x: 0, y: 0 });
    setIsHovered(false);
  };

  return (
    <animated.div
      className="overflow-hidden mb-3 md:mb-8 rounded-xl"
      onMouseMove={handleMouseMove}
      onMouseLeave={handleMouseLeave}
      style={{ transform: springProps.xys.to(trans) }}
    >
      <div
        ref={cardRef}
        className={`z-[1] relative bg-white dark:bg-[#161b22] h-full border-[#30363d] border-[0.5px] rounded-xl shadow-xl md:flex ${direction} justify-between`}
      >
        {children}
        {/* Blob元素 */}
        <div
          className={`absolute w-[500px] h-[1000px] z-[-1] transition-all duration-200 ${
            hovered ? "opacity-95" : "opacity-0"
          }`}
          style={{
            transform: `translate(${blobOffset.x}px, ${blobOffset.y}px)`,
            background: backgroundColor,
            borderRadius: "100%",
            mixBlendMode: "soft-light",
            filter: "blur(180px)",
            willChange: "transform",
            transition: "transform 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94)",
          }}
        ></div>
      </div>
    </animated.div>
  );
};

export default HoverCard;

修复说明

  • 卡片变换:基于卡片自身中心计算旋转角度,鼠标在卡片内移动时,卡片倾斜和平移更自然贴合光标位置
  • Blob跟随:通过getBoundingClientRect()获取卡片位置,计算鼠标相对卡片的位置,确保Blob准确跟随卡片内光标
  • 动画优化:调整react-spring配置参数,让卡片回弹效果更平滑;修正Blob过渡曲线,提升跟随流畅度
  • 简化Props:移除不必要的left参数,Blob初始位置通过transform自动调整

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 10:17:31