import { EVENT_NAMES_WAVE_LOADING } from '@src/common/constants' ; import easing from '@src/common/easing' ; import { CommonConfig } from '@src/types/common-config' ; import { ValueOf } from '@src/types/utility-types' ; import { InputOptions , Options } from '@src/types/wave-loading' ; import { isPlainObject , isString , merge } from '@src/utils' ; import Wave , { ComplexOptions , PlainOptions } from './wave' ; const plainOptionsWL = [ 'font' , 'textColor' , 'textFormatter' , 'borderRadius' , ] as const ; export type PlainOptionsWL = ValueOf < typeof plainOptionsWL > ; export default class WaveLoading extends Wave { static defaultConfig = { num : 1 , font : 'normal 400 16px Arial' , textColor : '#333' , textFormatter : 'loading...%d%' , fill : true , line : false , fillColor : '#27C9E5' , borderRadius : '50%' , offsetLeft : 0 , crestHeight : 4 , crestCount : 1 , speed : 0.3 , duration : 5000 , easing : 'swing' , } as unknown as Options ; static progressThreshold = 99.99 ; protected readonly options ! : Options & CommonConfig ; private progress = 0 ; private halfCH ! : number ; private isCompletedImmediately = false ; private fastStepValue = 1 ; private startTime ? : number ; constructor ( selector , options ? ) { super ( selector , merge ( { } , WaveLoading . defaultConfig , options ) ) ; } /**
     * 初始化数据和运行程序
     */ protected init ( ) { this . halfCH = this . canvasHeight / 2 ; this . setOffsetTop ( this . canvasHeight ) ; this . setCanvasStyle ( ) ; this . ownResizeEvent ( ) ; this . optionsNormalize ( ) ; this . loadMaskImage ( ) ; this . createDots ( ) ; this . waveLoadingResizeEvent ( ) ; } param top private setOffsetTop ( top ) { const { offsetTop } = this . options ; if ( Array . isArray ( offsetTop ) ) { offsetTop . forEach ( ( _item , i , arr ) => { arr [ i ] = top ; } ) ; } else { this . options . offsetTop = top ; } } /**
     * 设置画布 CSS 样式
     */ private setCanvasStyle ( ) { this . canvas . style . borderRadius = this . options . borderRadius ; } /**
     * 绘制入口：计算进度，绘制波纹等
     */ protected draw ( ) { this . calcProgress ( ) ; if ( this . progress < 100 ) { this . mainDrawing ( ) ; this . requestAnimationFrame ( ) ; } else { this . progress = 100 ; this . mainDrawing ( ) ; this . eventEmitter . trigger ( EVENT_NAMES_WAVE_LOADING . FINISHED ) ; } } /**
     * 绘制图案
     */ private mainDrawing ( ) { this . eventEmitter . trigger ( EVENT_NAMES_WAVE_LOADING . PROGRESS , this . progress ) ; this . calcOffsetTop ( ) ; this . clearCanvasAndSetGlobalAttrs ( ) ; this . renderMaskMode ( ( ) => { this . drawWaves ( ) ; } ) ; this . drawText ( ) ; } /**
     * 绘制进度文本
     */ private drawText ( ) { const { ctx , canvasWidth , halfCH , progress } = this ; const { font , textFormatter , textColor } = this . options ; if ( ! isString ( textFormatter ) || ! textFormatter ) return ; const text = textFormatter . replace ( /%d/g , String ( Math . floor ( progress ) ) ) ; ctx . save ( ) ; ctx . font = font ; const textWidth = ctx . measureText ( text ) . width ; const x = ( canvasWidth - textWidth ) / 2 ; ctx . textBaseline = 'middle' ; ctx . fillStyle = textColor ; ctx . font = font ; ctx . fillText ( text , x , halfCH ) ; ctx . restore ( ) ; } /**
     * 计算进度值
     */ private calcProgress ( ) { if ( this . isCompletedImmediately ) { this . progress += this . fastStepValue ; this . fastStepValue += 0.5 ; return ; } if ( this . progress >= WaveLoading . progressThreshold ) return ; if ( ! this . startTime ) { this . startTime = Date . now ( ) ; } const time = Date . now ( ) - this . startTime ; const percent = time / this . options . duration ; if ( percent <= 1 ) { this . progress = easing [ this . options . easing ] ( percent , time , 0 , 100 , this . options . duration ) ; if ( this . progress + 0.9 >= WaveLoading . progressThreshold ) { this . progress = WaveLoading . progressThreshold ; } } } /**
     * 根据进度计算波纹 offsetTop 值
     */ private calcOffsetTop ( ) { if ( ! this . isCompletedImmediately && this . progress >= WaveLoading . progressThreshold ) { return ; } const maxCrestHeight = Math . max ( ... ( this . options . crestHeight as number [ ] ) ) ; const top = this . progress === 100 ? - maxCrestHeight : Math . ceil ( ( ( 100 - this . progress ) / 100 ) * this . canvasHeight + maxCrestHeight ) ; this . setOffsetTop ( top ) ; } /**
     * 窗口尺寸调整事件
     */ protected waveLoadingResizeEvent ( ) { this . onResize ( ( ) => { this . halfCH = this . canvasHeight / 2 ; if ( this . progress === 100 ) { this . draw ( ) ; } } ) ; } /**
     * 方法：动态设置属性值
     */ setOptions ( newOptions ) { if ( ! this . isRunningSupported || ! isPlainObject ( newOptions ) ) return ; super . setOptions ( newOptions ) ; for ( const property in newOptions ) { if ( Object . hasOwnProperty . call ( newOptions , property ) && plainOptionsWL . indexOf ( property as never ) !== - 1 ) { const newValue = newOptions [ property as PlainOptionsWL ] ; this . options [ property as PlainOptionsWL ] = newValue as never ; if ( property === 'borderRadius' ) { this . setCanvasStyle ( ) ; } } } } /**
     * 方法：让进度立即加载完成
     */ done ( ) { if ( this . isRunningSupported && ! this . isCompletedImmediately ) { this . isCompletedImmediately = true ; } } /**
     * 事件：进度每次改变的时候触发
     */ onProgress ( ... args ) { this . eventEmitter . on ( EVENT_NAMES_WAVE_LOADING . PROGRESS , ... args ) ; return this ; } /**
     * 事件：进度加载到 100% 后触发
     */ onFinished ( ... args ) { this . eventEmitter . on ( EVENT_NAMES_WAVE_LOADING . FINISHED , ... args ) ; return this ; } }