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

Pavel Chelyuskin
Монографик
4 min readJun 13, 2018

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

Дизайн

Делаем дизайн всех состояний в Figma, определяем размер поля и отступы.

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

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

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

Создаем новый проект во Framer, переключаемся во вкладку Code.

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

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

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

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

В нашем инпуте не хватает редактируемой области. Во 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. Анимация лейбла

При клике в редактируемое поле, лейбл должен уменьшаться и смещаться вверх. Это значит что у inputLabel будут меняться значения y и fontSize.

Чтобы переключаться между двумя состояниями добавим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. Анимация линии

У каждого состояния инпута свой цвет линии подчеркивания. Реализуем эти состояния также при помощи states:

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")

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

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

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. Подсветка ошибки

Осталось добавить проверку на недопустимое заполнение поля. Для этого воспользуемся addEventListener "keyup":

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 = ""

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

Заключение

Готовый код можно скачать на github: https://github.com/monographic/framer-tutorial-input

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

--

--