React中自定义AutoAnimate弹性动画失效问题求助
问题:React集成AutoAnimate自定义弹性动画不生效
我在React项目中集成AutoAnimate插件,改写了自定义Hook实现弹性动画,但点击添加元素时,新元素直接静态显示,没有弹性动画效果。以下是相关代码,请求协助排查:
自定义Hook代码
import { useRef, useLayoutEffect } from 'react'; import autoAnimate, { getTransitionSizes } from '@formkit/auto-animate'; interface UseAutoAnimateOptions { parentElement: HTMLElement; } const useBouncyAutoAnimate = ({ parentElement }: UseAutoAnimateOptions) => { const ref = useRef(null); useLayoutEffect(() => { if (ref.current) { autoAnimate(parentElement, (el, action, oldCoords: any, newCoords: any) => { let keyframes; // ... (rest of the code remains the same) if (action === 'add') { keyframes = [ { transform: 'scale(0)', opacity: 0 }, { transform: 'scale(1.15)', opacity: 1, offset: 0.75 }, { transform: 'scale(1)', opacity: 1 }, ]; } if (action === 'remove') { keyframes = [ { transform: 'scale(1)', opacity: 1 }, { transform: 'scale(1.15)', opacity: 1, offset: 0.33 }, { transform: 'scale(0.75)', opacity: 0.1, offset: 0.5 }, { transform: 'scale(0.5)', opacity: 0 }, ]; } if (action === 'remain') { const deltaX = oldCoords.left - newCoords.left; const deltaY = oldCoords.top - newCoords.top; const [widthFrom, widthTo, heightFrom, heightTo] = getTransitionSizes( el, oldCoords, newCoords ); let start: any = { transform: `translate(${deltaX}px, ${deltaY}px)` }; let mid: any = { transform: `translate(${deltaX * -0.15}px, ${deltaY * -0.15}px)`, offset: 0.75, }; let end: any = { transform: `translate(0, 0)` }; if (widthFrom !== widthTo) { start.width = `${widthFrom}px`; mid.width = `${widthFrom >= widthTo ? widthTo / 1.05 : widthTo * 1.05}px`; end.width = `${widthTo}px`; } if (heightFrom !== heightTo) { start.height = `${heightFrom}px`; mid.height = `${heightFrom >= heightTo ? heightTo / 1.05 : heightTo * 1.05}px`; end.height = `${heightTo}px`; } keyframes = [start, mid, end]; } return new KeyframeEffect(el, keyframes as any, { duration: 600, easing: 'ease-out', }); }); } }, [ref]); return ref; }; export default useBouncyAutoAnimate;
使用代码(MultiList组件)
import React, { useRef, useState } from 'react' import Word from './Word'; import { useAutoAnimate } from '@formkit/auto-animate/react' import useBouncyAutoAnimate from './bouncy'; interface Word { id: number, visible: boolean, word: string } interface MultiListProps { words: Word[], actives: Word[], setWords: React.Dispatch<React.SetStateAction<Word[]>>, setActives: React.Dispatch<React.SetStateAction<Word[]>> } function MultiList({ words, setWords, actives, setActives }: MultiListProps) { const parentRef = useRef<HTMLElement | null>(null); const animateRef = useBouncyAutoAnimate({ parentElement: parentRef.current! }); const [ready, setReady] = useState(false); return ( <div ref={parentRef as any}> { actives.map(word => ( <button onClick={() => { const has = actives.find(w => w.word === word.word); if (!has) return; setActives(x => x.filter(w => w.id !== word.id)) const newWords = words.map(el => { if (el.id === word.id) el.visible = true; return el; }) setWords(newWords) }} ref={ word.id === (actives.at(-1)?.id || NaN) ? animateRef as any : undefined}> <Word word={word} /> </button> )) } </div> ) } export default MultiList
相关元素代码(SelectList组件)
import React, { useState } from 'react' import Word from './Word'; import classnames from 'classnames'; interface Word { id: number, visible: boolean, word: string } interface SelectListProps { words: Word[], actives: Word[], setWords: React.Dispatch<React.SetStateAction<Word[]>>, setActives: React.Dispatch<React.SetStateAction<Word[]>> } function SelectList({ words, setWords, actives, setActives }: SelectListProps) { const [ready, setReady] = useState(false); return ( <div> { words.map(word => ( <button onClick={() => { const has = actives.find(w => w.word === word.word); if (has) return; setActives(x => ([ ...x, word ])) const newWords = words.map(el => { if (el.id === word.id) el.visible = false; return el; }) setWords(newWords) }} className={classnames('transition-opacity', { 'opacity-50': !word.visible })}> <Word word={word} /> </button> )) } </div> ) } export default SelectList
问题排查与修复方案
1. 自定义Hook核心错误修复
- 依赖项与绑定时机错误:原Hook依赖
ref但ref.current变化不会触发effect,且初始化时parentElement传入的是未挂载的null。修改后监听parentElement,并在其存在时才绑定动画,同时添加清理函数避免内存泄漏:
import { useRef, useLayoutEffect } from 'react'; import autoAnimate, { getTransitionSizes } from '@formkit/auto-animate'; interface UseAutoAnimateOptions { parentElement: HTMLElement | null; } const useBouncyAutoAnimate = ({ parentElement }: UseAutoAnimateOptions) => { const ref = useRef(null); useLayoutEffect(() => { if (!parentElement) return; const cleanup = autoAnimate(parentElement, (el, action, oldCoords: any, newCoords: any) => { let keyframes; if (action === 'add') { keyframes = [ { transform: 'scale(0)', opacity: 0 }, { transform: 'scale(1.15)', opacity: 1, offset: 0.75 }, { transform: 'scale(1)', opacity: 1 }, ]; } if (action === 'remove') { keyframes = [ { transform: 'scale(1)', opacity: 1 }, { transform: 'scale(1.15)', opacity: 1, offset: 0.33 }, { transform: 'scale(0.75)', opacity: 0.1, offset: 0.5 }, { transform: 'scale(0.5)', opacity: 0 }, ]; } if (action === 'remain') { const deltaX = oldCoords.left - newCoords.left; const deltaY = oldCoords.top - newCoords.top; const [widthFrom, widthTo, heightFrom, heightTo] = getTransitionSizes( el, oldCoords, newCoords ); let start: any = { transform: `translate(${deltaX}px, ${deltaY}px)` }; let mid: any = { transform: `translate(${deltaX * -0.15}px, ${deltaY * -0.15}px)`, offset: 0.75, }; let end: any = { transform: `translate(0, 0)` }; if (widthFrom !== widthTo) { start.width = `${widthFrom}px`; mid.width = `${widthFrom >= widthTo ? widthTo / 1.05 : widthTo * 1.05}px`; end.width = `${widthTo}px`; } if (heightFrom !== heightTo) { start.height = `${heightFrom}px`; mid.height = `${heightFrom >= heightTo ? heightTo / 1.05 : heightTo * 1.05}px`; end.height = `${heightTo}px`; } keyframes = [start, mid, end]; } return new KeyframeEffect(el, keyframes as any, { duration: 600, easing: 'ease-out', }); }); return cleanup; }, [parentElement]); return ref; }; export default useBouncyAutoAnimate;
2. 使用组件错误修复
- 无效parentElement传入:直接将
parentRef.current传入Hook,由Hook处理null情况; - 错误绑定子元素ref:AutoAnimate只需绑定父元素,自动监听子元素增删,无需给单个子元素绑ref;
- 添加唯一key:React列表必须加
key,确保AutoAnimate正确识别元素变化; - 避免直接修改原数组:更新状态时返回新对象,确保React检测到状态变化。
修改后的MultiList组件:
import React, { useRef } from 'react' import Word from './Word'; import useBouncyAutoAnimate from './bouncy'; interface Word { id: number, visible: boolean, word: string } interface MultiListProps { words: Word[], actives: Word[], setWords: React.Dispatch<React.SetStateAction<Word[]>>, setActives: React.Dispatch<React.SetStateAction<Word[]>> } function MultiList({ words, setWords, actives, setActives }: MultiListProps) { const parentRef = useRef<HTMLElement | null>(null); useBouncyAutoAnimate({ parentElement: parentRef.current }); return ( <div ref={parentRef}> { actives.map(word => ( <button key={word.id} onClick={() => { setActives(x => x.filter(w => w.id !== word.id)) setWords(prevWords => prevWords.map(el => el.id === word.id ? {...el, visible: true} : el ) ) }} > <Word word={word} /> </button> )) } </div> ) } export default MultiList
内容的提问来源于stack exchange,提问作者MrFrontend
相关产品推荐
相关产品推荐

