В этой серии статей я разрабатываю и кодирую различные общие компоненты пользовательского интерфейса с помощью React и ‹размещаю здесь решение CSS›.

Предисловие

С тех пор, как я начал учиться программировать, я увлекся проектированием и созданием красивых веб- и мобильных пользовательских интерфейсов. Недавно я принял своего рода мышление сделай сам, когда дело доходит до реализации любых компонентов пользовательского интерфейса в наших приложениях React. Я знаю, что неразумно изобретать колесо, когда существует так много замечательных библиотек / наборов инструментов, которые предоставляют вам базовые строительные блоки пользовательского интерфейса, такие как Элементарный пользовательский интерфейс и Дизайн муравьев, но я утверждаю, что для того, чтобы действительно углубите свои навыки UI / UX, вы должны хотя бы раз попытаться построить собственное колесо (а).

Итак, в этой серии вы можете присоединиться и наблюдать, как я создаю эти различные компоненты пользовательского интерфейса с помощью React и какое решение CSS является самым популярным на улицах (на данный момент для меня это стилизованные компоненты). Компоненты, которые я буду создавать, представляют собой относительно простые компоненты, такие как кнопка, всплывающая подсказка, модальное окно, карточка и т. Д., Чтобы эти сообщения были краткими.

Ладно, хватит болтовни. Давайте перейдем к нашему первому компоненту пользовательского интерфейса.

Переключить переключатель

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

Вот GIF, показывающий, как выглядит простая версия:

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

Дизайн

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

  • Иногда у мяча есть небольшая задержка перед запуском скользящей анимации, вызывая ожидание и обеспечивая более ощутимое взаимодействие.
  • Скользящая анимация не является линейной, она идет медленнее в начале / конце и быстрее в середине.
  • Цвет фона имеет своего рода эффект растущей / сужающейся ряби при переключении переключателя.
  • У мяча есть тонкая тень, чтобы сделать его более различимым и кликабельным.

После этих наблюдений следующим шагом было подумать о свойствах моего компонента ToggleSwitch. Я знал, что хочу иметь возможность управлять разными цветами компонента с помощью свойств, а также, возможно, размером компонента. Кроме того, пользователь должен иметь возможность изначально установить переключатель в положение переключения. Итак, в конце концов я придумал эти defaultProps для компонента ToggleSwitch:

const green = '#22e222';
const lightGrey = '#f5f5f5';
const grey = '#ddd';
const white = '#fff';
ToggleSwitch.defaultProps = {
  initial: false,
  width: 80,
  padding: 3,
  ballColor: white,
  ballColorActive: lightGrey,
  bgToggled: green,
  bgClear: white,
  borderColor: grey,
};

«Начальный» реквизит должен быть самоописывающим, а реквизиты «ширина» и «заполнение» используются для управления размером, в то время как остальные реквизиты контролируют цвета в разных состояниях.

Теперь мы можем посмотреть, как мы можем использовать эти реквизиты в наших стилизованных компонентах.

Реализация

Последний компонент React выглядит так:

class ToggleSwitch extends Component {
  constructor(props) {
    super(props);
    this.toggle = this.toggle.bind(this);
    this.state = {
      toggled: props.initial || false,
    };
  }
toggle() {
    // Update local state first and then call toggle handler
    this.setState(state => ({ toggled: !state.toggled }),
      () => this.props.onToggle(this.state.toggled)
    );
  }
render() {
    return (
      <ToggleSwitchWrapper>
        <Toggle
          onClick={this.toggle}
          toggled={this.state.toggled}
          {...this.props}
        >
          <ToggleBall
            toggled={this.state.toggled}
            {...this.props}
          />
          <RippleBg
            visible={this.state.toggled}
            {...this.props}
          />
        </Toggle>
      </ToggleSwitchWrapper>
    );
  }
}

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

Так что давайте пропустим ToggleSwitchWrapper и сразу перейдем к хорошему, исследуя стилизованные компоненты Toggle и RippleBg.

const Toggle = styled.div`
  display: flex;
  align-items: center;
  overflow: hidden;
  position: relative;
  transform: translate3d(0, 0, 0);
  background-color: ${props => props.bgClear};
  height: ${props => (props.width / 2)}px;
  width: ${props => props.width}px;
  border-radius: ${props => props.width / 4}px;
  padding: ${props => props.padding}px;
  border: 1px solid ${props => props.toggled ?
    props.bgToggled :
    props.borderColor
  };
`;

Первым интересным моментом здесь является transform: translate3d(0,0,0); часть, которая позволяет нам использовать мощь GPU в наших анимациях. Без этой строки я заметил, что анимация пульсации (которую я покажу вам через секунду) выходит за пределы компонента контейнера Toggle.

Еще одна интересная вещь здесь - это то, как мы используем свойства, данные нашему компоненту, для определения ширины, высоты, отступов и стилей границ. На мой взгляд, возможность доступа к реквизитам внутри стилей - вот что делает стилизованные компоненты такими потрясающими. Если вы еще не пробовали, вам обязательно стоит попробовать! Прекратите восторженный маркетинговый голос.

Помните, как мы заметили на этапе проектирования, что изменение цвета фона имело волнообразную анимацию?

Я попытался воссоздать эту анимацию с помощью компонента в стиле RippleBg:

const RippleBg = styled.div`
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  position: absolute;
  z-index: 1;
  background-repeat: no-repeat;
  background-position: 50%;
  pointer-events: none;
  transition: transform 0.5s, opacity 0.3s ease;
  opacity: ${props => props.visible ? 1 : 0};
  background-image: radial-gradient(
    circle, ${props => props.bgToggled} 10%, transparent 10.01%
  );
  transform: ${props => props.visible ?
    'scale(10, 10)' :
    'scale(0, 0)'
  };
`;

Должен признать, что я беззастенчиво взял анимацию пульсации из этой удивительной реализации пульсации на чистом CSS, так как знал, что на то, чтобы понять, как это сделать самому, потребовалось бы слишком много времени (говоря о лицемерии - хотя, строя свое колесо, вы болты заворачивать тоже не нужно 😉).

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

Хорошо, теперь у нас есть только ToggleBall, на который можно посмотреть, и вот он:

const ToggleBall = styled.div`
  z-index: 2;
  border-radius: 50%;
  background-color: ${props => props.ballColor};
  box-shadow: 0px 0px 3px rgba(0,0,0,0.2);
  transition: transform 0.3s cubic-bezier(1,.19,.15,.7);
  transition-delay: 0.1s;
  will-change: transform;
  border: 1px solid ${props => props.borderColor};
  height: ${props => (props.width / 2) - (props.padding * 2)}px;
  width: ${props => (props.width / 2) - (props.padding * 2)}px;
  transform: ${props => props.toggled ?
    `translateX(${props.width - (props.width / 2)}px)` :
    'translateX(0px)'
  };
  &:active {
    background-color: ${props => props.ballColorActive};
  }
`;

Здесь учтены наблюдения, сделанные на этапе проектирования. У мяча есть прямоугольная тень, чтобы он выскочил, и мы применяем небольшую задержку к переходу, чтобы создать это предвкушение потения ладоней. Мы также используем кубическую кривую Безье, чтобы сделать переход медленным в начале, быстрым в середине и снова медленным в конце. Если вам интересно, как я создал эту кривую, проверьте этот онлайн-инструмент, чтобы найти простые кривые Безье. Здесь стоит обратить внимание на строку will-change: transform;, которая сообщает браузеру, какие свойства собираются изменить, чтобы он мог заранее выполнить некоторые оптимизации, прежде чем свойство действительно изменится. Использование will-change может помочь вам достичь этой сладкой цели 60 кадров в секунду для ваших анимаций.

Заключение

Сделать этот относительно простой компонент тумблера было круто! Я кое-что узнал о создании плавной анимации, тем более что я никогда раньше не реализовывал волновую анимацию, поэтому было интересно наконец понять, как ее можно создать с помощью чистого CSS. Я думаю, что это действительно добавило компоненту ценности UX.

Наконец, я надеюсь, что я объяснил все шаги, которые потребовались для создания этого компонента, с достаточной детализацией, чтобы ничто не казалось слишком волшебным или неполным. Я обнаружил, что на удивление сложно объяснить ваш мыслительный процесс, связанный с проектированием и кодированием чего-либо. Надеюсь, эта серия статей будет вам полезна или, по крайней мере, мотивирует вас начать собственный период обучения DIY в качестве веб-разработчика.

Вот полный исходный код простой версии без ярлыков:

Послесловие

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

Вот как можно использовать последний компонент ToggleSwitch с внутренними и внешними метками:

<ToggleSwitch
  onToggle={this.handleToggle}
  bgToggled='red'
  bgClear='blue'
  outerLabel='Select language'
  innerLabelLeft='fi'
  innerLabelRight='en'
  innerLabelColor='#fff'
/>

А вот полный исходный код для него:

Вот и все, до встречи в следующем выпуске!

Мы нанимаем!

Вы хотите создавать захватывающие и интересные приложения с использованием современных веб-технологий? Ознакомьтесь с нашими открытыми вакансиями и присоединяйтесь к нам в Taito United!

Не можете найти подходящие должности? Желаете приключений? Отправьте свое резюме и открытую заявку на адрес [email protected] - мы будем более чем рады выслушать вас и увидеть, что вы готовите 🍳