Please bookmark this page to avoid losing your image tool!

Image Overview Viewer

(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, initialZoom = "1", overviewWidth = "150") {
    // Main Container
    const container = document.createElement('div');
    container.style.fontFamily = 'system-ui, -apple-system, sans-serif';
    container.style.border = '1px solid #ccc';
    container.style.borderRadius = '8px';
    container.style.overflow = 'hidden';
    container.style.display = 'flex';
    container.style.flexDirection = 'column';
    container.style.width = '100%';
    container.style.maxWidth = '900px';
    container.style.backgroundColor = '#f9f9f9';
    container.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
    container.style.margin = '0 auto';

    function init() {
        container.innerHTML = ''; // Clean up any loading text

        // Header
        const header = document.createElement('div');
        header.style.padding = '12px 15px';
        header.style.backgroundColor = '#2c3e50';
        header.style.color = '#ecf0f1';
        header.style.fontWeight = '600';
        header.style.display = 'flex';
        header.style.justifyContent = 'space-between';
        header.style.alignItems = 'center';

        const title = document.createElement('span');
        title.innerText = 'Image Overview Viewer';
        header.appendChild(title);

        const info = document.createElement('span');
        info.style.fontSize = '13px';
        info.style.opacity = '0.9';
        info.innerText = `${originalImg.naturalWidth} × ${originalImg.naturalHeight} px`;
        header.appendChild(info);

        container.appendChild(header);

        // Interactive Body Container
        const bodyContainer = document.createElement('div');
        bodyContainer.style.position = 'relative';
        bodyContainer.style.width = '100%';
        bodyContainer.style.height = '600px'; // Fixed max view height
        bodyContainer.style.backgroundColor = '#e0e0e0';
        bodyContainer.style.overflow = 'hidden';
        bodyContainer.style.cursor = 'grab';
        bodyContainer.style.touchAction = 'none';
        
        // Checkered transparent background pattern
        bodyContainer.style.backgroundImage = 'repeating-linear-gradient(45deg, #cbd5e1 25%, transparent 25%, transparent 75%, #cbd5e1 75%, #cbd5e1), repeating-linear-gradient(45deg, #cbd5e1 25%, transparent 25%, transparent 75%, #cbd5e1 75%, #cbd5e1)';
        bodyContainer.style.backgroundPosition = '0 0, 10px 10px';
        bodyContainer.style.backgroundSize = '20px 20px';

        const mainCanvas = document.createElement('canvas');
        mainCanvas.style.display = 'block';
        
        // Overview Panel (Minimap)
        const overviewPanel = document.createElement('div');
        overviewPanel.style.position = 'absolute';
        overviewPanel.style.bottom = '15px';
        overviewPanel.style.right = '15px';
        overviewPanel.style.border = '2px solid rgba(255,255,255,0.8)';
        overviewPanel.style.boxShadow = '0 4px 8px rgba(0,0,0,0.3)';
        overviewPanel.style.backgroundColor = 'rgba(0, 0, 0, 0.6)';
        overviewPanel.style.cursor = 'pointer';
        overviewPanel.style.zIndex = '10';
        overviewPanel.style.borderRadius = '4px';
        overviewPanel.style.overflow = 'hidden';
        overviewPanel.style.touchAction = 'none';

        const overviewCanvas = document.createElement('canvas');
        overviewCanvas.style.display = 'block';
        overviewPanel.appendChild(overviewCanvas);

        const viewRect = document.createElement('div');
        viewRect.style.position = 'absolute';
        viewRect.style.border = '2px solid #3498db';
        viewRect.style.backgroundColor = 'rgba(52, 152, 219, 0.2)';
        viewRect.style.boxSizing = 'border-box';
        viewRect.style.pointerEvents = 'none'; 
        overviewPanel.appendChild(viewRect);

        // Zoom Controls
        const controls = document.createElement('div');
        controls.style.position = 'absolute';
        controls.style.top = '15px';
        controls.style.right = '15px';
        controls.style.display = 'flex';
        controls.style.flexDirection = 'column';
        controls.style.gap = '8px';
        controls.style.zIndex = '10';

        const zoomInBtn = document.createElement('button');
        zoomInBtn.innerText = '+';
        const zoomOutBtn = document.createElement('button');
        zoomOutBtn.innerText = '−'; 
        const resetBtn = document.createElement('button');
        resetBtn.innerText = '↺'; 
        
        [zoomInBtn, zoomOutBtn, resetBtn].forEach(btn => {
            btn.style.width = '32px';
            btn.style.height = '32px';
            btn.style.border = '1px solid #ccc';
            btn.style.backgroundColor = '#fff';
            btn.style.color = '#333';
            btn.style.borderRadius = '4px';
            btn.style.cursor = 'pointer';
            btn.style.fontSize = '18px';
            btn.style.fontWeight = 'bold';
            btn.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
            btn.style.display = 'flex';
            btn.style.alignItems = 'center';
            btn.style.justifyContent = 'center';
            btn.title = btn.innerText === '+' ? 'Zoom In' : (btn.innerText === '−' ? 'Zoom Out' : 'Reset View');
            
            btn.onmouseover = () => btn.style.backgroundColor = '#f0f0f0';
            btn.onmouseout = () => btn.style.backgroundColor = '#fff';
            
            controls.appendChild(btn);
        });

        bodyContainer.appendChild(mainCanvas);
        bodyContainer.appendChild(overviewPanel);
        bodyContainer.appendChild(controls);
        container.appendChild(bodyContainer);

        // Footer with Overview Statistics
        const footer = document.createElement('div');
        footer.style.padding = '12px 15px';
        footer.style.backgroundColor = '#fff';
        footer.style.color = '#555';
        footer.style.fontSize = '14px';
        footer.style.display = 'flex';
        footer.style.justifyContent = 'space-around';
        footer.style.flexWrap = 'wrap';
        footer.style.gap = '10px';
        footer.style.borderTop = '1px solid #eaeded';

        const pixels = originalImg.naturalWidth * originalImg.naturalHeight;
        const megapixels = (pixels / 1000000).toFixed(2);
        const ratio = (originalImg.naturalWidth / originalImg.naturalHeight).toFixed(2);

        footer.innerHTML = `
            <div><strong>Original Size:</strong> ${originalImg.naturalWidth} x ${originalImg.naturalHeight}</div>
            <div><strong>Estimated MP:</strong> ${megapixels} MP</div>
            <div><strong>Aspect Ratio:</strong> ${ratio}:1</div>
        `;
        container.appendChild(footer);

        // Interactive Viewport State
        const state = {
            zoom: parseFloat(initialZoom) || 1,
            panX: 0,
            panY: 0,
            isDraggingMain: false,
            isDraggingOverview: false,
            lastMouseX: 0,
            lastMouseY: 0
        };

        let cw = 0, ch = 0;
        let isInitialized = false;

        // Auto-handle resizing internally
        const resizeObserver = new ResizeObserver(entries => {
            for (let entry of entries) {
                const rect = entry.contentRect;
                if (rect.width > 0 && rect.height > 0) {
                    cw = rect.width;
                    ch = rect.height;
                    mainCanvas.width = cw;
                    mainCanvas.height = ch;
                    
                    if (!isInitialized) {
                        // Fit to screen on initial load
                        if ((parseFloat(initialZoom) || 1) === 1) {
                            const scaleX = cw / originalImg.naturalWidth;
                            const scaleY = ch / originalImg.naturalHeight;
                            state.zoom = Math.min(scaleX, scaleY, 1) * 0.9;
                        }
                        state.panX = (originalImg.naturalWidth * state.zoom - cw) / 2;
                        state.panY = (originalImg.naturalHeight * state.zoom - ch) / 2;
                        
                        updateOverviewSize();
                        isInitialized = true;
                    }
                    render();
                }
            }
        });

        resizeObserver.observe(bodyContainer);

        function updateOverviewSize() {
            const imgRatio = originalImg.naturalHeight / originalImg.naturalWidth;
            const ow = parseFloat(overviewWidth) || 150;
            const oh = ow * imgRatio;
            
            overviewCanvas.width = ow;
            overviewCanvas.height = oh;
            
            const octx = overviewCanvas.getContext('2d');
            octx.drawImage(originalImg, 0, 0, ow, oh);
        }

        function clampPan() {
            const maxPanX = Math.max(0, originalImg.naturalWidth * state.zoom - cw);
            const maxPanY = Math.max(0, originalImg.naturalHeight * state.zoom - ch);
            
            if (originalImg.naturalWidth * state.zoom < cw) {
                state.panX = 0;
            } else {
                state.panX = Math.max(0, Math.min(state.panX, maxPanX));
            }

            if (originalImg.naturalHeight * state.zoom < ch) {
                state.panY = 0;
            } else {
                state.panY = Math.max(0, Math.min(state.panY, maxPanY));
            }
        }

        function render() {
            if (!cw || !ch) return;
            clampPan();

            const ctx = mainCanvas.getContext('2d');
            ctx.clearRect(0, 0, cw, ch);

            const dw = originalImg.naturalWidth * state.zoom;
            const dh = originalImg.naturalHeight * state.zoom;

            let dx = -state.panX;
            let dy = -state.panY;

            // Center drawing mechanism when un-zoomed
            if (dw < cw) dx = (cw - dw) / 2;
            if (dh < ch) dy = (ch - dh) / 2;

            ctx.drawImage(originalImg, dx, dy, dw, dh);

            // Redraw viewpoint minimap box
            const ow = overviewCanvas.width;
            const oh = overviewCanvas.height;
            const rw = cw / dw;
            const rh = ch / dh;

            let rx = (state.panX / dw) * ow;
            let ry = (state.panY / dh) * oh;

            // Constrain visualization bounds when fully visible
            if (dw < cw) rx = 0; 
            if (dh < ch) ry = 0; 
            
            const rvw = Math.min(ow, rw * ow);
            const rvh = Math.min(oh, rh * oh);

            viewRect.style.left = `${rx}px`;
            viewRect.style.top = `${ry}px`;
            viewRect.style.width = `${rvw}px`;
            viewRect.style.height = `${rvh}px`;
        }

        // Button Controls
        zoomInBtn.addEventListener('click', () => {
            const oldZoom = state.zoom;
            state.zoom *= 1.25;
            adjustPanForZoom(oldZoom, state.zoom);
            render();
        });

        zoomOutBtn.addEventListener('click', () => {
            const oldZoom = state.zoom;
            state.zoom = Math.max(0.05, state.zoom / 1.25);
            adjustPanForZoom(oldZoom, state.zoom);
            render();
        });

        resetBtn.addEventListener('click', () => {
            const scaleX = cw / originalImg.naturalWidth;
            const scaleY = ch / originalImg.naturalHeight;
            state.zoom = Math.min(scaleX, scaleY, 1) * 0.9;
            state.panX = (originalImg.naturalWidth * state.zoom - cw) / 2;
            state.panY = (originalImg.naturalHeight * state.zoom - ch) / 2;
            render();
        });

        function adjustPanForZoom(oldZoom, newZoom) {
            const centerX = state.panX + cw / 2;
            const centerY = state.panY + ch / 2;
            const imgX = centerX / oldZoom;
            const imgY = centerY / oldZoom;
            state.panX = imgX * newZoom - cw / 2;
            state.panY = imgY * newZoom - ch / 2;
        }

        // Pointer Events for Main Area (Dragging)
        bodyContainer.addEventListener('pointerdown', (e) => {
            if (e.target === zoomInBtn || e.target === zoomOutBtn || e.target === resetBtn || overviewPanel.contains(e.target)) return;
            state.isDraggingMain = true;
            state.lastMouseX = e.clientX;
            state.lastMouseY = e.clientY;
            bodyContainer.style.cursor = 'grabbing';
            bodyContainer.setPointerCapture(e.pointerId);
        });

        bodyContainer.addEventListener('pointermove', (e) => {
            if (state.isDraggingMain) {
                const dx = e.clientX - state.lastMouseX;
                const dy = e.clientY - state.lastMouseY;
                state.panX -= dx;
                state.panY -= dy;
                state.lastMouseX = e.clientX;
                state.lastMouseY = e.clientY;
                render();
            }
        });

        const endMainDrag = (e) => {
            if (state.isDraggingMain) {
                state.isDraggingMain = false;
                bodyContainer.style.cursor = 'grab';
                bodyContainer.releasePointerCapture(e.pointerId);
            }
        };

        bodyContainer.addEventListener('pointerup', endMainDrag);
        bodyContainer.addEventListener('pointercancel', endMainDrag);

        // Pointer Events for Overview Panel
        function updatePanFromOverview(e) {
            const rect = overviewCanvas.getBoundingClientRect();
            let x = e.clientX - rect.left;
            let y = e.clientY - rect.top;

            x = Math.max(0, Math.min(x, rect.width));
            y = Math.max(0, Math.min(y, rect.height));

            const pctX = x / rect.width;
            const pctY = y / rect.height;

            const imgX = pctX * originalImg.naturalWidth * state.zoom;
            const imgY = pctY * originalImg.naturalHeight * state.zoom;

            state.panX = imgX - cw / 2;
            state.panY = imgY - ch / 2;
            
            render();
        }

        overviewPanel.addEventListener('pointerdown', (e) => {
            state.isDraggingOverview = true;
            overviewPanel.setPointerCapture(e.pointerId);
            updatePanFromOverview(e);
        });

        overviewPanel.addEventListener('pointermove', (e) => {
            if (state.isDraggingOverview) {
                updatePanFromOverview(e);
            }
        });

        const endOverviewDrag = (e) => {
            if (state.isDraggingOverview) {
                state.isDraggingOverview = false;
                overviewPanel.releasePointerCapture(e.pointerId);
            }
        };

        overviewPanel.addEventListener('pointerup', endOverviewDrag);
        overviewPanel.addEventListener('pointercancel', endOverviewDrag);

        // Wheel to zoom directly onto cursor
        bodyContainer.addEventListener('wheel', (e) => {
            e.preventDefault();
            const oldZoom = state.zoom;
            const zoomDelta = e.deltaY > 0 ? 0.9 : 1.1;
            state.zoom = Math.max(0.05, state.zoom * zoomDelta);

            const rect = mainCanvas.getBoundingClientRect();
            const mouseX = e.clientX - rect.left;
            const mouseY = e.clientY - rect.top;

            let currentX = state.panX;
            let currentY = state.panY;

            const dwOld = originalImg.naturalWidth * oldZoom;
            const dhOld = originalImg.naturalHeight * oldZoom;
            // Accounts for implicit pan if canvas image was auto-centered
            if (dwOld < cw) currentX = -(cw - dwOld) / 2;
            if (dhOld < ch) currentY = -(ch - dhOld) / 2;

            const imgX = (currentX + mouseX) / oldZoom;
            const imgY = (currentY + mouseY) / oldZoom;

            state.panX = imgX * state.zoom - mouseX;
            state.panY = imgY * state.zoom - mouseY;

            render();
        }, { passive: false });
    }

    if (originalImg.complete && originalImg.naturalWidth > 0) {
        init();
    } else {
        container.style.padding = '30px';
        container.style.textAlign = 'center';
        container.style.color = '#7f8c8d';
        container.innerText = "Loading image overview...";
        originalImg.addEventListener('load', () => {
            container.style.padding = '0';
            container.style.textAlign = 'left';
            container.style.color = 'inherit';
            init();
        });
    }

    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 Overview Viewer is an interactive tool designed to help users navigate and inspect high-resolution images. It features a main viewing area with zoom and pan capabilities, along with a minimap (overview panel) that provides a real-time thumbnail of the entire image and indicates your current viewing area. Users can zoom in using mouse wheels or controls, pan around the image by dragging, and quickly jump to different sections using the minimap. Additionally, the tool displays helpful technical metadata, including the image’s original dimensions, megapixels, and aspect ratio. This tool is ideal for photographers, designers, or anyone needing to inspect fine details in large-scale images without losing sight of the overall composition.

Leave a Reply

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