Теперь требования пользователей к фронтенд-страницам уже не удовлетворяются реализацией функций, а также должны быть красивыми и интересными. Помимо красоты общего пользовательского интерфейса, добавление правильных анимационных эффектов в нужных местах часто более выразительно, чем статические страницы, и позволяет добиться более естественного эффекта. Например, простая анимация загрузки или эффект переключения страниц могут не только облегчить пользователю настроение ожидания, но даже незаметно добиться эффекта продвижения бренда за счет использования логотипов брендов и других форм.
React, как популярный в последние годы фреймворк для фронтенд-разработки, предложил концепцию виртуального DOM.Все изменения DOM сначала происходили в виртуальном DOM.Фактические изменения веб-страницы анализировались через DOM diff, а затем отражались на реальном DOM, что значительно улучшило производительность веб-страницы. Однако, с точки зрения реализации анимации, React как фреймворк не обеспечивает напрямую эффекты анимации для компонентов и требует, чтобы разработчики реализовывали их самостоятельно, в то время как большинство традиционных веб-анимаций реализуются путем прямого манипулирования фактическими элементами DOM, что, очевидно, не используется в Реагировать. Итак, как анимация реализована в React?
Суть всех анимаций заключается в постоянном изменении одного или нескольких свойств элемента DOM для создания согласованных изменений в анимации форм. Реализация анимации в React по сути такая же, как и традиционная веб-анимация, но двумя способами: с помощью реализации анимации css3 и с помощью js для изменения свойств элемента. Однако в конкретной реализации, чтобы больше соответствовать характеристикам фреймворка React, его можно разделить на несколько категорий:
- Интервальная анимация на основе таймера или запроса ананиацииСрамена (RAF);
- Простая анимация на основе css3;
- Плагин анимации React
CssTransitionGroup
; - Комбинируйте хук, чтобы реализовать сложную анимацию;
- Другие сторонние библиотеки анимации.
1. Интервальная анимация по таймеру или RAF
В самом начале реализация анимации зависит от таймеров.setInterval
,setTimeout
илиrequestAnimationFrame
(RAF) Напрямую изменять свойства элементов DOM. Разработчики, незнакомые с функциями React, могут по привычке пройти мимоref
илиfindDOMNode()
Получите настоящий узел DOM и измените его стиль напрямую. Однако, поref
Не рекомендуется напрямую получать реальный DOM и работать с ним, и этого следует избегать, насколько это возможно.
Следовательно, нам нужно передавать такие методы, как таймеры или RAF, с атрибутами узла DOM.state
Связаться. Во-первых, вам нужно извлечь атрибуты, связанные с изменяющимся стилем, и заменить их наstate
, затем добавьте таймер в соответствующую функцию жизненного цикла илиrequestAnimationFrame
Постоянно изменятьstate
, запустите обновление компонента для достижения эффекта анимации.
Пример
Возьмите индикатор выполнения в качестве примера, код выглядит следующим образом:
// 使用requestAnimationFrame改变state
import React, { Component } from 'react';
export default class Progress extends Component {
constructor(props) {
super(props);
this.state = {
percent: 10
};
}
increase = () => {
const percent = this.state.percent;
const targetPercent = percent >= 90 ? 100 : percent + 10;
const speed = (targetPercent - percent) / 400;
let start = null;
const animate = timestamp => {
if (!start) start = timestamp;
const progress = timestamp - start;
const currentProgress = Math.min(parseInt(speed * progress + percent, 10), targetPercent);
this.setState({
percent: currentProgress
});
if (currentProgress < targetPercent) {
window.requestAnimationFrame(animate);
}
};
window.requestAnimationFrame(animate);
}
decrease = () => {
const percent = this.state.percent;
const targetPercent = percent < 10 ? 0 : percent - 10;
const speed = (percent - targetPercent) / 400;
let start = null;
const animate = timestamp => {
if (!start) start = timestamp;
const progress = timestamp - start;
const currentProgress = Math.max(parseInt(percent - speed * progress, 10), targetPercent);
this.setState({
percent: currentProgress
});
if (currentProgress > targetPercent) {
window.requestAnimationFrame(animate);
}
};
window.requestAnimationFrame(animate);
}
render() {
const { percent } = this.state;
return (
<div>
<div className="progress">
<div className="progress-wrapper" >
<div className="progress-inner" style = {{width: `${percent}%`}} ></div>
</div>
<div className="progress-info" >{percent}%</div>
</div>
<div className="btns">
<button onClick={this.decrease}>-</button>
<button onClick={this.increase}>+</button>
</div>
</div>
);
}
}
В примере мыincrease
а такжеdecrease
Построить линейную переходную функцию в функцииanimation
,requestAnimationFrame
Функция перехода выполняется до того, как браузер каждый раз перерисовывает для расчета текущего индикатора выполнения.width
свойства и обновитьstate
, что вызывает повторную визуализацию индикатора выполнения. Эффект этого примера следующий:
Эта реализация используетrequestAnimationFrame
Производительность хорошая, полностью реализован на чистом js, не зависит от css, при использовании таймера возможны выпадения кадров и зависания. Кроме того, разработчикам необходимо самостоятельно рассчитывать состояние по функции скорости, что является более сложным.
2. Простая анимация на основе css3
когда в css3animation
а такжеtransition
После появления и популяризации мы можем легко использовать CSS для изменения стиля элементов без расчета стилей в реальном времени вручную.
Пример
Мы по-прежнему берем приведенный выше график в качестве примера, используя CSS3 для достижения прогрессивных динамических эффектов, код выглядит следующим образом:
import React, { Component } from 'react';
export default class Progress extends Component {
constructor(props) {
super(props);
this.state = {
percent: 10
};
}
increase = () => {
const percent = this.state.percent + 10;
this.setState({
percent: percent > 100 ? 100 : percent,
})
}
decrease = () => {
const percent = this.state.percent - 10;
this.setState({
percent: percent < 0 ? 0 : percent,
})
}
render() {
// 同上例, 省略
....
}
}
.progress-inner {
transition: width 400ms cubic-bezier(0.08, 0.82, 0.17, 1);
// 其他样式同上,省略
...
}
В примереincrease
а такжеdecrease
функция больше не рассчитываетсяwidth
, но напрямую задайте увеличенную или уменьшенную ширину. Следует отметить, что стиль css установленtransition
Атрибут, когда изменяется атрибут ширины, автоматически реализуется эффект динамического изменения стиля, и можно установить кривую скорости для различных эффектов скорости. Результат этого примера показан на рисунке ниже.Можно обнаружить, что, в отличие от предыдущего примера, данные о ходе выполнения в правой части напрямую изменяются на целевое число, и нет конкретного процесса изменения, а динамический эффект индикатора выполнения больше не является линейным изменением. , эффект более яркий.
Реализация на основе css3 имеет высокую производительность и небольшое количество кода, но она может полагаться только на эффекты css, и сложно реализовать сложные анимации. Кроме того, модифицируяstate
Эффект анимации можно применить только к узлам, которые уже существуют в дереве DOM. Если вы хотите добавить анимацию входа и выхода к компонентам таким образом, вам нужно поддерживать по крайней мере дваstate
реализовать анимацию входа и выхода, один изstate
Используется для управления отображением элемента, другойstate
Используется для управления изменением свойств элемента во время анимации. В этом случае разработчикам нужно тратить много сил на поддержание анимационной логики компонента, что очень сложно и громоздко.
3. Плагин анимации ReactCssTransitionGroup
Реагировать, используемый для обеспечения анимационных плагинов для разработчиковreact-addons-css-transition-group
, а затем переданы сообществу на обслуживание, формируя текущуюreact-transition-group
, этот плагин может легко реализовать анимацию входа и выхода компонентов и требует дополнительной установки разработчиком при его использовании.react-transition-group
ВключатьCSSTransitionGroup
а такжеTransitionGroup
Два плагина анимации, последний является базовым API, а первый является дальнейшей инкапсуляцией последнего, который может более удобно реализовывать анимацию CSS.
Пример
В качестве примера возьмем динамически увеличивающуюся вкладку, код выглядит следующим образом:
import React, { Component } from 'react';
import { CSSTransitionGroup } from 'react-transition-group';
let uid = 2;
export default class Tabs extends Component {
constructor(props) {
super(props);
this.state = {
activeId: 1,
tabData: [{
id: 1,
panel: '选项1'
}, {
id: 2,
panel: '选项2'
}]
};
}
addTab = () => {
// 添加tab代码
...
}
deleteTab = (id) => {
// 删除tab代码
...
}
render() {
const { tabData, activeId } = this.state;
const renderTabs = () => {
return tabData.map((item, index) => {
return (
<div
className={`tab-item${item.id === activeId ? ' tab-item-active' : ''}`}
key={`tab${item.id}`}
>
{item.panel}
<span className="btns btn-delete" onClick={() => this.deleteTab(item.id)}>✕</span>
</div>
);
})
}
return (
<div>
<div className="tabs" >
<CSSTransitionGroup
transitionName="tabs-wrap"
transitionEnterTimeout={500}
transitionLeaveTimeout={500}
>
{renderTabs()}
</CSSTransitionGroup>
<span className="btns btn-add" onClick={this.addTab}>+</span>
</div>
<div className="tab-cont">
cont
</div>
</div>
);
}
}
/* tab动态增加动画 */
.tabs-wrap-enter {
opacity: 0.01;
}
.tabs-wrap-enter.tabs-wrap-enter-active {
opacity: 1;
transition: all 500ms ease-in;
}
.tabs-wrap-leave {
opacity: 1;
}
.tabs-wrap-leave.tabs-wrap-leave-active {
opacity: 0.01;
transition: all 500ms ease-in;
}
CSSTransitionGroup
Вы можете добавить дополнительные классы CSS к его дочерним узлам, а затем анимировать анимацию входа и выхода с помощью анимации CSS. Чтобы анимировать каждый узел вкладки, оберните ихCSSTransitionGroup
в компоненте. когда установленоtransitionName
собственность'tabs-wrapper'
,transitionEnterTimeout
Через 400 мс один разCSSTransitionGroup
Добавьте новый узел в новый узел, новый узел будет добавлен с классом css, когда он появится.'tabs-wrapper-enter'
, который затем добавляется в класс css в следующем кадре'tabs-wrapper-enter-active'
. Из-за разных свойств прозрачности и перехода css3, заданных в этих двух классах css, узел реализует входной эффект прозрачности от малого к большому. класс css через 400 мс'tabs-wrapper-enter'
а также'tabs-wrapper-enter-active'
будет одновременно удален, и узел завершит весь процесс анимации входа. Реализация анимации выхода похожа на анимацию входа, за исключением того, что добавленный класс css называется'tabs-wrapper-leave'
а также'tabs-wrapper-leave-active'
. Эффект этого примера показан на следующем рисунке:
CSSTransitionGroup
Поддерживаются следующие 7 свойств:
Среди них анимация входа и выхода включена по умолчанию и должна быть установлена при использованииtransitionEnterTimeout
а такжеtransitionLeaveTimeout
. Примечательно,CSSTransitionGroup
Он также обеспечивает анимацию (появление), которую необходимо установить при использованииtransitionAppearTimeout
. Итак, в чем разница между анимацией появления и анимацией входа? когда установленоtransitionAppear
дляtrue
час,CSSTransitionGroup
существуетпервый рендер, добавлен этап появления. На этом этапеCSSTransitionGroup
Существующие дочерние узлы класса css будут добавлены последовательно'tabs-wrapper-appear'
а также'tabs-wrapper-appear-active'
, чтобы добиться эффекта анимации. следовательно,Появляться анимация только дляCSSTransitionGroup
Дочерние узлы, существующие при первом рендеринге,однаждыCSSTransitionGroup
После завершения рендеринга его дочерние узлы могут иметь только анимацию входа (вход), а анимация (появление) невозможна.
Кроме того, используйтеCSSTransitionGroup
Необходимо отметить следующие моменты:
-
CSSTransitionGroup
Генерирует значение по умолчанию в дереве DOMspan
тег оборачивает свои дочерние узлы, если вы хотите использовать другие теги html, вы можете установитьCSSTransitionGroup
изcomponent
Атрибуты; -
CSSTransitionGroup
необходимо добавить дочерние элементыkey
Когда значение узла изменяется, он может точно вычислить, какие узлы должны добавить анимацию входа и какие узлы должны добавить анимацию выхода; -
CSSTransitionGroup
Эффект анимации действует только на прямые дочерние узлы, а не на внучатые узлы; - Время окончания анимации не зависит от продолжительности перехода в css, а зависит от
transitionEnterTimeout
,transitionLeaveTimeout
,TransitionAppearTimeout
превалируют, потому что в некоторых случаях событие transitionend не будет запущено.MDN transitionend.
CSSTransitionGroup
Преимущества реализации анимации:
- Простой и удобный в использовании, вы можете легко и быстро реализовать анимацию входа и выхода элементов;
- В сочетании с React производительность выше.
CSSTransitionGroup
Недостатки тоже очень очевидны:
- Ограничен анимацией внешнего вида, анимации входа и анимации выхода;
- Из-за необходимости сформулировать
transitionName
, недостаточно гибкий; - Простую анимацию можно получить, только полагаясь на css.
В-четвертых, объедините хук для реализации сложной анимации.
В реальных проектах могут потребоваться еще какие-то крутые анимационные эффекты, которых часто сложно добиться, полагаясь только на css3. На этом этапе мы могли бы также использовать некоторые зрелые сторонние библиотеки, такие как jQuery или GASP, для объединения методов ловушек жизненного цикла и функций ловушек в компонентах React для достижения сложных эффектов анимации. В дополнение к обычному жизненному циклу компонентов React,CSSTransitionGroup
базовый APITransitonGroup
Он также предоставляет ряд специальных функций-ловушек жизненного цикла для своих дочерних элементов.Объединение сторонних анимационных библиотек в этих функциях-ловушках может реализовать богатые эффекты анимации входа и выхода.
TransisitonGroup
Предоставьте следующие шесть функций ловушек жизненного цикла:
- componentWillAppear(callback)
- componentDidAppear()
- componentWillEnter(callback)
- componentDidEnter()
- componentWillLeave(callback)
- componentDidLeave()
Их синхронизация показана на рисунке ниже:
Пример
GASPЭто анимационная библиотека, разработанная в эпоху флэш-памяти, основанная на концепции видеокадров и особенно подходящая для долговременных анимационных эффектов. В этой статье мы используемTransitonGroup
а такжеreact-gsap-enhancer
(библиотека улучшений, которая может применять GSAP к React) для завершения галереи изображений, код выглядит следующим образом:
import React, { Component } from 'react';
import { TransitionGroup } from 'react-transition-group';
import GSAP from 'react-gsap-enhancer'
import { TimelineMax, Back, Sine } from 'gsap';
class Photo extends Component {
constructor(props) {
super(props);
}
componentWillEnter(callback) {
this.addAnimation(this.enterAnim, {callback: callback})
}
componentWillLeave(callback) {
this.addAnimation(this.leaveAnim, {callback: callback})
}
enterAnim = (utils) => {
const { id } = this.props;
return new TimelineMax()
.from(utils.target, 1, {
x: `+=${( 4 - id ) * 60}px`,
autoAlpha: 0,
onComplete: utils.options.callback,
}, id * 0.7);
}
leaveAnim = (utils) => {
const { id } = this.props;
return new TimelineMax()
.to(utils.target, 0.5, {
scale: 0,
ease: Sine.easeOut,
onComplete: utils.options.callback,
}, (4 - id) * 0.7);
}
render() {
const { url } = this.props;
return (
<div className="photo">
<img src={url} />
</div>
)
}
}
const WrappedPhoto = GSAP()(Photo);
export default class Gallery extends Component {
constructor(props) {
super(props);
this.state = {
show: false,
photos: [{
id: 1,
url: 'http://img4.imgtn.bdimg.com/it/u=1032683424,3204785822&fm=214&gp=0.jpg'
}, {
id: 2,
url: 'http://imgtu.5011.net/uploads/content/20170323/7488001490262119.jpg'
}, {
id: 3,
url: 'http://tupian.enterdesk.com/2014/lxy/2014/12/03/18/10.jpg'
}, {
id: 4,
url: 'http://img4.imgtn.bdimg.com/it/u=360498760,1598118672&fm=27&gp=0.jpg'
}]
};
}
toggle = () => {
this.setState({
show: !this.state.show
})
}
render() {
const { show, photos } = this.state;
const renderPhotos = () => {
return photos.map((item, index) => {
return <WrappedPhoto id={item.id} url={item.url} key={`photo${item.id}`} />;
})
}
return (
<div>
<button onClick={this.toggle}>toggle</button>
<TransitionGroup component="div">
{show && renderPhotos()}
</TransitionGroup>
</div>
);
}
}
В этом примере мы находимся в дочернем компонентеPhoto
изcomponentWillEnter
а такжеcomponentWillLeave
Добавлена анимация записи для каждого дочернего компонента в двух функциях хука.enterAnim
и анимация отъездаLeaveAnim
. В анимации входа используйтеTimeLineMax.from(target, duration, vars, delay)
способ создать анимацию на временной шкале, указав, что расстояние перемещения анимации каждого подкомпонента зависит отid
увеличивается и уменьшается, а время задержки увеличивается сid
увеличивается и увеличивается, время задержки каждого подкомпонента в анимации отправления увеличивается сid
увеличивается и уменьшается, позволяяid
Разные имеют разные эффекты анимации. В реальном использовании вы можете добавлять различные эффекты к любому подкомпоненту в соответствии с вашими потребностями. Эффект этого примера показан на следующем рисунке:
В использованииTransitionGroup
когда, вcomponentnWillAppear(callback)
,componentnWillEntercallback)
,componentnWillLeave(callback)
функция должнаВызывается после завершения логики функцииcallback
,обещалTransitionGroup
Может правильно поддерживать последовательность состояний дочерних узлов. Подробнее о том, как использовать GASP, см.Официальная документация ГПБПи сообщение в блогеGSAP, профессиональная библиотека веб-анимации, эта статья не будет их повторять.
Объединение хука для реализации анимации может поддерживать различные сложные анимации, такие как анимация временных рядов и т. д. Из-за зависимости от сторонних библиотек эффект анимации часто более плавный, а взаимодействие с пользователем — лучше. Однако внедрение сторонних библиотек требует от разработчиков дополнительного изучения соответствующих API, что также увеличивает сложность кода.
5. Другие сторонние библиотеки анимации
Кроме того, существует множество отличных сторонних анимационных библиотек, таких какreact-motionанимация,velocity-reactПодождите, у этих анимационных библиотек тоже есть свои преимущества и недостатки при их использовании.
Animated
Animated— это кроссплатформенная библиотека анимации, совместимая с React и React Native. Поскольку в процессе анимации мы заботимся только о начальном состоянии, конечном состоянии и функции изменения анимации и не заботимся о конкретном значении атрибута элемента в каждый момент, поэтому Animated использует декларативную анимацию и вычисляет объект css. с помощью определенного метода, который он предоставляет.Animated.div
Реализовать анимационные эффекты.
Пример
Мы используем Animated для достижения эффекта переворота изображения, код выглядит следующим образом.
import React, { Component } from 'react';
import Animated from 'animated/lib/targets/react-dom';
export default class PhotoPreview extends Component {
constructor(props) {
super(props);
this.state = {
anim: new Animated.Value(0)
};
}
handleClick = () => {
const { anim } = this.state;
anim.stopAnimation(value => {
Animated.spring(anim, {
toValue: Math.round(value) + 1
}).start();
});
}
render() {
const { anim } = this.state;
const rotateDegree = anim.interpolate({
inputRange: [0, 4],
outputRange: ['0deg', '360deg']
});
return (
<div>
<button onClick={this.handleClick}>向右翻转</button>
<Animated.div
style={{
transform: [{
rotate: rotateDegree
}]
}}
className="preivew-wrapper"
>
<img
alt="img"
src="http://img4.imgtn.bdimg.com/it/u=1032683424,3204785822&fm=214&gp=0.jpg"
/>
</Animated.div>
</div>
);
}
}
В этом примере мы хотим поворачивать изображение на 90° вправо при каждом нажатии кнопки. Когда компонент инициализируется, создается новый с начальным значением 0Animated
объектthis.state.anim
.Animated
Объект имеет функцию интерполяцииinterpolate
, при установке интервала вводаinputRange
и выходной интервалoutputRange
После этого интерполяционная функция может быть основана наAnimated
Текущее значение объекта линейно интерполируется, и вычисляется соответствующее значение отображения.
В этом примере мы предполагаем, что каждый раз, когда нажимается кнопка,this.state.anim
Добавьте 1 к значению, и изображение нужно повернуть на 90°. В функции рендера мы устанавливаем функцию интерполяцииthis.state.anim.interpolate
Диапазон ввода — [0, 4], а диапазон вывода — ['0deg', '360deg']. Когда анимация выполняется,this.state.anim
изменяет значение , функция интерполяции основана наthis.state.anim
Текущее значение, угол поворота рассчитываетсяrotateDegree
, который запускает повторный рендеринг компонента. Следовательно, еслиAnimated
Текущее значение объекта равно 2, а соответствующий угол поворота равен 180 градусов. В структуре рендеринга компонента вам нужно использоватьAnimated.div
оберните узел анимации и поместитеrotateDegree
Инкапсулирован как объект css, переданный как stlyeAnimated.div
В реализации изменения атрибута CSS узла.
В событии клика, учитывая, что кнопка может быть нажата несколько раз подряд, мы сначала используемstopAnimation
Остановите текущую анимацию, функция вернет объект {value : number} в функции обратного вызова, значение соответствует последнему значению свойства анимации. Согласно полученномуvalue
значение, затем используйтеAnimated.spring
Функция запускает новый процесс анимации пружины для достижения плавного эффекта анимации. Поскольку каждый раз, когда вращение останавливается, мы хотим, чтобы угол поворота изображения был целым числом, кратным 90°, поэтому нам нужно исправитьAnimated.spring
Значение завершения округляется в большую сторону. В итоге мы добились следующих результатов:
Есть несколько вещей, чтобы обратить внимание на использование:
-
Animated
И результаты его ценностей могут действовать только на объектахAnimated.div
узел; -
interpolate
По умолчанию линейная интерполяция будет выполняться в соответствии с входным интервалом и выходным интервалом.Если входное значение превышает входной интервал, это не будет затронуто.Результат интерполяции расширит интерполяцию в соответствии с выходным интервалом по умолчанию, который может быть установить, установивextrapolate
Свойство ограничивает интервал результата интерполяции.
Animated не изменяет компонент напрямую во время анимации.state
, но напрямую изменять свойства элемента через компоненты и методы его вновь созданного объекта, без многократного запуска функции рендеринга, которая является очень стабильной библиотекой анимации в React Native. Однако в React существует проблема совместимости браузеров с низкими версиями, и есть определенные затраты на обучение.
Эпилог
Когда мы реализуем анимацию в React, мы должны в первую очередь учитывать простоту анимации и сценарии использования.Для простых анимаций предпочтительнее реализация css3, а затем анимация временных интервалов на основе js. Если это анимация входа в элемент и анимация выхода, рекомендуется комбинироватьCSSTransitionGroup
илиTransitionGroup
выполнить. Когда анимационные эффекты будут достигнуты более сложными, вы можете попробовать несколько отличных сторонних библиотек, чтобы открыть дверь в замечательные анимационные эффекты.
Ps. Все примеры кодов в этой статье доступныgithubПроверить
Использованная литература:
A Comparison of Animation Technologies
Эта статья была впервые опубликована вБлог о технологиях Youzan.