基于Three.js Shader的React组件:多卡片图片重复与hover全局触发问题
解决Three.js Shader React图片组件的两个问题
问题分析
- 所有卡片显示同一张图片:
HoverImageShader的uniforms是全局共享对象,多个AnimatedImage实例复用同一组uniforms,后加载的纹理会覆盖之前的实例纹理。 - 所有图片同时触发hover效果:父组件使用单一全局
hover状态,所有组件的playAnim都绑定这个状态,导致hover任意卡片都会触发全部效果。
修复方案
1. 修复图片重复问题
修改AnimatedImage组件,每次实例化时复制一份uniforms,避免共享引用,同时补充图片宽高比的计算(修复Shader中比例错误导致的拉伸问题):
import { useMemo, useState, useEffect } from 'react'; import { TextureLoader } from 'three'; import { Canvas } from '@react-three/fiber'; import { useSpring, animated, config } from '@react-spring/three'; import { HoverImageShader } from 'shader/HoverImageShader'; const AnimatedImage = ({ src, playAnim = false, width = '100%', height = '100%', textureWidth = 10.8, textureHeight = 10.8, }) => { const imgTexture = useMemo(() => { const loader = new TextureLoader(); return loader.load(src); }, [src]); // 计算图片自身的宽高比 const [imageAspect, setImageAspect] = useState(1); useEffect(() => { const img = new Image(); img.src = src; img.onload = () => setImageAspect(img.width / img.height); }, [src]); // 计算画布容器的宽高比 const canvasAspect = useMemo(() => { const w = typeof width === 'number' ? width : 100; const h = typeof height === 'number' ? height : 100; return w / h; }, [width, height]); const { hoverValue } = useSpring({ hoverValue: playAnim ? 1 : 0, config: config.molasses, }); // 复制Shader的uniforms,避免全局共享 const shaderUniforms = useMemo(() => ({ ...HoverImageShader.uniforms, tDiffuse: { ...HoverImageShader.uniforms.tDiffuse, value: imgTexture }, imageAspectRatio: { ...HoverImageShader.uniforms.imageAspectRatio, value: imageAspect }, aspectRatio: { ...HoverImageShader.uniforms.aspectRatio, value: canvasAspect }, hover: { ...HoverImageShader.uniforms.hover, value: hoverValue }, }), [imgTexture, imageAspect, canvasAspect, hoverValue]); return ( <Canvas pixelratio={window.devicePixelRatio || 1} style={{ width, height }} camera={{ fov: 75, position: [0, 0, 7] }} > <animated.mesh> <planeBufferGeometry attach='geometry' args={[textureWidth, textureHeight]} /> <animated.shaderMaterial attach='material' transparent args={[ { ...HoverImageShader, uniforms: shaderUniforms, }, ]} /> </animated.mesh> </Canvas> ); }; export default AnimatedImage;
2. 修复hover效果全局触发问题
将全局hover状态改为每个卡片独立的状态,用索引跟踪当前hover的卡片:
// 父组件中替换原hover状态定义 const [hoveredIndex, setHoveredIndex] = useState(-1); // 映射渲染代码修改 <div ref={trackRef} className={styles.cardsTrack}> {cardsData?.map((card, index) => ( <div key={card.id} // 用卡片ID代替索引作为key,避免列表重排时的状态混乱 className={styles.cardContainer} > <Link to={`/cards/${card.id}`} className={styles.link}> <div className={styles.cardImageContainer} onMouseEnter={() => setHoveredIndex(index)} onMouseLeave={() => setHoveredIndex(-1)} > <AnimatedImage src={card.image} playAnim={hoveredIndex === index} /> </div> </Link> </div> ))} </div>
内容的提问来源于stack exchange,提问作者Oliver
相关产品推荐
相关产品推荐

