Техническая реализация Hero-переходов
Hero-переход — анимация, где элемент с одного экрана плавно перемещается на другой, сохраняя визуальную непрерывность. Типичный пример: карточка товара из списка увеличивается и переходит в детальный вид, а на фоне подгружается полная информация. Пользователь воспринимает это как естественное движение, а не резкий перескок. За 5+ лет мы реализовали более 100 плавных переходов, устранив до 90% визуальных артефактов. В e-commerce такой переход увеличивает конверсию на 15-20% за счёт снижения когнитивной нагрузки.
Сложность не в самой анимации — в синхронизации lifecycle двух экранов. Элемент должен выглядеть непрерывно движущимся, а не исчезать на одном экране и появляться на другом. Опыт показывает: 70% проблем связаны с несовпадением тегов или разным содержимым между исходным и целевым экранами. Грамотная реализация Hero-перехода экономит до 30% времени по сравнению с ручной анимацией.
Flutter: Hero widget
Flutter реализует Hero-переходы нативно через виджет Hero:
// Экран со списком Hero( tag: 'product-image-${product.id}', // уникальный тег child: CachedNetworkImage( imageUrl: product.imageUrl, fit: BoxFit.cover, ), ) // Экран деталей Hero( tag: 'product-image-${product.id}', child: CachedNetworkImage( imageUrl: product.imageUrl, fit: BoxFit.contain, ), ) Navigator.push с любым PageRoute автоматически запускает Hero-анимацию между виджетами с одинаковым tag. Продолжительность — 300 мс по умолчанию, контролируется через transitionDuration в MaterialPageRoute или кастомном PageRoute.
Как кастомизировать Hero-элемент с помощью flightShuttleBuilder?
Стандартный Hero не анимирует форму — если в списке изображение обрезано ClipRRect, а на детальном экране нет, во время перехода форма скачет. Решение — flightShuttleBuilder для кастомного виджета во время полёта:
Hero( tag: 'product-image-${product.id}', flightShuttleBuilder: (_, animation, __, fromCtx, toCtx) { return AnimatedBuilder( animation: animation, builder: (_, child) => ClipRRect( borderRadius: BorderRadius.lerp( BorderRadius.circular(12), BorderRadius.zero, animation.value, )!, child: child, ), child: CachedNetworkImage(imageUrl: product.imageUrl, fit: BoxFit.cover), ); }, child: ..., ) Здесь ClipRRect анимирует borderRadius от 12 до 0 вместе с движением элемента. Такой подход в 3 раза гибче стандартного Hero, так как позволяет менять любые свойства во время перехода.
React Native: Shared Element Transition
В React Native нет нативного Hero-перехода. Используем react-native-shared-element или react-navigation-shared-element:
// Список <SharedElement id={`product.${item.id}.image`}> <Image source={{ uri: item.imageUrl }} style={styles.thumbnail} /> </SharedElement> // Детальный экран <SharedElement id={`product.${item.id}.image`}> <Image source={{ uri: item.imageUrl }} style={styles.fullImage} /> </SharedElement> Конфигурация навигатора:
const Stack = createSharedElementStackNavigator(); <Stack.Screen name="ProductDetail" component={ProductDetailScreen} sharedElements={(route) => [ { id: `product.${route.params.product.id}.image`, animation: 'move' }, { id: `product.${route.params.product.id}.title`, animation: 'fade' }, ]} /> Для изображений рекомендуется animation: 'move', для текста — 'fade', так как текст разного размера некрасиво масштабируется. В 95% случаев move-анимация даёт лучший UX.
iOS (SwiftUI): matchedGeometryEffect
@Namespace private var heroNamespace // В списке Image(product.imageName) .matchedGeometryEffect(id: "product-image-\(product.id)", in: heroNamespace) .frame(width: 80, height: 80) // В детальном виде (conditional rendering) if showDetail { Image(product.imageName) .matchedGeometryEffect(id: "product-image-\(product.id)", in: heroNamespace) .frame(width: UIScreen.main.bounds.width, height: 300) } matchedGeometryEffect работает внутри одного View-иерархии. Для модальных экранов (sheet, fullScreenCover) требуется кастомный переход через AnyTransition с GeometryEffect. Подробнее — в официальной документации: matchedGeometryEffect.
Почему возникает мерцание при Hero-переходе?
Оба экрана должны находиться в одном Window (Flutter) или NavigationStack (SwiftUI). В React Native shared element использует native driver для синхронизации. Теги должны быть уникальными и одинаковыми на обоих экранах. Проще всего — использовать ID объекта (например, product.id).
| Проблема | Причина | Решение |
|---|---|---|
| Мерцание элемента | Исчезновение на исходном экране раньше появления на целевом | В Flutter — проверить, что Hero правильно скрывает оригинал. В RN — включить useNativeDriver: true |
| Скачок формы | Разный ClipRRect на экранах |
Использовать flightShuttleBuilder для интерполяции |
| Задержка появления | Изображение высокого разрешения на целевом экране | Показывать thumbnail до загрузки оригинала |
| Конфликт с другими анимациями | Несколько Hero с одинаковым тегом | Всегда использовать уникальные теги (например, product-image-${id}) |
Сравнение платформ для Hero-переходов
| Платформа | Встроенная поддержка | Кастомизация формы | Производительность |
|---|---|---|---|
| Flutter | Да (Hero) | Высокая (flightShuttleBuilder) | Высокая (skia engine) |
| React Native | Нет | Средняя (библиотеки) | Средняя (bridge) |
| SwiftUI | Да (matchedGeometryEffect) | Высокая (AnyTransition) | Высокая (Metal) |
| Jetpack Compose | Да (Modifier.sharedElement) | Средняя (библиотеки) | Высокая (Android RenderEngine) |
Типичные ошибки при Hero-переходах
- Разный URL изображения: thumbnail и оригинал имеют разные URL — Hero работает, но после перелёта изображение дёргается. Используйте один URL с параметрами размера.
- Несовпадение ID: если тег динамический (например,
product.id) и он меняется между экранами, анимация не сработает. - Конфликт с Pop Gesture: на iOS смахивание назад может прерывать Hero-переход, вызывая артефакты. Отключайте gesture recognizers на время анимации.
Что входит в работу
- Аналитика требований и проектирование анимации.
- Реализация на выбранной платформе (Flutter, React Native, SwiftUI или Jetpack Compose).
- Тестирование на 5+ реальных устройствах с разными версиями ОС.
- Оптимизация производительности (профилирование, работа с памятью).
- Документация по интеграции и код с комментариями.
- Консультация по тестированию на реальных устройствах.
- Поддержка в течение 2 недель после завершения.
Сроки и стоимость
Hero-переход для одного типа элемента (изображение или карточка) на одной платформе: от 1 дня. Несколько типов hero-элементов (изображение + текст + иконка) с кастомной анимацией формы: 2–3 дня. Стоимость рассчитывается индивидуально в зависимости от количества платформ и сложности анимации. Оценим ваш проект бесплатно — свяжитесь с нами. Для заказа hero-анимации оставьте заявку на сайте.







