import './polyfill' ; import commonConfig from '@src/common/config' ; import { defaultCanvasHeight , defaultCanvasWidth , EVENT_NAMES , isRuntimeSupported , } from '@src/common/constants' ; import Events from '@src/common/events' ; import { CommonConfig } from '@src/types/common-config' ; import { getNumberValueOfStyle , isElement , isPlainObject , isString , merge , observeElementRemoved , randomColor , } from '@src/utils' ; export interface GeneralElement { x : number ; y : number ; } export default abstract class Base < Options > { protected readonly options ! : Options & CommonConfig ; protected readonly container ? : HTMLElement | null ; protected readonly canvas ! : HTMLCanvasElement ; protected canvasWidth ! : number ; protected canvasHeight ! : number ; protected readonly ctx ! : CanvasRenderingContext2D ; protected readonly getColor ! : ( ) => string ; protected elements : unknown [ ] = [ ] ; protected isCanvasRemoved = false ; protected isPaused = false ; protected resizeHandler ? : ( ) => void ; protected eventEmitter = new Events ( ) ; protected isRunningSupported = false ; protected constructor ( defaultConfig , selector , options ? ) { if ( ! isRuntimeSupported ) return ; this . container = isElement ( selector ) ? ( selector as HTMLElement ) : document . querySelector < HTMLElement > ( selector as string ) ; this . isRunningSupported = ! ! this . container ; if ( this . container ) { this . options = merge ( { } , commonConfig , defaultConfig , options ) ; this . canvas = document . createElement ( 'canvas' ) ; this . ctx = this . canvas . getContext ( '2d' ) ! ; this . container . innerHTML = '' ; this . container . appendChild ( this . canvas ) ; this . getColor = this . makeColorMethod ( ) ; } } /**
     * 引导程序
     */ protected bootstrap ( ) { if ( this . isRunningSupported ) { this . setCanvasDimension ( ) ; this . observeCanvasRemoved ( ) ; this . resizeEvent ( ) ; this . init ( ) ; this . draw ( ) ; } } /**
     * 初始化数据或方法调用
     */ protected abstract init ( ) ; /**
     * 绘图入口
     */ protected abstract draw ( ) ; /**
     * 清除整个画布
     */ protected clearCanvasAndSetGlobalAttrs ( ) { const { ctx , canvasWidth , canvasHeight } = this ; ctx . clearRect ( 0 , 0 , canvasWidth , canvasHeight ) ; ctx . globalAlpha = this . options . opacity ; } /**
     * 生成 "getColor" 函数
     */ protected makeColorMethod ( ) { const { color } = this . options ; const colorLength = Array . isArray ( color ) ? color . length : 0 ; if ( isString ( color ) ) { return ( ) => color as string ; } if ( colorLength === 0 ) { return randomColor ; } return ( ) => ( color as string [ ] ) [ Math . floor ( Math . random ( ) * colorLength ) ] ; } /**
     * 设置画布尺寸
     */ protected setCanvasDimension ( ) { const dpr = window . devicePixelRatio ; const width = getNumberValueOfStyle ( this . container ! , 'width' ) || defaultCanvasWidth ; const height = getNumberValueOfStyle ( this . container ! , 'height' ) || defaultCanvasHeight ; this . canvasWidth = width ; this . canvasHeight = height ; this . canvas . width = width * dpr ; this . canvas . height = height * dpr ; this . canvas . style . width = width + 'px' ; this . canvas . style . height = height + 'px' ; this . ctx . scale ( dpr , dpr ) ; } /**
     * 监听画布从 DOM 中被移除时，做后期清理操作，如销毁事件等
     */ protected observeCanvasRemoved ( ) { observeElementRemoved ( this . canvas , ( ) => { this . isCanvasRemoved = true ; if ( this . resizeHandler ) { window . removeEventListener ( 'resize' , this . resizeHandler ) ; } this . eventEmitter . trigger ( EVENT_NAMES . DESTROY ) ; this . eventEmitter . off ( ) ; } ) ; } /**
     * 简单包装 window.requestAnimationFrame
     */ protected requestAnimationFrame ( ) { if ( ! this . isPaused && ! this . isCanvasRemoved ) { window . requestAnimationFrame ( this . draw . bind ( this ) ) ; } } /**
     * 窗口尺寸调整事件
     */ protected resizeEvent ( ) { if ( this . options . resize ) { this . resizeHandler = ( ) => { const preCW = this . canvasWidth ; const preCH = this . canvasHeight ; this . setCanvasDimension ( ) ; const scaleX = this . canvasWidth / preCW ; const scaleY = this . canvasHeight / preCH ; this . elements . forEach ( ( element ) => { if ( isPlainObject ( element ) ) { ; ( element as GeneralElement ) . x *= scaleX ; ( element as GeneralElement ) . y *= scaleY ; } } ) ; this . eventEmitter . trigger ( EVENT_NAMES . RESIZE , scaleX , scaleY ) ; this . isPaused && this . draw ( ) ; } ; window . addEventListener ( 'resize' , this . resizeHandler ) ; } } /**
     * 暂停运动
     */ pause ( ) { if ( this . isRunningSupported && ! this . isCanvasRemoved && ! this . isPaused ) { this . isPaused = true ; } } /**
     * 开启运动
     */ open ( ) { if ( this . isRunningSupported && ! this . isCanvasRemoved && this . isPaused ) { this . isPaused = false ; this . draw ( ) ; } } param args onDestroy ( ... args ) { this . eventEmitter . on ( EVENT_NAMES . DESTROY , ... args ) ; return this ; } param args onResize ( ... args ) { this . eventEmitter . on ( EVENT_NAMES . RESIZE , ... args ) ; return this ; } }