Please bookmark this page to avoid losing your image tool!

Image Video Player

(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.
/**
 * Creates a video player-like experience for a static image using the Ken Burns effect (pan and zoom).
 *
 * @param {Image} originalImg The original javascript Image object.
 * @param {number} duration The duration of the "video" clip in seconds.
 * @param {string} zoom A string representing the start and end zoom levels, separated by a hyphen. E.g., '1.0-1.5' zooms in by 50%. '1.5-1.0' zooms out.
 * @param {string} pan A string representing the start and end pan points, separated by a hyphen. Each point is a normalized 'x,y' coordinate (0,0 is top-left, 1,1 is bottom-right, 0.5,0.5 is center). E.g., '0,0-1,1' pans from top-left to bottom-right.
 * @returns {HTMLElement} A div element containing the canvas and player controls, which can be appended to the document.
 */
async function processImage(originalImg, duration = 10, zoom = '1.0-1.2', pan = '0.5,0.5-0.5,0.5') {

    // --- Parameter Parsing & Validation ---
    const zoomParts = String(zoom).split('-').map(parseFloat);
    if (zoomParts.length !== 2 || isNaN(zoomParts[0]) || isNaN(zoomParts[1]) || zoomParts[0] <= 0 || zoomParts[1] <= 0) {
        console.error("Invalid zoom parameter. Expected format 'start-end' with positive numbers, e.g., '1.0-1.2'. Defaulting to '1.0-1.2'.");
        zoomParts = [1.0, 1.2];
    }
    const [zoomStart, zoomEnd] = zoomParts;

    const panParts = String(pan).split('-');
    if (panParts.length !== 2) {
         console.error("Invalid pan parameter. Expected format 'x1,y1-x2,y2', e.g., '0,0-1,1'. Defaulting to '0.5,0.5-0.5,0.5'.");
         panParts = ['0.5,0.5', '0.5,0.5'];
    }
    const [panStartCoords, panEndCoords] = panParts.map(p => {
        const coords = p.split(',').map(parseFloat);
        if (coords.length !== 2 || isNaN(coords[0]) || isNaN(coords[1])) return [0.5, 0.5];
        // Clamp coordinates between 0 and 1
        return [Math.max(0, Math.min(1, coords[0])), Math.max(0, Math.min(1, coords[1]))];
    });
    const [[panStartX, panStartY], [panEndX, panEndY]] = [panStartCoords, panEndCoords];

    // --- Element Creation ---
    const container = document.createElement('div');
    const canvas = document.createElement('canvas');
    const controls = document.createElement('div');
    const playButton = document.createElement('button');
    const timeDisplay = document.createElement('span');
    const progressBarContainer = document.createElement('div');
    const progressBar = document.createElement('div');
    const ctx = canvas.getContext('2d');

    // --- Styling ---
    Object.assign(container.style, {
        position: 'relative',
        display: 'inline-block',
        background: '#000',
        lineHeight: '0',
        maxWidth: '100%',
        overflow: 'hidden',
        fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
        color: '#fff',
    });

    Object.assign(canvas.style, {
        display: 'block',
        width: '100%',
        height: 'auto',
    });

    Object.assign(controls.style, {
        position: 'absolute',
        bottom: '0',
        left: '0',
        right: '0',
        height: '40px',
        background: 'linear-gradient(to top, rgba(0,0,0,0.7), transparent)',
        display: 'flex',
        alignItems: 'center',
        padding: '0 10px',
        boxSizing: 'border-box',
        opacity: '0',
        transition: 'opacity 0.3s ease-in-out'
    });
    
    container.onmouseenter = () => { controls.style.opacity = '1'; };
    container.onmouseleave = () => { if (isPlaying) controls.style.opacity = '0'; };

    Object.assign(playButton.style, {
        background: 'none',
        border: 'none',
        color: 'white',
        fontSize: '24px',
        cursor: 'pointer',
        padding: '0 10px 0 0',
        lineHeight: '1',
    });

    Object.assign(timeDisplay.style, {
        fontSize: '12px',
        userSelect: 'none',
        minWidth: '75px',
    });

    Object.assign(progressBarContainer.style, {
        flexGrow: '1',
        height: '5px',
        background: 'rgba(255,255,255,0.3)',
        marginLeft: '10px',
        position: 'relative',
    });

    Object.assign(progressBar.style, {
        height: '100%',
        width: '0%',
        background: 'white',
        transition: isPlaying ? 'width 0.1s linear' : 'none',
    });

    // --- Setup ---
    canvas.width = originalImg.naturalWidth;
    canvas.height = originalImg.naturalHeight;
    container.style.aspectRatio = `${canvas.width} / ${canvas.height}`;

    // --- Animation State ---
    let animationId = null;
    let startTime = 0;
    let isPlaying = false;
    let elapsedTimeBeforePause = 0; // in milliseconds

    // --- Render Logic ---
    const formatTime = (seconds) => {
        const min = Math.floor(seconds / 60);
        const sec = Math.floor(seconds % 60);
        return `${min}:${sec.toString().padStart(2, '0')}`;
    };

    const render = (currentTime) => {
        if (!startTime) startTime = currentTime;
        
        const currentElapsedTime = elapsedTimeBeforePause + (currentTime - startTime);
        let progress = currentElapsedTime / (duration * 1000);
        progress = Math.min(progress, 1);

        const currentZoom = zoomStart + (zoomEnd - zoomStart) * progress;
        const currentPanX = panStartX + (panEndX - panStartX) * progress;
        const currentPanY = panStartY + (panEndY - panStartY) * progress;
        
        const sWidth = originalImg.naturalWidth / currentZoom;
        const sHeight = originalImg.naturalHeight / currentZoom;
        const sX = (originalImg.naturalWidth - sWidth) * currentPanX;
        const sY = (originalImg.naturalHeight - sHeight) * currentPanY;

        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(originalImg, sX, sY, sWidth, sHeight, 0, 0, canvas.width, canvas.height);
        
        progressBar.style.width = `${progress * 100}%`;
        timeDisplay.textContent = `${formatTime(currentElapsedTime / 1000)} / ${formatTime(duration)}`;

        if (progress < 1 && isPlaying) {
            animationId = requestAnimationFrame(render);
        } else if (progress >= 1) {
            isPlaying = false;
            playButton.innerHTML = '&#10227;'; // Replay icon
            controls.style.opacity = '1';
        }
    };

    // --- Control Logic ---
    const play = () => {
        if (isPlaying) return;
        
        const currentProgress = elapsedTimeBeforePause / (duration * 1000);
        if (currentProgress >= 1) elapsedTimeBeforePause = 0; // Reset if at the end

        isPlaying = true;
        playButton.innerHTML = '&#10074;&#10074;'; // Pause icon
        startTime = performance.now();
        animationId = requestAnimationFrame(render);
    };
    
    const pause = () => {
        if (!isPlaying) return;
        isPlaying = false;
        playButton.innerHTML = '&#9654;'; // Play icon
        cancelAnimationFrame(animationId);
        elapsedTimeBeforePause += performance.now() - startTime;
    };

    playButton.onclick = () => isPlaying ? pause() : play();
    
    // --- Assembly ---
    container.append(canvas, controls);
    controls.append(playButton, timeDisplay, progressBarContainer);
    progressBarContainer.append(progressBar);
    
    // --- Initial State ---
    render(0); // Draw the first frame at progress 0
    playButton.innerHTML = '&#9654;'; // Play icon

    // Wait for the image to be fully loaded before starting
    if (originalImg.complete) {
        render(0);
    } else {
        originalImg.onload = () => render(0);
    }

    return container;
}

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 Video Player tool allows users to create a dynamic viewing experience for static images by applying the Ken Burns effect, which incorporates zooming and panning animations. Users can specify the duration, zoom levels, and pan positions to enhance their images, making them more engaging and visually appealing. This tool is ideal for creating presentations, enhancing photo displays in digital galleries, or adding motion effects to images for social media posts or video projects.

Leave a Reply

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