You can edit the below JavaScript code to customize the image tool.
Apply Changes
/**
* Two Photo Comparison Search Tool
* Compares two images using an interactive Before/After slider, a visual diff, or side-by-side mode.
*
* @param {HTMLImageElement} originalImg - The primary image (Before/Original).
* @param {string} secondImageURL - Base64 Image URL or Data URL of the second image (After/Target). Defaults to an auto-generated grayscale image if empty.
* @param {string} comparisonMode - The display mode: "slider", "diff", or "side-by-side". Default is "slider".
* @returns {Promise<HTMLElement | HTMLCanvasElement>} A DOM element (div or canvas) containing the visualization.
*/
async function processImage(originalImg, secondImageURL = "", comparisonMode = "slider") {
const width = originalImg.width || 800;
const height = originalImg.height || 600;
comparisonMode = comparisonMode.toLowerCase().trim();
// Load the second image
const img2 = new Image();
img2.crossOrigin = "Anonymous";
await new Promise((resolve) => {
if (!secondImageURL) {
// Generate a grayscale fallback of the original image for demonstration purposes
const tempCanvas = document.createElement('canvas');
tempCanvas.width = width;
tempCanvas.height = height;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.drawImage(originalImg, 0, 0);
const tData = tempCtx.getImageData(0, 0, width, height);
for(let i = 0; i < tData.data.length; i += 4) {
let gray = (tData.data[i] + tData.data[i+1] + tData.data[i+2]) / 3;
tData.data[i] = gray; // R
tData.data[i+1] = gray; // G
tData.data[i+2] = gray; // B
}
tempCtx.putImageData(tData, 0, 0);
img2.src = tempCanvas.toDataURL();
resolve();
} else {
img2.onload = resolve;
img2.onerror = () => {
console.warn("Failed to load the second image. Falling back to the original image.");
img2.src = originalImg.src;
resolve();
};
img2.src = secondImageURL;
}
});
// MODE 1: Difference Highlighter (Highlights changed pixels)
if (comparisonMode === "diff") {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(originalImg, 0, 0, width, height);
const imgData1 = ctx.getImageData(0, 0, width, height);
ctx.clearRect(0, 0, width, height);
ctx.drawImage(img2, 0, 0, width, height);
const imgData2 = ctx.getImageData(0, 0, width, height);
const outData = ctx.createImageData(width, height);
for (let i = 0; i < imgData1.data.length; i += 4) {
const r1 = imgData1.data[i], g1 = imgData1.data[i+1], b1 = imgData1.data[i+2], a1 = imgData1.data[i+3];
const r2 = imgData2.data[i], g2 = imgData2.data[i+1], b2 = imgData2.data[i+2], a2 = imgData2.data[i+3];
const dr = Math.abs(r1 - r2);
const dg = Math.abs(g1 - g2);
const db = Math.abs(b1 - b2);
// Calculate absolute difference magnitude
const diff = dr + dg + db;
if (diff > 20 || Math.abs(a1 - a2) > 20) {
// Highlight modified pixels in vivid red to aid the "search tool" discovery
outData.data[i] = 255;
outData.data[i+1] = 0;
outData.data[i+2] = 0;
outData.data[i+3] = 255;
} else {
// Return dim grayscale version of unchanged pixels
const gray = 0.3 * r1 + 0.59 * g1 + 0.11 * b1;
outData.data[i] = gray * 0.4;
outData.data[i+1] = gray * 0.4;
outData.data[i+2] = gray * 0.4;
outData.data[i+3] = 255;
}
}
ctx.putImageData(outData, 0, 0);
return canvas;
}
// MODE 2: Side-By-Side Canvas
if (comparisonMode === "side-by-side") {
const canvas = document.createElement('canvas');
canvas.width = width * 2;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(originalImg, 0, 0, width, height);
ctx.drawImage(img2, width, 0, width, height);
// Visual separator
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(width - 2, 0, 4, height);
ctx.fillStyle = '#000000';
ctx.fillRect(width - 1, 0, 2, height);
return canvas;
}
// MODE 3: Interactive Before & After Slider Element
const container = document.createElement('div');
container.style.position = 'relative';
container.style.display = 'block';
container.style.width = '100%';
container.style.maxWidth = width + 'px';
container.style.aspectRatio = `${width} / ${height}`;
container.style.overflow = 'hidden';
container.style.userSelect = 'none';
container.style.backgroundColor = '#000';
container.style.boxShadow = '0 4px 10px rgba(0,0,0,0.1)';
container.style.borderRadius = '4px';
const createLabel = (text, isLeft) => {
const lbl = document.createElement('div');
lbl.textContent = text;
lbl.style.position = 'absolute';
lbl.style.top = '10px';
lbl.style.padding = '4px 8px';
lbl.style.background = 'rgba(0,0,0,0.6)';
lbl.style.color = '#fff';
lbl.style.fontFamily = 'system-ui, sans-serif';
lbl.style.fontSize = '12px';
lbl.style.fontWeight = 'bold';
lbl.style.borderRadius = '4px';
lbl.style.pointerEvents = 'none';
lbl.style.zIndex = '5';
if (isLeft) lbl.style.left = '10px';
else lbl.style.right = '10px';
return lbl;
};
// Right / Bottom Image Layer
const wrapper2 = document.createElement('div');
wrapper2.style.position = 'absolute';
wrapper2.style.top = '0';
wrapper2.style.left = '0';
wrapper2.style.width = '100%';
wrapper2.style.height = '100%';
const img2El = new Image();
img2El.src = img2.src;
img2El.style.width = '100%';
img2El.style.height = '100%';
img2El.style.objectFit = 'fill';
img2El.style.pointerEvents = 'none';
img2El.style.display = 'block';
wrapper2.appendChild(img2El);
wrapper2.appendChild(createLabel('Image 2 (After)', false));
container.appendChild(wrapper2);
// Left / Top Overlaid Image Layer
const overlay = document.createElement('div');
overlay.style.position = 'absolute';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '50%';
overlay.style.height = '100%';
overlay.style.overflow = 'hidden';
const img1El = new Image();
img1El.src = originalImg.src;
img1El.style.position = 'absolute';
img1El.style.top = '0';
img1El.style.left = '0';
img1El.style.height = '100%';
img1El.style.objectFit = 'fill';
img1El.style.pointerEvents = 'none';
img1El.style.display = 'block';
overlay.appendChild(img1El);
overlay.appendChild(createLabel('Image 1 (Before)', true));
container.appendChild(overlay);
// Draggable Slider Handle
const handle = document.createElement('div');
handle.style.position = 'absolute';
handle.style.top = '0';
handle.style.bottom = '0';
handle.style.left = '50%';
handle.style.width = '4px';
handle.style.backgroundColor = '#fff';
handle.style.cursor = 'ew-resize';
handle.style.transform = 'translateX(-50%)';
handle.style.zIndex = '10';
handle.style.boxShadow = '0 0 4px rgba(0,0,0,0.4)';
const handleButton = document.createElement('div');
handleButton.style.position = 'absolute';
handleButton.style.top = '50%';
handleButton.style.left = '50%';
handleButton.style.transform = 'translate(-50%, -50%)';
handleButton.style.width = '32px';
handleButton.style.height = '32px';
handleButton.style.backgroundColor = '#fff';
handleButton.style.borderRadius = '50%';
handleButton.style.display = 'flex';
handleButton.style.alignItems = 'center';
handleButton.style.justifyContent = 'center';
handleButton.style.boxShadow = '0 2px 6px rgba(0,0,0,0.3)';
handleButton.innerHTML = `<div style="display:flex; gap: 4px;">
<div style="width: 0; height: 0; border-top: 5px solid transparent; border-bottom: 5px solid transparent; border-right: 6px solid #555;"></div>
<div style="width: 0; height: 0; border-top: 5px solid transparent; border-bottom: 5px solid transparent; border-left: 6px solid #555;"></div>
</div>`;
handle.appendChild(handleButton);
container.appendChild(handle);
// Sync width of overlay's image dynamically
const syncWidth = () => {
img1El.style.width = container.clientWidth + 'px';
};
if (window.ResizeObserver) {
const ro = new ResizeObserver(syncWidth);
ro.observe(container);
} else {
syncWidth();
window.addEventListener('resize', syncWidth);
}
// Interaction Events
let isDragging = false;
const moveHandler = (e) => {
if (!isDragging) return;
if (e.cancelable && e.type === 'touchmove') e.preventDefault(); // Stop mobile scroll while sliding
const rect = container.getBoundingClientRect();
let clientX = e.clientX;
if (e.touches && e.touches.length > 0) {
clientX = e.touches[0].clientX;
}
if (clientX === undefined) return;
let xPos = clientX - rect.left;
xPos = Math.max(0, Math.min(xPos, rect.width));
const percent = (xPos / rect.width) * 100;
overlay.style.width = `${percent}%`;
handle.style.left = `${percent}%`;
};
const stopDrag = () => isDragging = false;
const startDrag = (e) => {
isDragging = true;
moveHandler(e);
};
container.addEventListener('mousedown', startDrag);
container.addEventListener('touchstart', startDrag, { passive: true });
window.addEventListener('mousemove', moveHandler);
window.addEventListener('touchmove', moveHandler, { passive: false });
window.addEventListener('mouseup', stopDrag);
window.addEventListener('touchend', stopDrag);
return container;
}
Apply Changes