269 lines
9.0 KiB
JavaScript
269 lines
9.0 KiB
JavaScript
/**
|
|
* Roof Cleaning Animation
|
|
* Shows a drone flying over a dirty roof and cleaning it in a continuous loop
|
|
*/
|
|
(function() {
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const heroSection = document.querySelector('.hero');
|
|
if (heroSection) {
|
|
initRoofCleaningAnimation(heroSection);
|
|
}
|
|
});
|
|
|
|
function initRoofCleaningAnimation(container) {
|
|
// Create canvas container
|
|
const canvasContainer = document.createElement('div');
|
|
canvasContainer.className = 'roof-cleaning-animation';
|
|
canvasContainer.style.position = 'absolute';
|
|
canvasContainer.style.top = '0';
|
|
canvasContainer.style.left = '0';
|
|
canvasContainer.style.width = '100%';
|
|
canvasContainer.style.height = '100%';
|
|
canvasContainer.style.zIndex = '1';
|
|
canvasContainer.style.pointerEvents = 'none';
|
|
canvasContainer.style.overflow = 'hidden';
|
|
|
|
// Create canvas element
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = container.offsetWidth;
|
|
canvas.height = container.offsetHeight;
|
|
canvas.style.position = 'absolute';
|
|
canvas.style.top = '0';
|
|
canvas.style.left = '0';
|
|
canvas.style.width = '100%';
|
|
canvas.style.height = '100%';
|
|
canvasContainer.appendChild(canvas);
|
|
|
|
// Add to container
|
|
container.appendChild(canvasContainer);
|
|
|
|
// Set up the animation
|
|
setupAnimation(canvas);
|
|
|
|
// Handle window resize
|
|
window.addEventListener('resize', function() {
|
|
canvas.width = container.offsetWidth;
|
|
canvas.height = container.offsetHeight;
|
|
setupAnimation(canvas);
|
|
});
|
|
}
|
|
|
|
function setupAnimation(canvas) {
|
|
const ctx = canvas.getContext('2d');
|
|
const width = canvas.width;
|
|
const height = canvas.height;
|
|
|
|
// Animation variables
|
|
let droneX = -100; // Start off-screen
|
|
const droneY = height * 0.4; // Position drone at 40% of height
|
|
const droneWidth = 80;
|
|
const droneHeight = 40;
|
|
const droneSpeed = 2;
|
|
const cleaningWidth = 80; // Width of cleaning path
|
|
|
|
// Create offscreen canvases for better performance
|
|
const roofCanvas = createRoofCanvas(width, height);
|
|
const cleanCanvas = createCleanCanvas(width, height);
|
|
const maskCanvas = document.createElement('canvas');
|
|
maskCanvas.width = width;
|
|
maskCanvas.height = height;
|
|
const maskCtx = maskCanvas.getContext('2d');
|
|
|
|
// Create drone image
|
|
const drone = new Image();
|
|
drone.src = 'images/spraying_drone1.png';
|
|
|
|
// Droplets array for water spray
|
|
const droplets = [];
|
|
|
|
// Animation loop
|
|
function animate() {
|
|
// Clear the canvas
|
|
ctx.clearRect(0, 0, width, height);
|
|
|
|
// Draw sky background
|
|
drawSky(ctx, width, height);
|
|
|
|
// Update drone position
|
|
droneX += droneSpeed;
|
|
if (droneX > width + 100) {
|
|
// Reset drone position and partially reset mask for continuous loop
|
|
droneX = -100;
|
|
// Reset left side of mask
|
|
maskCtx.clearRect(0, height * 0.45, width * 0.2, height * 0.55);
|
|
}
|
|
|
|
// Update cleaning mask
|
|
updateCleaningMask(maskCtx, droneX, droneY, cleaningWidth);
|
|
|
|
// Draw dirty roof
|
|
ctx.drawImage(roofCanvas, 0, 0);
|
|
|
|
// Apply cleaning mask to show clean areas
|
|
ctx.globalCompositeOperation = 'destination-out';
|
|
ctx.drawImage(maskCanvas, 0, 0);
|
|
ctx.globalCompositeOperation = 'source-over';
|
|
|
|
// Draw clean roof in masked areas
|
|
ctx.drawImage(cleanCanvas, 0, 0);
|
|
|
|
// Generate water droplets
|
|
if (Math.random() < 0.3) { // 30% chance each frame
|
|
createDroplet(droplets, droneX + droneWidth/2, droneY + droneHeight/2);
|
|
}
|
|
|
|
// Update and draw water droplets
|
|
updateDroplets(ctx, droplets);
|
|
|
|
// Draw the drone
|
|
ctx.drawImage(drone, droneX, droneY, droneWidth, droneHeight);
|
|
|
|
// Continue animation loop
|
|
requestAnimationFrame(animate);
|
|
}
|
|
|
|
// Wait for drone image to load before starting animation
|
|
drone.onload = function() {
|
|
animate();
|
|
};
|
|
}
|
|
|
|
function drawSky(ctx, width, height) {
|
|
// Create gradient for sky
|
|
const skyGradient = ctx.createLinearGradient(0, 0, 0, height * 0.45);
|
|
skyGradient.addColorStop(0, '#87CEEB'); // Sky blue
|
|
skyGradient.addColorStop(1, '#e6f7ff'); // Light blue
|
|
|
|
ctx.fillStyle = skyGradient;
|
|
ctx.fillRect(0, 0, width, height * 0.45);
|
|
}
|
|
|
|
function createRoofCanvas(width, height) {
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
// Draw dirty roof
|
|
const roofY = height * 0.45; // Start roof at 45% of height
|
|
const roofHeight = height * 0.55; // Roof takes up 55% of height
|
|
|
|
// Base roof color (dirty brownish)
|
|
ctx.fillStyle = '#8B4513';
|
|
ctx.fillRect(0, roofY, width, roofHeight);
|
|
|
|
// Add roof tiles texture
|
|
ctx.fillStyle = '#6B3501';
|
|
for (let y = roofY; y < height; y += 20) {
|
|
for (let x = 0; x < width; x += 60) {
|
|
const offset = (Math.floor(y / 20) % 2) * 30;
|
|
ctx.fillRect(x + offset, y, 30, 10);
|
|
}
|
|
}
|
|
|
|
// Add dirt and moss spots
|
|
for (let i = 0; i < 200; i++) {
|
|
const spotX = Math.random() * width;
|
|
const spotY = roofY + Math.random() * roofHeight;
|
|
const spotSize = 3 + Math.random() * 15;
|
|
|
|
ctx.fillStyle = Math.random() > 0.5 ?
|
|
'rgba(50, 30, 0, 0.4)' : // Dirt
|
|
'rgba(30, 70, 30, 0.3)'; // Moss
|
|
|
|
ctx.beginPath();
|
|
ctx.arc(spotX, spotY, spotSize, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
|
|
return canvas;
|
|
}
|
|
|
|
function createCleanCanvas(width, height) {
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
// Draw clean roof
|
|
const roofY = height * 0.45;
|
|
const roofHeight = height * 0.55;
|
|
|
|
// Base clean roof color (reddish-brown)
|
|
ctx.fillStyle = '#A0522D';
|
|
ctx.fillRect(0, roofY, width, roofHeight);
|
|
|
|
// Add clean roof tiles texture
|
|
ctx.fillStyle = '#8B4513';
|
|
for (let y = roofY; y < height; y += 20) {
|
|
for (let x = 0; x < width; x += 60) {
|
|
const offset = (Math.floor(y / 20) % 2) * 30;
|
|
ctx.fillRect(x + offset, y, 30, 10);
|
|
}
|
|
}
|
|
|
|
return canvas;
|
|
}
|
|
|
|
function updateCleaningMask(ctx, droneX, droneY, cleaningWidth) {
|
|
// Create radial gradient for cleaning effect
|
|
const gradient = ctx.createRadialGradient(
|
|
droneX + 40, droneY + 30, 5,
|
|
droneX + 40, droneY + 30, cleaningWidth
|
|
);
|
|
gradient.addColorStop(0, 'rgba(0, 0, 0, 1)');
|
|
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
|
|
|
|
ctx.fillStyle = gradient;
|
|
ctx.beginPath();
|
|
ctx.arc(droneX + 40, droneY + 30, cleaningWidth, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
|
|
function createDroplet(droplets, x, y) {
|
|
droplets.push({
|
|
x: x,
|
|
y: y,
|
|
size: 2 + Math.random() * 3,
|
|
speedX: -1 + Math.random() * 2,
|
|
speedY: 4 + Math.random() * 2,
|
|
opacity: 0.7 + Math.random() * 0.3
|
|
});
|
|
|
|
// Limit number of droplets for performance
|
|
if (droplets.length > 50) {
|
|
droplets.shift();
|
|
}
|
|
}
|
|
|
|
function updateDroplets(ctx, droplets) {
|
|
// Draw water droplets
|
|
ctx.fillStyle = '#82CAFF';
|
|
|
|
for (let i = 0; i < droplets.length; i++) {
|
|
const droplet = droplets[i];
|
|
|
|
// Update position
|
|
droplet.x += droplet.speedX;
|
|
droplet.y += droplet.speedY;
|
|
|
|
// Reduce opacity as it falls
|
|
droplet.opacity -= 0.02;
|
|
|
|
if (droplet.opacity <= 0) {
|
|
droplets.splice(i, 1);
|
|
i--;
|
|
continue;
|
|
}
|
|
|
|
// Draw droplet
|
|
ctx.globalAlpha = droplet.opacity;
|
|
ctx.beginPath();
|
|
ctx.arc(droplet.x, droplet.y, droplet.size, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
|
|
ctx.globalAlpha = 1;
|
|
}
|
|
})();
|