Реалізація Spin-the-Wheel (колесо фортуни) у мобільному додатку
Колесо фортуни — гейміфікаційний елемент, який може як захопити користувача, так і розчарувати зламаною механікою. Ми стикалися з проектами, де проста анімація обертання виглядала неприродно, а виграшний сектор визначався банальним таймером без урахування фізики. Розробляючи колесо під ключ, ми спираємося на точну фізичну модель, дискретні кроки зупинки та візуальний фідбек, який перетворює момент призу на міні-подію. Нижче — технічні деталі, перевірені на 40+ гейміфікаційних проектах за понад 5 років досвіду.
Фізика обертання: як досягти реалізму?
Колесо має імітувати справжній фізичний об'єкт з інерцією та тертям. Просте «крутити N секунд і зупинитися» не працює — потрібне експоненціальне уповільнення. Базова модель на iOS виглядає так:
// Кутова швидкість зменшується кожен кадр
angularVelocity *= decelerationFactor // 0.96–0.98 для повільного уповільнення
// CADisplayLink оновлює кут кожен кадр
currentAngle += angularVelocity * dt
wheelLayer.transform = CATransform3DMakeRotation(currentAngle, 0, 0, 1)
// Зупинка при досягненні мінімальної швидкості
if abs(angularVelocity) < 0.01 { stopAndSnap() }
Параметр decelerationFactor визначає відчуття: 0.99 — важкий барабан із тривалим обертанням, 0.95 — легкий і різкий. Ми підбираємо його під концепцію бренду. Для наочності порівняємо варіанти:
decelerationFactor |
Поведінка |
Час до зупинки (с) |
Типове застосування |
| 0.99 |
Повільне, плавне |
5–7 |
Преміум-бренди, великі призи |
| 0.97 |
Середнє |
3–4 |
Універсальне |
| 0.95 |
Швидке, різке |
1–2 |
Бонуси, щоденні нагороди |
Чому точна зупинка на потрібному секторі — не обман?
Чесна рандомізація та точна зупинка на визначеному секторі сумісні. Алгоритм:
- Виграшний сектор визначається до старту — сервером або клієнтським seed.
- Цільовий кут:
targetAngle = sectorMidpoint + randomOffset(±halfSectorAngle).
-
decelerationFactor коригується так, щоб колесо завершило цілу кількість обертів і потрапило в targetAngle.
Формула для обчислення:
let totalRotation = currentAngle + (Double(minFullRotations) * 2 * .pi) + targetAngle
decelerationFactor = 1 - initialVelocity / totalRotation
Це приховує зумовленість — обертання виглядає природно, а зупинка припадає на потрібний сектор.
Snap-анімація та візуальний фідбек: коли перемога відчувається реальною
Наприкінці обертання додаємо «відскок» через CASpringAnimation:
let snapAnimation = CASpringAnimation(keyPath: "transform.rotation.z")
snapAnimation.fromValue = currentAngle
snapAnimation.toValue = snappedAngle
snapAnimation.stiffness = 200
snapAnimation.damping = 15
snapAnimation.initialVelocity = angularVelocity
Після зупинки запускаємо:
-
Haptic feedback:
UINotificationFeedbackGenerator на iOS, VibrationEffect на Android.
-
Lottie-конфетті: overlay поверх колеса.
-
Підсвічування сектора:
CALayer з анімацією shadowOpacity (три мигання).
Реалізація по платформах: що входить у роботу
Для кожної платформи використовуємо нативні API:
- iOS:
CADisplayLink + CALayer або UIView. Сектори — CAShapeLayer / UIBezierPath.
- Android:
ValueAnimator + кастомний TimeInterpolator (логарифм). Canvas.drawArc.
- Flutter:
AnimationController + кастомний Simulation (спадкоємець SpringSimulation). CustomPainter.
- React Native:
Animated.Value + react-native-svg або готовий пакет.
Зазначимо, що входить у наш проект:
- Фізична модель з регульованим уповільненням
- Логіка зупинки на заданому секторі (із серверною верифікацією)
- Візуальний фідбек (snap, haptic, конфетті)
- Інтеграція з системою нагород (in-app purchase, аналітика)
- Тестування на 10+ пристроях з різними версіями ОС
- Документація з API та кастомізації
Як ми працюємо: етапи та терміни
- Аналіз — обговорюємо механіку, частоту випадання призів, бренд-стиль. Оцінюємо складність інтеграції з вашим бекендом.
- Проектування — обираємо платформу, готуємо прототип фізики в ізоляції.
- Реалізація — пишемо код, налаштовуємо decelerationFactor, тестуємо зупинку.
- Інтеграція — підключаємо логіку призів, аналітику (Firebase, Amplitude), A/B-тести.
- Деплой — завантажуємо в App Store / Google Play, налаштовуємо push-повідомлення (APNs/FCM) для нагадувань.
Терміни: від 2 до 5 днів залежно від складності. Вартість розраховується індивідуально після брифа. Отримайте консультацію — напишіть нам з описом проекту.
Типові помилки та як їх уникнути
- Занадто швидка або повільна анімація — вирішується налаштуванням
decelerationFactor на етапі прототипування.
- Помітна зумовленість — уникаємо фіксованої кількості обертів, використовуємо випадковий зсув у межах сектора.
- Відсутність зворотного зв'язку на старих пристроях — тестуємо haptic та анімації на пристроях 3-річної давності.
- Проблеми з App Store Review — слідкуємо, щоб колесо не імітувало азартні ігри (Section 4.2/5.1). Завжди використовуємо чесну рандомізацію з серверною перевіркою.
Наш досвід понад 5 років розробки гейміфікації гарантує, що колесо працюватиме стабільно та відповідатиме вимогам магазинів додатків. Зв'яжіться, щоб ми оцінили ваш проект.
Інтеграція з аналітикою та push-повідомленнями
Колесо фортуни — інструмент зростання, який потрібно правильно вимірювати. Ми налаштовуємо події: spin_started, spin_completed, prize_claimed, prize_dismissed. У Firebase Analytics або Amplitude це займає 2–3 години і дає повну картину воронки залученості.
A/B-тести через Firebase Remote Config або Amplitude Experiment дозволяють змінювати набори призів, коефіцієнт уповільнення та частоту показу колеса без нового релізу додатку. Це швидше, надійніше та дешевше за альтернативні підходи, що вимагають повного циклу публікації в App Store або Google Play.
Push-повідомлення через APNs та FCM нагадують користувачам про щоденну можливість прокрутити колесо. За даними наших клієнтів, такі нагадування підвищують DAU на 12–18% протягом першого місяця після запуску. Ми налаштовуємо шаблони повідомлень та логіку тригерів на стороні сервера, щоб система працювала автономно. Вартість інтеграції аналітики та push-повідомлень визначається після аналізу проекту.
Анімації в мобільних додатках: Lottie, Rive, Spring та Reanimated
Ми зробили анімації для десятків проєктів — від ігрових інтерфейсів до bank-grade додатків. Знаємо, як змусити 120 fps працювати навіть на Android з ProGuard. Якщо анімація смикається — проблема не в інструменті, а у виборі підходу. Нижче покажемо, як обирати між Lottie та Rive, чому Spring physics перемагає UIView.animate, і як Reanimated 3 вичавлює 60 fps на старих пристроях. Отримайте консультацію щодо вашого проєкту — оцінимо анімаційний шар безкоштовно.
Чому UIView.animate ламається на складних сценаріях
UIView.animate(withDuration:) та ObjectAnimator на Android — правильний вибір для простих переходів. Але щойно анімація стає інтерактивною (користувач тягне елемент, швидкість залежить від жесту), потрібен інший підхід.
На iOS для gesture-driven анімації правильний інструмент — UIViewPropertyAnimator. Він дозволяє призупиняти, обертати та модифікувати анімацію в процесі. Типовий кейс: bottom sheet, який слідує за пальцем, продовжує рух з інерцією після відпускання та притягується до найближчої позиції. З UIView.animate це або не працює зовсім, або потребує ручної фізики.
У SwiftUI withAnimation працює з коробки, але інтерактивність обмежена — немає прямого аналога UIViewPropertyAnimator. Обхідний шлях: .gesture(DragGesture()) + @GestureState + явне обчислення позиції. Або йдемо в SwiftUI Animations API з Animation.spring(duration:bounce:).
Як React Native Reanimated обходить JS-міст
React Native Animated API виконує анімації в JS thread — це джерело джанка при завантаженому bridge. Reanimated 3 вирішує проблему через worklets: функції, які компілюються та виконуються прямо на UI thread без перетину JS-моста.
Приклад: parallax scroll header. На базовому Animated.Value при швидкому скролі FPS падає до 40-45 на mid-range Android. На Reanimated з useAnimatedScrollHandler — стабільні 60 fps, тому що весь перерахунок позиції відбувається на UI thread.
Reanimated 3 з useSharedValue, useAnimatedStyle та withSpring/withTiming — стандарт для анімацій у React Native. Gesture Handler v2 щільно інтегрований: useAnimatedGestureHandler замінює PanResponder і теж працює на UI thread.
Чому Lottie може знижувати FPS на Android і як це виправити
Lottie експортує After Effects анімацію в JSON. На iOS з lottie-ios працює стабільно, але на Android при складних ефектах (blur, частинки, градієнти) векторний рендеринг через Canvas призводить до просадок до 30-40 fps. Рішення — або спрощувати анімацію, або використовувати Rive з апаратним рендерингом. Ми тестували: Lottie-файл з розмиттям 5 МБ на Xiaomi Redmi Note 10 дає 48 fps, аналогічна анімація в Rive (.riv 400 КБ) — 60 fps. Rive працює в 1.25 раза швидше на однаковому пристрої, а при складних ефектах — до 4 разів легше за розміром.
Як вибрати між Lottie та Rive для вашого проєкту?
Обидва інструменти вирішують задачу «дизайнер робить анімацію, розробник додає файл». Але принципово по-різному.
| Критерій |
Lottie |
Rive |
| Формат |
JSON векторна анімація |
Бінарний .riv |
| Інтерактивність |
Ні (лінійне відтворення) |
State Machine, реакції на ввід |
| Продуктивність |
Середня (blur/частинки важкі) |
Апаратний рендеринг Metal/OpenGL |
| Розмір файлу |
2-5 МБ |
200-500 КБ |
| Підтримка платформ |
iOS, Android, Web, Flutter, RN |
iOS, Android, Web, Flutter, RN |
Вибір простий: статична декоративна анімація (splash screen, onboarding ілюстрації) — Lottie. Інтерактивні UI-елементи зі станами — Rive. Наприклад, кнопка з hover, pressed, loading, success — одна Rive-анімація з чотирма станами проти чотирьох окремих Lottie-файлів. Це зменшує обсяг коду та спрощує підтримку.
Як впровадити анімації Lottie за 4 кроки?
-
Експорт з After Effects через плагін Bodymovin — отримуєте
.json.
-
Додавання файлу до ресурсів проєкту (assets або drawable).
-
Ініціалізація плеєра — в iOS використовуйте
LOTAnimationView, в Android — LottieAnimationView.
-
Запуск з параметрами —
loopMode, animationSpeed, contentMode. Для складних сценаріїв — кастомний AnimationListener.
На практиці цей підхід працює, але потребує контролю версій Lottie-файлів. Ми рекомендуємо версіонувати їх разом з кодом і тестувати на цільових пристроях.
Spring-фізика та Hero transitions: як досягти природності
Spring-анімація відчувається природньо тому що імітує фізику — масу, жорсткість і демпфування. У SwiftUI: Animation.spring(response:dampingFraction:). В Android Compose: spring(dampingRatio = Spring.DampingRatioMediumBouncy).
Для Hero-переходів (елемент «перелітає» між екранами) на iOS використовуємо UIViewControllerTransitioningDelegate + UIViewControllerAnimatedTransitioning. У SwiftUI — matchedTransitionSource + navigationTransition(.zoom). На Flutter — Hero віджет, який працює з коробки.
Як уникнути типових помилок у Hero transitions?
Анімація починається нормально, але на цільовому екрані елемент «стрибає» в фінальну позицію. Причина — AutoLayout constraints застосовуються до завершення анімації. Рішення: layoutIfNeeded() в блоці анімації або використання transform замість frame-змін. На Flutter — перевіряйте HeroFlightShuttleConfiguration. Також слідкуйте за VSync: на Android при 90 Гц дисплеї Hero-перехід може виглядати смиканим, якщо не виставлений Choreographer.
Таблиця: Строки виконання та типові сценарії
| Задача |
Час |
Коментар |
| Базові екранні переходи та мікроінтеракції |
1 тиждень |
Стандартні gesture-driven sheets, каруселі |
| Lottie/Rive інтеграція з дизайн-системою |
3-5 днів |
Після отримання фінальних файлів |
| Кастомна інтерактивність з фізикою (сustom sheet, drawer) |
1-2 тижні |
Включає тестування на 4+ пристроях |
Що входить в роботу: анімаційний шар під ключ
- Інтеграція Lottie/Rive-файлів в дизайн-систему
- Код gesture-driven переходів (bottom sheets, drawers, каруселі)
- Тестування на реальних пристроях (iOS, Android)
- Документація з анімацій (архітектура, ключі станів)
- Підтримка при оновленні дизайну (гарантія 30 днів)
У нас 5+ років досвіду в мобільній розробці, понад 30 проєктів з анімаціями, сертифіковані iOS/Android розробники. Замовте анімації під ключ — перша консультація безкоштовна. Напишіть нам у месенджер, щоб узгодити деталі.
Apple Developer Documentation – UIKit Animations використовується як базова специфікація.