Прототипування iOS-анімацій з Framer

Пропоную читачам «Хабрахабра» переклад статті «Using Framer to Prototype iOS Animations» з сайту raywenderlich.com.

Статичні, нерухомі прототипи, м'яко кажучи, відстій. Зі статичними прототипами можна показати візуальний дизайн, але не дизайн взаємодії.
Розмірковуючи про важливість дизайну взаємодії для додатків, можна сказати, що статичний прототип — це як пазл з відсутніми шматочками. Так чому б усім не створювати інтерактивні прототипи замість усього цього? Що ж, з допомогою утиліт начебто After Effects прототипування може зайняти занадто багато часу. А сам прототип може так і не знадобитися.
Спробуйте Framer: утиліта для дизайнерів і розробників досить проста у використанні.

У цьому уроці по Framer ми створимо анімоване меню, створене Voleg:


Сфокусуємося на самої цікавої частини — прототипировании анімації згортання і розгортання меню.

Початок
По-перше, скачайте і встановіть наступні програми (для цього уроку можна використовувати безкоштовні пробні версії):
Відкрийте Framer. З'явиться екран привітання:


Кликнемо на Animate (сама ліва іконка), і побачимо проект-зразок.


Зліва знаходиться Панель Коду, праворуч — Панель Прототипу. Між ними — Панель Шарів.
Побродите по коду, спробуйте розібратися в тому, що він робить. Не хвилюйтеся, якщо щось залишиться незрозумілим.
Закрийте цей проект. Ми будемо створювати свій власний.

Створюємо новий прототип
Створіть новий файл у Framer: File->New. Далі — Insert->Layer для створення нового шару.


Клікніть по порожньому місцю у редакторі коду для скасування перегляду атрибутів шару. Тепер ми можемо побачити новий шар в кожній панелі:
  • Як код в Панелі Коду
  • На посилання в Панелі Шарів
  • Як сірий квадрат в Панелі Прототипу


Наведіть курсор миші на ім'я шару в палітрі Шарів, щоб побачити його розташування на прототипі.


Змініть ім'я square в панелі коду.


Клікніть на квадратик зліва від рядка з кодом, щоб переглянути і змінити атрибути шару в палітрі Шарів і щоб переміщати його по Панелі Прототипів.


Перетягніть квадрат в центр прототипу, і подивіться на зміни в Панелях Коду і Верств.


Зміни в шарі шляхом взаємодії з прототипом тут же відображаються в коді — і навпаки. Можливість використовувати як код, так і візуальний редактор для внесення змін дає величезну перевагу прототипування під Framer в протиставленні з Xcode і Swift.
Видалити існуючий код, і впишіть наступний.
square = new Layer
x: 250
y: 542
height: 250
width: 250
backgroundColor: "rgba(255,25,31,0.8)"

І знову в прототипі все змінилося. Досить акуратно, чи не так?

Зауваження. Ви пишіть код у Framer, використовуючи CoffeeScript — проста мова, який компілюється в Javascript. Не хвилюйтеся, якщо ви ніколи його не використали — ви можете багато довідатися про синтаксис з цього уроку.
Зверніть увагу, що відступи мають значення CoffeeScript. Так що переконайтеся, що ваші відступи збігаються з моїми, інакше код не буде працювати. Відступи в CoffeScript замінюють {}.
Символи табуляції та пропуски — не одне і те ж. За замовчуванням Framer використовує табуляцію, так що якщо ви бачите код з пробілами, як цей:


Видаліть пробіли до самого початку рядка і замініть їх табуляцією:


Коли ви копипастите код і перейдіть на новий рядок, завжди видаляйте все до початку рядка. Інакше ваш код може бути інтерпретований як частина чогось іншого.

Перша Framer-анімація
Настав час магії. Ми змусимо червоний квадрат по натисненню перетворитися в помаранчевий коло. Додайте порожній рядок після опису шару, потім вставити наступне:
square.onTap ->
square.backgroundColor = "rgba(255,120,0,0.8)"
square.borderRadius = 150




Найбільша перевага прототипування з Framer перед Xcode і Swift — можливість взаємодіяти з прототипом відразу ж після внесення змін. Відсутність витратного побудови та запуску проекту в Xcode сильно збільшує швидкість прототипування.
Добре, я знаю, про що ви думаєте. Треба б сповільнити анімацію — перехід занадто раптовий, так і користувач не може повернутися назад до червоного квадрата. Це легко виправити.
Замість того, щоб уточнювати, що має відбуватися з квадратом після натискання, ми додамо новий стан.
Замініть доданий код на це:
# 1 
square.states.add
orangeCircle:
backgroundColor: "rgba(255,120,0,0.8)"
borderRadius: 150

# 2
square.onTap ->
square.states.next()

Давайте розберемося.
  1. Це описує новий стан orangeCircle square. Це новий стан встановлює атрибут backgroundColor в помаранчевий і borderRadius 150. Повний список властивостей можна побачити в framer.js документації.
  2. Тут налаштовується подія — натискання, за яким square переходить в наступний стан.
Натисніть на квадрат, щоб побачити новий перехід.


Фігура тепер анімовано переходить в стан orangeCircle і назад в початкову. Все тому, що ми додали стан в цикл станів шару, так що наступне стан після orangeCircle — стан за замовчуванням.

Спробуємо додати ще один стан. Додайте цей рядок після секції 1:
square.states.add
greenRounded:
backgroundColor: ("green") 
borderRadius: 50

Тепер натисніть на квадрат в Панелі Прототипів. Ви побачите цикл з усіх трьох станів.


Всього кількома рядками коду і майже ніякої установкою ми створили гладку (slick) анімацію.
Думаю, тепер ми можемо перейти до дечого складніше і крутіше, як, наприклад, UI.


Використовуємо Framer для створення анімації
Погляньте ще раз на анімацію, яку ми будемо створювати:


Найважливіша частина в створенні анімації — розбити її на прості компоненти. Це допоможе не тільки в розумінні анімації, але й у створенні покрокової інструкції для самого себе. У цій анімації потрібно вирішити три проблеми: selected-стан, deselected, і перехід між ними.

Deselected



Що бачить користувач спочатку:
  • 4 банера, за якими можна тапа.
  • У кожного банера є свій колір, іконка і заголовок.
  • Кожен банер відкидає тінь на нижче розташований банер.

Selected



Що бачить користувач після натискання на банер:
  • Верхня панель з кольором, заголовком і невеликим значком.
  • Білий задній фон.
  • Верхня панель відкидає на нижній шар тінь.
  • Чотири рівномірно розподілені подання внизу — два вгорі, два під ними.
  • Колір уявлень залежить від обраного банера.
Користувач вибирає перехід між двома станами. У deselected-стан користувач тапає на один з кольорових банерів. У selected-стан — на верхню панель.

Перехід від deselected до selected



Ось що відбувається в момент переходу:
  • Всі чотири банера розширюються і рухаються вниз так, що один починається там, де закінчується наступного. Кожен банер у підсумку займає чверть екрану.
  • Банери завжди впорядковані, тому банер, на який натиснули, не впливає на те, як банери з'являються у deselected-стан.
  • Анімація розширення банера стартує повільно, потім значно прискорюється, і закінчується з загасанням.
  • У кожного банера своя тінь.
  • Іконки розширюються від 0 до 100% свого розміру. Анімація починається швидко, закінчується з загасанням.
  • Текст пересувається на 4/5 вниз розширюється банера
Тепер, коли вся анімація розбита, можна приступати до самої веселої частини — її побудові.

Зауваження. Конкретно цю анімацію було неважко розбити, але зазвичай досить корисно записати анімацію за допомогою QuickTime і потім сповільнити її з After Effects або Adobe Photoshop, щоб виділити дрібні деталі. Наприклад, це методика допомогла мені розпізнати, чи варто робити зникнення іконки негайним по тапу на банер, або воно повинно відбуватися поступово.

Розташування
По-перше, завантажити набір картинок для уроку.
В архіві знаходиться все, що знадобиться для створення анімації — іконки, шрифт, текст і подання.
Встановіть Archive font. Це важливо зробити перед відкриттям Sketch-файлу, інакше він не буде коректно відображатися.
Далі, відкрийте SweetStuff.sketch і подивіться, що ми створили.


Повернемося до Framer і створимо новий файл File->New. Далі — Import.


Залиште розмір @1x і знову натисніть Import. Побачимо наступне:


Framer створив змінну sketch, яка містить посилання на всі верстви у нашому Sketch-файлі. В панелі Шарів можна їх побачити.
Зауваження. Переконайтеся, що Sketch-файл був відкритий до натискання Import, інакше Framer не зможе його впізнати.

Наш прототип повинен працювати на багатьох пристроях. Тому створимо змінну device як посилання на вибраний пристрій, і будемо мати все необхідне щодо розміру екрана.
Додайте наступне після змінної sketch:
device = Framer.Device.screen

Для простішого використання додамо константи для квітів.
blue = "rgb(97,213,242)"
green = "rgb(150,229,144)"
yellow = "rgb(226,203,98)"
red = "rgb(231,138,138)"

Далі створимо шар-контейнер для зберігання всього іншого.
container = new Layer
width: device.width
height: device.height
backgroundColor: 'rgba(255, 255, 255 1)'
borderRadius: 5
clip: true

Тут ми створюємо шар container і встановлюємо розмір, рівний розміру екрану пристрою. Потім встановлюємо backgroundColor білий — це буде заднім фоном для вистав. Всі інші верстви будемо додавати в цей шар. Встановлюючи clip true, ми вказуємо, що нічого за межами контейнера показуватися не буде.

Deselected-стан
Почнемо з установки deselected-екрану.


Шари меню. Так як ми знаємо ширину і висоту кожного шару, визначимо їх як константи:
menuHeight = container.height/4
menuWidth = container.width

Додайте наступний код і подивіться, що вийде.
cookieMenu = new Layer
height: menuHeight
width: menuWidth
x: 0
y: 0
backgroundColor: blue

Тут ми створюємо новий шар для меню з печивом, встановлюємо блакитний задній фон, x, y-координати і висоту з шириною.

Тепер потрібно проробити те ж саме з рештою меню, але пам'ятайте — y-координата починається з кінця попереднього шару: y: prevousMenu.y + previousMenu.height.
cupcakeMenu = new Layer
height: menuHeight
width: menuWidth
x: 0
y: cookieMenu.y + cookieMenu.height
backgroundColor: green

fruitMenu = new Layer
height: menuHeight
width: menuWidth
x: 0
y: cupcakeMenu.y + cupcakeMenu.height
backgroundColor: yellow

iceCreamMenu = new Layer
height: menuHeight
width: menuWidth
x: 0
y: fruitMenu.y + fruitMenu.height
backgroundColor: red

Тепер додамо тіні для створення ілюзії того, що кожне меню починається на верху іншого.
В кінці опису кожного шару потрібно додати наступне:
тіньові: 2
shadowBlur: 40
shadowSpread: 3
shadowColor: "rgba(25,25,25,0.3)"

Ура! Але стоп, вона не відображається. Все тому, що шари були додані поверх один одного зверху вниз, з iceCreamMenu нагорі, а не навпаки.


Створимо функцію для зміни положення меню та їх відображення в правильному порядку. Сигнатура визначення функції у Framer виглядає наступним чином:
functionName = ([params]) ->

Додайте наступну функцію для перестановки меню після визначення шарів:
repositionMenus = () ->
iceCreamMenu.bringToFront()
fruitMenu.bringToFront()
cupcakeMenu.bringToFront()
cookieMenu.bringToFront()

repositionMenus()

Функція repositionMenus не приймає аргументів і знизу вгору переносить шари меню наверх. Далі йде виклик функції.
Подивіться на Панель Прототипу — тепер тіні відображаються в правильному порядку.


Додаємо іконки і заголовки
Почнемо з cookieMenu. Додамо наступні рядки коду в кінці нашого файлу:
cookieIcon = sketch.Cookie
cookieIcon.superLayer = cookieMenu

cookieText = sketch.CookieText
cookieText.superLayer = cookieMenu

Тут створюються дві змінні: cookieIcon та cookieText, які ініціалізуються відповідними об'єктами з Sketch — Cookie та CookieText. Потім властивості superLayer присвоюємо шар cookieMenu.

Наступне завдання — розташувати ці шари. cookieIcon повинен розташовуватися в центрі шару-батька (superLayer), а cookieText вирівняний по центру горизонтально, але по вертикалі зміщений вниз відносно шару-батька на 4/5.
Пропишіть, щоб центрувати іконку:
cookieIcon.center()

Для встановлення положення тексту додайте наступне:
cookieText.centerX()
cookieText.y = cookieText.superLayer.height * 0.8

Тепер просто повторіть все це для решти пунктів меню.
cookieIcon = sketch.Cookie
cookieIcon.superLayer = cookieMenu
cookieIcon.center()

cookieText = sketch.CookieText
cookieText.superLayer = cookieMenu
cookieText.centerX()
cookieText.y = cookieText.superLayer.height * 0.8

cupcakeIcon = sketch.Cupcake
cupcakeIcon.superLayer = cupcakeMenu
cupcakeIcon.center()

cupcakeText = sketch.CupcakeText
cupcakeText.superLayer = cupcakeMenu
cupcakeText.centerX()
cupcakeText.y = cupcakeText.superLayer.height * 0.8

fruitIcon = sketch.Raspberry
fruitIcon.superLayer = fruitMenu
fruitIcon.center()

fruitText = sketch.FruitText
fruitText.superLayer = fruitMenu
fruitText.centerX()
fruitText.y = fruitText.superLayer.height * 0.8

iceCreamIcon = sketch.IceCream
iceCreamIcon.superLayer = iceCreamMenu
iceCreamIcon.center()

iceCreamText = sketch.IceCreamText
iceCreamText.superLayer = iceCreamMenu
iceCreamText.centerX()
iceCreamText.y = iceCreamText.superLayer.height * 0.8

Тепер це більш-менш схоже на deselected-стан.


Рефакторинг
Проте, озираючись назад, розуміємо, що як-то на екрані надто багато коду.


Тільки задумайтеся — яким громіздким буде код, коли доведеться прописувати всі стани в деталях.
Замість того, щоб створювати для кожного пункту меню окремо шар, іконку і текст, для спрощення читання і акуратності напишемо функцію.
Замініть все, що ми написали після визначення menuHeight та menuWidth:
# 1
menuItems = []
colors = [blue, green, yellow, red]
icons = [sketch.Cookie, sketch.Cupcake, sketch. Raspberry, sketch.IceCream]
titles = [sketch.CookieText, sketch.CupcakeText, sketch.FruitText, sketch.IceCreamText]

# 2
addIcon = (index, sup) ->
icon = icons[index]
icon.superLayer = sup
icon.center()
icon.name = "icon"

# 3
addTitle = (index, sup) ->
title = titles[index]
title.superLayer = sup
title.centerX()
title.y = sup.height - sup.height*0.2
title.name = "title"

# 4
for menuColor, i in colors
menuItem = new Layer
height: menuHeight
width: menuWidth
x: 0
y: container.height/4 * i
тіньовий: 2
shadowBlur: 40
shadowSpread: 3
shadowColor: "rgba(25,25,25,0.3)"
superLayer: container
backgroundColor: menuColor
scale: 1.00 
menuItems.push(menuItem)
addIcon(i, menuItem)
addTitle(i, menuItem)

repositionMenus = () ->
menuItems[3].bringToFront()
menuItems[2].bringToFront()
menuItems[1].bringToFront()
menuItems[0].bringToFront()

repositionMenus()

Що цей код робить:
  1. Оголошуємо масиви для зберігання пунктів меню, квітів, іконок і заголовків.
  2. Ця функція додає іконки на кожен шар пункту меню.
  3. Те ж саме, тільки з заголовками.
  4. Тут ми проходимо в циклі по кожному пункту меню, створюємо новий шар і викликаємо наші функції для додавання іконок і заголовків. Зауважте, що ці шари зберігаються в масиві menuItems для швидкого доступу до них у майбутньому.
І це, пані та панове, і є чистий код. Йдемо далі.

Перехід до selected-станом
Перший крок — додати новоре стан з іменем collapse головний цикл з menuItems. Обміркуйте це. Що потрібно зробити з кожним menuItem, коли воно переходить у стан collapse?


Потрібно зробити перехід від expanded-стану collapsed-стану


Огляд майбутніх змін:
  • Y-координата шару стає рівною 0.
  • Висота зменшується з ¼ екрану до 1/9 екрану.
  • Іконка поступово зникає.
  • Y-координата тексту змінюється так, що він рухається вгору.
  • Видно тінь тільки від обраного menuItem.
Для початку зосередьтеся на простих речах: висота і y-координата menuItem. Закоментуйте 2 рядки в циклі for, але не видаляйте — вони пізніше знадобляться.
# addIcon(i, menuItem)
# addTitle(i, menuItem)

Зауваження. Щоб закоментувати рядок, натисніть Command +/.

Додамо константу collapsedMenuHeight до інших констант після шару container.
collapsedMenuHeight = container.height/9

Додамо collapsed-стан передmenuItems.push(menuItem):
menuItem.states.add
collapse:
height: collapsedMenuHeight
y : 0

Тепер потрібно змусити menuItems реагувати на подію натискання.
Оголосіть подія для кожного menuItem, відразу після циклу, перед repositionMenus.
#onTap listeners
menuItems[0].onTap ->
for menuItem in menuItems 
menuItem.states.next()
this.bringToFront()

menuItems[1].onTap ->
for menuItem in menuItems 
menuItem.states.next()
this.bringToFront()

menuItems[2].onTap ->
for menuItem in menuItems 
menuItem.states.next()
this.bringToFront()

menuItems[3].onTap ->
for menuItem in menuItems 
menuItem.states.next()
this.bringToFront()

При кожному натисканні на menuItem, цикл проходить по всім елементам menuItems і переводить кожен з них у свій наступний стан. This.bringToFront() виставляє вибране menuItem поверх інших шарів. Пізніше події можна буде легко змінити, так як ми оголосили їх окремо один від одного.

Зауваження. Ключове слово this може бути корисним, коли потрібно звернутися до оброблюваного на даний момент об'єкту, замість використання його імені напряму. Це чистіше, в більшості випадків коротше, і покращує читаність коду.

Перевірте, як працюють натискання.


Останні штрихи
Залишилося повернути іконки і заголовки, і пофіксити кілька проблем. Для цього нам потрібно відстежувати, коли menuItem було обрано.
Додамо змінну після циклу, перед подіями onTap.
selected = false

Ініціалізуємо selected false, тому що спочатку у нас нічого не обрано.

Тепер ми можемо написати функцію для перемикання між selected — і deselected-станами. Перед функцією repositionMenus додамо наступне:
# 1
menuStateChange = (currentItem) ->
# 2
for menuItem in menuItems
menuItem.states.next()
# 3
if !selected
currentItem.bringToFront()
# 4
else
repositionMenus()
# 5
selected = !selected

  1. Приймає параметр currentItem — нажатый пункт меню menuItem.
  2. Проходить за всіма menuItems і переводить кожен елемент в наступний стан.
  3. Якщо жоден menuItem не був обраний, тоді selected дорівнює false, тому виставляємо currentItem вперед.
  4. Якщо menuItem був обраний, тоді selected дорівнює true, тому потрібно повернути меню в початковий стан через функцію repositionMenus().
  5. Нарешті, selected присвоюємо значення, протилежне поточним.
Тепер можна використовувати цю функцію в імплементації onTap. Для кожного пункту меню змініть onTap:
menuItems[0].onTap ->
menuStateChange(this)

Круто. Тепер, якщо подивитися уважно, можна помітити, що коли пункти меню стискуються, тінь виглядає як-то занадто жирно.
Все тому, що всі чотири тіні від шарів накладаються один на одного.


Для виправлення, у menuStateChange змініть цикл:
for menuItem in menuItems
if menuItem isnt currentItem
menuItem.тіньовий = 0
menuItem.shadowSpread = 0
menuItem.shadowBlur = 0
menuItem.states.next()

Якщо шар не є обраним — тінь прибирається, коли шари стискаються.
Навіть зараз анімація виглядає досить класно, але остутствуют дві речі: ікона і заголовок.
Розкоментуйте ті два рядки в циклі menuItems (переконайтеся, що вони — останні рядки в циклі).
addIcon(i, menuItem)
addTitle(i, menuItem)

Пам'ятаєте, коли ми додавали імена дочірнім верствам addIcon та addTitle? Зараз це стане в нагоді. Ці імена допоможуть нам розрізняти шари у menuItem.
Додамо наступні рядки для стиснення меню menuStateChange():
collapse = (currentItem) ->
# 1
for menuItem in menuItems 
# 2
for sublayer in menuItem.subLayers
# 3
if sublayer.name is "icon"
sublayer.animate
властивості:
scale: 0
opacity: 0
time: 0.3
# 4
if sublayer.name is "title"
sublayer.animate
властивості:
y: collapsedMenuHeight/2
time: 0.3

Пройдемося по коду.
  1. Перебираємо всі елементи menuItems.
  2. Для кожного menuItem перебираємо його дочірні шари (subLayers).
  3. Якщо попався шар icon, анимируем до масштабу = 0, непрозорості = 0, протягом 0.3 секунди.
  4. Якщо попався шар title — анимируем Y-координату до центру поточного пункту меню.
Тепер додамо ще одну функцію, одразу після попередньої доданої.
expand = () ->
# 1
for menuItem in menuItems 
# 2
for sublayer in menuItem.subLayers
# 3
if sublayer.name is "icon"
sublayer.animate
властивості:
scale: 1
opacity: 1
time: 0.3
# 4
if sublayer.name is "title"
sublayer.animate
властивості:
y: menuHeight * 0.8
time: 0.3

  1. Перебираємо елементи menuItems.
  2. Перебираємо дочірні шари кожного menuItem.
  3. Якщо дочірній шар — icon, анимируем масштаю до 100%, непрозорість — до 1, протягом 0.3 секунди.
  4. Якщо дочірній шар — title, анимируем Y-координату до menuHeight * 0.8.
Додайте виклики функцій collapse() та expand() menuStateChange().
menuStateChange = (currentItem) ->
# remove shadow for layers not in front
for menuItem in menuItems
if menuItem isnt currentItem
menuItem.тіньовий = 0
menuItem.shadowSpread = 0
menuItem.shadowBlur = 0
menuItem.states.next()
if !selected
currentItem.bringToFront()
collapse(currentItem)
else
expand()
repositionMenus()
selected = !selected

Перевірте Панель Прототипу — анімація іконок і заголовків працює, як треба.


Ми майже закінчили. Залишилося небагато! :]

Налаштування анімації
У Framer будь шар можна анімувати. Настройка анімації визначається наступними параметрами:
  1. properties: width, height, scale, borderRadius і т. д. Ширина, висота, масштаб і радіус кордону відповідно. Шар трансформується з будь-якого стану в той, який ви тут набудували.
  2. time: як довго анімація триває.
  3. repeat: скільки разів повторювати анімацію.
  4. delay: потрібна пауза перед анімацією, і скільки вона повинна тривати.
  5. curve: швидкість анімації.
  6. сurve options: точна настройка швидкості для кривої анімації.
Curve та сurve options виглядають досить заплутано чи не так? Їх можна використовувати для створення прототипу кривий анімації з опціями Framer.js Animation Playground

Так як ми не визначили криву анімації для нашого прототипу, за замовчуванням Framer використовує криву easy:


Виглядає жорстко і неприродно. Нам більше підійде крива spring — вона дозволить краще контролювати все, що відбувається на кроці переходу.
Тепер переведемо все вищесказане цифри налаштувань curveOptions.
Кривий spring (пружина) потрібні наступні параметри:
  1. tension. З якого «матеріалу» вона нібито зроблена. Чим більше число, тим більше швидкість і відскок.
  2. friction. Величина опору. Чим більше число, тим швидше анімація буде затухати.
  3. velocity. Початкова швидкість анімації.
Навіть якщо ви не знаєте цих цифр, просто поиграйтесь з ними.
У нашому прототипі хотілося б бачити дві анімації з різними швидкостями:
Анімація меню: стартує повільно, значно прискоряться і різко сповільнюється.


Анімація іконок і заголовків: розмір іконки змінюється від 100% до 0%, і анімація досить раптова. Вона стартує швидко, але різко згасає.
Порівняно з попередньою анімацією, у цій менше напруженості, і переходи між різними швидкостями відбуваються швидше.

Перед menuItems.push(menuItem) додайте наступне:
menuItem.states.animationOptions =
curve: "spring"
curveOptions:
tension: 200
friction: 25
velocity: 8
time: 0.25

Тут ми присвоюємо spring-анімацію для пунктів меню і виставляємо tension = 200, friction = 25, velocity = 8. Тепер анімація рухається швидше іконок і заголовоков.

Знайдіть всі sublayer.animate і додайте після рядка time в секції properties наступне:
curve: "spring"
curveOptions:
tension: 120
friction: 18
velocity: 5

Тут додадуться однакові spring-анімації для заголовків, іконок.

Ми додамо цей код чотири рази: двічі у collapse і двічі в expand для іконок, і для заголовків. Для порівняння результатів — зразок функції:
collapse = (currentItem) ->
for menuItem in menuItems 
for sublayer in menuItem.subLayers
if sublayer.name is "icon"
sublayer.animate
властивості:
scale: 0
opacity: 0
time: 0.3
curve: "spring"
curveOptions:
tension: 120
friction: 18
velocity: 5
if sublayer.name is "title"
sublayer.animate
властивості:
y: collapsedMenuHeight/2
time: 0.3
curve: "spring"
curveOptions:
tension: 120
friction: 18
velocity: 5

Ось як все виглядає в результаті:


Куди рухатися далі?
Все вийшло! Вітаю з першим прототипом Framer.
Проект можна скачати тут. Він йде трохи далі цього уроку — показує, як відобразити подання в кожному пункті меню. Повний проект разом з таким же прототипом, створений в Xcode + Swift.
  1. 1. Більше дізнатися про Framer можна в офіційній документації їх уроках
  2. Шари Framer можуть реагувати і на інші події, як наприклад pan-, swipe — і pinch-жести. Дізнатися побільше про такі події можна тут.
  3. Що можна робити з станами.
  4. Про curves можна почитати в Framer's Easing Curves у розділі Animate.
  5. Про animation curves і інших технологіях анімацій Framer подивіться документацію по анимациям.
  6. Набратися натхнення можна тут.
  7. Нарешті, дізнатися про анімація зі Swift і Xcode можна в книзі iOS Animations by Tutorials.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.