import { doublePi } from '@src/common/constants' ; import Mask from '@src/common/mask' ; import { CommonConfig } from '@src/types/common-config' ; import { ValueOf } from '@src/types/utility-types' ; import { IElement , Options , StdOptions , StrNumBool } from '@src/types/wave' ; import { calcQuantity , isPlainObject , isUndefined , randomColor , randomInRange , } from '@src/utils' ; const complexOptions = [ 'fill' , 'fillColor' , 'line' , 'lineColor' , 'lineWidth' , 'offsetLeft' , 'offsetTop' , 'crestHeight' , 'speed' , ] as const ; const plainOptions = [ 'opacity' , 'mask' , 'maskMode' ] as const ; const stdProperties = [ ... complexOptions , 'crestCount' ] as const ; export type ComplexOptions = ValueOf < typeof complexOptions > ; export type PlainOptions = ValueOf < typeof plainOptions > ; export default class Wave extends Mask < Options > { static defaultConfig : Options = { num : 3 , fill : false , fillColor : [ ] , line : true , lineColor : [ ] , lineWidth : [ ] , offsetLeft : [ ] , offsetTop : [ ] , crestHeight : [ ] , crestCount : [ ] , speed : [ ] , } ; protected elements ! : IElement [ ] [ ] ; protected waveLength : number [ ] = [ ] ; constructor ( selector , options ? ) { super ( Wave . defaultConfig , selector , options ) ; this . bootstrap ( ) ; } /**
     * 初始化数据和运行程序
     */ protected init ( ) { this . ownResizeEvent ( ) ; this . optionsNormalize ( ) ; this . loadMaskImage ( ) ; this . createDots ( ) ; } /**
     * 标准化配置项
     */ protected optionsNormalize ( ) { stdProperties . forEach ( ( property ) => { let num = this . options . num ; const rawValue = this . options [ property ] ; const stdValue = [ ] ; const scaleRange = property === 'offsetLeft' ? this . canvasWidth : this . canvasHeight ; while ( num -- ) { const value = Array . isArray ( rawValue ) ? rawValue [ num ] : rawValue ; stdValue [ num ] = isUndefined ( value ) ? this . getOptionDefaultValue ( property ) : Wave . getOptionProcessedValue ( property , value , scaleRange ) ; if ( property === 'crestCount' ) { this . waveLength [ num ] = this . canvasWidth / ( stdValue [ num ] as number ) ; } } this . options [ property ] = stdValue as never ; } ) ; } param property protected getOptionDefaultValue ( property ) { const { canvasWidth , canvasHeight } = this ; switch ( property ) { case 'lineColor' : case 'fillColor' : return randomColor ( ) ; case 'lineWidth' : return randomInRange ( 2 , 0.2 ) ; case 'offsetLeft' : return Math . random ( ) * canvasWidth ; case 'offsetTop' : case 'crestHeight' : return Math . random ( ) * canvasHeight ; case 'crestCount' : return randomInRange ( canvasWidth / 2 , 1 ) ; case 'speed' : return randomInRange ( 0.4 , 0.1 ) ; case 'fill' : return false ; case 'line' : return true ; } } param property param value param range protected static getOptionProcessedValue ( property , value , range ) { if ( property === 'offsetTop' || property === 'offsetLeft' || property === 'crestHeight' ) { return calcQuantity ( value as number , range ) ; } return value ; } /**
     * 创建波浪线条像素点
     */ protected createDots ( ) { const { canvasWidth , waveLength } = this ; let { num } = this . options ; while ( num -- ) { const line = [ ] ; const step = doublePi / waveLength [ num ] ; for ( let i = 0 ; i <= canvasWidth ; i ++ ) { line . push ( { x : i , y : i * step , } ) ; } this . elements [ num ] = line ; } } /**
     * 绘图
     */ protected draw ( ) { this . clearCanvasAndSetGlobalAttrs ( ) ; this . renderMaskMode ( ( ) => { this . drawWaves ( ) ; } ) ; this . requestAnimationFrame ( ) ; } /**
     * 绘制波浪效果
     */ protected drawWaves ( ) { const { ctx , canvasWidth , canvasHeight , isPaused } = this ; const options = this . options as StdOptions & CommonConfig ; this . elements . forEach ( ( lines , i ) => { const crestHeight = options . crestHeight [ i ] ; const offsetLeft = options . offsetLeft [ i ] ; const offsetTop = options . offsetTop [ i ] ; const speed = options . speed [ i ] ; ctx . save ( ) ; ctx . beginPath ( ) ; lines . forEach ( ( dot , j ) => { ctx [ j ? 'lineTo' : 'moveTo' ] ( dot . x , crestHeight * Math . sin ( dot . y + offsetLeft ) + offsetTop ) ; ! isPaused && ( dot . y -= speed ) ; } ) ; if ( options . fill [ i ] ) { ctx . lineTo ( canvasWidth , canvasHeight ) ; ctx . lineTo ( 0 , canvasHeight ) ; ctx . closePath ( ) ; ctx . fillStyle = options . fillColor [ i ] ; ctx . fill ( ) ; } if ( options . line [ i ] ) { ctx . lineWidth = options . lineWidth [ i ] ; ctx . strokeStyle = options . lineColor [ i ] ; ctx . stroke ( ) ; } ctx . restore ( ) ; } ) ; } /**
     * 窗口尺寸调整事件
     */ protected ownResizeEvent ( ) { const props = [ 'offsetLeft' , 'offsetTop' , 'crestHeight' ] as const ; const options = this . options as StdOptions ; this . onResize ( ( scaleX , scaleY ) => { props . forEach ( ( prop ) => { const scale = prop === 'offsetLeft' ? scaleX : scaleY ; options [ prop ] . forEach ( ( value , i , array ) => { array [ i ] = value * scale ; } ) ; } ) ; this . elements . forEach ( ( lines ) => { lines . forEach ( ( dot ) => { dot . x *= scaleX ; dot . y *= scaleY ; } ) ; } ) ; } ) ; } param property param newValue protected updateComplexOptions ( property , newValue ? ) { if ( ! newValue ) return ; const scaleRange = property === 'offsetLeft' ? this . canvasWidth : this . canvasHeight ; const options = this . options as StdOptions & CommonConfig ; const isArrayType = Array . isArray ( newValue ) ; options [ property ] . forEach ( ( curValue , i , array ) => { let value = isArrayType ? ( newValue as StrNumBool [ ] ) [ i ] : newValue ; value = Wave . getOptionProcessedValue ( property , value as StrNumBool , scaleRange ) ; if ( isUndefined ( value ) ) { value = curValue ; } array [ i ] = value ; } ) ; } param property param newValue protected updatePlainOptions ( option , newValue ? ) { this . options [ option ] = newValue as never ; if ( option === 'mask' ) { this . loadMaskImage ( ) ; } } /**
     * 动态设置 options 选项值
     */ setOptions ( newOptions ) { if ( ! this . isRunningSupported || ! isPlainObject ( newOptions ) ) return ; for ( const property in newOptions ) { if ( Object . hasOwnProperty . call ( newOptions , property ) ) { if ( plainOptions . indexOf ( property as never ) !== - 1 ) { this . updatePlainOptions ( property as PlainOptions , newOptions [ property as PlainOptions ] ) ; } else if ( complexOptions . indexOf ( property as never ) !== - 1 ) { this . updateComplexOptions ( property as ComplexOptions , newOptions [ property as ComplexOptions ] ) ; } } } } }