如何在React中复用onMouseEnter/onMouseLeave函数单独控制多个Lottie图标动画
完全可以实现函数复用,这里提供两种常见的优化方案,你可以根据自身需求选择:
方案1:当前组件内轻量化改造
不需要改动现有结构,只需要整合变量、复用公共函数即可:
- 用
useRef代替createRef(函数组件中useRef会在多次渲染间保持引用不变,更符合使用场景) - 把分散的动画实例统一存入对象,用唯一key区分
- 给每个卡片增加自定义标识属性,公共函数通过标识匹配对应动画实例执行操作
改造后的完整代码如下:
import React, { useRef, useEffect } from "react" import lottie from "lottie-web" import heart from "../assets/data/heart.json" import wine from "../assets/data/wine.json" import party from "../assets/data/party.json" import { useStaticQuery, graphql } from "gatsby" import styled from "styled-components" import { StaticImage } from "gatsby-plugin-image" export const query = graphql` { contentfulMatrimonio { titolo sottotitolo } } ` const Wedding = () => { const data = useStaticQuery(query) // 统一存储ref const animationRefs = { heart: useRef(null), wine: useRef(null), party: useRef(null) } // 统一存储动画实例 const animations = useRef({ heart: null, wine: null, party: null }) useEffect(() => { // 统一初始化动画 const animationConfigs = [ { key: 'heart', data: heart }, { key: 'wine', data: wine }, { key: 'party', data: party } ] animationConfigs.forEach(({key, data}) => { animations.current[key] = lottie.loadAnimation({ container: animationRefs[key].current, animationData: data, renderer: "svg", loop: true, autoplay: false, }) }) // 组件卸载时销毁动画,避免内存泄漏 return () => { Object.values(animations.current).forEach(anim => anim?.destroy()) } }, []) // 复用的启动函数 const startAnimation = (e) => { const animKey = e.currentTarget.dataset.animKey animations.current[animKey]?.play() } // 复用的暂停函数 const stopAnimation = (e) => { const animKey = e.currentTarget.dataset.animKey animations.current[animKey]?.pause() } return ( <Wrapper> <div className="title-container"> <h3>{data.contentfulMatrimonio.titolo}</h3> <p>{data.contentfulMatrimonio.sottotitolo}</p> </div> <div className="program-container"> <StaticImage src="../assets/images/spots.png" placeholder="tracedSVG" layout="constrained" className="background-style" /> <div className="program-card" data-anim-key="heart" onMouseEnter={startAnimation} onMouseLeave={stopAnimation} > <div className="animation-container" ref={animationRefs.heart}></div> <p className="time-style">ore 17:00</p> <p>cerimonia</p> </div> <div className="program-card" data-anim-key="wine" onMouseEnter={startAnimation} onMouseLeave={stopAnimation} > <div className="animation-container" ref={animationRefs.wine}></div> <p className="time-style">ore 19:00</p> <p>cena</p> </div> <div className="program-card" data-anim-key="party" onMouseEnter={startAnimation} onMouseLeave={stopAnimation} > <div className="animation-container" ref={animationRefs.party}></div> <p className="time-style">ore 22:00</p> <p>tutti si balla</p> </div> </div> </Wrapper> ) }
方案2:抽离独立组件(更推荐)
如果后续可能新增更多卡片,优先选择把单个卡片逻辑封装成独立组件,每个组件自己维护动画实例和交互逻辑,完全不需要在父组件写冗余的控制代码:
首先抽离ProgramCard组件:
// ProgramCard.jsx import React, { useRef, useEffect } from 'react' import lottie from 'lottie-web' const ProgramCard = ({ animationData, time, text }) => { const containerRef = useRef(null) const animation = useRef(null) useEffect(() => { animation.current = lottie.loadAnimation({ container: containerRef.current, animationData, renderer: "svg", loop: true, autoplay: false, }) return () => animation.current?.destroy() }, [animationData]) const start = () => animation.current?.play() const stop = () => animation.current?.pause() return ( <div className="program-card" onMouseEnter={start} onMouseLeave={stop} > <div className="animation-container" ref={containerRef}></div> <p className="time-style">{time}</p> <p>{text}</p> </div> ) } export default ProgramCard
父组件改造后代码会非常简洁:
// Wedding.jsx 省略其他不变的引入 import ProgramCard from './ProgramCard' const Wedding = () => { const data = useStaticQuery(query) // 把卡片配置统一存入数组,后续加卡片只需要加这个数组的项即可 const cardList = [ { animationData: heart, time: 'ore 17:00', text: 'cerimonia' }, { animationData: wine, time: 'ore 19:00', text: 'cena' }, { animationData: party, time: 'ore 22:00', text: 'tutti si balla' } ] return ( <Wrapper> <div className="title-container"> <h3>{data.contentfulMatrimonio.titolo}</h3> <p>{data.contentfulMatrimonio.sottotitolo}</p> </div> <div className="program-container"> <StaticImage src="../assets/images/spots.png" placeholder="tracedSVG" layout="constrained" className="background-style" /> {/* 直接遍历渲染即可 */} {cardList.map((card, idx) => <ProgramCard key={idx} {...card} />)} </div> </Wrapper> ) }
两种方案都可以实现你要的效果,方案1改动量小适合现有代码快速优化,方案2可维护性更高适合长期迭代的项目。
内容的提问来源于stack exchange,提问作者Alice
相关产品推荐
相关产品推荐

