Вопрос или проблема
Я пытаюсь анимировать список компонентов в React, используя Chakra UI и/или Motion, который создаётся из данных, предоставленных хуком, и хочу, чтобы при добавлении элемента в данные были анимации входа и выхода, но, похоже, что когда данные обновляются, всё просто перезаписывается вместо того, чтобы сначала анимироваться. Вот псевдокод того, что у меня сейчас есть, который, надеюсь, содержит только самое главное:
const item = (itemDetails) => {
const MotionBox = motion(Box)
return {
<MotionBox key="itemDetails.key" initial={false} animate={{opacity: 1, scaleY: 1}} exit={{opacity: 0, scaleY: 0}}>
//Содержимое элемента
</MotionBox>
}
const container = () => {
const data = useDataHook()
const [displayedData, setDisplayedData] = useState([])
const initialRender = useRef(true)
useEffect(() => {
if (initialRender.current) {
setDisplayedData(data)
} elseif (data.length !== displayedData.length) {
setDisplayedData(data)
}
}, [data, displayedData])
return {
<Box>
<AnimatePresence initial={false}>
{data.map((item, index) => {
return (
<Item key={index} itemData={displayedData}/>
)
})
</AnimatePresence>
</Box>
}
}
Я попробовал множество способов, если я не ставлю initial=false, то каждый раз, когда родительский компонент рендерится, всё анимируется снова, из-за чего это выглядит так, словно они мигают (они находятся в аккордеоне, так что не хочу, чтобы они анимировались в начале, только когда добавляются в данные). Но даже без анимации сейчас они не анимируются на выход, если только список не становится пустым, потому что когда данные обновляются, он перерисовывается, как я предполагаю, до того, как анимация может произойти. К сожалению, я не могу предотвратить перерисовку родительского компонента, когда данные не обновлены.
Я пробовал такие вещи, как попытка применить элемент через кнопку применения, чтобы вызвать сервер, но установить переменную состояния, чтобы предотвратить обновление displayedItems до успешного возвращения результата и завершения onAnimationComplete на элементе движения, но это всё равно не анимируется.
Идеально было бы, если бы только новые компоненты элементов анимировались, масштабируясь и появляясь (в идеале плавно сдвигая другие элементы вниз, но я пока не знаю, как это сделать), и когда элемент удаляется, он анимируется при масштабировании и исчезновении. С чем-то подобным, как мне заставить только отдельные элементы, которые я хочу анимировать на или снимать анимацию, делать это вместо всех или ни одного из них? Я могу провести сравнение, чтобы выяснить, какие элементы были добавлены или удалены, но не смог понять, как использовать это, чтобы конкретный элемент анимировался или исчезал.
Ответ или решение
Вопрос: существует ли способ анимации элементов при добавлении или удалении элементов в функции map
, где данные обновляются из хуков?
Анимация элементов списка в React на основе данных, получаемых из хуков, представляет собой распространённую задачу в разработке интерфейсов. Для успешного осуществления анимаций появления и исчезновения элементов, важно правильно управлять состоянием и жизненным циклом компонентов, особенно когда данные обновляются.
Проблема
Как вы описали, проблема заключается в том, что при обновлении данных компоненты заменяются заново, что не предоставляет достаточно времени для анимации. Вместо этого необходимо сосредоточиться на использовании библиотеки для анимации, такой как Framer Motion, вместе с корректным управлением состоянием.
Решение: использование AnimatePresence
и состояния
Чтобы анимация работала должным образом, вам необходимо:
- Использовать
AnimatePresence
от Framer Motion для управления анимацией при добавлении и удалении элементов. - Создать механизм контроля состояния, который будет отслеживать новые элементы и удалять исчезающие элементы с анимацией.
Вот пример, как можно улучшить вашу реализацию:
import { motion, AnimatePresence } from 'framer-motion';
import { Box } from '@chakra-ui/react';
import { useEffect, useRef, useState } from 'react';
const MotionBox = motion(Box);
const Item = ({ itemData }) => {
return (
<MotionBox
key={itemData.key}
initial={{ opacity: 0, scaleY: 0 }}
animate={{ opacity: 1, scaleY: 1 }}
exit={{ opacity: 0, scaleY: 0 }}
>
{/* Содержимое элемента */}
</MotionBox>
);
};
const Container = () => {
const data = useDataHook(); // Хук для получения данных
const [displayedData, setDisplayedData] = useState([]);
useEffect(() => {
// Устанавливаем новое состояние что-либо изменится
if (JSON.stringify(data) !== JSON.stringify(displayedData)) {
setDisplayedData(data);
}
}, [data]); // Отслеживаем обновления данных
return (
<Box>
<AnimatePresence>
{displayedData.map((item) => (
<Item key={item.key} itemData={item} />
))}
</AnimatePresence>
</Box>
);
};
Объяснение решения
-
Использование
AnimatePresence
. Эта обертка позволяет управлять анимацией появления и исчезновения дочерних элементов. Она отслеживает, когда какой-либо дочерний элемент удаляется, и автоматически запускает анимацию выхода перед его удалением из DOM. -
Сравнение состояния. В
useEffect
происходит сравнение текущих данных с отображаемыми данными. Если они различаются, происходит обновление состоянияdisplayedData
. Это позволяет компонентам плавно анимироваться при изменении. -
Анимация элементов. Каждый
Item
анимируется с помощьюinitial
,animate
иexit
свойств. Состояние анимации задаётся на основе состояния компонента, что позволяет избежать "мигания" при рендеринге.
Заключение
Интеграция анимаций в компоненты React с использованием хуков – это процесс, требующий внимания к деталям и правильного управления состояниями. Применение библиотеки Framer Motion совместно с корректным использованием AnimatePresence
позволяет достигать желаемых эффектов анимации при добавлении и удалении элементов списка. Убедитесь, что данные, передаваемые в анимации, обновляются корректно, чтобы предотвратить ненужные перерендеры. Таким образом, ваши UI-элементы будут выглядеть гораздо более плавно и профессионально.