/** * 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; }