class PreviewImage extends HTMLElement { static get observedAttributes() { return ["src", "alt"]; } constructor(){ super(); this._src = this.getAttribute("src") || ""; this._alt = this.getAttribute("alt") || ""; this.attachShadow({ mode: "open" }); const root = document.createElement("div"); root.className = "root"; const img = document.createElement("img"); root.appendChild(img); this.shadowRoot.append(style, root); this._img = img; this._render(); this._img.addEventListener("click", (e)=>{ e.stopPropagation(); this.init(this._src); }); // 保存一个全局实例(与 bi.js 风格一致,便于外部静态调用) window.previewImageComponent = this; } attributeChangedCallback(name, oldVal, newVal){ if(name === "src") this._src = newVal || ""; if(name === "alt") this._alt = newVal || ""; this._render(); } _render(){ if(this._img){ this._img.src = this._src; this._img.alt = this._alt; } } init(src){ PreviewImage.open(src || this._src); } static open(src){ if(!src) return; const mask = document.createElement("div"); const box = document.createElement("div"); const img = document.createElement("img"); mask.style.width = "100%"; mask.style.height = "100%"; mask.style.maxWidth = "none"; mask.style.maxHeight = "none"; mask.style.border = "none"; mask.style.position = "fixed"; mask.style.top = "0"; mask.style.left = "0"; mask.style.backgroundColor = "rgba(255, 255, 255, 0.8)"; mask.style.zIndex = "10002"; mask.style.display = "flex"; mask.style.alignItems = "center"; mask.style.justifyContent = "center"; // box.className = "detail-image flexcenter"; box.style.width = "80vw"; box.style.height = "80vh"; box.style.borderRadius = "8px"; box.style.backgroundColor = "rgba(17, 17, 17, 0.9)"; box.style.overflow = "hidden"; box.style.display = "flex"; box.style.alignItems = "center"; box.style.justifyContent = "center"; box.style.position = "relative"; // img.className = "detail-img"; img.style.maxWidth = "100%"; img.style.maxHeight = "100%"; img.src = src; box.appendChild(img); mask.appendChild(box); let scale = 1, tx = 0, ty = 0, dragging = false, sx = 0, sy = 0; const apply = () => { img.style.transform = `translate(${tx}px, ${ty}px) scale(${scale})`; img.style.cursor = dragging ? "grabbing" : "grab"; }; const onWheel = (ev) => { ev.preventDefault(); const step = 0.1; scale += (ev.deltaY > 0 ? -step : step); if (scale < 0.1) scale = 0.1; if (scale > 5) scale = 5; apply(); }; const onDown = (ev) => { ev.preventDefault(); dragging = true; sx = ev.clientX; sy = ev.clientY; apply(); }; const onMove = (ev) => { if (!dragging) return; const dx = ev.clientX - sx, dy = ev.clientY - sy; sx = ev.clientX; sy = ev.clientY; tx += dx; ty += dy; apply(); }; const onUp = () => { dragging = false; apply(); }; img.addEventListener("wheel", onWheel, { passive: false }); img.addEventListener("mousedown", onDown); document.addEventListener("mousemove", onMove); document.addEventListener("mouseup", onUp); img.addEventListener("click", (ev) => ev.stopPropagation()); const onKey = (ev) => { if (ev.key === "Escape") close(); }; const close = () => { document.body.style.overflow = "auto"; img.removeEventListener("wheel", onWheel); img.removeEventListener("mousedown", onDown); document.removeEventListener("mousemove", onMove); document.removeEventListener("mouseup", onUp); document.removeEventListener("keydown", onKey); mask.remove(); }; mask.addEventListener("click", (ev)=>{ if (ev.target === mask) close(); }); box.addEventListener("click", (ev)=>ev.stopPropagation()); const dl = document.createElement("button"); dl.textContent = "下载图片"; Object.assign(dl.style, { position: "absolute", right: "24px", bottom: "24px", zIndex: "10001", padding: "8px 14px", borderRadius: "6px", border: "none", cursor: "pointer", backgroundColor: "#50e3c2", color: "#000" }); dl.addEventListener("click", (ev) => { ev.stopPropagation(); const name = (src.split("/").pop() || "image").split("?")[0]; fetch(src).then((r) => r.blob()).then((blob) => { const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = name || "image"; document.body.appendChild(a); a.click(); a.remove(); setTimeout(()=>URL.revokeObjectURL(url),0); }).catch(() => { const a = document.createElement("a"); a.href = src; a.target = "_blank"; document.body.appendChild(a); a.click(); a.remove(); }); }); box.appendChild(dl); document.body.appendChild(mask); document.addEventListener("keydown", onKey); document.body.style.overflow = "hidden"; apply(); } } if (!customElements.get("preview-image")) customElements.define("preview-image", PreviewImage); // 3. 暴露统一对象(可使用 previewImage.initComponent(url)) PreviewImage.initComponent = function(url){ if (window.previewImageComponent) { window.previewImageComponent.init(url); return true; } PreviewImage.open(url); return true; }; window.previewImage = PreviewImage;