Після завершення ключової дії у додатку користувач очікує візуального відгуку. Без анімації тріумфу емоція гасне, а retention падає. Confetti-анімація — це інженерне завдання: синтезувати десятки частинок, не викликавши просадок FPS і не порушивши UX. Наші інженери з 10-річним досвідом та понад 40 успішними проектами з інтеграції particle effects гарантують плавну анімацію. Зв'яжіться з нами, щоб обговорити інтеграцію під ключ.
Технічні складності рендерингу confetti
Основна проблема — рендеринг багатьох частинок без просадок FPS. На iOS CAEmitterLayer вирішує це апаратно, але неправильні параметри (надто високий birthRate, складні форми) можуть навантажити навіть флагмани. На Android кастомний Canvas.onDraw() — вузьке місце: кожне перемальовування синхронне з UI-потоком. При 200+ частинках потрібен SurfaceView або TextureView з окремим потоком. У Flutter CustomPainter працює на Dart-потоці, але при активній фізиці може викликати затримки.
Друга складність — узгодження з платформенними гайдлайнами. App Store Review Guidelines (Section 4.2) вимагають, щоб анімація не заважала основному функціоналу. Для Android важливо не блокувати back-button. Ми враховуємо ці нюанси на етапі проектування.
iOS: CAEmitterLayer — нативний підхід
CAEmitterLayer — правильний інструмент для particle effects на iOS. Як зазначено в документації Apple, це рекомендований спосіб. Рендеринг через Metal, анімація на render thread — main thread не бере участі.
func startConfetti(in view: UIView) {
let emitter = CAEmitterLayer()
emitter.emitterPosition = CGPoint(x: view.bounds.midX, y: -10)
emitter.emitterShape = .line
emitter.emitterSize = CGSize(width: view.bounds.width, height: 0)
let colors: [UIColor] = [.systemRed, .systemBlue, .systemYellow, .systemGreen, .systemPurple]
let shapes = ["square", "circle", "triangle"]
emitter.emitterCells = colors.flatMap { color in
shapes.map { _ in
let cell = CAEmitterCell()
cell.contents = UIImage(systemName: "circle.fill")?.withTintColor(color).cgImage
cell.birthRate = 8
cell.lifetime = 4.0
cell.velocity = 200
cell.velocityRange = 100
cell.emissionLongitude = .pi
cell.emissionRange = .pi / 4
cell.spin = 3.5
cell.spinRange = 1.0
cell.scaleRange = 0.5
cell.scale = 0.4
cell.color = color.cgColor
cell.alphaSpeed = -0.15
return cell
}
}
view.layer.addSublayer(emitter)
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
emitter.birthRate = 0
}
DispatchQueue.main.asyncAfter(deadline: .now() + 6.0) {
emitter.removeFromSuperlayer()
}
}
CAEmitterCell.birthRate — частинок в секунду на комірку. При 5 кольорах × 3 формах × 8 частинок/с = 120 частинок/с сумарно. На 4 секунди lifetime — до 480 частинок одночасно на екрані. На iPhone 12+ — комфортно, FPS не падає нижче 58 кадрів/с. На iPhone SE 2nd gen — починає грітися при 480 частинках; знижуємо birthRate до 4–5 для mid-range пристроїв.
Для кастомних форм confetti (прямокутники, зірки): створюємо UIGraphicsImageRenderer, малюємо форму, конвертуємо в CGImage для cell.contents.
Детальні параметри CAEmitterCell
- `birthRate`: кількість частинок в секунду (рекомендується 4–8 для confetti) - `lifetime`: час життя частинки (сек) - `velocity`: початкова швидкість в пікселях/с - `emissionLongitude`: кут вильоту в радіанах - `spin`: початкове обертання в радіанах/с - `scale`: масштаб частинкиПорівняння: CAEmitterLayer vs SpriteKit vs готові бібліотеки
| Рішення | Продуктивність | Гнучкість | Складність |
|---|---|---|---|
| CAEmitterLayer | Висока (Metal) | Середня (тільки частинки) | Низька |
| SpriteKit | Середня (CPU/GPU) | Висока (фізика, сцени) | Середня |
| ConfettiSwiftUI | Висока (обгортка над CAEmitter) | Низька (обмежені параметри) | Дуже низька |
Android: Canvas-based або бібліотека
Кастомний ConfettiView через Canvas:
class ConfettiView(context: Context) : View(context) {
private val particles = mutableListOf<ConfettiParticle>()
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private var animator: ValueAnimator? = null
data class ConfettiParticle(
var x: Float, var y: Float,
var vx: Float, var vy: Float,
val color: Int,
val size: Float,
var rotation: Float,
val rotationSpeed: Float,
var alpha: Float = 1f
)
fun start() {
repeat(80) {
particles.add(ConfettiParticle(
x = Random.nextFloat() * width,
y = -Random.nextFloat() * 100,
vx = Random.nextFloat() * 6 - 3,
vy = Random.nextFloat() * 4 + 3,
color = listOf(Color.RED, Color.BLUE, Color.YELLOW, Color.GREEN).random(),
size = Random.nextFloat() * 12 + 6,
rotation = Random.nextFloat() * 360,
rotationSpeed = Random.nextFloat() * 6 - 3
))
}
animator = ValueAnimator.ofFloat(0f, 1f).apply {
duration = 5000
addUpdateListener {
updateParticles()
invalidate()
}
start()
}
}
private fun updateParticles() {
particles.forEach { p ->
p.x += p.vx
p.vy += 0.1f
p.y += p.vy
p.rotation += p.rotationSpeed
if (p.y > height * 0.7f) p.alpha -= 0.02f
}
particles.removeAll { it.alpha <= 0 || it.y > height + 50 }
}
override fun onDraw(canvas: Canvas) {
particles.forEach { p ->
paint.color = p.color
paint.alpha = (p.alpha * 255).toInt()
canvas.save()
canvas.translate(p.x, p.y)
canvas.rotate(p.rotation)
canvas.drawRect(-p.size/2, -p.size/2, p.size/2, p.size/2, paint)
canvas.restore()
}
}
}
ValueAnimator + invalidate() — це перемальовування Canvas на кожному кадрі. При 80 частинках завантаження CPU становить менше 10% на Snapdragon 8xx. При 200+ частинках зі складними формами — переходимо на SurfaceView з окремим render thread.
Готова бібліотека: nl.dionsegijn:konfetti:2.0.4 — пристойна, підтримує кастомні форми та KonfettiView.
Порівняння: кастомний Canvas vs konfetti
| Рішення | Продуктивність | Гнучкість | Залежності |
|---|---|---|---|
| Кастомний Canvas | Середня (залежить від частинок) | Висока | Немає |
| konfetti | Висока (оптимізований) | Середня | Одна бібліотека |
Flutter
// Через CustomPainter
class ConfettiPainter extends CustomPainter {
final List<ConfettiParticle> particles;
ConfettiPainter(this.particles);
@override
void paint(Canvas canvas, Size size) {
for (final p in particles) {
final paint = Paint()..color = p.color.withOpacity(p.alpha);
canvas.save();
canvas.translate(p.x, p.y);
canvas.rotate(p.rotation);
canvas.drawRect(Rect.fromCenter(center: Offset.zero, width: p.size, height: p.size * 0.5), paint);
canvas.restore();
}
}
@override
bool shouldRepaint(ConfettiPainter old) => true;
}
У Flutter простіше використовувати готовий пакет confetti: ^0.7.0 — там є ConfettiController з play(), stop(), параметри для напрямку, кольорів, форм.
Як інтегрувати confetti за 4 кроки?
- Визначте сценарії виклику: після покупки, завершення рівня, підписки.
- Виберіть платформу та стек: нативний модуль або кроссплатформне рішення.
- Реалізуйте модуль confetti з урахуванням дизайну та оптимізації.
- Протестуйте на реальних пристроях різних сегментів.
Чому CAEmitterLayer у 2–3 рази продуктивніший за SpriteKit?
CAEmitterLayer рендерить частинки через Metal на GPU, повністю минаючи main thread. SpriteKit використовує окремий процес, але потребує більше пам'яті для сцен. Для простих частинок (без фізики) CAEmitterLayer ефективніший на 60% по CPU.
Чому важлива оптимізація під різні пристрої?
Confetti з 480 частинок на iPhone SE 2nd gen може викликати троттлінг та перегрів. На флагманах це непомітно. Наша практика показує, що для mid-range Android-пристроїв (Snapdragon 6xx) безпечний ліміт — 120 частинок. Ми автоматично визначаємо пристрій і зменшуємо birthRate або використовуємо менш ресурсоємні форми. Гарантуємо, що анімація не підвісить UI.
Як ми забезпечуємо якість та гарантії?
Ми пройшли модерацію понад 40 додатків з анімаціями. Наші інженери сертифіковані з iOS та Android (Apple Certified iOS Developer, Google Associate Android Developer). Надаємо гарантію на інтеграцію: безкоштовна підтримка 30 днів після деплою. Замовте консультацію — оцінимо ваш проект.
Етапи роботи та що входить
| Етап | Що робимо | Результат |
|---|---|---|
| Аналітика | Визначаємо сценарії виклику confetti, вимоги до форм та поведінки | Технічне завдання |
| Проектування | Вибираємо стек, проектуємо архітектуру (модуль конфетті) | Діаграма компонентів |
| Реалізація | Пишемо код, інтегруємо в додаток | Працюючий прототип |
| Тестування | Профілюємо на реальних пристроях (5+ моделей), виправляємо баги | Звіт тестування |
| Деплой | Публікуємо в сторах, налаштовуємо прапорці включення | Доступ до білду |
Зазначимо: Що входить в роботу:
- Вихідний код модуля confetti
- Документація по API та кастомізації
- Налаштування push-сповіщень, якщо confetti прив'язана до подій
- Навчання ваших розробників (1 година онлайн)
- Підтримка 30 днів після здачі
Терміни та вартість
Базова інтеграція через готову бібліотеку — 4–8 годин. Кастомна реалізація з унікальними формами, фізикою та оптимізацією під різні пристрої — від 1 до 3 днів. Вартість розраховується індивідуально. Отримайте консультацію — ми запропонуємо оптимальне рішення.







