/**
* Snow Animation for LeafBox Technologies Website
* Creates a beautiful snowfall effect using Canvas API
*/
class SnowAnimation {
constructor(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.snowflakes = [];
this.animationId = null;
this.isActive = false;
this.init();
}
init() {
this.setupCanvas();
this.createSnowflakes();
this.startAnimation();
this.handleVisibilityChange();
// Optimize for performance
this.throttleResize = this.throttle(this.resize.bind(this), 100);
window.addEventListener('resize', this.throttleResize);
}
setupCanvas() {
this.resize();
this.canvas.style.pointerEvents = 'none';
this.canvas.style.position = 'fixed';
this.canvas.style.top = '0';
this.canvas.style.left = '0';
this.canvas.style.width = '100%';
this.canvas.style.height = '100%';
this.canvas.style.zIndex = '1';
this.canvas.style.opacity = '0.6';
}
resize() {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
// Recalculate snowflake properties
this.adjustSnowflakeCount();
}
createSnowflakes() {
this.snowflakes = [];
const flakeCount = this.getFlakeCount();
for (let i = 0; i < flakeCount; i++) {
this.snowflakes.push(this.createSnowflake());
}
}
createSnowflake() {
return {
x: Math.random() * this.canvas.width,
y: Math.random() * this.canvas.height,
radius: Math.random() * 3 + 1,
speed: Math.random() * 2 + 0.5,
opacity: Math.random() * 0.5 + 0.3,
drift: Math.random() * 0.5 + 0.1,
angle: Math.random() * Math.PI * 2,
rotationSpeed: (Math.random() - 0.5) * 0.02,
sway: Math.random() * Math.PI * 2,
swaySpeed: Math.random() * 0.02 + 0.005,
color: this.getRandomSnowColor()
};
}
getRandomSnowColor() {
const colors = [
'#B89B5F', // Antique Gold
'#FDFDFC', // Snowfall White
'#FFFFFF', // Pure White
'#E8E8E8' // Light Gray
];
return colors[Math.floor(Math.random() * colors.length)];
}
getFlakeCount() {
const area = this.canvas.width * this.canvas.height;
const baseCount = area / 15000; // Adjust density based on screen area
// Reduce count on mobile for performance
if (window.innerWidth < 768) {
return Math.min(baseCount * 0.6, 50);
}
return Math.min(baseCount, 150);
}
adjustSnowflakeCount() {
const targetCount = this.getFlakeCount();
const currentCount = this.snowflakes.length;
if (currentCount < targetCount) {
// Add more snowflakes
for (let i = currentCount; i < targetCount; i++) {
this.snowflakes.push(this.createSnowflake());
}
} else if (currentCount > targetCount) {
// Remove excess snowflakes
this.snowflakes = this.snowflakes.slice(0, targetCount);
}
}
updateSnowflake(flake) {
// Update position
flake.y += flake.speed;
flake.x += Math.sin(flake.sway) * flake.drift;
flake.angle += flake.rotationSpeed;
flake.sway += flake.swaySpeed;
// Wrap around screen
if (flake.y > this.canvas.height) {
flake.y = -flake.radius;
flake.x = Math.random() * this.canvas.width;
}
if (flake.x > this.canvas.width) {
flake.x = -flake.radius;
} else if (flake.x < -flake.radius) {
flake.x = this.canvas.width + flake.radius;
}
// Update opacity based on height for depth effect
const depthOpacity = 1 - (flake.y / this.canvas.height) * 0.5;
flake.currentOpacity = flake.opacity * depthOpacity;
}
drawSnowflake(flake) {
this.ctx.save();
this.ctx.globalAlpha = flake.currentOpacity || flake.opacity;
this.ctx.fillStyle = flake.color;
this.ctx.translate(flake.x, flake.y);
this.ctx.rotate(flake.angle);
// Draw snowflake shape
this.drawSnowflakeShape(flake);
this.ctx.restore();
}
drawSnowflakeShape(flake) {
const ctx = this.ctx;
const radius = flake.radius;
ctx.beginPath();
// Create a simple star-like snowflake
for (let i = 0; i < 6; i++) {
const angle = (i * Math.PI) / 3;
const x = Math.cos(angle) * radius;
const y = Math.sin(angle) * radius;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
// Add smaller branches
const branchAngle = angle + Math.PI / 6;
const branchX = Math.cos(branchAngle) * radius * 0.5;
const branchY = Math.sin(branchAngle) * radius * 0.5;
ctx.moveTo(x * 0.7, y * 0.7);
ctx.lineTo(branchX, branchY);
}
ctx.closePath();
ctx.fill();
// Add center dot for extra detail
ctx.beginPath();
ctx.arc(0, 0, radius * 0.2, 0, Math.PI * 2);
ctx.fill();
}
animate() {
if (!this.isActive) return;
// Clear canvas with fade effect for smooth trails
this.ctx.fillStyle = 'rgba(253, 253, 252, 0.05)';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
// Update and draw all snowflakes
this.snowflakes.forEach(flake => {
this.updateSnowflake(flake);
this.drawSnowflake(flake);
});
this.animationId = requestAnimationFrame(() => this.animate());
}
start() {
if (this.isActive) return;
this.isActive = true;
this.animate();
}
stop() {
this.isActive = false;
if (this.animationId) {
cancelAnimationFrame(this.animationId);
this.animationId = null;
}
}
destroy() {
this.stop();
window.removeEventListener('resize', this.throttleResize);
// Clear canvas
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
handleVisibilityChange() {
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
this.stop();
} else {
this.start();
}
});
}
// Utility functions
throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
}
}
// Public methods for external control
pause() {
this.stop();
}
resume() {
this.start();
}
setIntensity(low, medium, high) {
// This could be used to adjust snow intensity
// Currently not implemented but could be useful
}
}
// Initialize snow animation when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
const canvas = document.getElementById('snowCanvas');
if (!canvas) {
console.warn('Snow canvas not found');
return;
}
// Check for reduced motion preference
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReducedMotion) {
console.log('Reduced motion preferred, skipping snow animation');
return;
}
// Create snow animation instance
const snowAnimation = new SnowAnimation(canvas);
// Make it globally accessible for debugging
window.snowAnimation = snowAnimation;
// Start animation
snowAnimation.start();
// Pause on hover over interactive elements (optional)
const interactiveElements = document.querySelectorAll('button, a, input, textarea');
interactiveElements.forEach(element => {
element.addEventListener('mouseenter', () => {
snowAnimation.pause();
});
element.addEventListener('mouseleave', () => {
snowAnimation.resume();
});
});
// Performance optimization: reduce snow on slower devices
if (navigator.hardwareConcurrency && navigator.hardwareConcurrency < 4) {
console.log('Lower-end device detected, reducing snow animation intensity');
// Could implement additional optimizations here
}
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
snowAnimation.destroy();
});
});
// Export for module systems
if (typeof module !== 'undefined' && module.exports) {
module.exports = SnowAnimation;
}