Screenshot with dragging selection

High-precision selection screenshot for Via Browser (CORS Image & Gradient Fix), works on Via browser, xbrowser perfectly.Almost all websites are supported

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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);
})();