UNPKG

telesoft-waves

Version:

The Siri wave replicated in a JS library.

226 lines (180 loc) 6.07 kB
import SiriWave, { ICurve, IiOS9CurveDefinition } from "./index"; export class iOS9Curve implements ICurve { ctrl: SiriWave; definition: IiOS9CurveDefinition; spawnAt: number; noOfCurves: number; prevMaxY: number; phases: number[]; amplitudes: number[]; despawnTimeouts: number[]; offsets: number[]; speeds: number[]; finalAmplitudes: number[]; widths: number[]; verses: number[]; GRAPH_X = 25; AMPLITUDE_FACTOR = 0.8; SPEED_FACTOR = 1; DEAD_PX = 2; ATT_FACTOR = 4; DESPAWN_FACTOR = 0.02; NOOFCURVES_RANGES: [number, number] = [2, 5]; AMPLITUDE_RANGES: [number, number] = [0.3, 1]; OFFSET_RANGES: [number, number] = [-3, 3]; WIDTH_RANGES: [number, number] = [1, 3]; SPEED_RANGES: [number, number] = [0.5, 1]; DESPAWN_TIMEOUT_RANGES: [number, number] = [500, 2000]; constructor(ctrl: SiriWave, definition: IiOS9CurveDefinition) { this.ctrl = ctrl; this.definition = definition; this.noOfCurves = 0; this.spawnAt = 0; this.prevMaxY = 0; this.phases = []; this.offsets = []; this.speeds = []; this.finalAmplitudes = []; this.widths = []; this.amplitudes = []; this.despawnTimeouts = []; this.verses = []; } private getRandomRange(e: [number, number]): number { return e[0] + Math.random() * (e[1] - e[0]); } private spawnSingle(ci: number): void { this.phases[ci] = 0; this.amplitudes[ci] = 0; this.despawnTimeouts[ci] = this.getRandomRange(this.DESPAWN_TIMEOUT_RANGES); this.offsets[ci] = this.getRandomRange(this.OFFSET_RANGES); this.speeds[ci] = this.getRandomRange(this.SPEED_RANGES); this.finalAmplitudes[ci] = this.getRandomRange(this.AMPLITUDE_RANGES); this.widths[ci] = this.getRandomRange(this.WIDTH_RANGES); this.verses[ci] = this.getRandomRange([-1, 1]); } private getEmptyArray(count: number): number[] { return new Array(count); } private spawn(): void { this.spawnAt = Date.now(); this.noOfCurves = Math.floor(this.getRandomRange(this.NOOFCURVES_RANGES)); this.phases = this.getEmptyArray(this.noOfCurves); this.offsets = this.getEmptyArray(this.noOfCurves); this.speeds = this.getEmptyArray(this.noOfCurves); this.finalAmplitudes = this.getEmptyArray(this.noOfCurves); this.widths = this.getEmptyArray(this.noOfCurves); this.amplitudes = this.getEmptyArray(this.noOfCurves); this.despawnTimeouts = this.getEmptyArray(this.noOfCurves); this.verses = this.getEmptyArray(this.noOfCurves); for (let ci = 0; ci < this.noOfCurves; ci++) { this.spawnSingle(ci); } } private globalAttFn(x: number): number { return Math.pow(this.ATT_FACTOR / (this.ATT_FACTOR + Math.pow(x, 2)), this.ATT_FACTOR); } private sin(x: number, phase: number): number { return Math.sin(x - phase); } private yRelativePos(i: number): number { let y = 0; for (let ci = 0; ci < this.noOfCurves; ci++) { // Generate a static T so that each curve is distant from each oterh let t = 4 * (-1 + (ci / (this.noOfCurves - 1)) * 2); // but add a dynamic offset t += this.offsets![ci]; const k = 1 / this.widths![ci]; const x = i * k - t; y += Math.abs(this.amplitudes[ci] * this.sin(this.verses[ci] * x, this.phases[ci]) * this.globalAttFn(x)); } // Divide for NoOfCurves so that y <= 1 return y / this.noOfCurves; } private yPos(i: number): number { return ( this.AMPLITUDE_FACTOR * this.ctrl.heightMax * this.ctrl.amplitude * this.yRelativePos(i) * this.globalAttFn((i / this.GRAPH_X) * 2) ); } private xPos(i: number): number { return this.ctrl.width * ((i + this.GRAPH_X) / (this.GRAPH_X * 2)); } private drawSupportLine() { const { ctx } = this.ctrl; const coo: [number, number, number, number] = [0, this.ctrl.heightMax, this.ctrl.width, 1]; const gradient = ctx.createLinearGradient.apply(ctx, coo); gradient.addColorStop(0, "transparent"); gradient.addColorStop(0.1, "rgba(255,255,255,.5)"); gradient.addColorStop(1 - 0.1 - 0.1, "rgba(255,255,255,.5)"); gradient.addColorStop(1, "transparent"); ctx.fillStyle = gradient; ctx.fillRect.apply(ctx, coo); } draw() { const { ctx } = this.ctrl; ctx.globalAlpha = 0.7; ctx.globalCompositeOperation = "lighter"; if (this.spawnAt === 0) { this.spawn(); } if (this.definition.supportLine) { // Draw the support line return this.drawSupportLine(); } for (let ci = 0; ci < this.noOfCurves; ci++) { if (this.spawnAt + this.despawnTimeouts[ci] <= Date.now()) { this.amplitudes[ci] -= this.DESPAWN_FACTOR; } else { this.amplitudes[ci] += this.DESPAWN_FACTOR; } this.amplitudes[ci] = Math.min(Math.max(this.amplitudes[ci], 0), this.finalAmplitudes[ci]); this.phases[ci] = (this.phases[ci] + this.ctrl.speed * this.speeds[ci] * this.SPEED_FACTOR) % (2 * Math.PI); } let maxY = -Infinity; let minX = +Infinity; // Write two opposite waves for (const sign of [1, -1]) { ctx.beginPath(); for (let i = -this.GRAPH_X; i <= this.GRAPH_X; i += this.ctrl.opt.pixelDepth!) { const x = this.xPos(i); const y = this.yPos(i); ctx.lineTo(x, this.ctrl.heightMax - sign * y); minX = Math.min(minX, x); maxY = Math.max(maxY, y); } ctx.closePath(); ctx.fillStyle = `rgba(${this.definition.color}, 1)`; ctx.strokeStyle = `rgba(${this.definition.color}, 1)`; ctx.fill(); } if (maxY < this.DEAD_PX && this.prevMaxY > maxY) { this.spawnAt = 0; } this.prevMaxY = maxY; return null; } static getDefinition(): IiOS9CurveDefinition[] { return [ { color: "255,255,255", supportLine: true, }, { // blue color: "15, 82, 169", }, { // red color: "173, 57, 76", }, { // green color: "48, 220, 155", }, ]; } }