You can edit the below JavaScript code to customize the image tool.
Apply Changes
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;
}
Apply Changes