High-precision selection screenshot for Via Browser (CORS Image & Gradient Fix), works on Via browser, xbrowser perfectly.Almost all websites are supported
// ==UserScript==
// @name Screenshot with dragging selection
// @namespace http://tampermonkey.net/
// @version 20.05.2026
// @description High-precision selection screenshot for Via Browser (CORS Image & Gradient Fix), works on Via browser, xbrowser perfectly.Almost all websites are supported
// @author Spuramgemi
// @match *://*/*
// @run-at document-start
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @require https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js
// ==/UserScript==
(function() {
'use strict';
let btn, isSelecting = false, startX, startY, selectionBox;
let menuCommandId = null;
// Toggle function that can be called from both button and GM menu
function toggleSelectionMode() {
if (!btn) return;
isSelecting = !isSelecting;
btn.style.opacity = isSelecting ? '0.7' : '0.35';
btn.innerHTML = isSelecting ? '❌' : '✂️';
}
function attachSelectionLogic() {
window.removeEventListener('touchstart', handleStart, { passive: false });
window.removeEventListener('touchmove', handleMove, { passive: false });
window.removeEventListener('touchend', handleEnd);
window.addEventListener('touchstart', handleStart, { passive: false });
window.addEventListener('touchmove', handleMove, { passive: false });
window.addEventListener('touchend', handleEnd);
}
function handleStart(e) {
if (!isSelecting || e.target === btn) return;
const touch = e.touches[0];
startX = touch.clientX;
startY = touch.clientY;
selectionBox = document.createElement('div');
selectionBox.style.cssText = `position:fixed; border:2px solid #007AFF; background:rgba(0,122,255,0.2); z-index:99998; left:${startX}px; top:${startY}px; pointer-events:none;`;
document.body.appendChild(selectionBox);
}
function handleMove(e) {
if (!isSelecting || !selectionBox) return;
e.preventDefault();
const touch = e.touches[0];
const curX = touch.clientX;
const curY = touch.clientY;
const width = Math.abs(curX - startX);
const height = Math.abs(curY - startY);
const left = Math.min(curX, startX);
const top = Math.min(curY, startY);
selectionBox.style.width = width + 'px';
selectionBox.style.height = height + 'px';
selectionBox.style.left = left + 'px';
selectionBox.style.top = top + 'px';
}
function handleEnd() {
if (!isSelecting || !selectionBox) return;
const rect = selectionBox.getBoundingClientRect();
selectionBox.remove();
selectionBox = null;
isSelecting = false;
btn.style.background = 'transparent';
btn.style.opacity = '0.35';
btn.innerHTML = '✂️';
setTimeout(() => {
if (!window.html2canvas) {
alert("html2canvas not loaded yet, please retry.");
return;
}
const bodyRect = document.body.getBoundingClientRect();
const docRect = document.documentElement.getBoundingClientRect();
const scrollX = window.scrollX || window.pageXOffset || Math.abs(docRect.left) || Math.abs(bodyRect.left) || 0;
const scrollY = window.scrollY || window.pageYOffset || Math.abs(docRect.top) || Math.abs(bodyRect.top) || 0;
const dpr = window.devicePixelRatio || 1;
const targetWidth = Math.max(document.documentElement.clientWidth, window.innerWidth, document.body.clientWidth);
const targetHeight = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
window.html2canvas(document.body, {
x: Math.floor(rect.left + scrollX),
y: Math.floor(rect.top + scrollY),
width: Math.ceil(rect.width),
height: Math.ceil(rect.height),
scrollX: 0,
scrollY: 0,
windowWidth: targetWidth,
windowHeight: targetHeight,
scale: dpr,
useCORS: true,
allowTaint: false, // Turned false so .toDataURL() never breaks
logging: false,
onclone: async (clonedDoc) => {
// 1. CRASH PROTECTION FILTER: Remove dynamic gradients
const allClonedElements = clonedDoc.getElementsByTagName('*');
for (let i = 0; i < allClonedElements.length; i++) {
const style = window.getComputedStyle(allClonedElements[i]);
if (style.backgroundImage && style.backgroundImage.includes('gradient')) {
allClonedElements[i].style.setProperty('background-image', 'none', 'important');
}
}
// 2. CORS FORCE FILTER: Convert images inside selection to base64 inline strings
const clonedImages = clonedDoc.getElementsByTagName('img');
const promises = Array.from(clonedImages).map(img => {
return new Promise((resolve) => {
if (!img.src || img.src.startsWith('data:')) return resolve();
// Native fetch bypass for same-origin or loose CORS endpoints
fetch(img.src)
.then(response => response.blob())
.then(blob => {
const reader = new FileReader();
reader.onloadend = () => {
img.src = reader.result;
resolve();
};
reader.readAsDataURL(blob);
})
.catch(() => {
// Fallback: Force crossOrigin attribute as backup
img.crossOrigin = "anonymous";
resolve();
});
});
});
await Promise.all(promises);
}
}).then(canvas => {
const overlay = document.createElement('div');
overlay.style.cssText = 'position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.85); z-index:100000; display:flex; flex-direction:column; align-items:center; justify-content:center;';
const preview = document.createElement('div');
preview.style.cssText = 'background:white; padding:15px; border-radius:8px; max-width:90%; max-height:75%; overflow:auto; text-align:center;';
const notice = document.createElement('p');
notice.innerHTML = "📸 <b>Preview Ready</b><br><span style='font-size:12px;color:#666;'>Long press the preview image to save it natively.</span>";
notice.style.cssText = 'margin:0 0 12px 0; font-family:sans-serif; font-size:14px; color:#333; line-height:1.4;';
preview.appendChild(notice);
const visualWrapper = document.createElement('div');
visualWrapper.style.cssText = 'position:relative; display:inline-block;';
canvas.style.cssText = `
max-width: 100% !important;
height: auto !important;
display: block !important;
margin: 0 auto !important;
padding-right: 4px !important;
box-sizing: border-box !important;
`;
visualWrapper.appendChild(canvas);
try {
const nativeImgPlaceholder = document.createElement('img');
nativeImgPlaceholder.src = canvas.toDataURL("image/png");
nativeImgPlaceholder.style.cssText = 'position:absolute; top:0; left:0; width:100%; height:100%; opacity:0.01; -webkit-touch-callout:default !important;';
visualWrapper.appendChild(nativeImgPlaceholder);
} catch(e) {
canvas.style.cssText += '; -webkit-touch-callout:default !important; user-select:element !important;';
}
preview.appendChild(visualWrapper);
const btnRow = document.createElement('div');
btnRow.style.cssText = 'margin-top:14px; display:flex; gap:10px; justify-content:center;';
const confirmBtn = document.createElement('button');
confirmBtn.textContent = 'Save File';
confirmBtn.style.cssText = 'padding:10px 20px; background:#007AFF; color:white; border:none; border-radius:6px; font-size:15px; font-weight:bold;';
confirmBtn.onclick = () => {
try {
const link = document.createElement('a');
link.download = `crop_${Date.now()}.png`;
link.href = canvas.toDataURL("image/png");
link.click();
overlay.remove();
} catch(e) {
alert("Please use Via Browser's native long-press on the image below to save directly.");
}
};
const cancelBtn = document.createElement('button');
cancelBtn.textContent = 'Cancel';
cancelBtn.style.cssText = 'padding:10px 20px; background:#FF3B30; color:white; border:none; border-radius:6px; font-size:15px; font-weight:bold;';
cancelBtn.onclick = () => overlay.remove();
btnRow.appendChild(confirmBtn);
btnRow.appendChild(cancelBtn);
preview.appendChild(btnRow);
overlay.appendChild(preview);
document.body.appendChild(overlay);
}).catch(err => alert("Capture failed: " + err));
}, 100);
}
function createButton() {
if (btn && (document.body.contains(btn) || document.documentElement.contains(btn))) return;
btn = document.createElement('div');
btn.innerHTML = '✂️';
btn.style.cssText = `
position:fixed !important;
bottom:140px !important;
right:12px !important;
z-index:2147483647 !important;
width:28px !important;
height:28px !important;
background:transparent !important;
color:rgba(0,0,0,0.35) !important;
border:none !important;
border-radius:50% !important;
display:flex !important;
align-items:center !important;
justify-content:center !important;
font-size:14px !important;
box-shadow:none !important;
padding:0 !important;
margin:0 !important;
touch-action:none !important;
opacity:0.35 !important;
`;
if (document.body) document.body.appendChild(btn);
document.documentElement.appendChild(btn);
btn.onclick = (e) => {
e.preventDefault();
toggleSelectionMode();
};
attachSelectionLogic();
}
// Register GM menu command
function registerMenuCommand() {
if (menuCommandId !== null) {
GM_unregisterMenuCommand(menuCommandId);
}
menuCommandId = GM_registerMenuCommand("📸 Toggle Screenshot Mode", function() {
if (btn) {
toggleSelectionMode();
}
});
}
function ensureButton() {
if (!document.body) {
setTimeout(ensureButton, 100);
return;
}
createButton();
registerMenuCommand();
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", ensureButton);
} else {
ensureButton();
}
const observer = new MutationObserver(() => {
if (!btn || (!document.body.contains(btn) && !document.documentElement.contains(btn))) {
ensureButton();
}
attachSelectionLogic();
registerMenuCommand();
});
observer.observe(document.documentElement, { childList: true, subtree: true });
document.addEventListener("readystatechange", () => {
if (document.readyState === "interactive" || document.readyState === "complete") {
ensureButton();
}
});
setInterval(ensureButton, 1500);
})();