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 <div key={i} className="addMoneyAnim" style={{ animationDelay: `${-Math.random() * 0.6}s` }} /> 19 )); 20 return ( 21 <div className="snow-demo-wrapper" > 22 <div className="snow-demo"> 23 24 <Snow onEnd={this.onEnd} > 25 {children} 26 </Snow> 27 28 </div> 29 </div> 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, '100%', height: 50, 34 }, 35 endArea: { 36 x: -200, y: '100%', '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 </TweenOne> 127 ) : item; 128 return ( 129 <TweenOne 130 animation={animation} 131 style={style} 132 key={`${item}-${i.toString()}`} 133 className="snowChild" 134 > 135 {child} 136 </TweenOne> 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 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 </div> 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 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 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 <div> 26 <div 27 onMouseEnter={() => { this.onMouseEnter(1); }} 28 onMouseLeave={this.onMouseLeave} 29 > 30 <div> 31 {Column && React.createElement(Column, { 32 hover:this.state.hover === 1, 33 })} 34 </div> 35 </div> 36 </div> 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 <svg width="328px" height="150px" viewBox="0 0 328 150"> 27 <defs> 28 <linearGradient x1="50%" y1="3.05125957%" x2="50%" y2="157.404891%" id="linearGradient-1"> 29 <stop stopColor="#2898FF" offset="0%" /> 30 </linearGradient> 31 </defs> 32 <g id="Page-1" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd"> 33 <TweenOneG animation={hover ? [{ y: -20 }, { y: -10 }, { y: -30 }] : this.default1Anim}> 34 <g id="Group-33" transform="translate(0.000000, 116.000000)"> 35 <rect id="Rectangle-15" fill="#1890FF" opacity="0.03" x="0" y="2" width="20" height="145" /> 36 </g> 37 </TweenOneG> 38 </g> 39 </svg>); 40 } 41 }
上面展示的只是部分代码,如需完整的代码,请先留言评论加关注