Please bookmark this page to avoid losing your image tool!

Image Meteor Shower Effect Adder

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, meteorCount = "60", meteorColor = "#ffffff", angleDegrees = "45", speed = "20", backgroundStars = "150", animated = "1") {
    
    const canvas = document.createElement('canvas');
    canvas.width = originalImg.width;
    canvas.height = originalImg.height;
    const ctx = canvas.getContext('2d');
    
    // Parse arguments
    const count = parseInt(meteorCount, 10) || 60;
    const angle = parseFloat(angleDegrees) || 45;
    const speedVal = parseFloat(speed) || 20;
    const numStars = parseInt(backgroundStars, 10) || 0; 
    const isAnim = parseInt(animated, 10) !== 0;

    // Helper to extract exact RGB values for foolproof alpha gradients (avoids the Safari grey-transparent bug)
    function getRgba(color, alpha) {
        const tempCanvas = document.createElement('canvas');
        tempCanvas.width = 1;
        tempCanvas.height = 1;
        const tCtx = tempCanvas.getContext('2d');
        tCtx.fillStyle = '#000000'; // Default black fallback if invalid string
        tCtx.fillStyle = color;
        tCtx.fillRect(0, 0, 1, 1);
        const data = tCtx.getImageData(0, 0, 1, 1).data;
        return `rgba(${data[0]}, ${data[1]}, ${data[2]}, ${alpha})`;
    }
    
    const solidCol = getRgba(meteorColor, 1);
    const transparentCol = getRgba(meteorColor, 0);

    const angleRads = angle * Math.PI / 180;
    const dx = Math.cos(angleRads);
    const dy = Math.sin(angleRads);

    class Meteor {
        constructor(initial = false) {
            this.reset(initial);
            if (initial) {
                // Randomize lifespan stage so frame 1 looks like a meteor shower in progress
                this.life = Math.random() * this.maxLife;
            }
        }
        
        reset(initial = false) {
            const maxDim = Math.max(canvas.width, canvas.height);
            this.length = Math.random() * (maxDim * 0.1) + 20;
            this.thickness = Math.random() * 1.5 + 0.5;
            
            // Adjust speed proportionally to canvas size
            this.speed = (Math.random() * (speedVal / 2) + speedVal) * (maxDim / 800);
            this.maxLife = Math.random() * 40 + 30; // 30-70 frames
            this.life = 0;
            
            // Generate standard starting position across the surface bounds
            this.x = Math.random() * canvas.width;
            this.y = Math.random() * canvas.height;
            
            if (!initial) {
                // Nudge backwards along path so they smoothly appear entering from edges/sky naturally
                const backwardsOffset = Math.random() * maxDim * 0.5;
                this.x -= dx * backwardsOffset;
                this.y -= dy * backwardsOffset;
            }
        }
        
        update() {
            this.life++;
            if (this.life >= this.maxLife) {
                this.reset();
            } else {
                this.x += this.speed * dx;
                this.y += this.speed * dy;
            }
        }
        
        draw() {
            const lifeRatio = this.life / this.maxLife;
            // Naturally fade in and fade out as it travels
            const opacity = Math.sin(lifeRatio * Math.PI); 
            const currentLength = this.length * Math.sin(lifeRatio * Math.PI);

            // Avoid drawing rendering artifacts for invisible meteors
            if (currentLength < 0.5) return;

            const tailX = this.x - currentLength * dx;
            const tailY = this.y - currentLength * dy;

            ctx.save();
            ctx.globalAlpha = opacity;
            
            // Draw Meteor Trail
            ctx.beginPath();
            const grad = ctx.createLinearGradient(tailX, tailY, this.x, this.y);
            grad.addColorStop(0, transparentCol);
            grad.addColorStop(1, solidCol);

            ctx.strokeStyle = grad;
            ctx.lineWidth = this.thickness;
            ctx.lineCap = "round";
            ctx.shadowBlur = this.thickness * 4;
            ctx.shadowColor = meteorColor;

            ctx.moveTo(tailX, tailY);
            ctx.lineTo(this.x, this.y);
            ctx.stroke();
            
            // Draw Meteor Head (Fireball impact glare)
            ctx.beginPath();
            ctx.fillStyle = '#ffffff';
            ctx.arc(this.x, this.y, this.thickness * 0.8, 0, Math.PI * 2);
            ctx.shadowBlur = this.thickness * 6;
            ctx.shadowColor = '#ffffff';
            ctx.fill();

            ctx.restore();
        }
    }

    const meteors = [];
    for (let i = 0; i < count; i++) {
        meteors.push(new Meteor(true));
    }

    const stars = [];
    for (let i = 0; i < numStars; i++) {
        stars.push({
            x: Math.random() * canvas.width,
            y: Math.random() * canvas.height,
            r: Math.random() * 1.5,
            baseOpacity: Math.random() * 0.6 + 0.1,
            twinkleSpeed: Math.random() * 0.05 + 0.01,
            time: Math.random() * Math.PI * 2
        });
    }

    let animationId;
    function render() {
        // Draw the primary reference image
        ctx.globalCompositeOperation = 'source-over';
        ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);

        // Render effects using "screen" composite mode to add brightness (lights mapping overlay)
        ctx.globalCompositeOperation = 'screen';
        
        // Render optional atmospheric starry sky
        if (numStars > 0) {
            ctx.save();
            ctx.fillStyle = '#ffffff';
            for (let s of stars) {
                if (isAnim) {
                    s.time += s.twinkleSpeed;
                }
                
                let op = s.baseOpacity + Math.sin(s.time) * 0.3;
                if (op < 0) op = 0;
                if (op > 1) op = 1;

                ctx.globalAlpha = op;
                ctx.beginPath();
                ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
                ctx.fill();
            }
            ctx.restore();
        }

        // Render falling meteors
        for (let m of meteors) {
            if (isAnim) m.update();
            m.draw();
        }

        // Setup the loop if configured for animation
        if (isAnim) {
            animationId = requestAnimationFrame(render);
        }
    }
    
    // Execute a starting render frame
    render();

    // Attach a convenient teardown method so apps dynamically rendering this canvas can halt animation and free up resources
    canvas.stopAnimation = () => {
        if (animationId) cancelAnimationFrame(animationId);
    };

    return canvas;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

The Image Meteor Shower Effect Adder allows you to overlay dynamic celestial effects onto your existing images. This tool can add a customizable number of falling meteors with adjustable colors, speeds, and angles, as well as a twinkling background star field. Users can choose to create a static image with the effect or an animated version. It is ideal for enhancing night sky photography, creating magical or cinematic visual content for social media, or designing atmospheric backgrounds for digital projects.

Leave a Reply

Your email address will not be published. Required fields are marked *