Рубрики
Без рубрики

Как построить анимированные микроинтехии в реакции

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

Автор оригинала: Christian Sepulveda.

Микроинжития направляйте пользователя через ваше приложение. Они усиливают ваш пользовательский опыт и предоставляют восторг.

Возможно, вы видели некоторые из примеров Microinteraction на Дриблинг или Кодепен Отказ Но вы знаете, как построить собственную библиотеку подобных виджетов пользовательских интерфейсов?

В этой статье я сосредоточусь на анимированных микроинтеграциях, используя Реагировать , Популярное, ориентированное на компонент-ориентированные пользовательские интерфейсы. Я построю три взаимодействия для поиска:

  • Открыть и закройте текстовое поле
  • Перейти к верхней части экрана
  • Встряхнуть (указывает на ошибку)

Я буду использовать несколько разных реализаций:

Вот a Живая демонстрация и код, который включает в себя Отказ

Что такое микроинжитие?

Дэн Саффер (кто написал книгу) дает нам это Определение : «Микроинжития содержат моменты продукта, которые вращаются вокруг одного случая использования – у них есть одна главная задача».

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

Почему я должен заботиться о микроинтяциях?

Microinteraction могут предоставить обратную связь и сделать ваше приложение незабываемым. Когда пользователи имеют так много вариантов приложений, лучшие микроинтегии могут быть Clichéd лучше Mousetrap, который вы должны построить.

Но я не дизайнер UX. Так что я предлагаю читать Ник Бабич ‘s пост о микроинтеграциях.

Начиная

Я буду использовать Create-React-App Для загрузки приложения React Application, но любой метод настройки реагирования будет работать. Кроме того, мне нравится Материал-ui Так что я тоже импортирую. (Этот выбор произвольно – вы можете использовать другую библиотеку виджета или вручную вручную элементы.)

create-react-app search-box-animation
cd search-box-animation
npm install --save material-ui react-tap-event-plugin

Компонент: простой поле поиска

Я создам простую окно поиска. Он будет содержать два элемента: кнопка иконки поиска и текстовое поле. Я создам функциональный компонент без гражданства для поля поиска. (Функциональные компоненты без гражданства – это функции, которые представляют компоненты реагирования и не поддерживают состояние, то есть используйте SetState . Вы можете узнать больше в этом Учебное пособие или мой предыдущий Post .)

Searchbox.js.

import React from 'react';
import {TextField, IconButton} from 'material-ui'
import SearchIcon from 'material-ui/svg-icons/action/search';
const SearchBox = ({isOpen, onClick}) => {
    const baseStyles = {
        open: {
            width: 300,
        },
        closed: {
            width: 0,
        },
        smallIcon: {
            width: 30,
            height: 30
        },
        icon: {
            width: 40,
            height: 40,
            padding: 5,
            top: 10
        },
        frame: {
            border: 'solid 1px black',
            borderRadius: 5
        }
    };
const textStyle = isOpen ? baseStyles.open : baseStyles.closed;
const divStyle = Object.assign({}, textStyle, baseStyles.frame);
    divStyle.width += baseStyles.icon.width + 5;
return (
        
onClick()}>
); }; export default SearchBox;

(Я буду использовать OnClick Callback позже.)

isopen Свойство устанавливает Поиск поле открыть или закрытый рендеринг.

Используя компоненты более высокого порядка для отдельных проблем

Я мог бы изменить Поиск Для обычного компонента и добавить код, который откроется и закрывает текстовое поле при нажатии, например.

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

Компоненты высшего порядка (HOC) – это функции, которые возвращают новый компонент. Этот компонент обернут компонент (ы) и добавляет функциональность. Я создам HOC, чтобы добавить поведение Open/Close для Поиск Отказ

Создать Расширение-анимация.js.

import React, {Component} from 'react';
const makeExpanding = (Target) => {
    return class extends Component {
        constructor(props) {
            super(props);
            this.state = {isOpen: false};
        }

        onClick = () => {
            this.setState({isOpen: !this.state.isOpen});
        };

        render() {
            return (
                
            );
        }
    }
};
export default makeExpanding;

Обновить App.js следующим образом:

import React, {Component} from 'react';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';

// (Make material-ui happy)
// Needed for onTouchTap
// http://stackoverflow.com/a/34015469/988941
import injectTapEventPlugin from 'react-tap-event-plugin';
injectTapEventPlugin();

import SearchBox from './SearchBox'
import makeExpanding from './expanding-animation';

const ExpandingSearchBox = makeExpanding(SearchBox);

class App extends Component {
    render() {
        //https://css-tricks.com/quick-css-trick-how-to-center-an-object-exactly-in-the-center/
        const style = {
            position: 'fixed',
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
        };

        return (
            
                
); } } export default App;

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

Работает, но открытие и закрытие – это Jarring. Анимация может сгладить эффект.

Анимация

Есть три общих подхода к анимациям.

  1. CSS переходы
  2. CSS анимации
  3. Быстрый и многократный рендеринг элемента для моделирования движения (обрамление к ручному ключу)

CSS переходы Измените значение свойства (как ширина) в течение некоторой продолжительности. Изменение не должно быть линейным; Вы можете указать функции для изменения значений.

CSS анимации Измените стиль для элемента (как размер, цвет и положение). Каждый постепенный стиль является ключевым кадром. Вы создаете серии KeyFrame для достижения желаемого эффекта.

Оба тактики CSS неоднократно представляют элементы для моделирования движения. Вы можете сделать расчеты самостоятельно, то есть вариант (3). Несколько JavaScript Animation Frameworks используют этот подход, управляя расчетами. (Я буду использовать реактивное движение в более позднем примере.)

Я буду использовать все эти методы в приведенных ниже примерах, но я начну с переходов CSS.

Расширение поля поиска

Расширяющаяся анимация текстовой коробки нуждается в одном свойстве CSS: переход

Изменить Расширение анимации.js следующим образом,

import React, {Component} from 'react';
const animationStyle = {
    transition: 'width 0.75s cubic-bezier(0.000, 0.795, 0.000, 1.000)'
};
const makeExpanding = (Target) => {
    return class extends Component {
        constructor(props) {
            super(props);
            this.state = {isOpen: false};
        }

        onClick = () => {
            this.setState({isOpen: !this.state.isOpen});
        };

        render() {
            return (
                
            );
        }
    }
};
export default makeExpanding;

Глядя на изменение в строке 21, Дополнительные слова , Поиск Будет ли этот стиль с его существующими стилями в строке 29 и 31 ниже. (Я вернусь в свойство перехода CSS в строке 2 в минуту.)

Обновить Searchbox.js.

import React from 'react';
import {TextField, IconButton} from 'material-ui'
import SearchIcon from 'material-ui/svg-icons/action/search';
const SearchBox = ({isOpen, onClick, additionalStyles}) => {
    const baseStyles = {
        open: {
            width: 300,
        },
        closed: {
            width: 0,
        },
        smallIcon: {
            width: 30,
            height: 30
        },
        icon: {
            width: 40,
            height: 40,
            padding: 5,
            top: 10
        },
        frame: {
            border: 'solid 1px black',
            borderRadius: 5
        }
    };
    
    let textStyle = isOpen ? baseStyles.open : baseStyles.closed;
    textStyle = Object.assign(textStyle, additionalStyles ? additionalStyles.text : {});
    
    const divStyle = Object.assign({}, textStyle, baseStyles.frame, additionalStyles ? additionalStyles.frame : {});
    divStyle.width += baseStyles.icon.width + 5;
    
    return (
        
onClick()}>
); }; export default SearchBox;

С объединенными стилями анимация вступит в силу.

Результатом является гладкое расширение ширины текстового поля, давая внешний вид, который он открывается. CSS Переход Свойство контролирует это (от линии 2 в Расширение анимации.js ).

transition: 'width 0.75s cubic-bezier(0.000, 0.795, 0.000, 1.000)'

Я призываю вас читать Документация Для имущества перехода CSS, так как существует множество вариантов. В примере есть три параметра:

  1. Свойство для изменения: ширина
  2. Продолжительность перехода: 0,75с
  3. Функция для контроля времени: Cubic-Bezier (0,000, 0,795, 0,000, 1.000) '

Пока я выбрал Cubic-Bezier Как функция, линейный или легкость одними размеры. Есть интерактивные инструменты, которые помогут вам выбрать эти значения, такие как этот Cubic-Bezier Builder Отказ

Перемещение поля поиска

Проверьте следующую концепцию анимации, которую я нашел на Dribble:

В взаимодействии есть несколько элементов; Но я хотел бы сосредоточиться на движении окна поиска в верхнюю часть экрана.

Я могу переместить мою скромную поисковую коробку с переходом CSS. Создать новый HOC, Read-Up-Animation.js

import React, {Component} from 'react';
const animationStyle = {
    transform: 'translateY(-150px)',
    transition: 'transform 1s ease'
};
const makeMoveUp = (Target) => {
    return class extends Component {
        constructor(props) {
            super(props);
            this.state = {moveTop: false};
        }

        onClick = () => {
            this.setState({moveTop: !this.state.moveTop});
        };

        render() {
            return (
                
            );
        }
    }
};
export default makeMoveUp;
view rawmove-up-animation.js hosted with ❤ by GitHub

Это как MakeExpanding Функция HOC, кроме перевода (двигаться вверх). Кроме того, стиль анимации применяется только к внешней рамке ( div ).

Обновить App.js ,

import React, {Component} from 'react';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';

// (Make material-ui happy)
// Needed for onTouchTap
// http://stackoverflow.com/a/34015469/988941
import injectTapEventPlugin from 'react-tap-event-plugin';
injectTapEventPlugin();

import SearchBox from './SearchBox'
import makeMoveUp from './move-up-animation';
const MoveUpSearchBox = makeMoveUp(SearchBox);
class App extends Component {
    render() {
        //https://css-tricks.com/quick-css-trick-how-to-center-an-object-exactly-in-the-center/
        const style = {
            position: 'fixed',
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
        };

        return (
            
                
); } } export default App; view rawApp.js-2 hosted with ❤ by GitHub

и вы должны увидеть

Возможно, вы хотите надувной эффект. Вы могли бы использовать Реактивное движение Отказ Это популярная библиотека RACT, которая использует динамику пружины для контроля анимации. (Хорошее введение, по Nash Vail , это здесь .)

npm install --save react-motion

Создать Spring-Up-Animation.js

import React, {Component} from 'react';
import {Motion, spring, presets} from 'react-motion'
const makeSpringUp = (Target) => {
    return class extends Component {
        constructor(props) {
            super(props);
            this.state = {moveTop: false};
        }

        onClick = () => {
            this.setState({moveTop: !this.state.moveTop});
        };

        render() {
            const style = {
                translateY: this.state.moveTop ? spring(-150, presets.wobbly) : spring(0)
            };
            return (
                
                    {({translateY}) => (
                        
                    )}
                
            );
        }
    }
};
export default makeSpringUp;
view rawspring-up-animation.js hosted with ❤ by GitHub

Поскольку это не учебное пособие на реагирование, я кратко обобщусь, как это работает. Реактивное движение обертывается анимированный компонент, Цель с собственным компонентом Движение Отказ (Есть и другие компоненты реагирования-движения, такие как Переходноемость и в шахматном движении .)

Интерполаты с реактивным движением, используя динамику пружины, серию временных значений. Он обеспечивает значения анимированным компонентом в виде стиля. Этот стиль определяет визуальный переход в анимации.

Изображение ниже показывает результат (с шаткой пружиной, чтобы выделить эффект).

Вы можете использовать реагирование для диапазона эффектов. Например, вы можете изменить текстовое поле для расширения, как весна.

( Spring-Up-Animation.js и Move-Up-Animation.js имеют одинаковую onclick Государственная логика, поэтому я изменил общие части. Детали здесь . Несомненно

Встряхивая окно поиска

Я хочу предоставить отзыв пользователям о ошибочных запросах. Вы можете использовать сообщения об ошибках, но я хотел бы сделать что-то более причудливое: встряхните поле поиска.

Я мог бы использовать React-Motion, но я хотел бы посмотреть на другую технику: анимация ключевых кадров.

Реактивные анимации это библиотека RACT для анимации ключевых кадров. Он введет ключевые кадры CSS в стиль DOM. (Другие примеры использовали только встроенные стили.)

npm install --save react-animations

Мне также нужна библиотека, как Радий или Афродита , чтобы обрабатывать впрыск листа стиль CSS. Я выбрал Афродиту, как я использовал его раньше.

npm install --save aphrodite

Создайте другой HOC, Shake-Animation.js.

import React, {Component} from 'react';
import {headShake} from 'react-animations';
import {StyleSheet, css} from 'aphrodite';
const styles = StyleSheet.create({
    headShake: {
        animationName: headShake,
        animationDuration: '1s'
    }
});
const makeValidationErrorAnimation = (Target) => {
    return class extends Component {
        constructor(props) {
            super(props);
            this.state = {shouldShake: false};
        }

        onClick = () => {
            this.setState({shouldShake: true}, () => {
                const self = this;
                setTimeout(() => self.setState({shouldShake: false}), 1000);
            });
        };

        render() {
            return (
                
            );
        }
    }
};
export default makeValidationErrorAnimation;

Есть несколько ключевых секций. Линия 4 использует ATHHRODITE для создания стиля для эффекта реагирования, Главный коктейль Отказ Линия 29 Устанавливает класс CSS для анимации на Цель Отказ (Это требует твика для Searchbox использовать класс CSS. Посмотрите на использование FrameClass в Источник Searchbox.js .). OnClick Обработчик на линии 17 более сложно.

Перезапуск анимации

Я хотел бы сделать «Head Shake» на каждый Ошибка валидации (или любой на триггере используется). Но поскольку анимация – это класс CSS, я не могу просто установить один и тот же класс снова; Это не было бы никакого эффекта. Это CSS трюки пост описывает несколько вариантов. SimpleSt – это время ожидания, который удаляет класс анимации CSS. Когда вы добавляете его снова (для нового события), вы увидите «Head Shake».

Вмещает его вместе: составляя сложный компонент

Я создал несколько HOC для разных анимаций. Но вы также можете цепить HOCS для создания составного компонента. Он откроет текстовое поле при нажатии и встряхнув на ошибочный вход.

Во-первых, вам нужно сделать несколько изменений в Поисковая строка

import React from 'react';
import {TextField, IconButton} from 'material-ui'
import SearchIcon from 'material-ui/svg-icons/action/search';
const baseStyles = {
    open: {
        width: 300,
    },
    closed: {
        width: 0,
    },
    smallIcon: {
        width: 30,
        height: 30
    },
    icon: {
        width: 40,
        height: 40,
        padding: 5,
        top: 10
    },
    frame: {
        border: 'solid 1px black',
        borderRadius: 5
    }
};
const SearchBox = ({isOpen, query, onClick, onSubmit, onQueryUpdate, additionalStyles, frameClass}) => {
    const handleKeyDown = (event) => {
        const ENTER_KEY = 13;
        if (event.keyCode === ENTER_KEY) {
            event.preventDefault();
            onSubmit();
        }
    };
    let textStyle = isOpen ? baseStyles.open : baseStyles.closed;
    textStyle = Object.assign(textStyle, additionalStyles ? additionalStyles.text : {});
    const divStyle = Object.assign({}, textStyle, baseStyles.frame, additionalStyles ? additionalStyles.frame : {});
    divStyle.width += baseStyles.icon.width + 5;
    return (
        
onClick()}> onQueryUpdate(value)}/>
); }; export default SearchBox;

Поиск сейчас a Управляемый компонент (Необработанный термин для использования реагирования на управление входным значением текстового поля). Это также предоставляет обратный вызов, OnsUbmit Для отправки поискового запроса (когда пользователь нажимает Enter ключ).

Вам также нужно изменить Shake-Animation.js Отказ Нажатие на значок поиска не должен вызывать дрожание. Вместо этого я хочу, чтобы другой компонент определил, когда «встряхнуть». Это отделяет логику проверки из кода, которая контролирует анимацию.

startshake это флаг для сброса анимации. Но это деталь реализации. Он должен быть инкапсулирован, как внутреннее состояние, в Стаякаканация HOC.

import React, {Component} from 'react';
import {headShake} from 'react-animations';
import {StyleSheet, css} from 'aphrodite';
const styles = StyleSheet.create({
    headShake: {
        animationName: headShake,
        animationDuration: '1s'
    }
});
const makeShakeAnimation = (Target) => {
    return class extends Component {
        constructor(props) {
            super(props);
            this.state = {startShake: props.shouldShake};
        }

        componentWillReceiveProps(nextProps) {
            this.setState({startShake: nextProps.shouldShake}, () => {
                const self = this;
                setTimeout(() => self.setState({startShake: false}), 1000);
            });
            //https://css-tricks.com/restart-css-animation/ for discussion on restart
        }

        render() {
            return (
                
            );
        }
    }
};
export default makeShakeAnimation;

startshake зависит от Подпитки Отказ Я могу использовать ComponentWillReceiveProps реагировать на изменения опор. (Это родитель, компонент проверки, предоставляет эти реквизиты.) Итак, я перевел предыдущий OnClick логика к ComponentWillReceiveProps Отказ

Изменение в строке 27, {... this.props} проходит все реквизиты к обернутую компоненту, Цель Отказ (Мне нужно аналогично изменить Render Метод Расширяясь-анимация.js . Детали здесь .)

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

Создать Search-Box-Controller.js

import React, {Component} from 'react';

import makeExpanding from './expanding-animation';
import makeShakingAnimation from './shake-animation';

const makeAnimatedValidationSearchBox = (Target) => {
    const WrappedComponent = makeShakingAnimation(makeExpanding(Target));

    return class extends Component {
        constructor(props) {
            super(props);
            this.state = {query: '', hasError: false};
        }

        onQueryUpdate = (value) => {
            this.setState({query: value, hasError:false});
        };

        onSubmit = () => {
            this.setState({hasError: true});
        };

        render() {
            return (
                
            );
        }
    }
};

export default makeAnimatedValidationSearchBox;

Это еще одна HOC. У него нет визуальных элементов, но он контролирует логическое поведение обернутого компонента. ( Дэн Абрамов имеет хорошее пост Объяснение такого разделения.) В этом случае все запросы как ошибочные, но в реальном приложении я проверял запросы и подключаю к API.

Наконец, я хочу подчеркнуть, что MakeanimatedValidationsearchbox это HOC, которая цепи два других HOCS.

const WrappedComponent =makeShakingAnimation(makeExpanding(Target));

Другое небольшое обновление до App.js.

import React, {Component} from 'react';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';

// (Make material-ui happy)
// Needed for onTouchTap
// http://stackoverflow.com/a/34015469/988941
import injectTapEventPlugin from 'react-tap-event-plugin';
injectTapEventPlugin();
import SearchBox from './SearchBox'

import makeAnimatedValidationSearchBox from './search-box-controller';
const AnimatedSearchBox = makeAnimatedValidationSearchBox(SearchBox);

class App extends Component {
    render() {
        //https://css-tricks.com/quick-css-trick-how-to-center-an-object-exactly-in-the-center/
        const style = {
            position: 'fixed',
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
        };
        return (
            
                
); } } export default App;

(Линия 12 использует новый HOC)

и выполнить запустить начало NPM

Я создал сложный компонент, который использует несколько микроиндейтов. Они многоразовые и дискретные.

Обертывание

Я пробовал каждый из подходов: CSS-переходов, реагирование и реагистрационные анимации. Хотел бы вы выбрать один подход, но трудно поднять один подход для всех случаев использования. К счастью, вы можете смешивать библиотеки и методы и методы. И вы можете инкапсулировать детали в многоразовых HOCS.

Возможно, вы захотите проверить библиотеки такие пересчитать , что делают создание HOC проще.

Репо GitHub для этого проекта – здесь Отказ

Пожалуйста, ♡ этот пост и следуй за мной на будущие истории. Спасибо за прочтение.