// ==UserScript==
// @name Double Ctrl Image Zoom (Edge Style)
// @namespace https://products.agarmen.com
// @version 1.02
// @description Double Ctrl to zoom images like Edge browser
// @match *://*/*
// @grant none
// @author @emberasim
// @license MIT
// ==/UserScript==
(function () {
'use strict';
let lastCtrlTime = 0;
const DOUBLE_PRESS_INTERVAL = 400; // ms
let overlayDiv = null;
let zoomedImg = null;
let toolbar = null;
// Track mouse position
let mouseX = 0, mouseY = 0;
document.addEventListener('mousemove', e => {
mouseX = e.clientX;
mouseY = e.clientY;
});
// Create overlay and controls
function createOverlay(imgSrc) {
disposeOverlay();
overlayDiv = document.createElement('div');
overlayDiv.style.position = 'fixed';
overlayDiv.style.top = 0;
overlayDiv.style.left = 0;
overlayDiv.style.width = '100vw';
overlayDiv.style.height = '100vh';
overlayDiv.style.backgroundColor = 'rgba(0,0,0,0.85)';
overlayDiv.style.display = 'flex';
overlayDiv.style.alignItems = 'center';
overlayDiv.style.justifyContent = 'center';
overlayDiv.style.zIndex = 999999;
overlayDiv.style.backdropFilter = 'blur(3px)';
overlayDiv.style.opacity = '0';
overlayDiv.style.transition = 'opacity 0.25s ease';
// Image setup
zoomedImg = document.createElement('img');
zoomedImg.src = imgSrc;
zoomedImg.style.maxWidth = '95vw';
zoomedImg.style.maxHeight = '90vh';
zoomedImg.style.borderRadius = '8px';
zoomedImg.style.boxShadow = '0 0 25px rgba(0,0,0,0.7)';
zoomedImg.style.transformOrigin = 'center center';
zoomedImg.style.transition = 'transform 0.25s ease';
zoomedImg.style.userSelect = 'none';
zoomedImg.style.pointerEvents = 'none'; // let clicks pass through to overlay for closing
overlayDiv.appendChild(zoomedImg);
document.body.appendChild(overlayDiv);
requestAnimationFrame(() => (overlayDiv.style.opacity = '1'));
createToolbar(imgSrc);
overlayDiv.appendChild(toolbar);
overlayDiv.addEventListener('click', (e) => {
if (e.target === overlayDiv) disposeOverlay();
});
document.addEventListener('keydown', escListener, { once: true });
// Prevent background scroll and add wheel zoom
overlayDiv.addEventListener('wheel', (e) => {
e.preventDefault(); // stop page scroll
e.stopPropagation();
const delta = e.deltaY;
if (delta < 0) adjustZoom(1.25); // scroll up → zoom in
else if (delta > 0) adjustZoom(1 / 1.15); // scroll down → zoom out
}, { passive: false });
}
function createToolbar(imgSrc) {
toolbar = document.createElement('div');
toolbar.style.position = 'absolute';
toolbar.style.bottom = '30px';
toolbar.style.left = '50%';
toolbar.style.transform = 'translateX(-50%)';
toolbar.style.background = 'rgba(30,30,30,0.8)';
toolbar.style.borderRadius = '8px';
toolbar.style.padding = '6px 10px';
toolbar.style.display = 'flex';
toolbar.style.gap = '8px';
toolbar.style.zIndex = '1000000';
toolbar.style.fontFamily = 'sans-serif';
toolbar.style.userSelect = 'none';
toolbar.style.transition = 'opacity 0.25s ease';
toolbar.style.opacity = '0';
requestAnimationFrame(() => (toolbar.style.opacity = '1'));
const buttons = [
{ label: '🔍+', title: 'Zoom In', action: () => adjustZoom(1.2) },
{ label: '🔍−', title: 'Zoom Out', action: () => adjustZoom(1 / 1.2) },
{ label: '⟳', title: 'Rotate', action: rotateImage },
{ label: '💾', title: 'Download', action: () => downloadImage(imgSrc) },
{ label: '↗', title: 'Open in New Tab', action: () => window.open(imgSrc, '_blank') },
{ label: '✖', title: 'Close', action: disposeOverlay }
];
buttons.forEach(btn => {
const b = document.createElement('button');
b.textContent = btn.label;
b.title = btn.title;
Object.assign(b.style, {
background: 'transparent',
color: 'white',
border: 'none',
fontSize: '20px',
cursor: 'pointer',
padding: '4px 8px',
borderRadius: '5px',
});
b.addEventListener('click', e => {
e.stopPropagation(); // don't close overlay
btn.action();
});
b.addEventListener('mouseenter', () => (b.style.background = 'rgba(255,255,255,0.15)'));
b.addEventListener('mouseleave', () => (b.style.background = 'transparent'));
toolbar.appendChild(b);
});
overlayDiv.appendChild(toolbar);
}
// Zoom / rotate logic
let zoomScale = 1;
let rotation = 0;
function adjustZoom(factor) {
zoomScale *= factor;
zoomScale = Math.max(0.2, Math.min(zoomScale, 5)); // limit 0.2x–5x
updateTransform();
}
function rotateImage() {
rotation = (rotation + 90) % 360;
updateTransform();
}
function updateTransform() {
zoomedImg.style.transform = `scale(${zoomScale}) rotate(${rotation}deg)`;
}
function downloadImage(src) {
const a = document.createElement('a');
a.href = src;
a.download = src.split('/').pop().split('?')[0] || 'image';
document.body.appendChild(a);
a.click();
a.remove();
}
function escListener(e) {
if (e.key === 'Escape') disposeOverlay();
}
function disposeOverlay() {
if (overlayDiv) {
overlayDiv.style.opacity = '0';
if (toolbar) toolbar.style.opacity = '0';
setTimeout(() => {
overlayDiv?.remove();
overlayDiv = null;
zoomedImg = null;
toolbar = null;
zoomScale = 1;
rotation = 0;
}, 250);
}
}
// Wait until image is ready before computing smart zoom
const applySmartZoom = () => {
if (!zoomedImg || !zoomedImg.complete) {
requestAnimationFrame(applySmartZoom);
return;
}
const vw = window.innerWidth * 0.9;
const vh = window.innerHeight * 0.9;
const iw = zoomedImg.naturalWidth;
const ih = zoomedImg.naturalHeight;
if (!iw || !ih) return;
// Compute how much scaling is needed to fit in screen
const fitScale = Math.min(vw / iw, vh / ih);
// Only enlarge if BOTH dimensions are significantly smaller than viewport
const isSmall = iw < vw * 0.8 && ih < vh * 0.8;
let targetZoom;
if (isSmall) {
// Small image → gentle enlargement
targetZoom = Math.min(fitScale * 1.4, 1.6);
} else {
// Large image → fit perfectly inside screen
targetZoom = Math.min(fitScale, 1.0);
}
zoomScale = targetZoom;
updateTransform();
}
// Detect double Ctrl
window.addEventListener('keydown', (e) => {
if (e.key === 'Control') {
const now = Date.now();
if (now - lastCtrlTime < DOUBLE_PRESS_INTERVAL) {
const elems = document.elementsFromPoint(mouseX, mouseY);
const hoveredImg = elems.find(el => el.tagName && el.tagName.toLowerCase() === 'img');
if (hoveredImg) {
createOverlay(hoveredImg.src);
e.preventDefault();
applySmartZoom();
}
}
lastCtrlTime = now;
} else if (e.key === 'Escape') {
disposeOverlay();
}
});
})();
//Script by #EMBER