自写一个文字按行渐入动画组件
今天,在我原先的AnimationList组件上增加了个新功能,话不多说,直接上代码(由于网络垃圾进不去github,就直接粘代码了)。
index.tsx文件 :
1 import React,{Fragment} from 'react'; 2 import styles from './index.less'; 3 import undefined from '@/e2e/__mocks__/antd-pro-merge-less'; 4 export interface State { 5 list:Array<any>, 6 cacheList:Array<any>, 7 eventIF:boolean, 8 } 9 export interface Props { 10 style?:any, 11 styleSon?:any, 12 val?:valFrom, 13 dataSource?:Array<dataSource>, 14 onClickSon?:any, 15 onMouseEnterSon?:any, 16 onMouseLeaveSon?:any, 17 text?:text, 18 } 19 interface text{ 20 textEffect:boolean,//是否开启文本动画 21 testDataSource:string,//文本 22 typesetting?:'transverse' | 'vertical',//排版方向 23 separate?:string,//分隔符 24 interval?:number //行或列间隔 25 rowDelay?:number //行延时 26 } 27 interface valFrom{ 28 type?:TYPE|string,//动画类型 29 direction?:DIRECTION|string,//方向 30 time?:number,//时间 单位s 31 delay?:number,//动画执行前的延时时间 单位s 32 sonDelay?:number,//列表子项动画延时 33 domId?:string,//事件绑定dom id 34 event?:EVENT|string,//动画执行事件 35 hideModel?:boolean//背景是否显示 36 typesetting?:'transverse' | 'vertical',//排版方向 37 } 38 export const enum TYPE{FADEIN} 39 export const enum DIRECTION{TOP,BUTTOM,LEFT,REGIST,TOPLEFT,TOPREGIST,BUTTOMLEFT,BUTTOMREGIST} 40 export const enum EVENT{CLICK,MOUSEENTER} 41 interface dataSource{keys:any,title:any,style?:any} 42 export class Father extends React.Component<Props, State> { 43 constructor(props: Props) { 44 super(props); 45 this.state = { 46 list:[],//列表项 47 cacheList:[],//暂时存储,观望是否绑定dom 48 eventIF:false,//是否触发了event事件 49 50 }; 51 if(this.props.val !== undefined){ 52 const val:valFrom = this.props.val; 53 if(this.props.val.type != undefined && !(val.type===TYPE.FADEIN || val.type==="fadeIn")){ 54 throw Error(`type定义错误:错误值为 ${val.type},type取值为{enum:TYPE,'fadeIn'}`,); 55 } 56 if(this.props.val.direction != undefined && !(val.direction === DIRECTION.TOP || val.direction === DIRECTION.BUTTOM || 57 val.direction === DIRECTION.LEFT||val.direction === DIRECTION.REGIST || val.direction === DIRECTION.TOPLEFT || 58 val.direction === DIRECTION.TOPREGIST || val.direction === DIRECTION.BUTTOMLEFT || val.direction === DIRECTION.BUTTOMREGIST || 59 val.direction === 'top' || val.direction === 'buttom' || val.direction=== 'left' || val.direction === 'regist' || 60 val.direction=== 'topLeft' || val.direction === 'topRegist' || val.direction === 'buttomLeft' || val.direction === 'buttomRegist')){ 61 throw Error(`direction定义错误:错误值为 ${val.direction},direction取值为{enum:DIRECTION,'top','buttom','left','regist', 62 'topLeft','topRegist','buttomLeft','buttomRegist'}`); 63 } 64 window.onload = function(){ 65 if(val.domId !== undefined){ 66 if(document.getElementById(val.domId)===undefined || document.getElementById(val.domId)===null){ 67 throw Error(`指定id的DOM元素不存在!`,); 68 } 69 if(val.event === undefined){ 70 console.warn(`指定DOM元素情况下未指定绑定事件event!`); 71 } 72 } 73 } 74 if(val.event !== undefined){ 75 if(!(val.event === EVENT.CLICK || val.event === EVENT.MOUSEENTER || val.event === 'click' || 76 val.event === 'mouseEnter')){ 77 throw Error(`event定义错误:错误值为 ${val.event},event取值为{enum:EVENT,'click','mouseEnter'}`,); 78 } 79 if(val.domId === undefined){ 80 console.warn(`绑定事件后未指定DOM元素!`); 81 } 82 } 83 } 84 } 85 isWidth=(strs:Array<any>):number=>{ 86 let str : Array<string> = []; 87 for(let i=0;i<strs.length;i++){ 88 if(strs[i].type!==undefined && strs[i].type===Son){ 89 str.push(strs[i].props.children); 90 } 91 } 92 let max:number = 0; 93 let reg:RegExp = /[\u4E00-\u9FA5\uF900-\uFA2D]/i; 94 str.forEach(element => { 95 let forMax = 0; 96 for(let i=0;i<element.length;i++){ 97 if(reg.test(element.charAt(i))){ 98 forMax+=2; 99 }else{ 100 forMax++; 101 } 102 } 103 if(forMax > max){ 104 max = forMax; 105 } 106 }); 107 return max; 108 } 109 isWidth1=(maxWidth:number,data:Array<dataSource>):number=>{ 110 let max:number = maxWidth; 111 let reg:RegExp = /[\u4E00-\u9FA5\uF900-\uFA2D]/i; 112 data.forEach(element => { 113 let forMax = 0; 114 for(let i=0;i<element.title.length;i++){ 115 if(reg.test(element.title.charAt(i))){ 116 forMax+=2; 117 }else{ 118 forMax++; 119 } 120 } 121 if(forMax > max){ 122 max = forMax; 123 } 124 }); 125 return max; 126 } 127 setList=():void=>{ 128 //清零 129 this.state.list.length = 0; 130 const list = [...this.state.cacheList]; 131 this.setState({list,eventIF:true}); 132 //解除绑定 133 if(this.props.val != undefined && this.props.val.domId != undefined){ 134 let dom:any = document.getElementById(this.props.val.domId); 135 let event:string = "click"; 136 if(this.props.val.event === EVENT.MOUSEENTER){ 137 event = "mouseenter"; 138 } 139 dom.removeEventListener(event,this.setList); 140 } 141 } 142 bindEvent=(val:any):void=>{ 143 if(this.props.val != undefined && this.props.val.domId != undefined && this.props.val.event != undefined){ 144 const dom:any = document.getElementById(this.props.val.domId); 145 let event:string = "click"; 146 if(this.props.val.event === EVENT.MOUSEENTER){ 147 event = "mouseenter"; 148 } 149 dom.addEventListener(event,this.setList); 150 } 151 } 152 textReturn = (data:Array<Array<any>>,width:number):any =>{ 153 if(this.props.text !== undefined){ 154 const typesetting:string = this.props.text.typesetting !== undefined ? this.props.text.typesetting : 'vertical'; 155 const wid:number = this.props.text.interval !== undefined ? this.props.text.interval : width; 156 return data[0].map((data)=>{return <Texts data={data} typesetting={typesetting} interval={wid}/>}); 157 }else{ 158 return undefined; 159 } 160 } 161 render() { 162 //默认动画效果 163 const defVal:valFrom = { 164 type:TYPE.FADEIN, 165 direction:DIRECTION.LEFT, 166 time:.5, 167 sonDelay:.1, 168 delay:0, 169 }; 170 const defV = {...defVal,...this.props.val} 171 let styleSon:any | undefined = this.props.styleSon; 172 if(this.props.val !== undefined && this.props.val.typesetting !== undefined && this.props.val.typesetting =='transverse'){ 173 if(this.props.styleSon !== undefined){ 174 styleSon = {...this.props.styleSon,float:'left'} 175 }else{ 176 styleSon = {float:'left'} 177 } 178 } 179 //Son项数 180 let index:number = 0; 181 //最大文字占格 182 let width:number=0; 183 //字体大小 184 let fontSize:number = 13; 185 //Son高度 186 let formatHeight:number = 26; 187 //Father及Son宽度 188 let formatWidth:number = 0; 189 190 let sonStr:any = this.props.children; 191 //宽高自适应 192 if(this.props.children != undefined){ 193 width = this.isWidth(sonStr); 194 } 195 if(this.props.dataSource != undefined){ 196 width = this.isWidth1(width,this.props.dataSource); 197 } 198 fontSize = this.props.style!==undefined && this.props.style.fontSize!==undefined?Number.parseInt(this.props.style.fontSize):13; 199 formatHeight = fontSize*2; 200 //调整特效字体默认宽度 201 if(width === 0){ 202 width = 5; 203 } 204 formatWidth = fontSize*width*0.6; 205 //绑定dom后是否隐藏模板 206 let hideModel = "visible"; 207 if(!this.state.eventIF){ 208 //清零 209 this.state.list.length = 0; 210 this.state.cacheList.length = 0; 211 //子项写入 212 //为文字特效 213 if(this.props.text !== undefined && this.props.text.textEffect){ 214 const separate:string = this.props.text.separate !== undefined ? this.props.text.separate : ' '; 215 const dataSource:Array<string> = this.props.text.testDataSource.split(separate); 216 let newData:Array<any> = new Array(); 217 let ind:number = 0; 218 for(let i=0;i<dataSource.length;i++){ 219 let data:Array<any> = new Array(); 220 for(let j=0;j<dataSource[i].length;j++){ 221 //若存在行延时 222 if(this.props.text.rowDelay !== undefined){ 223 data.push(<List title={dataSource[i].substring(j,j+1)} index={ind++} 224 styleSon={styleSon} animation={defV} formatHeight={formatHeight} formatWidth = {formatWidth} 225 keys={Number.MAX_VALUE-i-j} rowDelay={this.props.text.rowDelay*i}/>); 226 }else{ 227 data.push(<List title={dataSource[i].substring(j,j+1)} index={ind++} 228 styleSon={styleSon} animation={defV} formatHeight={formatHeight} formatWidth = {formatWidth} 229 keys={Number.MAX_VALUE-i-j}/>); 230 } 231 232 } 233 newData.push(data); 234 } 235 this.state.cacheList.push(newData); 236 }else{ 237 //为普通排版 238 //Son子项追加 239 if(this.props.children != null && this.props.children != undefined){ 240 for(let i=0;i<sonStr.length;i++){ 241 if(sonStr[i].type!==undefined && sonStr[i].type===Son){ 242 this.state.cacheList.push(<List title={sonStr[i].props.children} style={sonStr[i].props.style} styleSon={this.props.styleSon} 243 animation={defV} index={index++} formatHeight={formatHeight} 244 formatWidth = {formatWidth} keys={this.props.children[i].props.keys !==undefined? 245 this.props.children[i].props.keys:Number.MAX_VALUE-i} onClick={this.props.children[i].props.onClick} 246 onClickSon={this.props.onClickSon} onMouseEnter={this.props.children[i].props.onMouseEnter} 247 onMouseEnterSon={this.props.onMouseEnterSon} onMouseLeave={this.props.children[i].props.onMouseLeave} 248 onMouseLeaveSon={this.props.onMouseLeaveSon}/>); 249 } 250 } 251 } 252 //dataSource追加 253 if(this.props.dataSource !== undefined){ 254 for(let i=0;i<this.props.dataSource.length;i++){ 255 this.state.cacheList.push(<List title={this.props.dataSource[i].title} style={this.props.dataSource[i].style} index={index++} 256 styleSon={styleSon} animation={defV} formatHeight={formatHeight} formatWidth = {formatWidth} keys= 257 {this.props.dataSource[i].keys}/>); 258 } 259 } 260 } 261 //无dom绑定 262 if(defV.domId ===undefined || defV.event ===undefined){ 263 for(let i =0;i<this.state.cacheList.length;i++){ 264 this.state.list.push(this.state.cacheList[i]); 265 } 266 267 }else{ 268 //有dom绑定 269 if(this.props.val!=undefined && this.props.val.hideModel){ 270 hideModel = "hidden"; 271 } 272 //事件绑定 273 const _this = this; 274 //切换菜单后window.onload不会执行,但dom已经重置 275 if(this.props.val != undefined && this.props.val.domId != undefined && this.props.val.event != undefined && 276 document.getElementById(this.props.val.domId)==null){ 277 let interval = window.setInterval(()=>{ 278 let dom:any = null; 279 if(_this.props.val!=undefined && _this.props.val.domId != undefined){ 280 dom = document.getElementById(_this.props.val.domId); 281 } 282 if(dom !== null && dom !==undefined && dom !=="null"){ 283 _this.bindEvent(defV); 284 285 window.clearInterval(interval); 286 } 287 }, 100); 288 } 289 } 290 }else { 291 index = this.state.list.length; 292 } 293 294 //Father默认样式 295 296 const defFatherStyle:any = { 297 //border:"1px solid #91D5FF", 298 //backgroundColor: "#E6F7FF", 299 fontSize:"13px", 300 color:"#000", 301 paddimg:`${fontSize}px`, 302 height: `${formatHeight*index+2}px`, 303 width:`${formatWidth+2}px`, 304 visibility:`${hideModel}`, 305 } 306 const style = {...defFatherStyle,...this.props.style}; 307 return ( 308 <Fragment> 309 { 310 this.props.text !== undefined && this.props.text.textEffect ? 311 this.textReturn(this.state.list,formatWidth) 312 : 313 ( 314 <div style={style} className={styles.fDiv}> 315 <div className={styles.ul}> 316 {this.state.list} 317 </div> 318 </div> 319 ) 320 } 321 </Fragment> 322 ); 323 } 324 } 325 interface TestsProps{ 326 data:any,//一行或一列数据 327 typesetting:string,//排版方法 328 interval:number,//宽或高 329 } 330 class Texts extends React.Component<TestsProps, {}>{ 331 render(){ 332 return( 333 this.props.typesetting === 'vertical' ? 334 ( 335 <div style={{float:"left",width:`${this.props.interval}px`}}> 336 {this.props.data} 337 </div> 338 ):( 339 <div style={{height:`${this.props.interval}px`}}> 340 {this.props.data} 341 </div> 342 ) 343 ) 344 } 345 } 346 interface SonProps{ 347 style?:any,//样式 348 keys?:any,//单个元素的key 349 onClick?:any,//鼠标点击事件 350 onMouseEnter?:any,//鼠标移入事件 351 onMouseLeave?:any//鼠标移出事件 352 } 353 export class Son extends React.Component<SonProps, {}> { 354 } 355 interface ListProps{ 356 title:string,//文本 357 style?:any,//样式 358 styleSon?:any,//样式,渲染等级低于style 359 animation:valFrom,//动画样式 360 keys:any,//单个元素key 361 index:number,//当前元素排名,用于延时渲染 362 formatHeight:number,//单个元素高 363 formatWidth:number,//单个元素宽 364 onClick?:any,//鼠标点击事件,用于Son 365 onClickSon?:any,//鼠标点击事件,用于Father 366 onMouseEnter?:any,//鼠标移入事件,用于Son 367 onMouseEnterSon?:any,//鼠标移入事件,用于Father 368 onMouseLeave?:any,//鼠标移出事件,用于Son 369 onMouseLeaveSon?:any,//鼠标移出事件,用于Father 370 rowDelay?:number//行延时 371 } 372 class List extends React.Component<ListProps,{}> { 373 click = (key:any,title:any)=>{ 374 if(this.props.onClick !== undefined){ 375 this.props.onClick(key,title); 376 }else if(this.props.onClickSon !== undefined){ 377 this.props.onClickSon(key,title); 378 } 379 } 380 mouseEnter = (key:any,title:any)=>{ 381 if(this.props.onMouseEnter !== undefined){ 382 this.props.onMouseEnter(key,title); 383 }else if(this.props.onMouseEnterSon !== undefined){ 384 this.props.onMouseEnterSon(key,title); 385 } 386 } 387 mouseLeave = (key:any,title:any)=>{ 388 if(this.props.onMouseLeave !== undefined){ 389 this.props.onMouseLeave(key,title); 390 }else if(this.props.onMouseLeaveSon !== undefined){ 391 this.props.onMouseLeaveSon(key,title); 392 } 393 } 394 395 396 397 render() { 398 const val:valFrom = this.props.animation; 399 const style = {animation:'',animationDelay:'0s',animationTimingFunction:'ease-out'}; 400 //加载页面后直接执行 401 if(val.type === TYPE.FADEIN && val.direction === DIRECTION.TOP || val.type === 'fadeIn' && val.direction === 'top' 402 || val.type === TYPE.FADEIN && val.direction === 'top' || val.type === 'fadeIn' && val.direction === DIRECTION.TOP){ 403 style.animation= `${styles.fadeInTop} ${val.time}s forwards`; 404 }else if(val.type === TYPE.FADEIN && val.direction === DIRECTION.BUTTOM || val.type === 'fadeIn' && val.direction === 'buttom' 405 || val.type === TYPE.FADEIN && val.direction === 'buttom' || val.type === 'fadeIn' && val.direction === DIRECTION.BUTTOM){ 406 style.animation = `${styles.fadeInButtom} ${val.time}s forwards`; 407 }else if(val.type === TYPE.FADEIN && val.direction === DIRECTION.LEFT || val.type === 'fadeIn' && val.direction === 'left' 408 || val.type === TYPE.FADEIN && val.direction === 'left' || val.type === 'fadeIn' && val.direction === DIRECTION.LEFT){ 409 style.animation = `${styles.fadeInLeft} ${val.time}s forwards`; 410 }else if(val.type === TYPE.FADEIN && val.direction === DIRECTION.REGIST || val.type === 'fadeIn' && val.direction === 'regist' 411 || val.type === TYPE.FADEIN && val.direction === 'regist' || val.type === 'fadeIn' && val.direction === DIRECTION.REGIST){ 412 style.animation = `${styles.fadeInRegist} ${val.time}s forwards`; 413 }else if(val.type === TYPE.FADEIN && val.direction === DIRECTION.TOPLEFT || val.type === 'fadeIn' && val.direction === 'topLeft' 414 || val.type === TYPE.FADEIN && val.direction === 'topLeft' || val.type === 'fadeIn' && val.direction === DIRECTION.TOPLEFT){ 415 style.animation = `${styles.fadeInTopLeft} ${val.time}s forwards`; 416 }else if(val.type === TYPE.FADEIN && val.direction === DIRECTION.TOPREGIST || val.type === 'fadeIn' && val.direction === 'topRegist' 417 || val.type === TYPE.FADEIN && val.direction === 'topRegist' || val.type === 'fadeIn' && val.direction === DIRECTION.TOPREGIST){ 418 style.animation = `${styles.fadeInTopRegist} ${val.time}s forwards`; 419 }else if(val.type === TYPE.FADEIN && val.direction === DIRECTION.BUTTOMLEFT || val.type === 'fadeIn' && val.direction === 'buttomLeft' 420 || val.type === TYPE.FADEIN && val.direction === 'buttomLeft' || val.type === 'fadeIn' && val.direction === DIRECTION.BUTTOMLEFT){ 421 style.animation = `${styles.fadeInButtomLeft} ${val.time}s forwards`; 422 }else if(val.type === TYPE.FADEIN && val.direction === DIRECTION.BUTTOMREGIST || val.type === 'fadeIn' && val.direction === 'buttomRegist' 423 || val.type === TYPE.FADEIN && val.direction === 'buttomRegist' || val.type === 'fadeIn' && val.direction === DIRECTION.BUTTOMREGIST){ 424 style.animation = `${styles.fadeInButtomRegist} ${val.time}s forwards`; 425 } 426 if(val.sonDelay !== undefined && val.delay !== undefined){ 427 if(this.props.rowDelay !== undefined){ 428 style.animationDelay = `${this.props.index*val.sonDelay+val.delay+this.props.rowDelay}s`; 429 }else{ 430 style.animationDelay = `${this.props.index*val.sonDelay+val.delay}s`; 431 } 432 433 } 434 //Son默认样式 435 const defStyle:any = { 436 textAlign: "center", 437 width:`${this.props.formatWidth}px`, 438 height:`${this.props.formatHeight}px`, 439 lineHeight:`${this.props.formatHeight}px`, 440 } 441 const sty = {...defStyle,...this.props.styleSon,...this.props.style,...style}; 442 return ( 443 <li className={styles.li} style={sty} key={this.props.keys} onClick={this.click.bind(this,this.props.keys,this.props.title)} 444 onMouseEnter = {this.mouseEnter.bind(this,this.props.keys,this.props.title)} onMouseLeave= 445 {this.mouseLeave.bind(this,this.props.keys,this.props.title)}>{this.props.title}</li> 446 ); 447 } 448 }
index.less文件:
1 @top:50px; 2 @left:50px; 3 .fDiv,.li,.ul,body,div{ 4 padding: 0px; 5 margin: 0px; 6 border: 0px; 7 } 8 .fDiv{ 9 position: relative; 10 } 11 .li{ 12 list-style:none; 13 visibility:hidden; 14 cursor: pointer; 15 font:12px/1.5 Tahoma,Helvetica,Arial,'宋体',sans-serif; 16 } 17 18 // .li:hover{ 19 // background-color: #A1E5FF; 20 // text-decoration:underline; 21 // } 22 // .ul{ 23 // position: absolute; 24 // z-index: 999; 25 // display: inline-block; 26 // } 27 @keyframes fadeInTop{ 28 0%{ 29 opacity: 0; 30 margin-top: @top; 31 visibility:visible; 32 } 33 100%{ 34 opacity: 1; 35 margin-top: 0px; 36 visibility:visible; 37 } 38 } 39 @keyframes fadeInButtom{ 40 0%{ 41 opacity: 0; 42 margin-top: -@top; 43 visibility:visible; 44 } 45 100%{ 46 opacity: 1; 47 margin-top: 0px; 48 visibility:visible; 49 } 50 } 51 @keyframes fadeInLeft{ 52 0%{ 53 opacity: 0; 54 margin-left: @left; 55 visibility:visible; 56 } 57 100%{ 58 opacity: 1; 59 margin-left: 0px; 60 visibility:visible; 61 } 62 } 63 @keyframes fadeInRegist{ 64 0%{ 65 opacity: 0; 66 margin-left: -@left; 67 visibility:visible; 68 } 69 100%{ 70 opacity: 1; 71 margin-left: 0px; 72 visibility:visible; 73 } 74 } 75 @keyframes fadeInTopLeft{ 76 0%{ 77 opacity: 0; 78 margin-top: @top; 79 margin-left: @left; 80 visibility:visible; 81 } 82 100%{ 83 opacity: 1; 84 margin-top: 0px; 85 margin-left: 0px; 86 visibility:visible; 87 } 88 } 89 @keyframes fadeInTopRegist{ 90 0%{ 91 opacity: 0; 92 margin-top: @top; 93 margin-left: -@left; 94 visibility:visible; 95 } 96 100%{ 97 opacity: 1; 98 margin-top: 0px; 99 margin-left: 0px; 100 visibility:visible; 101 } 102 } 103 @keyframes fadeInButtomLeft{ 104 0%{ 105 opacity: 0; 106 margin-top: -@top; 107 margin-left: @left; 108 visibility:visible; 109 } 110 100%{ 111 opacity: 1; 112 margin-top: 0px; 113 margin-left: 0px; 114 visibility:visible; 115 } 116 } 117 @keyframes fadeInButtomRegist{ 118 0%{ 119 opacity: 0; 120 margin-top: -@top; 121 margin-left: -@left; 122 visibility:visible; 123 } 124 100%{ 125 opacity: 1; 126 margin-top: 0px; 127 margin-left: 0px; 128 visibility:visible; 129 } 130 }
API:
代码示例:
运行结果:
电脑端: