"use strict"; import random from './Random'; // Bounded random number generation import Vector from './Vector'; // 2D vector class import Range from './Range'; // Range representation // Subclasses import Star from './Star'; // ~~~ class StarlightRenderer { constructor(canvas) { // The colour of the sky in the background. this.skyColour1 = "hsla(248, 100%, 23%, 1)"; this.skyColour2 = "hsla(248, 100%, 5%, 1)"; this.starCount = 1000; this.starSize = new Range(2, 10); this.twinkleDuration = new Range(0.5, 1.5); this.twinkleSize = new Range(1.2, 2); // ~~~ this.canvas = canvas; this.context = canvas.getContext("2d"); this.trackWindowSize(); this.lastFrame = +new Date() / 1000; // ~~~ this.stars = []; for(let i = 0; i < this.starCount; i++) { let nextStar = new Star( this.canvas, new Vector( random(0, this.canvas.width), random(0, this.canvas.height) ), random(this.starSize.min, this.starSize.max) ); nextStar.pointCount = random(4, 8); // Make larger stars tend towards having longer points nextStar.innerRingRatio = random(0.2, 0.8, true); nextStar.innerRingRatio = (nextStar.innerRingRatio + nextStar.innerRingRatio*(1 - (nextStar.size / this.starSize.max))) / 2; nextStar.rotation = random(0, Math.PI*2, true); nextStar.rotationStep = random(0.1, 1, true); if(random(0, 2) == 0) nextStar.rotationStep *= -1; nextStar.alpha = random(0.2, 0.9, true); nextStar.twinkleDuration = random(this.twinkleDuration.min, this.twinkleDuration.max, true); nextStar.twinkleSize = random(this.twinkleSize.min, this.twinkleSize.max, true); this.stars.push(nextStar); } } generateGradients(canvas, context) { this.generateBackgroundGradient(canvas, context); this.generateOverlayGradient(canvas, context); } generateBackgroundGradient(canvas, context) { this.backgroundGradient = context.createRadialGradient( canvas.width / 2, canvas.height, canvas.height / 5, canvas.width / 2, canvas.height, canvas.height * 1.2 ); this.backgroundGradient.addColorStop(0, this.skyColour1); this.backgroundGradient.addColorStop(1, this.skyColour2); } generateOverlayGradient(canvas, context) { this.overlayGradient = context.createRadialGradient( canvas.width / 2, canvas.height, canvas.height / 5, canvas.width / 2, canvas.height, canvas.height * 1.2 ); this.overlayGradient.addColorStop(0, "hsla(248, 100%, 7%, 0.6)"); this.overlayGradient.addColorStop(1, "hsla(248, 100%, 2%, 0.01)"); } nextFrame() { this.update(); this.render(this.canvas, this.context); requestAnimationFrame(this.nextFrame.bind(this)); } update() { // Calculate the time between this frame and the last one this.currentFrame = (+new Date()) / 1000; this.currentDt = this.currentFrame - this.lastFrame; // Update all the stars for(let star of this.stars) star.update(this.currentDt); this.lastFrame = this.currentFrame; } render(canvas, context) { // Background context.fillStyle = this.backgroundGradient; context.fillRect(0, 0, this.canvas.width, this.canvas.height); // Stars for(let star of this.stars) star.render(context); // Overlay context.fillStyle = this.overlayGradient; context.fillRect(0, 0, this.canvas.width, this.canvas.height); } /** * Updates the canvas size to match the current viewport size. */ matchWindowSize() { this.canvas.width = window.innerWidth; this.canvas.height = window.innerHeight; this.generateGradients(this.canvas, this.context); //this.render(this.context); } /** * Makes the canvas size track the window size. */ trackWindowSize() { this.matchWindowSize(); window.addEventListener("resize", this.matchWindowSize.bind(this)); } } window.addEventListener("load", function (event) { var canvas = document.getElementById("canvas-main"), renderer = new StarlightRenderer(canvas); renderer.nextFrame(); window.renderer = renderer; });