Framer Tutorial: Custom device, Input поля, перетекающий loading и радуга в конце

В моей прошлой статье я рассказала про то, как начать с работу с Framer, создавать красивые прототипы с помощью импорта слоев из Sketch, а также показала, как создать симпатичный loading с процентами.

После того, как статью опубликовали на habrahabr — вот здесь, я решила продолжить свои эксперименты с Framer, просто потому что уже не вижу своих дизайнерских будней без этой увлекательной штуковины.

Целью нового прототипа стал опять же loading, но уже другого формата, созданный без импорта слоев из чего бы то ни было. Прототип служит для иллюстрации возможностей Framer и для описания моих экспериментов с кодом.

Итак, вот что мы создадим в этот туториале:

Три экрана простого прототипа:

  1. Экран ввода логина и пароля
  2. Экран загрузки
  3. Экран приветствия

Результат можно посмотреть здесь: http://share.framerjs.com/s6q2nzb8xf3f/

Все элементы интерфейса мы будем создавать сразу во Framer (без импорта слоев из Sketch или Photoshop).

Мы усложним задачу, попробовав создать прототип для девайса, которого нет в списке Viewer во Framer. Но, сперва, смотрим какие девайсы все же есть:

Как видим, здесь нет Samsung Galaxy Tablet. Его то мы и и будем использовать.

Если для прототипа важна точность, тогда нужно найти картинку Samsung Galaxy Tablet равную натуральным размерам планшета (2560*1600 px).

В данном туториале натуральные размеры не принципиальны, как и то, что изображено на экране девайса.

Я использовала вот эту картинку: http://www.androidheadlines.com/wp-content/uploads/2014/09/81WUwHeaVKL._SL1500_.jpg

Ее размеры (1500*1000 px).

Записываем параметры:

Framer.DeviceView.Devices["custom"] =
"deviceType": "tablet"
"screenWidth": 1258
"screenHeight": 784
"deviceImage":"http://www.androidheadlines.com/wp-content/uploads/2014/09/81WUwHeaVKL._SL1500_.jpg"
"deviceImageWidth": 1500
"deviceImageHeight": 1000
Framer.Device.deviceType = "custom"

Размеры экрана 1258 * 784 нам подходят, так как полностью закрывают то, что изображено на экране девайса на картинке.

Создадим бэкграунд слой для экрана “Sign In” :

signInBackground = new BackgroundLayer
backgroundColor: "#fff"

Теперь, можно рисовать на этом слое поля для логина, пароля и кнопку “Sign in”.

Для того, чтобы можно было в поля вписывать пароль и логин, нужно скачать модуль InputField.coffee. Я использовала модуль, созданный Jordan Robert Dobson. Скачать его можно здесь: http://share.framerjs.com/mtdn1oamu2su/

После загрузки, заходим в папку “modules,” копируем файл InputField.coffee и вставляем в папку “modules” своего проекта.

Во Framer записываем:

{InputField} = require 'InputField'

Теперь, мы можем создавать input - поля. Начнем с поля пароль, так как это поле будет расположено по центру экрана:

Password = new InputField
width: 640
height: 100
color: "#212121"
backgroundColor: "#fff"
borderWidth: 1
borderColor: "#60B9F4"
borderRadius: 10
fontSize: 36
fontFamily: "Proxima Nova"
indent: 48
placeHolder: "Type Password"
placeHolderFocus: "********"
Password.center()

В параметрах, мы, кроме основных характеристик, также описали, что должно отображаться в поле, когда мы будем ставить туда курсор — в данном случае просто звездочки: placeHolderFocus: “********”

Дальше, записываем параметры поля логин:

Login = new InputField
width: 640
height: 100
color: "#212121"
y: Password.y - 130
backgroundColor: "#fff"
borderWidth: 1
borderColor: "#60B9F4"
borderRadius: 10
fontSize: 36
fontFamily: "Proxima Nova"
indent: 48
placeHolder: "Type Login"
placeHolderFocus: "example@gmail.com"
Login.centerX()

Как видим, мы задали этому полю положение y = Password.y — 130, то есть на 130 px выше поля Password и выровняли его по X координате по центру.

Теперь создаем кнопку, которая будет находиться ниже поля Password на 160 px:

Button = new Layer
width: 640
height: 100
borderWidth: 1
borderColor: "#60B9F4"
borderRadius: 10
html: "SIGN IN"
backgroundColor: "#60B9F4"
y: Password.y + 160
Button.centerX()
Button.style.color = "#fff"
Button.style.fontSize = "48px"
Button.style.fontFamily = "Proxima Nova"
Button.style.lineHeight = "100px"
Button.style.textAlign = "center"

Напишем заголовок данного экрана. Например, фразу “Welcome to our system!” .

Чтобы в дальнейшем не задавать заголовку отдельно определенное состояние, можем задать для него родительский слой — superLayer. Им будет, например, поле Password. Теперь, заголовок будет менять состояние вместе с родительским для него слоем — полем Password.

Text = new Layer
backgroundColor: null
width: 640
html: "Welcome to our system!"
color: "#212121"
superLayer: Password
y: - 240
Text.centerX()
Text.style.fontSize = "48px"
Text.style.fontFamily = "Proxima Nova"
Text.style.textAlign = "center"

Так как мы уже можем вписывать в поля свой логин и пароль, настало время нажать на кнопку “Sign in”. Сделаем для нее hover.

В состоянии hover кнопка и надпись на ней будут менять цвет и немного увеличиваться в размерах. Чтобы цвет менялся плавно, предварительно зададим кнопке стиль transition и spring curve:

Button.style.transition = "background-color 1s ease"
Button.states.animationOptions = curve: "spring(600,60,0)"

Теперь, можно записывать событие onMouseOver:

Button.onMouseOver ->
Button.style.backgroundColor = "white"
Button.style.color = "#60B9F4"
Button.scale = 1.05

Чтобы кнопка возвращалась в исходное состояние, когда мы уводим от нее мышку, нужно создать обратное событие onMouseOut:

Button.onMouseOut ->
Button.style.backgroundColor = "#60B9F4"
Button.style.color = "white"
Button.scale = 1

Первый экран готов.

Следующий экран загрузки будет появляться после того, как мы нажмем на кнопку.

Первым делом нам надо убрать экран логина и пароля. Пусть он будет сдвигаться влево при нажатии на кнопку. Задаем состояния для элементов экрана.

signInBackground.states.add
Pressed:
x: -2000
Button.states.add
Pressed:
x: -2000
Login.states.add
Pressed:
x: -2000
Password.states.add
Pressed:
x: -2000

Теперь все элементы имеют новое состояние — их положение сдвинуто влево на 2000 px от первоначального. Как видим, мы не задаем отдельное состояние для слоя Text — так как он будет сдвигаться вместе со своим родительским элементом — слоем signInBackground.

Создаем событие — клик на кнопку:

Button.on Events.Click, ->
Button.states.switch("Pressed", time: .4, curve: "easy out")
Password.states.switch("Pressed", time: .4, curve: "easy")
Login.states.switch("Pressed", time: .4, curve: "easy")
signInBackground.states.switch("Pressed", time: 1, curve: "easy out")

Здесь мы также написали время, с которым каждый элемент будет менять свое состояние, а также задали кривую этих изменений.

Начинаем создавать экран загрузки. Нам понадобится 2 бэкграунд слоя. Первый мы сделаем голубым, второй белым.

Причем для второго слоя родительским сделаем первый слой.

Второму слою зададим режим наложения — экран (mixBlendMode : “screen”).

loadColor = new BackgroundLayer
backgroundColor: "#60B9F4"

backgroundLayer = new BackgroundLayer
backgroundColor: "#fff"
style: mixBlendMode : "screen"
superLayer: loadColor

Рисуем 4 абсолютно одинаковых круга (0, 1, 2, 3, 4) с абсолютно одинаковым положением в центре экрана. Чтобы не рисовать отдельно каждый, упростим себе задачу, создав массив (array).

circles = []
for i in [0..3]
circle = new Layer
backgroundColor: "black"
superLayer: backgroundLayer
borderRadius: 100
width: 50
height: 50
blur: 20
name: "circle #{i}"
circle.center()
circles.push(circle)

Родительским слоем для них сделаем слой backgroundLayer. Так как этот слой имеет другой режим наложения (screen) и для него родительским слоем является слой loadColor голубого цвета, то круги стали голубыми, хотя мы задали им backgroundColor: “black”.

Например, если мы напишем вместо “black” — “red”, то они станут розовыми.

Мы также сделали круги размытыми (blur: 20), чтобы они плавно перетекали один в другой.

Но тут возникает вопрос, как сделать так, чтобы они перетекали один в другой, но при этом не выглядели размытыми.

Решается все просто. Мы возвращаемся выше, к тому месту, где описывали слой backgroundLayer и добавляем еще одну строчку — задаем сильный контраст:

backgroundLayer = new BackgroundLayer
backgroundColor: "#fff"
style: mixBlendMode : "screen"
superLayer: loadColor
contrast: 10000

Теперь, круг выглядит как круг, а не как размытое пятно.

Заставим круги двигаться. Для этого создадим анимации.

Их будет 2 — для первого и второго круга, а круг 0 и 3 не двигаются.

load1 = new Animation
layer: circles[1]
properties:
x: circles[0].x - 180
time: .7
curve: "easy"
load2 = new Animation
layer: circles[2]
properties:
x: circles[0].x - 90
time: .7
curve: "easy"

Здесь, первый круг (circles[1]) отодвигается от нулевого круга на 180 px по х координате — x: circles[0].x — 180. Второй круг соответственно — на 90 px.

Мы создали движение в одну сторону — влево, теперь создадим такое же движение вправо.

load11 = new Animation
layer: circles[1]
properties:
x: circles[0].x + 180
time: .7
curve: "easy"
load22 = new Animation
layer: circles[2]
properties:
x: circles[0].x + 90
time: .7
curve: "easy"

Возвращаемся к нашему событию — клик по “Sign In” кнопке и дописываем туда наши новые анимации:

Button.on Events.Click, ->
Button.states.switch("Pressed", time: .4, curve: "easy out")
Password.states.switch("Pressed", time: .4, curve: "easy")
Login.states.switch("Pressed", time: .4, curve: "easy")
signInBackground.states.switch("Pressed", time: 1, curve: "easy out")
load1.start()
Utils.delay .1, ->
load2.start()
Utils.delay .1, ->
load2.on Events.AnimationEnd, ->
load11.start()
Utils.delay .1, ->
load22.start()
Utils.delay .1, ->

Как видим, между каждым перемещением кругов, мы даем задержку в 0,1 секунды ( Utils.delay .1). Движение в правую сторону происходит после того, как заканчивается движение в левую сторону . Поэтому, мы написали load2.on Events.AnimationEnd, ->….., что значит —

когда заканчивается load2 , начинается load11 и load22.

Чтобы анимация длилась вечно , замыкаем цикл, дописывая ниже:

load22.on Events.AnimationEnd, ->
load1.start()
Utils.delay .1, ->
load2.start()
Utils.delay .1, ->

Второй экран готов.

Приступаем к 3 экрану с приветствием и изменением цвета.

Создадим анимацию для круга 0, при котором он будет расширяться на весь экран:

big = new Animation
layer: circles[0]
properties:
borderRadius: 0
width: backgroundLayer.width
height: backgroundLayer.height
y:0
x: 0
blur: 0
curve: "spring(300, 30, 0)"

Теперь, дописываем старт этой анимации ниже в событии клик по кнопке, делая перед ним задержку 2:

Utils.delay 2, ->
big.start()

Нарисуем невидимый слой TextFinish, который представляет собой слово “Hello!”:

TextFinish = new Layer
backgroundColor: null
width: 640
y: 300
opacity: 0
html: "Hello!"
color: "#fff"
TextFinish.centerX()
TextFinish.style.fontSize = "148px"
TextFinish.style.fontFamily = "Proxima Nova"
TextFinish.style.textAlign = "center"
TextFinish.style.lineHeight = "154px"

Добавим ему 3 состояния (одно, при котором он становится видимым, и 2 остальных, где слово Hello меняется на Привет и на Вітаю):

TextFinish.states.add
activate:
opacity:1
nactivate:
html: "Привет!"
oactivate:
html: "Вітаю!"
TextFinish.states.animationOptions = time: .6, curve: "easy out"

Дописываем смену состояний слоя TextFinish в событие клик по кнопке. Теперь полностью все событие выглядит так:

Button.on Events.Click, ->
Button.states.switch("Pressed", time: .4, curve: "easy out")
Password.states.switch("Pressed", time: .4, curve: "easy")
Login.states.switch("Pressed", time: .4, curve: "easy")
signInBackground.states.switch("Pressed", time: 1, curve: "easy out")
load1.start()
Utils.delay .1, ->
load2.start()
Utils.delay .1, ->
load2.on Events.AnimationEnd, ->
load11.start()
Utils.delay .1, ->
load22.start()
Utils.delay .1, ->
load22.on Events.AnimationEnd, ->
load1.start()
Utils.delay .1, ->
load2.start()
Utils.delay .1, ->
Utils.delay 2, ->
big.start()
TextFinish.states.next()

Здесь не хватает только одной, последней строчки — изменения цвета бэкграунда. Чтобы ее добавить, добавляем слою loadColor два состояния:

loadColor.states.add
active:
backgroundColor: "#f87b36"
nactivate:
backgroundColor: "#fa635f"
loadColor.states.animationOptions = time: .6, curve: "easy"

И дописываем в событие:

loadColor.states.next()

Готово!

Я надеюсь мой рассказ пролил свет на то, какие крутые вещи каждый дизайнер может создать благодаря Framer.
С удовольствием послушаю ваши мысли по поводу Framer, и вообще как считаете — пора ли нам дизайнерам начинать кодить?
На этом заканчиваю, но буду и дальше писать статьи на полезные материалы по дизайнерскому прототипированию — программированию.