Анимация во Framer для начинающих

Nadiya Abrosimova
8 min readFeb 3, 2016

--

Что такое 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 состояния:

  1. toFront (назвать можно как угодно) — при этом состоянии круг становится в половину больше
  2. toRectangle — круг становится прямоугольником с параметрами точно такими же, как белый прямоугольник в нашем Sketch файле.
  3. 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

Получаем итоговую анимацию!

--

--