Всем привет! Сегодня делимся с вами познавательным материалом, перевод которого подготовлен специально для студентов курса «ReactJS/React Native-разработчик».



Итак, начнем.


Все мы видели такие поля для ввода:



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

Выглядит великолепно. Гладко. Безупречно.

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

Вы можете посмотреть видео или продолжить читать. Все зависит от того, что вам больше по душе.


Чего мы добиваемся?


Есть два варианта того, с чем мы имеем дело.
Первый – когда на поле ввода нет фокуса.



Надпись появляется внутри поля ввода, а ее размер равен размеру текстового поля. Цвет тусклый. Это очень похоже на свойство placeholder.

И второй вариант, когда на поле ввода есть фокус.



Надпись оказывается над полем ввода, ее размер меньше, и цвет отличается от цвета вводимого текста.

Простейшая реализация


Наконец-то мы можем приступить к работе. Пока без каких-либо анимаций.
Как оказалось, у нас есть два состояния UI:

  1. На поле нет фокуса и надпись внутри поля.
  2. На поле есть фокус, надпись над полем ввода.



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

Поскольку надпись должна находиться в разных местах, и мы не хотим, чтобы она влияла на размещение компонентов, мы будем позиционировать ее абсолютно. Чтобы убедиться, что места для нее хватит, придется добавить в wrapping view отступ сверху.

class FloatingLabelInput extends Component {
  state = {
    isFocused: false,
  };

  handleFocus = () => this.setState({ isFocused: true });
  handleBlur = () => this.setState({ isFocused: false });

  render() {
    const { label, ...props } = this.props;
    const { isFocused } = this.state;
    const labelStyle = {
      position: 'absolute',
      left: 0,
      top: !isFocused ? 18 : 0,
      fontSize: !isFocused ? 20 : 14,
      color: !isFocused ? '#aaa' : '#000',
    };
    return (
      <View style={{ paddingTop: 18 }}>
        <Text style={labelStyle}>
          {label}
        </Text>
        <TextInput
          {...props}
          style={{ height: 26, fontSize: 20, color: '#000', borderBottomWidth: 1, borderBottomColor: '#555' }}
          onFocus={this.handleFocus}
          onBlur={this.handleBlur}
        />
      </View>
    );
  }
}

<FloatingLabelInput
  label="Email"
  value={this.state.value}
  onChange={this.handleTextChange}
/>

После предыдущих шагов, мы можем достичь следующего:

https://snack.expo.io/Sk006AbdW?session_id=snack-session-JRMksbYK3

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

Почему бы не использовать placeholder?


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

Вместо этого, мы хотим, чтобы надпись находилась внутри текстового поля, когда на нем фокус. А еще мы хотим, чтобы она сдвигалась вверх, когда на поле ввода появляется фокус, и этого можно достичь только, если мы работаем с одним и тем же элементом.

Как насчет анимации?


На самом деле осталась самая легкая часть.

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

Из этого руководства мы можем выделить следующее:

  • Animated.Value будет означать, есть фокус на поле (1) или нет (0);
  • Мы постепенно будем менять число на (1) при фокусировке и на (0) в противном случае;
  • В виде этого числа мы будем отражать стиль надписи: в (0) и (1) мы определим стили, а React Native автоматически вычислит и применит промежуточные стили. И даже цвета.

Реализовать это все несложно.

Animated.Value нам нужно будет инициализировать в componentWillMount.

componentWillMount() {
  this._animatedIsFocused = new Animated.Value(0);
}

Затем, поскольку значение этого числа должно основываться на том, есть ли фокус на поле ввода или нет, и поскольку у нас уже есть этот бит информации о состоянии, мы можем добавить функцию componentDidUpdate, которая будет менять это число в зависимости от this.state:

componentDidUpdate() {
  Animated.timing(this._animatedIsFocused, {
    toValue: this.state.isFocused ? 1 : 0,
    duration: 200,
  }).start();
}

Теперь, чтобы отразить стиль надписи в таких терминах, нам понадобится внести всего два изменения:

Меняем на Animated.Text.

Вместо того, чтобы использовать условия для определения стилей, определяйте их следующим образом:

const labelStyle = {
  position: 'absolute',
  left: 0,
  top: this._animatedIsFocused.interpolate({
    inputRange: [0, 1],
    outputRange: [18, 0],
  }),
  fontSize: this._animatedIsFocused.interpolate({
    inputRange: [0, 1],
    outputRange: [20, 14],
  }),
  color: this._animatedIsFocused.interpolate({
    inputRange: [0, 1],
    outputRange: ['#aaa', '#000'],
  }),
};

https://snack.expo.io/Hk8VCR-dZ?session_id=snack-session-AJ4vulSVw

Еще кое-что


Если в демо выше вы попробуете что-нибудь ввести, а затем убрать фокус с поля ввода, то увидите нечто странное.



К счастью, это довольно просто исправить. Нам нужно всего лишь поменять две строчки в коде.

Мы хотим проверить, пустое ли поле ввода и изменить состояние на “unfocused”, только если оба следующих условия выполняются:

  • Значение поля ввода пустое;
  • На поле нет фокуса.

В противном случае, мы хотим, чтобы применялся стиль “focused”, а замещающая надпись поднималась вверх.

Теперь, когда мы отслеживаем состояние поля ввода, мы можем легко получить доступ к его значению с помощью this.props.



https://snack.expo.io/ByZBAC-dZ?session_id=snack-session-YNZSqhqOC


На этом всё. До встречи на бесплатном вебинаре.

Комментарии (5)


  1. dvmedvedev
    16.09.2019 17:02
    +1

    Горшочек, не вари!


  1. bisquitie
    16.09.2019 18:12

    Борщ мне в глаза! Это же реализуется парочкой css-правил.


    1. Fox_exe
      16.09.2019 21:17

      Поправка — Для плавности анимации нужен уже CSS 3+ (Transition/Animation), а без него — да, хватит и :hover + position.


    1. kahi4
      17.09.2019 10:34

      Да, только статья о React Native, в котором нет css и анимации ручками нужно делать.


  1. apxi
    17.09.2019 09:51
    -1

    >> Выглядит великолепно. Гладко. Безупречно.
    Это кому как, для меня выглядит так себе.

    Потом сидим и думаем, а почему сайт тормозит, а откуда столько говна кода лезет.
    На картошку таких улучшателей дизайна нужно отправить.
    Как Вы будете себя чувствовать если ваша IDE вместо того чтобы быстро работать, начнет красиво анимировать выделение текста, плавно открывать мешюшку секунд 20, при компиляции выводить прогресс бар сияющий всеми цветами радуги, а еще после каждого обновления все будет меняться (цвет, положения кнопок и иерархия меню)?