Прототип интерактивного текстового поля с помощью Framer. Часть 1.

Pavel Chelyuskin
Jun 13, 2018 · 4 min read

Я расскажу как реализовать в своем прототипе интерактивные текстовые поля.

Дизайн

Image for post
Image for post

У текстового поля будет 4 состояния:

  • Базовое
  • При наведении
  • Активное
  • Ошибка

Создание элементов прототипа

Делаем новый слой в котором будут лежать все элементы текстового поля:

inputContainer = new Layer
width: 360
height: 76
x: 170
y: 162
Image for post
Image for post

Чтобы избавиться от серого фона, допишем:

backgroundColor: "transparent"

Теперь добавим лейбл. Для этого вместо обычного Layer будем использовать TextLayer :

inputLabel = new TextLayer
¬ text: "Your name"
¬ parent: inputContainer # Размещаем inputLabel внутри inputContainer
¬ fontFamily: "Basis Grotesque Pro"
¬ fontSize: 16
¬ lineHeight: 1.25
¬ y: 18
¬ color: "#808080"

Добавляем линию подчеркивания текстового поля:

inputLine = new Layer
¬ parent: inputContainer
¬ height: 2
¬ width: inputContainer.width
¬ y: 54
¬ backgroundColor: "#e5e5e5"

И сообщение об ошибке:

inputError = new TextLayer
¬ text: "" # Пока оставляем это поле пустым
¬ parent: inputContainer
¬ fontFamily: "Basis Grotesque Pro"
¬ fontSize: 12
¬ lineHeight: 1.25
¬ y: 60
¬ color: "#bf3048"
¬ opacity: 0
Image for post
Image for post

В нашем инпуте не хватает редактируемой области. Во Framer нет компонента для создания редактируемого поля, но так как Framer сделан на Javascript, мы можем воспользоваться методами document.createElement и _element.appendChild чтобы cамостоятельно создать в DOM нашего прототипа редактируемое поле:

inputControl = document.createElement("input")
inputControl.style["width"] = "360px"
inputControl.style["height"] = "56px"
inputControl.style["font-family"] = "Basis Grotesque Pro"
inputControl.style["font-size"] = "16px"
inputControl.style["line-height"] = "2"
inputControl.style["outline"] = "none"
inputControl.style["color"] = "#000000"
inputControl.placeholder = ""
inputControl.value = ""
inputControl.focus()
inputContainer._element.appendChild(inputControl)

Итак, теперь у нас есть все необходимые элементы нашего редактируемого поля.

Интерактив

1. Анимация лейбла

Чтобы переключаться между двумя состояниями добавимstates к нашему лейблу:

inputLabel.states =
¬ basic:
¬ ¬ fontSize: 16
¬ ¬ y: 18
¬ active:
¬ ¬ fontSize: 12
¬ ¬ y: 0

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

inputContainer.onClick ->
¬ inputLabel.animate("active")

Анимация работает, но выглядит не очень приятно. Анимация по умолчанию во Framer медленная и линейная. Чтобы не задавать настройки анимации каждый раз, я устанавливаю дефолтное значение анимации для всего прототипа, в самом начале кода:

Framer.Defaults.Animation =
¬ curve: "ease-in-out"
¬ time: 0.15
Дефолтная линейная анимация
Исправленная анимация

2. Анимация линии

inputLine.states =
¬ basic:
¬ ¬ backgroundColor: "#e5e5e5" # Серый
¬ hover:
¬ ¬ backgroundColor: "#bfbfbf" # Темно-серый
¬ active:
¬ ¬ backgroundColor: "#000000" # Черный
¬ error:
¬ ¬ backgroundColor: "#bf3048" # Красный

Добавим анимацию линии при наведении и при клике:

inputContainer.onClick ->
¬ inputLine.animate("active")
¬ inputLabel.animate("active")
inputContainer.onMouseOver ->
¬ inputLine.animate("hover")
inputContainer.onMouseOut ->
¬ inputLine.animate("basic")

Пока еще поле ведет себя не так, как хотелось бы. При клике линия становится темной, но как только мы двигаем курсор мыши, она становится серой, потому что срабатывает событие onMouseOut.

Чтобы это исправить, добавим проверку — является ли поле в фокусе. Для этого в самом начале зададим переменную inputFocus = false. И перепишем наши события:

inputContainer.onClick ->
inputFocus = true
¬ inputLine.animate("active")
¬ inputLabel.animate("active")
inputContainer.onMouseOver ->
if !inputFocus
¬ ¬ inputLine.animate("hover")
inputContainer.onMouseOut ->
if !inputFocus
¬ ¬ inputLine.animate("basic")
Image for post
Image for post

Теперь клик и ввод текста работает хорошо. Но мы не сделали еще две вещи:

  • Что происходит, когда пользователь закончил вводить текст? Сейчас поле все еще остается активным даже если кликнуть за его пределами;
  • Если пользователь ничего не ввел, мы должны показать возвращение лейбла обратно в середину поля.

3. Возврат в базовое состояние

backLayer = new Layer
¬ width: Canvas.width
¬ height: Canvas.height
¬ backgroundColor: "transparent"

И добавим несколько событий, происходящих при клике на него:

backLayer.onClick ->
¬ inputFocus = false
¬ inputLine.animate("basic") # Возвращаем линию в базовое состояние
¬ if inputControl.value == "" # Проверяем, пустое ли поле ввода
¬ ¬ inputLabel.animate("basic")

4. Подсветка ошибки

inputControl.addEventListener "keyup", (ev) ->
¬ if inputControl.value == "Donald Trump" # Недопустимое значение
¬ ¬ inputError.text = "You can't use this name" # Текст ошибки
¬ ¬ inputError.animate(opacity: 1)
¬ ¬ inputLine.animate("error")
¬ else # Если ошибка исправлена, возвращаем все обратно
¬ ¬ inputError.animate(opacity: 0)
¬ ¬ inputLine.animate("active")
¬ ¬ Utils.delay 0.15, -> # Задержка, чтобы надпись успела плавно исчезнуть
¬ ¬ ¬ inputError.text = ""

Посмотрим, что получилось:

Заключение

У меня получилось больше 100 строчек кода. Довольно много для одного инпута. К счастью, нам не нужно писать столько кода для каждого текстового поля. В следующей части я расскажу, как переиспользовать получившийся код.

Монографик

Проектирование цифровых продуктов

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store