Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const styles = () => {

const sw = () => {
return gulp
.src('src/sw.js')
.src(['src/sw.js'])
.pipe(
esbuild({
target: 'es2015',
Expand All @@ -63,7 +63,7 @@ const sw = () => {

const scripts = () => {
return gulp
.src('src/scripts/index.js')
.src(['src/scripts/index.js', 'src/scripts/workers/snow-worker-25.js'])
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не уверен, что этого достаточно, чтобы файл оказался где надо во всех режимах, система сборки у вас довольно сложная, буду рад советам.

.pipe(
esbuild({
target: 'es2015',
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/includes/blocks/snow-25.njk
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<canvas id="snowCanvas"></canvas>
<canvas role="presentation" id="snowCanvas"></canvas>
114 changes: 27 additions & 87 deletions src/scripts/modules/snow-toggle-25.js
Original file line number Diff line number Diff line change
@@ -1,96 +1,42 @@
import throttle from '../libs/throttle.js'

window.onload = function () {
class Snowfall {
class SnowfallWorker {
constructor(canvas) {
this.canvas = canvas
this.context = canvas.getContext('2d')
this.snowCounter = 0
this.speedMultiplier = 0
this.snowflakes = []
this.animationFrameId = null

this.resize()
}

createSnowflake() {
const radius = getRandomInt(1, 10)
return {
xpos: getRandomInt(0, this.canvas.width),
ypos: getRandomInt(-this.canvas.height, 0),
radius: radius,
opacity: radius * 10,
speed: this.speedMultiplier * (radius / 6),
dx: (Math.random() - 0.5) * 2,
}
}

drawSnowflake(flake) {
this.context.beginPath()
this.context.arc(flake.xpos, flake.ypos, flake.radius, 0, Math.PI * 2)
this.context.fillStyle = `hsl(202.33deg 53.09% 84.12% / ${flake.opacity}%)`
this.context.fill()
// Передаем контроль над канвасом в воркер, чтобы отрисовка не блокировала основной поток
const offscreenCanvas = canvas.transferControlToOffscreen()
this.worker = new Worker('/scripts/workers/snow-worker-25.js') // Файл генерируется в gulpfile, не импортом
this.worker.postMessage({ type: 'canvas', canvas: offscreenCanvas }, [offscreenCanvas])
}

updateSnowflake(flake) {
flake.xpos += flake.dx
flake.ypos += flake.speed

if (flake.ypos - flake.radius > this.canvas.height) {
flake.ypos = getRandomInt(-this.canvas.height, 0)
flake.xpos = getRandomInt(0, this.canvas.width)
}
createResizeObserver() {
this.resizer = throttle(() => {
this.resize()
}, 33)
}

start() {
this.snowCounter = 100
this.speedMultiplier = 1

this.stop()
this.snowflakes = Array.from({ length: this.snowCounter }, () => this.createSnowflake())
this.animate()
}

animate() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
this.snowflakes.forEach((flake) => {
this.updateSnowflake(flake)
this.drawSnowflake(flake)
})
this.animationFrameId = requestAnimationFrame(this.animate.bind(this))
}

stop() {
if (this.animationFrameId) {
cancelAnimationFrame(this.animationFrameId)
this.animationFrameId = null
this.resize() // У воркера нет информацию о размере окна, поэтому мы должны обновить его вручную перед началом
if (!this.resizer) {
this.resizer = this.createResizeObserver()
}

snowfall.snowflakes = []
snowfall.context.clearRect(0, 0, canvas.width, canvas.height)
window.addEventListener('resize', this.resizer)
this.worker.postMessage({ type: 'start' })
}

setCounter(newCount) {
this.snowCounter = newCount
this.snowflakes = Array.from({ length: this.snowCounter }, () => this.createSnowflake())
stop() {
this.worker.postMessage({ type: 'stop' })
window.removeEventListener('resize', this.resizer)
this.resizer?.cancel()
this.resizer = null
}

resize() {
this.canvas.width = window.innerWidth
this.canvas.height = window.innerHeight
const { innerWidth, innerHeight } = window
this.worker.postMessage({ type: 'resize', window: { innerWidth, innerHeight } })
}
}

function debounce(func, wait) {
let timeout
return function (...args) {
clearTimeout(timeout)
timeout = setTimeout(() => func.apply(this, args), wait)
}
}

function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min
}

function changeSnowAnimation(animationName) {
if (animationName === 'none') {
snowfall.stop()
Expand All @@ -103,10 +49,8 @@ window.onload = function () {

const canvas = document.getElementById('snowCanvas')
const snowToggle = document.querySelector('.snow-toggle')
const snowfall = new Snowfall(canvas)
const snowfall = new SnowfallWorker(canvas)
const pageTitle = document.title
snowfall.start()
document.title = '❄️ ' + pageTitle
const storageKey = 'snow'

let currentStorage = localStorage.getItem(storageKey)
Expand All @@ -115,6 +59,9 @@ window.onload = function () {
snowToggle.querySelector(`.snow-toggle__control[value='${currentStorage}']`).checked = true

changeSnowAnimation(currentStorage)
} else {
snowToggle.querySelector(`.snow-toggle__control[value='snowfall']`).checked = true
changeSnowAnimation('snowfall')
}

window.addEventListener('storage', () => {
Expand All @@ -128,11 +75,4 @@ window.onload = function () {
changeSnowAnimation(value)
})
})

window.addEventListener(
'resize',
debounce(() => {
snowfall.resize()
}, 150),
)
}
97 changes: 97 additions & 0 deletions src/scripts/workers/snow-worker-25.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
self.onmessage = function (event) {
const { type } = event.data
if (type === 'canvas') {
self.snowfall?.stop()
self.snowfall = new Snowfall(event.data.canvas)
} else if (type === 'resize') {
self.snowfall.resize(event.data.window)
} else if (type === 'stop') {
self.snowfall?.stop()
} else if (type === 'start') {
self.snowfall?.start()
} else if (type === 'clear') {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
}
}

class Snowfall {
constructor(canvas) {
this.canvas = canvas
this.context = canvas.getContext('2d')
this.snowCounter = 0
this.speedMultiplier = 0
this.snowflakes = []
this.animationFrameId = null
}

createSnowflake() {
const radius = getRandomInt(1, 10)
return {
xpos: getRandomInt(0, this.canvas.width),
ypos: getRandomInt(-this.canvas.height, 0),
radius: radius,
opacity: radius * 10,
speed: this.speedMultiplier * (radius / 6),
dx: (Math.random() - 0.5) * 2,
}
}

drawSnowflake(flake) {
this.context.beginPath()
this.context.arc(flake.xpos, flake.ypos, flake.radius, 0, Math.PI * 2)
this.context.fillStyle = `hsl(202.33deg 53.09% 84.12% / ${flake.opacity}%)`
this.context.fill()
}

updateSnowflake(flake) {
flake.xpos += flake.dx
flake.ypos += flake.speed

if (flake.ypos - flake.radius > this.canvas.height) {
flake.ypos = getRandomInt(-this.canvas.height, 0)
flake.xpos = getRandomInt(0, this.canvas.width)
}
}

start() {
this.snowCounter = 100
this.speedMultiplier = 1

this.stop()
this.snowflakes = Array.from({ length: this.snowCounter }, () => this.createSnowflake())
this.animate()
}

animate() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
this.snowflakes.forEach((flake) => {
this.updateSnowflake(flake)
this.drawSnowflake(flake)
})
this.animationFrameId = requestAnimationFrame(this.animate.bind(this))
}

stop() {
if (this.animationFrameId) {
cancelAnimationFrame(this.animationFrameId)
this.animationFrameId = null
}

this.snowflakes = []
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
}

setCounter(newCount) {
this.snowCounter = newCount
this.snowflakes = Array.from({ length: this.snowCounter }, () => this.createSnowflake())
}

resize(window) {
this.canvas.width = window.innerWidth
this.canvas.height = window.innerHeight
}
}

function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min
}