Shared element transitions

Shared Element Transitions (SET) — это техника анимации, при которой один и тот же визуальный элемент плавно перемещается между двумя разными маршрутами, создавая эффект непрерывного перехода. В React Router эта концепция реализуется через сочетание маршрутизации, состояния компонентов и анимационных библиотек, таких как Framer Motion.


Принцип работы

Идея SET заключается в идентификации общих элементов на двух маршрутах и синхронизации их анимации при смене маршрута. Основные шаги включают:

  1. Определение общего элемента: элемент, который будет анимироваться между маршрутами, должен иметь уникальный идентификатор.
  2. Слежение за состоянием элемента: необходимо сохранить информацию о позиции и размере элемента перед переходом.
  3. Анимация перехода: при монтировании нового маршрута элемент плавно перемещается из исходного состояния в конечное.

Базовая структура с React Router

Для демонстрации используется комбинация BrowserRouter, Routes и Route. Основная задача — корректно обрабатывать состояние элемента между маршрутами:

import { BrowserRouter as Router, Routes, Route, useLocation } from "react-router-dom";
import { AnimatePresence, motion } from "framer-motion";
import Home from "./Home";
import Detail from "./Detail";

function App() {
  const location = useLocation();

  return (
    <AnimatePresence mode="wait">
      <Routes location={location} key={location.pathname}>
        <Route path="/" element={<Home />} />
        <Route path="/detail/:id" element={<Detail />} />
      </Routes>
    </AnimatePresence>
  );
}

export default function Root() {
  return (
    <Router>
      <App />
    </Router>
  );
}

Ключевые моменты:

  • AnimatePresence отвечает за анимацию выхода элементов из DOM.
  • location.key или location.pathname используется для отслеживания изменений маршрута, что позволяет корректно синхронизировать анимацию.
  • Каждый маршрут должен содержать компонент, готовый к анимации через motion.

Идентификация Shared Elements

Чтобы два разных маршрута понимали, что один элемент является общим, используется уникальный идентификатор. В библиотеке Framer Motion применяется атрибут layoutId:

// Home.js
import { Link } from "react-router-dom";
import { motion } from "framer-motion";

export default function Home() {
  return (
    <div>
      <Link to="/detail/1">
        <motion.img
          src="/image1.jpg"
          alt="Item"
          layoutId="image-1"
        />
      </Link>
    </div>
  );
}

// Detail.js
import { motion } from "framer-motion";

export default function Detail() {
  return (
    <motion.div>
      <motion.img
        src="/image1.jpg"
        alt="Item"
        layoutId="image-1"
      />
    </motion.div>
  );
}

layoutId — уникальный ключ, связывающий элементы на разных маршрутах. Framer Motion автоматически анимирует изменения позиции, размера и других свойств.


Плавная анимация перехода

Для достижения эффекта плавного перемещения можно настроить свойства анимации:

<motion.img
  src="/image1.jpg"
  layoutId="image-1"
  initial={{ opacity: 0 }}
  animate={{ opacity: 1 }}
  exit={{ opacity: 0 }}
  transition={{ duration: 0.5, ease: "easeInOut" }}
/>

Здесь:

  • initial — начальное состояние элемента при монтировании.
  • animate — конечное состояние.
  • exit — состояние при размонтировании.
  • transition — параметры анимации (длительность, кривизна).

Работа с несколькими элементами

Если необходимо анимировать несколько общих элементов, каждому присваивается уникальный layoutId. Например:

<motion.div layoutId="card-1">Карточка 1</motion.div>
<motion.div layoutId="card-2">Карточка 2</motion.div>

При переходе на другой маршрут с идентичными layoutId элементы будут плавно перемещаться на новые позиции, сохраняя визуальную непрерывность.


Особенности при работе с React Router

  • Ключ маршрута: всегда использовать location.pathname или location.key в Routes, чтобы анимация срабатывала корректно.
  • Lazy-loading компонентов: при асинхронной загрузке компонентов стоит обрабатывать состояние загрузки, чтобы избежать “скачков” элементов.
  • Состояние данных: данные элементов (например, изображения или текст) должны быть одинаковыми на обоих маршрутах, иначе эффект теряется.
  • Scroll Restoration: при анимации элементов важно учитывать прокрутку страницы, чтобы элементы перемещались корректно относительно видимой области.

Примеры расширенных эффектов

  1. Смена размера и формы:
<motion.div
  layoutId="card-1"
  style={{ borderRadius: 20 }}
  animate={{ borderRadius: 0 }}
/>
  1. Комбинация с фоновыми слоями: можно создавать затемнение или наложение при переходе между маршрутами:
<motion.div
  className="overlay"
  initial={{ opacity: 0 }}
  animate={{ opacity: 0.5 }}
  exit={{ opacity: 0 }}
/>
  1. Параллакс-эффекты: с помощью motion можно анимировать разные свойства (например, смещение фона), чтобы создать более глубину в переходе.

Рекомендации по производительности

  • Минимизировать количество одновременно анимируемых элементов.
  • Использовать will-change и GPU-ускоренные свойства (transform, opacity) для плавной анимации.
  • Избегать тяжелых рендеров внутри motion-компонентов, особенно при списках или сетках.

Shared Element Transitions позволяют создать эффект визуальной непрерывности при навигации, делая интерфейс React-приложений более динамичным и интерактивным. Ключевым аспектом является корректная идентификация общих элементов и синхронизация их состояния между маршрутами.