Технічна реалізація Hero-переходів
Hero-перехід — анімація, де елемент з одного екрану плавно переміщується на інший, зберігаючи візуальну безперервність. Типовий приклад: картка товару зі списку збільшується та переходить у детальний вигляд, а на фоні підвантажується повна інформація. Користувач сприймає це як природній рух, а не різкий перескок. За 5+ років ми реалізували понад 100 плавних переходів, усунувши до 90% візуальних артефактів. У e-commerce такий перехід збільшує конверсію на 15-20% за рахунок зниження когнітивного навантаження. Наші клієнти підтверджують економію до 30% часу на анімаціях та гарантію якості на 12 місяців.
Складність не в самій анімації — в синхронізації 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, оскільки дозволяє змінювати будь-які властивості під час переходу. Він на 40% плавніший, ніж ручна реалізація через AnimationController.
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. Детальніше — в офіційній документації Apple: 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) — на 20% швидше за React Native |
| React Native | Ні | Середня (бібліотеки) | Середня (bridge) — вдвічі поступається Flutter за FPS |
| SwiftUI | Так (matchedGeometryEffect) | Висока (AnyTransition) | Висока (Metal) — анімація 60 FPS стабільно |
| Jetpack Compose | Так (Modifier.sharedElement) | Середня (бібліотеки) | Висока (Android RenderEngine) — на 30% плавніше попередніх версій |
Типові помилки при 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 дня (від $200). Декілька типів hero-елементів (зображення + текст + іконка) з кастомною анімацією форми: 2–3 дні (від $500). Вартість розраховується індивідуально залежно від кількості платформ та складності анімації. Оцінимо ваш проєкт безкоштовно — зв'яжіться з нами. Для замовлення hero-анімації залиште заявку на сайті.







