const { random } = Math const randomColor = [ ['#95e1d3', '#eaffd0', '#fce38a', '#f38181'], ['#6a2c70', '#b83b5e', '#f08a5d', '#f9ed69'], ['#edb1f1', '#d59bf6', '#9896f1', '#8ef6e4'], ['#ff9de2', '#8c82fc', '#b693fe', '#7effdb'], ['#fff5a5', '#ffaa64', '#ff8264', '#ff6464'] ] export interface IRibbonOptions { zIndex: number; alpha: number; size: number; } interface IRibbonPoint { x: number; y: number; } const getRandomInt = (value: number) => { return Math.floor(Math.random() * value) } export class Ribbon { /** * 画布元素 */ canvas: HTMLCanvasElement; /** * Canvas中的Context */ ctx: CanvasRenderingContext2D; /** * 轨迹路径 */ path: IRibbonPoint[]; /** * 区域宽度 */ width: number; /** * 区域高度 */ height: number; /** * 颜色色系 */ colorIndex: number; /** * 配置项 */ options: IRibbonOptions constructor(options: IRibbonOptions = { alpha: 0.6, size: 300, zIndex: 0 }) { this.options = options // 生成Canvas元素 并添加到Body中 this.canvas = document.createElement('canvas') this.canvas.classList.add('ribbon-canvas') this.canvas.style.cssText = `position:fixed;top:0;left:0;z-index:${this.options.zIndex}` document.getElementsByTagName('body')[0].appendChild(this.canvas) this.ctx = this.canvas.getContext('2d') as CanvasRenderingContext2D this.width = window.innerWidth this.height = window.innerHeight // 返回实际宽高 const dpr = window.devicePixelRatio || 1 this.canvas.width = this.width * dpr this.canvas.height = this.height * dpr // 水平、竖直方向缩放 this.ctx.scale(dpr, dpr) // 图形透明度 this.ctx.globalAlpha = this.options.alpha this.path = [] this.colorIndex = 0 this.reDraw() } /** * 重新绘制 */ reDraw() { // 清除之前绘制的图形 this.ctx.clearRect(0, 0, this.width, this.height) // 生成新的点和路径 const point1: IRibbonPoint = { x: 0, y: this.height * 0.7 + this.options.size } const point2: IRibbonPoint = { x: 0, y: this.height * 0.7 - this.options.size } this.path = [point1, point2] // 随机选择色系 this.colorIndex = getRandomInt(randomColor.length) // 路径没有填满屏幕宽度时,绘制路径 while (this.path[1].x < this.width + this.options.size) { this.draw(this.path[0], this.path[1]) } } /** * 绘制线 * @param start 起点 * @param end 终点 */ draw(start: IRibbonPoint, end: IRibbonPoint) { // 绘制当前点 this.ctx.beginPath() this.ctx.moveTo(start.x, start.y) this.ctx.lineTo(end.x, end.y) const nextX = this.geneX(end.x) const nextY = this.geneY(end.y) this.ctx.lineTo(nextX, nextY) this.ctx.closePath() // 随机生成颜色 const color = randomColor[this.colorIndex] this.ctx.fillStyle = `${color[getRandomInt(color.length)]}` // 根据当前样式填充路径 this.ctx.fill() // 继续绘制下一个点 // 起点更新为当前终点 this.path[0] = this.path[1] // 更新终点 this.path[1] = { x: nextX, y: nextY } } /** * 生成下一个x坐标 * @param x 当前x坐标 */ geneX(x: number) { // 当前坐标向上平移 return x + this.options.size } /** * 生成下一个y坐标 * @param y 当前y坐标 */ geneY(y: number) { // 当前坐标向右平移 const temp = y + (random() * 2 - 1.1) * this.options.size return (temp > this.height || temp < 0) ? this.height : temp } /** * 清除画布 */ clear() { this.canvas.remove() } }