(function (global) { var rotation = 0; var zoomed = false; var bound = false; var lastSrc = ''; var baseW = 0; var baseH = 0; function el(id) { return document.getElementById(id); } function ensureDom() { var root = el('cert-viewer'); if (root) return root; root = document.createElement('div'); root.id = 'cert-viewer'; root.className = 'cert-viewer'; root.setAttribute('hidden', ''); root.setAttribute('role', 'dialog'); root.setAttribute('aria-modal', 'true'); root.setAttribute('aria-label', '证书大图'); root.innerHTML = '
' + '点击图片放大/还原,放大后可拖动查看,点「旋转」调整角度
'; document.body.appendChild(root); return root; } function getStage() { var root = el('cert-viewer'); return root ? root.querySelector('.cert-viewer-stage') : null; } function getCanvas() { var root = el('cert-viewer'); return root ? root.querySelector('.cert-viewer-canvas') : null; } function getRotator() { var root = el('cert-viewer'); return root ? root.querySelector('.cert-viewer-rotator') : null; } function clearImgSize(img) { img.style.width = ''; img.style.height = ''; img.style.maxWidth = ''; img.style.maxHeight = ''; img.style.position = ''; img.style.left = ''; img.style.top = ''; } function clearBoxSize(node) { if (!node) return; node.style.width = ''; node.style.height = ''; node.style.minWidth = ''; node.style.minHeight = ''; } /** * 用 naturalWidth/Height + 舞台可视区域计算适配尺寸。 * 避免依赖 clientWidth(hidden/未完成布局时常为 0,导致放大宽高异常、无法拖动)。 */ function calcFitSize(img) { var nw = img.naturalWidth || 0; var nh = img.naturalHeight || 0; if (!nw || !nh) return { w: 0, h: 0 }; var stage = getStage(); var maxW = stage && stage.clientWidth ? stage.clientWidth - 24 : window.innerWidth - 24; var maxH = stage && stage.clientHeight ? stage.clientHeight - 16 : window.innerHeight - 110; maxW = Math.min(Math.max(1, maxW), Math.max(1, window.innerWidth - 24)); maxH = Math.min(Math.max(1, maxH), Math.max(1, window.innerHeight - 110)); var scale = Math.min(maxW / nw, maxH / nh, 1); return { w: Math.max(1, Math.round(nw * scale)), h: Math.max(1, Math.round(nh * scale)) }; } function ensureBaseSize(img) { if (baseW > 0 && baseH > 0) return true; var fit = calcFitSize(img); if (!fit.w || !fit.h) return false; baseW = fit.w; baseH = fit.h; return true; } function centerScroll() { var stage = getStage(); if (!stage) return; stage.scrollLeft = Math.max(0, (stage.scrollWidth - stage.clientWidth) / 2); stage.scrollTop = Math.max(0, (stage.scrollHeight - stage.clientHeight) / 2); } function applyTransform() { var img = el('cert-viewer-img'); var canvas = getCanvas(); var rotator = getRotator(); if (!img) return; if (zoomed) { if (!ensureBaseSize(img)) { // 长宽尚未就绪:保持放大意图,等 onload 再应用,避免写成 0px img.classList.remove('is-zoomed'); img.style.transform = 'rotate(' + rotation + 'deg)'; clearImgSize(img); clearBoxSize(rotator); clearBoxSize(canvas); } else { var zw = baseW * 2; var zh = baseH * 2; var swapped = (rotation % 180) !== 0; var boxW = swapped ? zh : zw; var boxH = swapped ? zw : zh; img.classList.add('is-zoomed'); img.style.maxWidth = 'none'; img.style.maxHeight = 'none'; img.style.width = zw + 'px'; img.style.height = zh + 'px'; // 绝对居中 + 旋转:滚动区域由 rotator 包围盒撑开(transform 本身不扩展 scroll) img.style.position = 'absolute'; img.style.left = '50%'; img.style.top = '50%'; img.style.transform = 'translate(-50%, -50%) rotate(' + rotation + 'deg)'; if (rotator) { rotator.style.width = boxW + 'px'; rotator.style.height = boxH + 'px'; } if (canvas) { canvas.style.width = ''; canvas.style.height = ''; canvas.style.minWidth = '100%'; canvas.style.minHeight = '100%'; } requestAnimationFrame(function () { requestAnimationFrame(centerScroll); }); } } else { img.classList.remove('is-zoomed'); clearImgSize(img); clearBoxSize(rotator); clearBoxSize(canvas); img.style.transform = 'rotate(' + rotation + 'deg)'; baseW = 0; baseH = 0; var stage = getStage(); if (stage) { stage.scrollLeft = 0; stage.scrollTop = 0; } } var zoomBtn = document.querySelector('#cert-viewer [data-cert-zoom]'); if (zoomBtn) { zoomBtn.textContent = zoomed ? '还原' : '放大'; } } function bindOnce(root) { if (bound) return; bound = true; root.addEventListener('click', function (event) { if (event.target.closest('[data-cert-close]')) { close(); return; } if (event.target.closest('[data-cert-rotate]')) { rotation = (rotation + 90) % 360; applyTransform(); return; } if (event.target.closest('[data-cert-zoom]')) { zoomed = !zoomed; if (!zoomed) { baseW = 0; baseH = 0; } applyTransform(); return; } if (event.target === el('cert-viewer-img')) { zoomed = !zoomed; if (!zoomed) { baseW = 0; baseH = 0; } applyTransform(); return; } if (event.target.classList.contains('cert-viewer-stage') || event.target.classList.contains('cert-viewer-canvas') || event.target.classList.contains('cert-viewer-rotator')) { if (!zoomed) close(); } }); document.addEventListener('keydown', function (event) { var viewer = el('cert-viewer'); if (!viewer || viewer.hidden) return; if (event.key === 'Escape') { event.stopPropagation(); close(); } }, true); } function open(src) { if (!src) return; var root = ensureDom(); bindOnce(root); var img = el('cert-viewer-img'); rotation = 0; zoomed = false; baseW = 0; baseH = 0; function onReady() { // 图片解码完成后再量适配尺寸;若用户已点放大则一并套用 applyTransform(); } img.onload = onReady; img.onerror = function () { baseW = 0; baseH = 0; zoomed = false; applyTransform(); }; if (lastSrc !== src) { img.removeAttribute('src'); lastSrc = src; } img.src = src; // 先展示再测量,避免 hidden 下 client/stage 尺寸为 0 root.hidden = false; document.body.style.overflow = 'hidden'; if (img.complete && img.naturalWidth) { onReady(); } else { applyTransform(); } } function close() { var root = el('cert-viewer'); if (!root || root.hidden) return; root.hidden = true; rotation = 0; zoomed = false; baseW = 0; baseH = 0; applyTransform(); // 若底部证书 sheet 仍打开,保持 body 锁定;由 sheet 自己维护 var sheetOpen = document.querySelector('#bottom-sheet:not([hidden]), #detail-sheet:not([hidden])'); if (!sheetOpen) { document.body.style.overflow = ''; } } function isOpen() { var root = el('cert-viewer'); return !!(root && !root.hidden); } global.ExpertCertViewer = { open: open, close: close, isOpen: isOpen }; })(window);