Capture the approved AttachmentUploadField preview enhancements (rotate, fit-full, wheel/button zoom, drag pan) before implementation. Co-authored-by: Cursor <cursoragent@cursor.com>
654 lines
20 KiB
HTML
654 lines
20 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>附件图片预览 · 左转 / 右转 Demo</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||
<style>
|
||
:root {
|
||
--primary: #10b981;
|
||
--primary-dark: #059669;
|
||
--primary-bg: #ecfdf5;
|
||
--text: #1e293b;
|
||
--text-muted: #64748b;
|
||
--border: #d9e8e2;
|
||
--bg-page: #f8fafc;
|
||
--surface: #ffffff;
|
||
--radius: 10px;
|
||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
|
||
body {
|
||
min-height: 100vh;
|
||
font-family: Inter, -apple-system, BlinkMacSystemFont, 'PingFang SC', sans-serif;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
color: var(--text);
|
||
background: var(--bg-page);
|
||
padding: 28px 20px 48px;
|
||
}
|
||
|
||
.page {
|
||
max-width: 920px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.intro h1 {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
margin-bottom: 6px;
|
||
}
|
||
.intro p {
|
||
font-size: 13px;
|
||
color: var(--text-muted);
|
||
margin-bottom: 4px;
|
||
}
|
||
.intro code {
|
||
font-size: 12px;
|
||
background: #fff;
|
||
border: 1px solid #cbd5e1;
|
||
border-radius: 4px;
|
||
padding: 1px 6px;
|
||
}
|
||
.legend {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
margin: 14px 0 20px;
|
||
}
|
||
.legend span {
|
||
font-size: 12px;
|
||
background: #fff;
|
||
border: 1px solid #cbd5e1;
|
||
border-radius: 999px;
|
||
padding: 4px 10px;
|
||
color: #475569;
|
||
}
|
||
|
||
.panel {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
box-shadow: var(--shadow);
|
||
padding: 20px;
|
||
}
|
||
.panel-top {
|
||
margin-bottom: 14px;
|
||
}
|
||
.panel-top h2 {
|
||
position: relative;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
padding-left: 10px;
|
||
}
|
||
.panel-top h2::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: 0;
|
||
top: 2px;
|
||
bottom: 2px;
|
||
width: 3px;
|
||
border-radius: 2px;
|
||
background: var(--primary);
|
||
}
|
||
.panel-tip {
|
||
margin-top: 6px;
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.attachment-list {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.upload-card {
|
||
width: 140px;
|
||
height: 96px;
|
||
border: 1px dashed #a7f3d0;
|
||
background: #f0fdf4;
|
||
border-radius: 8px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
color: var(--primary);
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
cursor: default;
|
||
user-select: none;
|
||
}
|
||
.upload-card .plus {
|
||
font-size: 28px;
|
||
line-height: 1;
|
||
font-weight: 400;
|
||
}
|
||
|
||
.attachment-item {
|
||
width: 140px;
|
||
}
|
||
.attachment-preview {
|
||
width: 140px;
|
||
height: 96px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
background: #f8fafc;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.attachment-preview:hover {
|
||
border-color: var(--primary);
|
||
}
|
||
.attachment-preview img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
display: block;
|
||
}
|
||
.attachment-preview.is-doc {
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
color: var(--text-muted);
|
||
font-size: 12px;
|
||
cursor: default;
|
||
}
|
||
.attachment-name {
|
||
display: block;
|
||
margin-top: 6px;
|
||
font-size: 12px;
|
||
color: var(--text);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* 预览弹窗 */
|
||
.mask {
|
||
display: none;
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(15, 23, 42, 0.45);
|
||
backdrop-filter: none;
|
||
z-index: 1000;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 24px;
|
||
}
|
||
.mask.show { display: flex; }
|
||
|
||
.dialog {
|
||
width: min(860px, 100%);
|
||
background: #fff;
|
||
border-radius: 10px;
|
||
box-shadow: 0 20px 50px rgba(15, 23, 42, 0.18);
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
max-height: calc(100vh - 48px);
|
||
}
|
||
.dialog-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
padding: 14px 18px;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.dialog-title {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.dialog-close {
|
||
width: 28px;
|
||
height: 28px;
|
||
border: none;
|
||
background: transparent;
|
||
border-radius: 6px;
|
||
color: var(--text-muted);
|
||
font-size: 18px;
|
||
cursor: pointer;
|
||
line-height: 1;
|
||
}
|
||
.dialog-close:hover {
|
||
background: #f1f5f9;
|
||
color: var(--text);
|
||
}
|
||
|
||
.dialog-body {
|
||
padding: 16px 18px 18px;
|
||
overflow: auto;
|
||
}
|
||
|
||
.preview-toolbar {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
margin-bottom: 12px;
|
||
}
|
||
.toolbar-sep {
|
||
width: 1px;
|
||
height: 20px;
|
||
background: var(--border);
|
||
margin: 0 4px;
|
||
}
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
height: 36px;
|
||
padding: 0 14px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: #fff;
|
||
color: var(--text);
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
}
|
||
.btn:hover {
|
||
border-color: var(--primary);
|
||
color: var(--primary-dark);
|
||
background: var(--primary-bg);
|
||
}
|
||
.btn:active { transform: translateY(1px); }
|
||
.btn:disabled {
|
||
opacity: 0.45;
|
||
cursor: not-allowed;
|
||
pointer-events: none;
|
||
}
|
||
.btn svg {
|
||
width: 16px;
|
||
height: 16px;
|
||
flex-shrink: 0;
|
||
}
|
||
.status-chip {
|
||
min-width: 108px;
|
||
text-align: center;
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.preview-stage {
|
||
height: min(62vh, 520px);
|
||
min-height: 320px;
|
||
background: #f8fafc;
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
padding: 16px;
|
||
cursor: grab;
|
||
touch-action: none;
|
||
}
|
||
.preview-stage.is-dragging {
|
||
cursor: grabbing;
|
||
}
|
||
.preview-stage img {
|
||
width: auto;
|
||
height: auto;
|
||
max-width: none;
|
||
max-height: none;
|
||
border-radius: 4px;
|
||
transition: transform 0.15s ease;
|
||
transform-origin: center center;
|
||
user-select: none;
|
||
-webkit-user-drag: none;
|
||
will-change: transform;
|
||
pointer-events: none;
|
||
}
|
||
.preview-stage.is-zooming img,
|
||
.preview-stage.is-dragging img {
|
||
transition: none;
|
||
}
|
||
|
||
.note {
|
||
margin-top: 16px;
|
||
padding: 12px 14px;
|
||
background: var(--primary-bg);
|
||
border: 1px solid #a7f3d0;
|
||
border-radius: 8px;
|
||
font-size: 12px;
|
||
color: #065f46;
|
||
}
|
||
.note strong { font-weight: 600; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="page">
|
||
<div class="intro">
|
||
<h1>附件图片预览 · 旋转 + 缩放</h1>
|
||
<p>对齐 <code>AttachmentUploadField</code>:缩略图点击打开预览弹窗;仅图片显示工具条。</p>
|
||
<p>交互:左转 / 右转 · 放大 / 缩小 / 重置 · 滚轮缩放 · 拖拽平移 · 关闭后重置。</p>
|
||
<p><strong>完整可见:</strong>缩放倍率 = 1 时,旋转后按外接矩形完整装入预览区;放大后可拖拽查看细节。</p>
|
||
<div class="legend">
|
||
<span>Fresh Mint · #10b981</span>
|
||
<span>弹窗 860px</span>
|
||
<span>滚轮缩放</span>
|
||
<span>仅预览态(不写回文件)</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="panel">
|
||
<div class="panel-top">
|
||
<h2>协议附件(示意)</h2>
|
||
<p class="panel-tip">点击下方图片缩略图体验预览旋转;PDF 卡片不可预览旋转。</p>
|
||
</div>
|
||
<div class="attachment-list">
|
||
<div class="upload-card" title="示意:正式页为上传入口">
|
||
<span class="plus">+</span>
|
||
<span>上传附件</span>
|
||
</div>
|
||
|
||
<div class="attachment-item">
|
||
<div class="attachment-preview" id="thumb-a" title="点击预览">
|
||
<img id="thumb-a-img" alt="现场照片-01.jpg" />
|
||
</div>
|
||
<span class="attachment-name">现场照片-01.jpg</span>
|
||
</div>
|
||
|
||
<div class="attachment-item">
|
||
<div class="attachment-preview" id="thumb-b" title="点击预览">
|
||
<img id="thumb-b-img" alt="证件扫描.jpg" />
|
||
</div>
|
||
<span class="attachment-name">证件扫描.jpg</span>
|
||
</div>
|
||
|
||
<div class="attachment-item">
|
||
<div class="attachment-preview is-doc" title="文档:本 Demo 不演示旋转">
|
||
<span style="font-size:22px">📄</span>
|
||
<span>PDF</span>
|
||
</div>
|
||
<span class="attachment-name">协议正文.pdf</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="note">
|
||
<strong>推荐落点:</strong>工具条 = 左转 / 右转 · 缩小 / 放大 / 重置;
|
||
预览区滚轮缩放 + 按住拖拽平移。
|
||
倍率 1 = 完整装入。全部仅预览态,不改 MinIO 原文件。
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mask" id="mask" role="dialog" aria-modal="true" aria-labelledby="dialog-title">
|
||
<div class="dialog">
|
||
<div class="dialog-header">
|
||
<div class="dialog-title" id="dialog-title">附件预览</div>
|
||
<button type="button" class="dialog-close" id="btn-close" aria-label="关闭">×</button>
|
||
</div>
|
||
<div class="dialog-body">
|
||
<div class="preview-toolbar">
|
||
<button type="button" class="btn" id="btn-left" title="逆时针 90°">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
|
||
<path d="M3 3v5h5" />
|
||
</svg>
|
||
左转
|
||
</button>
|
||
<button type="button" class="btn" id="btn-right" title="顺时针 90°">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||
<path d="M21 12a9 9 0 1 1-9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" />
|
||
<path d="M21 3v5h-5" />
|
||
</svg>
|
||
右转
|
||
</button>
|
||
<span class="toolbar-sep" aria-hidden="true"></span>
|
||
<button type="button" class="btn" id="btn-zoom-out" title="缩小">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">
|
||
<circle cx="11" cy="11" r="7" />
|
||
<path d="M21 21l-4.3-4.3M8 11h6" />
|
||
</svg>
|
||
缩小
|
||
</button>
|
||
<button type="button" class="btn" id="btn-zoom-in" title="放大">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">
|
||
<circle cx="11" cy="11" r="7" />
|
||
<path d="M21 21l-4.3-4.3M11 8v6M8 11h6" />
|
||
</svg>
|
||
放大
|
||
</button>
|
||
<button type="button" class="btn" id="btn-zoom-reset" title="重置为完整可见">
|
||
重置
|
||
</button>
|
||
<span class="status-chip" id="status-chip">0° · 100%</span>
|
||
</div>
|
||
<div class="preview-stage" id="preview-stage" title="滚轮缩放 · 按住拖拽平移">
|
||
<img id="preview-img" alt="预览" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
/** 生成带方向标记的示意图(无需外网) */
|
||
function makeDemoImage(label, hue, w, h) {
|
||
const svg =
|
||
`<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}">` +
|
||
`<defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1">` +
|
||
`<stop stop-color="hsl(${hue},45%,88%)"/><stop offset="1" stop-color="hsl(${hue},40%,72%)"/>` +
|
||
`</linearGradient></defs>` +
|
||
`<rect width="100%" height="100%" fill="url(#g)"/>` +
|
||
`<rect x="24" y="24" width="120" height="36" rx="6" fill="#059669"/>` +
|
||
`<text x="36" y="48" fill="#fff" font-size="16" font-family="Inter,sans-serif" font-weight="600">TOP</text>` +
|
||
`<text x="${w / 2}" y="${h / 2}" text-anchor="middle" fill="#1e293b" font-size="28" font-family="Inter,sans-serif" font-weight="600">${label}</text>` +
|
||
`<text x="${w / 2}" y="${h / 2 + 36}" text-anchor="middle" fill="#64748b" font-size="14" font-family="Inter,sans-serif">${w}×${h} · 旋转 · 滚轮 · 拖拽</text>` +
|
||
`<polygon points="${w / 2 - 16},${h - 56} ${w / 2 + 16},${h - 56} ${w / 2},${h - 28}" fill="#059669"/>` +
|
||
`<text x="${w / 2}" y="${h - 12}" text-anchor="middle" fill="#059669" font-size="12" font-family="Inter,sans-serif">BOTTOM</text>` +
|
||
`</svg>`
|
||
return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg)
|
||
}
|
||
|
||
const files = {
|
||
a: { name: '现场照片-01.jpg', src: makeDemoImage('宽图现场照 A', 150, 960, 360) },
|
||
b: { name: '证件扫描.jpg', src: makeDemoImage('竖图证件 B', 175, 420, 640) }
|
||
}
|
||
|
||
document.getElementById('thumb-a-img').src = files.a.src
|
||
document.getElementById('thumb-b-img').src = files.b.src
|
||
|
||
const ZOOM_MIN = 0.5
|
||
const ZOOM_MAX = 5
|
||
const ZOOM_STEP = 0.15
|
||
|
||
const mask = document.getElementById('mask')
|
||
const stage = document.getElementById('preview-stage')
|
||
const previewImg = document.getElementById('preview-img')
|
||
const dialogTitle = document.getElementById('dialog-title')
|
||
const statusChip = document.getElementById('status-chip')
|
||
const btnZoomOut = document.getElementById('btn-zoom-out')
|
||
const btnZoomIn = document.getElementById('btn-zoom-in')
|
||
|
||
let angle = 0
|
||
/** 相对「完整装入」的倍率:1 = 旋转后整图可见 */
|
||
let zoom = 1
|
||
let offsetX = 0
|
||
let offsetY = 0
|
||
let dragging = false
|
||
let dragStartX = 0
|
||
let dragStartY = 0
|
||
let dragOriginX = 0
|
||
let dragOriginY = 0
|
||
|
||
function clampZoom(v) {
|
||
return Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, Math.round(v * 100) / 100))
|
||
}
|
||
|
||
function fitBaseScale() {
|
||
const padX = 32
|
||
const padY = 32
|
||
const stageW = Math.max(stage.clientWidth - padX, 1)
|
||
const stageH = Math.max(stage.clientHeight - padY, 1)
|
||
const natW = previewImg.naturalWidth || 1
|
||
const natH = previewImg.naturalHeight || 1
|
||
const rad = (angle * Math.PI) / 180
|
||
const cos = Math.abs(Math.cos(rad))
|
||
const sin = Math.abs(Math.sin(rad))
|
||
const boundW = natW * cos + natH * sin
|
||
const boundH = natW * sin + natH * cos
|
||
return Math.min(stageW / boundW, stageH / boundH)
|
||
}
|
||
|
||
function applyTransform() {
|
||
const natW = previewImg.naturalWidth || 1
|
||
const natH = previewImg.naturalHeight || 1
|
||
const base = fitBaseScale()
|
||
const scale = base * zoom
|
||
previewImg.style.width = natW + 'px'
|
||
previewImg.style.height = natH + 'px'
|
||
previewImg.style.transform =
|
||
'translate(' + offsetX + 'px,' + offsetY + 'px) rotate(' + angle + 'deg) scale(' + scale + ')'
|
||
const deg = ((angle % 360) + 360) % 360
|
||
statusChip.textContent = deg + '° · ' + Math.round(zoom * 100) + '%'
|
||
btnZoomOut.disabled = zoom <= ZOOM_MIN
|
||
btnZoomIn.disabled = zoom >= ZOOM_MAX
|
||
}
|
||
|
||
function resetView(keepAngle) {
|
||
if (!keepAngle) angle = 0
|
||
zoom = 1
|
||
offsetX = 0
|
||
offsetY = 0
|
||
applyTransform()
|
||
}
|
||
|
||
function setZoom(next, fromWheel) {
|
||
zoom = clampZoom(next)
|
||
if (zoom <= 1) {
|
||
offsetX = 0
|
||
offsetY = 0
|
||
}
|
||
if (fromWheel) stage.classList.add('is-zooming')
|
||
applyTransform()
|
||
if (fromWheel) {
|
||
requestAnimationFrame(function () {
|
||
stage.classList.remove('is-zooming')
|
||
})
|
||
}
|
||
}
|
||
|
||
function openPreview(key) {
|
||
const file = files[key]
|
||
dialogTitle.textContent = file.name
|
||
angle = 0
|
||
zoom = 1
|
||
offsetX = 0
|
||
offsetY = 0
|
||
mask.classList.add('show')
|
||
previewImg.onload = function () {
|
||
applyTransform()
|
||
}
|
||
previewImg.src = file.src
|
||
if (previewImg.complete) applyTransform()
|
||
}
|
||
|
||
function closePreview() {
|
||
mask.classList.remove('show')
|
||
angle = 0
|
||
zoom = 1
|
||
offsetX = 0
|
||
offsetY = 0
|
||
previewImg.style.transform = ''
|
||
statusChip.textContent = '0° · 100%'
|
||
}
|
||
|
||
document.getElementById('thumb-a').addEventListener('click', function () { openPreview('a') })
|
||
document.getElementById('thumb-b').addEventListener('click', function () { openPreview('b') })
|
||
document.getElementById('btn-close').addEventListener('click', closePreview)
|
||
document.getElementById('btn-left').addEventListener('click', function () {
|
||
angle -= 90
|
||
applyTransform()
|
||
})
|
||
document.getElementById('btn-right').addEventListener('click', function () {
|
||
angle += 90
|
||
applyTransform()
|
||
})
|
||
btnZoomOut.addEventListener('click', function () {
|
||
setZoom(zoom - ZOOM_STEP)
|
||
})
|
||
btnZoomIn.addEventListener('click', function () {
|
||
setZoom(zoom + ZOOM_STEP)
|
||
})
|
||
document.getElementById('btn-zoom-reset').addEventListener('click', function () {
|
||
resetView(true)
|
||
})
|
||
|
||
stage.addEventListener(
|
||
'wheel',
|
||
function (e) {
|
||
if (!mask.classList.contains('show')) return
|
||
e.preventDefault()
|
||
const delta = e.deltaY > 0 ? -ZOOM_STEP : ZOOM_STEP
|
||
setZoom(zoom + delta, true)
|
||
},
|
||
{ passive: false }
|
||
)
|
||
|
||
stage.addEventListener('pointerdown', function (e) {
|
||
if (e.button !== 0) return
|
||
dragging = true
|
||
dragStartX = e.clientX
|
||
dragStartY = e.clientY
|
||
dragOriginX = offsetX
|
||
dragOriginY = offsetY
|
||
stage.classList.add('is-dragging')
|
||
stage.setPointerCapture(e.pointerId)
|
||
})
|
||
stage.addEventListener('pointermove', function (e) {
|
||
if (!dragging) return
|
||
offsetX = dragOriginX + (e.clientX - dragStartX)
|
||
offsetY = dragOriginY + (e.clientY - dragStartY)
|
||
applyTransform()
|
||
})
|
||
function endDrag(e) {
|
||
if (!dragging) return
|
||
dragging = false
|
||
stage.classList.remove('is-dragging')
|
||
try {
|
||
stage.releasePointerCapture(e.pointerId)
|
||
} catch (_) {}
|
||
}
|
||
stage.addEventListener('pointerup', endDrag)
|
||
stage.addEventListener('pointercancel', endDrag)
|
||
|
||
mask.addEventListener('click', function (e) {
|
||
if (e.target === mask) closePreview()
|
||
})
|
||
document.addEventListener('keydown', function (e) {
|
||
if (!mask.classList.contains('show')) return
|
||
if (e.key === 'Escape') closePreview()
|
||
if (e.key === 'ArrowLeft') { angle -= 90; applyTransform() }
|
||
if (e.key === 'ArrowRight') { angle += 90; applyTransform() }
|
||
if (e.key === '+' || e.key === '=') setZoom(zoom + ZOOM_STEP)
|
||
if (e.key === '-' || e.key === '_') setZoom(zoom - ZOOM_STEP)
|
||
if (e.key === '0') resetView(true)
|
||
})
|
||
window.addEventListener('resize', function () {
|
||
if (mask.classList.contains('show')) applyTransform()
|
||
})
|
||
</script>
|
||
</body>
|
||
</html>
|