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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 21:06:03