halo-theme-dream
Version:
梦之城,童话梦境,动漫类型博客主题。
183 lines (151 loc) • 4.65 kB
JavaScript
function snowflakeCursor(options) {
let hasWrapperEl = options && options.element
let element = hasWrapperEl || document.body
let possibleEmoji = ['❄️']
let width = window.innerWidth
let height = window.innerHeight
let cursor = { x: width / 2, y: width / 2 }
let particles = []
let canvas, context
let canvImages = []
function init() {
canvas = document.createElement('canvas')
context = canvas.getContext('2d')
canvas.style.top = '0px'
canvas.style.left = '0px'
canvas.style.pointerEvents = 'none'
if (hasWrapperEl) {
canvas.style.position = 'absolute'
element.appendChild(canvas)
canvas.width = element.clientWidth
canvas.height = element.clientHeight
} else {
canvas.style.position = 'fixed'
document.body.appendChild(canvas)
canvas.width = width
canvas.height = height
}
context.font = '12px serif'
context.textBaseline = 'middle'
context.textAlign = 'center'
possibleEmoji.forEach((emoji) => {
let measurements = context.measureText(emoji)
let bgCanvas = document.createElement('canvas')
let bgContext = bgCanvas.getContext('2d')
bgCanvas.width = measurements.width
bgCanvas.height = measurements.actualBoundingBoxAscent * 2
bgContext.textAlign = 'center'
bgContext.font = '12px serif'
bgContext.textBaseline = 'middle'
bgContext.fillText(
emoji,
bgCanvas.width / 2,
measurements.actualBoundingBoxAscent
)
canvImages.push(bgCanvas)
})
bindEvents()
loop()
}
// Bind events that are needed
function bindEvents() {
element.addEventListener('mousemove', onMouseMove)
element.addEventListener('touchmove', onTouchMove, { passive: true })
element.addEventListener('touchstart', onTouchMove, { passive: true })
window.addEventListener('resize', onWindowResize)
}
function onWindowResize(e) {
width = window.innerWidth
height = window.innerHeight
if (hasWrapperEl) {
canvas.width = element.clientWidth
canvas.height = element.clientHeight
} else {
canvas.width = width
canvas.height = height
}
}
function onTouchMove(e) {
if (e.touches.length > 0) {
for (let i = 0; i < e.touches.length; i++) {
addParticle(
e.touches[i].clientX,
e.touches[i].clientY,
canvImages[Math.floor(Math.random() * canvImages.length)]
)
}
}
}
function onMouseMove(e) {
if (hasWrapperEl) {
const boundingRect = element.getBoundingClientRect()
cursor.x = e.clientX - boundingRect.left
cursor.y = e.clientY - boundingRect.top
} else {
cursor.x = e.clientX
cursor.y = e.clientY
}
addParticle(
cursor.x,
cursor.y,
canvImages[Math.floor(Math.random() * possibleEmoji.length)]
)
}
function addParticle(x, y, img) {
particles.push(new Particle(x, y, img))
}
function updateParticles() {
context.clearRect(0, 0, width, height)
// Update
for (let i = 0; i < particles.length; i++) {
particles[i].update(context)
}
// Remove dead particles
for (let i = particles.length - 1; i >= 0; i--) {
if (particles[i].lifeSpan < 0) {
particles.splice(i, 1)
}
}
}
function loop() {
updateParticles()
requestAnimationFrame(loop)
}
/**
* Particles
*/
function Particle(x, y, canvasItem) {
const lifeSpan = Math.floor(Math.random() * 60 + 80)
this.initialLifeSpan = lifeSpan //
this.lifeSpan = lifeSpan //ms
this.velocity = {
x: (Math.random() < 0.5 ? -1 : 1) * (Math.random() / 2),
y: 1 + Math.random(),
}
this.position = { x: x, y: y }
this.canv = canvasItem
this.update = function(context) {
this.position.x += this.velocity.x
this.position.y += this.velocity.y
this.lifeSpan--
this.velocity.x += ((Math.random() < 0.5 ? -1 : 1) * 2) / 75
this.velocity.y -= Math.random() / 300
const scale = Math.max(this.lifeSpan / this.initialLifeSpan, 0)
const degrees = 2 * this.lifeSpan
const radians = degrees * 0.0174533 // not perfect but close enough
context.translate(this.position.x, this.position.y)
context.rotate(radians)
context.drawImage(
this.canv,
(-this.canv.width / 2) * scale,
-this.canv.height / 2,
this.canv.width * scale,
this.canv.height * scale
)
context.rotate(-radians)
context.translate(-this.position.x, -this.position.y)
}
}
init()
}
new snowflakeCursor()