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在卡片内跟随光标移动。
修复方案
问题根源
- Blob坐标计算错误:使用全局窗口坐标计算偏移,未基于卡片自身位置和尺寸,导致偏移严重
- 卡片变换逻辑问题:原
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
相关产品推荐
相关产品推荐

