stacker.news/components/lightning.js

161 lines
4.2 KiB
JavaScript
Raw Normal View History

import React, { useRef, useEffect, useContext } from 'react'
import { randInRange } from '@/lib/rand'
2021-04-22 22:14:32 +00:00
export const LightningContext = React.createContext(() => {})
2021-04-22 22:14:32 +00:00
export class LightningProvider extends React.Component {
state = {
bolts: []
}
/**
* Strike lightning on the screen, if the user has the setting enabled
* @returns boolean indicating whether the strike actually happened, based on user preferences
*/
strike = () => {
2023-07-25 14:14:45 +00:00
const should = window.localStorage.getItem('lnAnimate') || 'yes'
if (should === 'yes') {
this.setState(state => {
return {
bolts: [...state.bolts, <Lightning key={state.bolts.length} onDone={() => this.unstrike(state.bolts.length)} />]
}
})
return true
}
return false
2021-04-22 22:14:32 +00:00
}
2021-04-20 00:47:40 +00:00
unstrike = (index) => {
this.setState(state => {
const bolts = [...state.bolts]
bolts[index] = null
return { bolts }
})
}
2021-04-22 22:14:32 +00:00
render () {
const { props: { children } } = this
2021-04-22 22:14:32 +00:00
return (
<LightningContext.Provider value={this.strike}>
{this.state.bolts}
2021-04-22 22:14:32 +00:00
{children}
</LightningContext.Provider>
)
}
}
export const LightningConsumer = LightningContext.Consumer
export function useLightning () {
return useContext(LightningContext)
}
2021-04-20 00:47:40 +00:00
export function Lightning ({ onDone }) {
2021-04-20 00:47:40 +00:00
const canvasRef = useRef(null)
useEffect(() => {
const canvas = canvasRef.current
if (canvas.bolt) return
2021-04-20 00:47:40 +00:00
const context = canvas.getContext('2d')
canvas.width = window.innerWidth
canvas.height = window.innerHeight
canvas.bolt = new Bolt(context, {
2021-04-20 00:47:40 +00:00
startPoint: [Math.random() * (canvas.width * 0.5) + (canvas.width * 0.25), 0],
length: canvas.height,
2021-04-22 22:14:32 +00:00
speed: 100,
spread: 30,
branches: 20,
onDone
2021-04-20 00:47:40 +00:00
})
canvas.bolt.draw()
2021-04-20 00:47:40 +00:00
}, [])
return <canvas className='position-fixed' ref={canvasRef} style={{ zIndex: 100, pointerEvents: 'none' }} />
2021-04-20 00:47:40 +00:00
}
function Bolt (ctx, options) {
this.options = {
startPoint: [0, 0],
length: 100,
angle: 90,
speed: 30,
spread: 50,
branches: 10,
maxBranches: 10,
2021-04-22 22:14:32 +00:00
lineWidth: 3,
2021-04-20 00:47:40 +00:00
...options
}
this.point = [this.options.startPoint[0], this.options.startPoint[1]]
this.branches = []
this.lastAngle = this.options.angle
this.children = []
ctx.shadowColor = 'rgba(250, 218, 94, 1)'
ctx.shadowBlur = 5
ctx.shadowOffsetX = 0
ctx.shadowOffsetY = 0
ctx.fillStyle = 'rgba(250, 250, 250, 1)'
ctx.strokeStyle = 'rgba(250, 218, 94, 1)'
2021-04-22 22:14:32 +00:00
ctx.lineWidth = this.options.lineWidth
2021-04-20 00:47:40 +00:00
this.draw = (isChild) => {
ctx.beginPath()
ctx.moveTo(this.point[0], this.point[1])
const angleChange = randInRange(1, this.options.spread)
2021-04-20 00:47:40 +00:00
this.lastAngle += this.lastAngle > this.options.angle ? -angleChange : angleChange
const radians = this.lastAngle * Math.PI / 180
this.point[0] += Math.cos(radians) * this.options.speed
this.point[1] += Math.sin(radians) * this.options.speed
ctx.lineTo(this.point[0], this.point[1])
ctx.stroke()
const d = Math.sqrt(
Math.pow(this.point[0] - this.options.startPoint[0], 2) +
Math.pow(this.point[1] - this.options.startPoint[1], 2)
)
if (randInRange(0, 99) < this.options.branches && this.children.length < this.options.maxBranches) {
2021-04-20 00:47:40 +00:00
this.children.push(new Bolt(ctx, {
startPoint: [this.point[0], this.point[1]],
length: d * 0.8,
angle: this.lastAngle + randInRange(350 - this.options.spread, 370 + this.options.spread),
2021-04-20 00:47:40 +00:00
resistance: this.options.resistance,
speed: this.options.speed - 2,
spread: this.options.spread - 2,
2021-04-22 22:14:32 +00:00
branches: this.options.branches,
lineWidth: ctx.lineWidth
2021-04-20 00:47:40 +00:00
}))
}
this.children.forEach(child => {
child.draw(true)
})
if (isChild) {
return
}
if (d < this.options.length) {
window.requestAnimationFrame(() => { this.draw() })
} else {
2021-04-22 22:14:32 +00:00
ctx.canvas.style.opacity = 1
2021-04-20 00:47:40 +00:00
this.fade()
}
}
this.fade = function () {
2021-04-22 22:14:32 +00:00
ctx.canvas.style.opacity -= 0.04
if (ctx.canvas.style.opacity <= 0) {
2021-04-20 00:47:40 +00:00
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight)
this.options.onDone()
2021-04-20 00:47:40 +00:00
return
}
setTimeout(() => { this.fade() }, 50)
}
}