Анимация во Framer для начинающих
Что такое Framer ?
Framer это программа для создания прототипов, в которой прототип создается сразу в коде, на языке CoffeeScript, который компилируется в JavaScript.
Framer позволяет создавать интерактивные анимации, облегчает разработчикам понимание того, как прототип должен функционировать и как представленную анимацию можно воплотить в жизнь. Еще он учит дизайнера понимать код.
Созданный с помощью Framer прототип можно отдавать тестировать клиентам прямо с девайса в натуральную величину, при этом времени на его создание значительно меньше, чем если бы прототип создавали разработчики.
Как может показаться в начале, Framer — сложная программа, требующая знания кода, хотя на самом деле писать код в ней намного легче, чем кажется.
Для работы с Framer достаточно нарисовать прототип в Sketch или Photoshop и придумать взаимодействие между слоями. Также здесь можно создавать свои слои, анимировать и менять их свойства.
Само написание кода во Framer интересный и увлекательный процесс, который не требует особой подготовки, к тому же на сайте программы есть очень подробная документация с примерами и туториалами.
В анимации, о которой речь пойдет дальше, я хотела отобразить loading, где проценты загрузки выполняют роль индикатора процесса.
Для создания данной анимации нужно знать 4 вещи: Layer, State, Event, Animation.
Layer — слой, может быть создан непосредственно во Framer либо им может быть любой слой из Sketch или Photoshop.
State — это состояние объекта (слоя). Например, объект будет иметь какое то default состояние (такое, каким мы его нарисовали в Sketch или Photoshop) и бесконечное число состояний, которые мы ему можем дать, меняя его свойства (прозрачность, тень, размытость, размеры, положение и т.д.)
Event — это событие. Им может быть клик, swipe, drag, scroll и тд. При возникновении каждого из этих событий, мы можем менять состояния объектов (State), анимируя слои.
Animation — кроме состояний, мы можем создавать непосредственно во Framer либо задавать слоям, анимации (например вращение, изменение прозрачности и тд).
Итак, приступим к созданию анимации.
В результате у нас должно получиться следующее:
1 Шаг:
Рисуем прототип в Sketch или Photoshop.
Я пользовалась Sketch. Скачать мой прототип можно здесь
Импортируем этот прототип во Framer.
Sketch (или Photoshop) с прототипом при импорте должны быть открыты.
В Framer выбираем тип девайса (иконка Viewer), в моем случае это iPhone 6+.
Так как мой прототип Sketch нарисован в маштабе 1:1, а для iPhone 6+ мы пользуемся множителем 3, то при импорте выбираем из выпадающего списка @3x и не забываем об этом (после, при описании позиции объекта по x и y координатам, которые можно посмотреть в Sketch, мы их умножаем на 3 соответственно).
Все слои в Sketch должны быть объединены в группы, только так они будут видимыми во Framer. Импорт не произойдет, если название хоть одной группы будет начинаться с цифры (выдаст ошибку).
После импорта должна появиться слудующая строчка:
# Import file "iPhone_loading" (sizes and positions are scaled 1:3)
Sketch = Framer.Importer.load("imported/iPhone_loading@3x")
“loading@3x” — означает, что прототип импортировался с множителем 3х, который мы предварительно выбрали. “Sketch” можно заменить любым словом или знаком (например $) — и ставить его впереди описания каждого слоя из Sketch прототипа. Например, в Sketch прототипе все слои включены по умолчанию, но поскольку верхние слои нас пока не интересуют, мы можем сделать их прозрачными:
Sketch.loadingfinish.opacity = 0 или можно написать $.loadingfinish.opacity = 0
где $ = Sketch
loadingfinish — это самый верхний слой.
Для того, чтобы не писать постоянно Sketch.слой или $.слой можно вставить следующую строчку:
Utils.globalLayers $, тогда чтобы сделать слой loadingfinish прозрачным, достаточно написать loadingfinish.opacity = 0
Детальнее про Utils можно прочитать во Framer документации здесь.
Здесь я оставила знак $.
Далее зададим для слоя header родительский элемент (SuperLayer), по отношению к которому header будет дочерним.
$.header.superLayer = Framer.Device.screen
Теперь header — дочерний элемент экрана девайса, а значит всегда будет видимым и неподвижным.
2 Шаг:
Задаем дальше параметры для слоев loadingfinish, oval, textButton, bgorange, ovalsecond, делая их прозрачными:
$.loadingfinish.opacity = 0
$.oval.opacity = 0
$.textButton.opacity = 0
$.bgorange.opacity = 0
$.ovalsecond.opacity = 0
Меняем положение y координаты для слоев textButton, blueButton, buttonbg, loadingfinish, loadingbg, time, text, чтобы они сдвинулись по вертикали либо вверх (значение с минусом), либо вниз. Как видим, мы установили для слоя loadingfinish прозрачность и сдвинули его вниз на 100px.
$.loadingfinish.y = 100
$.textButton.y = 3201
$.blueButton.y = 3201
$.buttonbg.y = 3201
$.loadingbg.y = 3201
$.time.y = -100
$.text.y = 3201
Для слоя line меняем x координату, чтобы слой выдвигался слева по горизонтали.
$.line.x = -1700
И наконец для слоя blueButton устанавливаем scale = 0, чтобы слой исчез.
$.blueButton.scale = 0
В итоге мы получили слой с пустыми белыми прямоугольниками.
Дальше нам нужно, чтобы при клике на второй прямоугольник сверху, появлялся оранжевый круг, расширялся (заливая оранжевым цветом белый прямоугольник), и затем увеличивался на весь экран. Для этого мы создаем новый слой (которого нет в Sketch прототипе), называем его Rectangle и задаем ему параметры как в CSS.
Первоначально это квадрат с заданными параметрами высоты и ширины (436px), оранжевого цвета, расположенный точно по центру белого прямоугольника( x: 410,
y: 730). Чтобы из него получился круг, мы задаем ему border radius равный половине стороны(436/2 = 218px), делаем его невидимым с помощью параметра scale:0.
Rectangle = new Layer
width: 436
height: 436
backgroundColor: "#FF7841"
borderRadius: 218
x: 410
y: 730
scale: 1
В JavaScript это бы выглядело так:
(function () {
var Rectangle;
Rectangle = new Layer({
width: 436,
height: 436,
backgroundColor: '#FF7841'
borderRadius: 218,
x: 410,
y: 730,
scale: 1
});
}.call(this));
Как видим, в CoffeeScript, в отличие от JavaScript, нет многочисленных кавычек и запятых, тут они заменяются пробелами — поэтому следим, чтобы параметры в описании объекта начинались с новой строки через 3 пробела, иначе будет выдавать ошибку.
3 Шаг:
Чтобы наш круг из невидимого стал видимым, а потом собственно прямоугольником, задаем ему новые состояния с помощью Rectangle.states.add:
Rectangle.states.add
toFront:
scale:0.5
toRectangle:
scale: 1
opacity: 1
x: 37
y: 727
width: 1168
height: 436
borderRadius: 12
All:
scale:7
time:1.1
Мы добавили 3 состояния:
- toFront (назвать можно как угодно) — при этом состоянии круг становится в половину больше
- toRectangle — круг становится прямоугольником с параметрами точно такими же, как белый прямоугольник в нашем Sketch файле.
- All — прямоугольник разворачивается на весь экран. Здесь мы также задали время 1.1 секунд, с которым это происходит.
4 Шаг
Теперь создаем событие (Events) — Клик (Click), при котором круг начнет трансформироваться согласно заданным состояниям (States).
Клик будет идти на второй прямоугольник сверху, который называется в Sketch прототипе слоем second.
Пишем — $.second.on Events.Click — что на человеческом языке читается так — при клике на второй прямоугольник происходит следующее — и описываем что именно:
$.second.on Events.Click, ->
Rectangle.states.switch("toFront", curve: "spring(300, 30, 0)")
Utils.delay .2, ->
Rectangle.states.switch("toRectangle", time: 1, curve: "easy")
Utils.delay 1, ->
Rectangle.states.switch("All", time: 1, curve: "ease out")
Состояние Rectangle переключается на toFront, потом 0,2 секундная задержка ( Utils.delay .2) — затем переключается на toRectangle — задержка 1 секунда ( Utils.delay 1) и последнее переключение на состояние All. Здесь также задаются параметры curve и time — меняя их, анимация будет происходить по разному.
Получаем:
5 Шаг
Дальше нам нужно задать состояния для остальных слоев, которые мы определили в начале. Пишем:
$.text.states.add
Show:
y: 921$.time.states.add
Show:
y: 321$.line.states.add
Show:
x: 36
$.buttonbg.states.add
Show:
y:1509$.blueButton.states.add
Show:
scale:1
y: 1773
Extend:
scale: 10$.textButton.states.add
Show:
opacity: 1
y:1845$.loadingbg.states.add
Show:
y:0$.oval.states.add
Show:
opacity: 1
$.loadingfinish.states.add
Succes:
y: 861
opacity: 1
Для слоя blueButton мы добавили 2 состояния — в первом (Show) он просто показывается, во втором (Extend) — расширяется на весь экран.
6 Шаг:
Теперь мы должны уничтожить Rectangle, который мы создали, чтобы он не мешал остальным слоям. Мы пишем:
Rectangle.on "end", ->
Rectangle.destroy()
$.bgorange.opacity = 1
$.text.states.switch("Show", time: 1.2, curve: "ease")
$.time.states.switch("Show", time: 1.2, curve: "ease")
$.line.states.switch("Show", time: .8, curve: "spring(500, 50, 0)")
$.buttonbg.states.switch("Show", time: 2, curve: "easy out")
$.blueButton.states.switch("Show", time: 2, curve: "easy")
$.textButton.states.switch("Show", time: 2, curve: "easy out")
Что означает — когда Rectangle закончил свои трансформации — он уничтожается (destroy), на его месте показываются указанные нами слои, согласно своим, заданным ранее, состояниям. Слои меняют свои состояния с определенным нами временем и по определенной нами кривой (например: time: 1.2, curve: “ease”)
Не забываем обновлять экран ( ⌘+R ).
Получаем:
7 Шаг:
Теперь нам нужно создать текстовый слой — который будет отображать проценты при loading.
textLabel = new Layer
width: 500
height: 150
backgroundColor: null
textLabel.center()
# Style text
textLabel.style.color = "#fff"
textLabel.style.fontSize = "100px"
textLabel.style.fontFamily = "Proxima Nova"
textLabel.style.lineHeight = "100px"
textLabel.style.textAlign = "center"
Задаем ему backgroundColor: null, то есть пустой (если backgroundColor мы не укажем, то он по умолчанию станет синим) и выравниваем по центру всего экрана благодаря функции textLabel.center().
Описываем параметры стиля # Style text.
Причем заметим, что мы не пишем какой именно текст будет отображаться, иначе мы бы написали html: “какой то Text”.
Чтобы менялись проценты мы добавим строчку кода в конце.
А пока нам нужно вспомнить, что в Sketch прототипе есть слой под названием “Oval”, который должен крутиться вокруг своей оси. Чтобы это происходило — мы создаем новую анимацию и называем ее “spinLoad”.
spinLoad = new Animation
layer: $.oval
properties:
rotation: 760
time: 2
curve: "easy"
Rotation мы поставили 760 — значит он будет крутиться 2 полных оборота и 40 градусов дополнительно (360 + 360 + 40). Можно поставить любое значение градусов.
Добавляем еще одну анимацию showTextLabel:
showTextLabel = new Animation
layer: textLabel
properties:
opacity: 1
time: 4
curve: "easy"
Теперь создаем условие, при котором будет начинаться смена процентов при вращении овала. Пишем:
$.oval.on "change:rotation", ->
percentage = Utils.modulate $.oval.rotation, [0,760], [0,100]
textLabel.html = Utils.round percentage, 0
Тут мы тоже поставили градус вращения овала 760, а смену процентов — от 0 до 100 (то есть за 2,4 оборота овала проценты сменяться от 0 до 100).
8 Шаг:
Все, указанные выше, анимации, будет происходить при нажатии на кнопку “Save” (слой blueButton), поэтому создаем новое событие клик (Events.Click) :
$.blueButton.on Events.Click, ->
$.blueButton.states.switch("Extend", time: 1, curve: "easy in")
$.loadingbg.states.switch("Show", time: .5, curve: "spring(500, 50, 0)")
$.textButton.states.switch("default")
$.oval.states.switch("Show", time: 1, curve: "easy in")
spinLoad.start()
Utils.delay 1.5, ->
showTextLabel.start()
Что означает — по клику на кнопку, голубой бэкграунд (слой blueButton) начинает расширяться (состояние Extend), слой loadingbg меняет свое состояние на “Show”, текст “Save” на кнопке возвращается к своему первоначальному состоянию: $.textButton.states.switch(“default”) (то есть исчезает), появляется овал и запускается анимация spinLoad.start(), даем задержку 1,5 секунд и запускаем анимацию showTextLabel.start().
И наконец, последнее условие — после того, как проценты дойдут до 100 и овал совершит все свои обороты, должен появиться круг с галочкой, означающий успешное завершение операции.
spinLoad.on Events.AnimationEnd, ->
$.loadingfinish.states.switch('Succes')
$.oval.opacity = 0
textLabel.opacity = 0
Получаем итоговую анимацию!