function mountainHeight(position, roughness) { let frequencies = [1721, 947, 547, 233, 73, 31, 7] return frequencies.reduce((height, freq) => height * roughness - Math.cos(freq * position), 0) } export default class Godrays { constructor(canvas) { this.canvas = canvas this.ctx = canvas.getContext('2d') this.frame = 0 this.godraysCanvas = canvas.cloneNode() this.godraysCtx = this.godraysCanvas.getContext('2d') this.rAF = null this.stopSign = false this.resize = this.resize.bind(this) this.loop = this.loop.bind(this) this.start() } start() { window.addEventListener('resize', this.resize) this.resize() this.loop() } stop() { window.removeEventListener('resize', this.resize) this.stopSign = true this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) this.godraysCtx.clearRect(0, 0, this.godraysCanvas.width, this.godraysCanvas.height) } loop() { if (this.stopSign) { this.rAF && window.cancelAnimationFrame(this.rAF) return } this.draw() this.rAF = window.requestAnimationFrame(this.loop) } draw() { this.canvas.width = 512 this.canvas.height = 256 this.godraysCanvas.width = 128 this.godraysCanvas.height = 64 let sunY = Math.cos(this.frame++ / 512) * 24 let emissionGradient = this.godraysCtx.createRadialGradient( this.godraysCanvas.width / 2, this.godraysCanvas.height / 2 + sunY, // The sun's center. 0, // Start radius. this.godraysCanvas.width / 2, this.godraysCanvas.height / 2 + sunY, // Sun's center again. 44 // End radius. ) this.godraysCtx.fillStyle = emissionGradient emissionGradient.addColorStop(.1, '#0C0804') // Color for pixels in radius 0 to 4.4 (44 * .1). emissionGradient.addColorStop(.2, '#060201') // Color for everything past radius 8.8. this.godraysCtx.fillRect(0, 0, this.godraysCanvas.width, this.godraysCanvas.height) this.godraysCtx.fillStyle = '#000' let skyGradient = this.ctx.createLinearGradient(0, 0, 0, this.canvas.height) skyGradient.addColorStop(0, '#2a3e55') // Blueish at the top. skyGradient.addColorStop(.7, '#8d4835') // Reddish at the bottom. this.ctx.fillStyle = skyGradient this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height) for (let i = 0; i < 4; i++) { this.ctx.fillStyle = `hsl(7, 23%, ${23 - i * 6}%)` for (let x = this.canvas.width; x--;) { let mountainPosition = (this.frame + this.frame * i * i) / 3000 + x / 2000 let mountainRoughness = i / 19 - .5 let y = 128 + i * 25 + mountainHeight(mountainPosition, mountainRoughness) * 45 this.ctx.fillRect(x, y, 1, 999) this.godraysCtx.fillRect(x / 4, y / 4 + 1, 1, 999) } } this.ctx.globalCompositeOperation = this.godraysCtx.globalCompositeOperation = 'lighter' for (let scaleFactor = 1.07; scaleFactor < 5; scaleFactor *= scaleFactor) { this.godraysCtx.drawImage( this.godraysCanvas, (this.godraysCanvas.width - this.godraysCanvas.width * scaleFactor) / 2, (this.godraysCanvas.height - this.godraysCanvas.height * scaleFactor) / 2 - sunY * scaleFactor + sunY, this.godraysCanvas.width * scaleFactor, this.godraysCanvas.height * scaleFactor ) } this.ctx.drawImage(this.godraysCanvas, 0, 0, this.canvas.width, this.canvas.height) } resize() { this.canvas.width = window.innerWidth this.canvas.height = window.innerHeight } }