您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a small download button on images; click to save the original image (uses GM_download when available).
// ==UserScript== // @name Easy Image Downloader (hover button) // @namespace https://greasyfork.org/en/users/your-name // @version 1.0.0 // @description Adds a small download button on images; click to save the original image (uses GM_download when available). // @author you // @license MIT // @match *://*/* // @exclude *://greasyfork.org/* // @run-at document-idle // @grant GM_download // ==/UserScript== (function () { "use strict"; // ========== Config ========== const MIN_W = 120; // only show button on images >= MIN_W x MIN_H const MIN_H = 120; const BUTTON_TEXT = "↓"; const BUTTON_CLASS = "eid-btn"; const BUTTON_STYLES = ` .${BUTTON_CLASS} { position: absolute; right: 6px; bottom: 6px; z-index: 2147483647; padding: 4px 8px; font: 600 12px/1 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; background: rgba(0,0,0,.75); color: #fff; border: 0; border-radius: 6px; cursor: pointer; opacity: 0; transition: opacity .15s ease-in-out, transform .15s ease-in-out; transform: translateY(2px); user-select: none; } .eid-wrap:hover .${BUTTON_CLASS} { opacity: 1; transform: translateY(0); } `; // Inject CSS const style = document.createElement("style"); style.textContent = BUTTON_STYLES; document.documentElement.appendChild(style); // Observe images added later const observer = new MutationObserver(() => decorateAll()); observer.observe(document.documentElement, { childList: true, subtree: true }); // Initial pass decorateAll(); function decorateAll() { const imgs = document.querySelectorAll("img:not([data-eid])"); for (const img of imgs) { // Skip tiny/hidden images const w = img.naturalWidth || img.width; const h = img.naturalHeight || img.height; if (w < MIN_W || h < MIN_H) { img.setAttribute("data-eid", "skip"); continue; } // Wrap the image in a relatively positioned container (non-destructive) const wrap = document.createElement("span"); wrap.className = "eid-wrap"; wrap.style.position = "relative"; wrap.style.display = "inline-block"; // Some sites have display:block on images; preserve layout width/height wrap.style.width = img.width ? img.width + "px" : ""; wrap.style.height = img.height ? img.height + "px" : ""; // Insert wrapper and move the image inside img.parentNode && img.parentNode.insertBefore(wrap, img); wrap.appendChild(img); // Create button const btn = document.createElement("button"); btn.type = "button"; btn.className = BUTTON_CLASS; btn.textContent = BUTTON_TEXT; btn.title = "Download image"; btn.addEventListener("click", (ev) => { ev.preventDefault(); ev.stopPropagation(); const url = resolveImageURL(img); const filename = suggestFilename(img, url); downloadImage(url, filename); }); wrap.appendChild(btn); img.setAttribute("data-eid", "done"); } } // Try to get the highest quality URL if the site uses srcset function resolveImageURL(img) { // Prefer currentSrc when available (handles srcset) if (img.currentSrc) return img.currentSrc; return img.src || ""; } function suggestFilename(img, url) { try { const u = new URL(url, location.href); const pathName = u.pathname.split("/").pop() || "image"; const clean = decodeURIComponent(pathName).split("?")[0].split("#")[0]; const base = clean || (img.alt ? sluggify(img.alt) : "image"); const ext = guessExtFromURL(url) || "jpg"; return ensureExt(base, ext); } catch { const base = (img.alt ? sluggify(img.alt) : "image"); return ensureExt(base, "jpg"); } } function sluggify(s) { return s .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-+|-+$/g, "") .slice(0, 64) || "image"; } function guessExtFromURL(url) { const m = url.toLowerCase().match(/\.(png|jpe?g|webp|gif|bmp|svg|avif)(?:$|\?|\#)/); return m ? m[1].replace("jpeg", "jpg") : null; } function ensureExt(name, ext) { if (!name.toLowerCase().endsWith(`.${ext}`)) return `${name}.${ext}`; return name; } async function downloadImage(url, filename) { // Prefer GM_download when available (cross-origin friendly) if (typeof GM_download === "function") { try { GM_download({ url, name: filename, headers: { Referer: location.href }, // helps on some hosts onerror: () => fallbackDownload(url, filename), }); return; } catch { // fall through } } // Fallback fallbackDownload(url, filename); } async function fallbackDownload(url, filename) { try { // If same-origin or CORS allowed const res = await fetch(url, { credentials: "omit" }); const blob = await res.blob(); const a = document.createElement("a"); const objectUrl = URL.createObjectURL(blob); a.href = objectUrl; a.download = filename; document.body.appendChild(a); a.click(); setTimeout(() => { URL.revokeObjectURL(objectUrl); a.remove(); }, 1000); } catch (e) { // Last resort: open in a new tab (user can save manually) window.open(url, "_blank", "noopener,noreferrer"); console.warn("[Easy Image Downloader] Fallback open:", e); } } })();