React之动画实现


React之动画实现

一,介绍与需求

1.1,介绍

1,Ant Motion

Ant Motion能够快速在 React 框架中使用动画。在 React 框架下,只需要一段简单的代码就可以实现动画效果

2,SVG 

  • SVG 指可伸缩矢量图形 (Scalable Vector Graphics)
  • SVG 用来定义用于网络的基于矢量的图形
  • SVG 使用 XML 格式定义图形
  • SVG 图像在放大或改变尺寸的情况下其图形质量不会有所损失
  • SVG 是万维网联盟的标准
  • SVG 与诸如 DOM 和 XSL 之类的 W3C 标准是一个整体

1.2,需求

提高网站的交互效果,提高用户体验。界面动效能加强用户认知且增加活力。

二,基于Ant Motion的react动画

2.1,动画效果

1,snow掉落效果

2,聚集与散开

 2.2,动画实现方式

以掉落效果为例:

 1 import React from 'react';
 2 import Snow from './Snow';
 3 import './index.less';
 4 class App extends React.Component {
 5   constructor() {
 6     super(...arguments);
 7     this.state = {
 8       show: true,
 9     };
10   }
11   onEnd = () => {
12     this.setState({
13       show: false,
14     });
15   }
16   render() {
17     const children = Array(5).fill(1).map((c, i) => (
18       
19 )); 20 return ( 21
22
23 24 this.onEnd} > 25 {children} 26 27 28
29
30 ); 31 } 32 } 33 34 export default App;

组件snow代码:

  1   import React from 'react';
  2   import TweenOne from 'rc-tween-one';
  3   import BezierPlugin from 'rc-tween-one/lib/plugin/BezierPlugin';
  4   import PropTypes from 'prop-types';
  5   
  6   import './index.less';
  7   
  8   TweenOne.plugins.push(BezierPlugin);
  9   
 10   class Snow extends React.Component {
 11     static propTypes = {
 12       children: PropTypes.any,
 13       className: PropTypes.string,
 14       prefixCls: PropTypes.string,
 15       amount: PropTypes.number,
 16       repeat: PropTypes.number,
 17       ease: PropTypes.string,
 18       startArea: PropTypes.object,
 19       endArea: PropTypes.object,
 20       startDelayRandom: PropTypes.number,
 21       basicToDuration: PropTypes.number,
 22       randomToDuration: PropTypes.number,
 23       rotateRandom: PropTypes.number,
 24       bezierSegmentation: PropTypes.number,
 25       onEnd: PropTypes.func,
 26     }
 27     static defaultProps = {
 28       prefixCls: 'snow',
 29       amount: 10,
 30       repeat: 0,
 31       ease: 'linear',
 32       startArea: {
 33         x: 0, y: -200, width: '100%', height: 50,
 34       },
 35       endArea: {
 36         x: -200, y: '100%', width: '120%', height: 100,
 37       },
 38       basicToDuration: 1200,
 39       randomToDuration: 800,
 40       startDelayRandom: 800,
 41       rotateRandom: 180,
 42       bezierSegmentation: 2,
 43       onEnd: () => { },
 44     };
 45   
 46     constructor(props) {
 47       super(props);
 48       this.state = {
 49         children: null,
 50       };
 51     }
 52     componentDidMount() {
 53       this.setChilrenToState();
 54     }
 55   
 56     onAnimEnd = () => {
 57       this.animEnd += 1;
 58       if (this.animEnd >= this.props.amount) {
 59         this.animEnd = 0;
 60         if (this.props.onEnd) {
 61           this.props.onEnd();
 62         }
 63       }
 64     }
 65   
 66     setChilrenToState() {
 67       const children = this.getChildrenToRender();
 68       this.setState({
 69         children,
 70       });
 71     }
 72   
 73     getChildrenToRender = () => {
 74       const {
 75         bezierSegmentation, basicToDuration, randomToDuration,
 76         amount, ease, startDelayRandom, repeat, rotateRandom,
 77       } = this.props;
 78       const children = React.Children.toArray(this.props.children);
 79       const rect = this.wrapperDom.getBoundingClientRect();
 80       const startArea = this.dataToNumber(this.props.startArea, rect);
 81       const endArea = this.dataToNumber(this.props.endArea, rect);
 82       return Array(amount).fill(1).map((k, i) => {
 83         const item = children[Math.floor(Math.random() * children.length)];
 84         const vars = Array(bezierSegmentation).fill(1).map((c, j) => {
 85           const hegiht = endArea.y - startArea.y - startArea.height;
 86           const y = (hegiht / bezierSegmentation) * (j + 1);
 87           const x = Math.random() * (Math.max(startArea.width, endArea.width)
 88             + Math.min(startArea.x, endArea.x));
 89           // console.log(hegiht, startArea, endArea, y);
 90           return {
 91             y,
 92             x,
 93           };
 94         });
 95         const delay = Math.random() * startDelayRandom;
 96         const animation = {
 97           bezier: {
 98             type: 'soft',
 99             autRotate: true,
100             vars,
101           },
102           ease,
103           repeat,
104           repeatDelay: delay,
105           delay,
106           duration: basicToDuration + Math.random() * randomToDuration,
107           onComplete: this.onAnimEnd,
108         };
109         const style = {
110           transform: `translate(${Math.random() * (startArea.width) + startArea.x}px, ${
111             Math.random() * (startArea.height) + startArea.y
112           }px)`,
113         };
114         const child = rotateRandom ? (
115           <TweenOne
116             className="snowRotate"
117             style={{ transform: `rotate(${Math.random() * rotateRandom}deg)` }}
118             animation={{
119               rotate: 0,
120               duration: animation.duration * 4 / 5,
121               delay: animation.delay,
122               repeat: animation.repeat,
123             }}
124           >
125             {item}
126           
127         ) : item;
128         return (
129           <TweenOne
130             animation={animation}
131             style={style}
132             key={`${item}-${i.toString()}`}
133             className="snowChild"
134           >
135             {child}
136           
137         );
138       });
139     }
140     dataToNumber = (obj, rect) => {
141       const toNumber = (v, full) => {
142         if (typeof v === 'number') {
143           return v;
144         }
145         const unit = v.replace(/[0-9|.]/g, '');
146         switch (unit) {
147           case '%':
148             return parseFloat(v) * full / 100;
149           case 'em':
150             return parseFloat(v) * 16;
151           default:
152             return null;
153         }
154       };
155       return {
156         x: toNumber(obj.x, rect.width),
157         y: toNumber(obj.y, rect.height),
158         width: toNumber(obj.width, rect.width),
159         height: toNumber(obj.height, rect.height),
160       };
161     }
162     animEnd = 0;
163     render() {
164       const { prefixCls, ...props } = this.props;
165       const { children } = this.state;
166       [
167         'amount',
168         'repeat',
169         'ease',
170         'startArea',
171         'endArea',
172         'basicToDuration',
173         'randomToDuration',
174         'startDelayRandom',
175         'bezierSegmentation',
176         'rotateRandom',
177         'onEnd',
178       ].forEach(k => delete props[k]);
179       const className = `${prefixCls}${props.className ? ` ${props.className}` : ''}`;
180       return (
181         <div
182           {...props}
183           ref={(c) => {
184             this.wrapperDom = c;
185           }}
186           className={className}
187         >
188           {children}
189         
190 ); 191 } 192 } 193 export default Snow 194

样式代码:

 1 .snow-demo-wrapper {
 2   background: #DFEAFF;
 3   overflow: hidden;
 4   height: 500px;
 5   display: flex;
 6   align-items: center;
 7   position: relative;
 8 }
 9 
10 .snow-demo {
11   width: 300px;
12   height: 90%;
13   margin: auto;
14   position: relative;
15   background-image: url(https://gw.alipayobjects.com/zos/rmsportal/dNpuKMDHFEpMGrTxdLVR.jpg);
16   background-position: top;
17   background-size: 100% auto;
18   box-shadow: 0 0 32px rgba(0, 0, 0, 0.15);
19 }
20 
21 .snow {
22   width: 100%;
23   height: 100%;
24   position: absolute;
25   top: 0;
26   overflow: hidden;
27 }
28 
29 .snowChild {
30   position: absolute;
31   top: 0;
32   left: 0;
33 }
34 
35 .snowRotate {
36   transform-origin: center center;
37 } 

 2.3,动画分类

1,单元素动画rc-tween-one

1 cnpm install rc-tween-one --save

2,css样式动画rc-animate

1 cnpm install rc-animate --save

3,QueueAnim进出场动画

1 cnpm install rc-queue-anim --save

4,TextyAnim文字动画

1 cnpm install rc-texty --save

5,ScrollAnim页面滚动动画

1 cnpm install rc-scroll-anim --save

6,Banner动画

1 cnpm install rc-banner-anim --save

详细动画实例可查看官网

三,基于svg的react动画

3.1,动画效果

鼠标移入动画执行,鼠标移出动画停止

1,纵队动画

2,俄罗斯方块

3,坐标动画

3.2,动画实现方式

以纵队动画为例如下代码:

 1 import React from 'react';
 2 import Column from '../technology/Column';//实现动画的svg组件
 3 
 4 export default class ReactAnimation extends React.Component {
 5   constructor(props) {
 6     super(props);
 7     this.state = {
 8       hover: null,//是否有鼠标的移入
 9     };
10   }
11 
12   onMouseEnter = (hover) => {//鼠标移入
13     this.setState({
14       hover,
15     });
16   };
17   onMouseLeave = () => {//鼠标移出
18     this.setState({
19       hover: null,
20     });
21   };
22   render() {
23 
24     return (
25       
26 <div 27 onMouseEnter={() => { this.onMouseEnter(1); }} 28 onMouseLeave={this.onMouseLeave} 29 > 30
31 {Column && React.createElement(Column, { 32 hover:this.state.hover === 1, 33 })} 34
35
36
37 ); 38 } 39 }

React.createElement(): 根据指定的第一个参数创建一个React元素。

1 React.createElement(
2   type,
3   [props],
4   [...children]
5 )

第一个参数是必填,传入的是似HTML标签名称,如: ul, li 
第二个参数是选填,表示的是属性,如: className 
第三个参数是选填, 子节点,如: 要显示的文本内容

SVG配置组件Column.jsx:

 1 import React from 'react';
 2 import TweenOne from 'rc-tween-one';//引入动画插件
 3 
 4 function TweenOneG(props) {
 5   function getAnimation() {
 6     return props.animation.map((item, i) => {
 7       return { ...item, duration: 400 };
 8     });
 9   }
10   return (
11     <TweenOne
12       component="g"
13       {...props}
14       animation={
15         props.animation ?
16           getAnimation() :
17           null
18       }
19     />);
20 }
21 
22 export default class Column extends React.PureComponent {
23   render() {
24     const { hover } = this.props;
25     return (
26       
27         
28           
29             
30           
31         
32         
33           this.default1Anim}>
34             
35               
36             
37           
38         
39       );
40   }
41 }

 上面展示的只是部分代码,如需完整的代码,请先留言评论加关注