|
|
图片加文字水印软件工具
给图片加水印,日常多用于证件照加文字水印,一个简单的网页即可
软件成品下载地址:
源代码:
- [hide]<!DOCTYPE html>
- <html lang="zh-cn">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
- <title>批量文字水印工具</title>
- <style>
- * { box-sizing: border-box; margin: 0; padding: 0; }
- body { font-family: 'Microsoft YaHei', SimSun, sans-serif; background: #f7f8fa; }
- .navbar {
- width: 100%; background: #2E65BD; color: #fff; padding: 0;
- box-shadow: 0 2px 8px #e0e6f6; display: flex; align-items: center;
- justify-content: space-between; height: 54px; position: sticky; top: 0; z-index: 100;
- }
- .navbar .nav-left { font-size: 20px; font-weight: bold; letter-spacing: 2px; margin-left: 140px; }
- .navbar .back-link { position: absolute; left: 40px; text-decoration: none; color: #fff; font-size: 15px; }
- .navbar .back-link:hover { text-decoration: underline; }
- .container { max-width: 1400px; margin: 20px auto; background: #fff; border-radius: 8px; box-shadow: 0 2px 8px #eee; padding: 24px 28px; }
- h2 { font-size: 22px; margin-bottom: 6px; }
- .desc { color: #666; font-size: 14px; margin-bottom: 16px; line-height: 1.6; }
-
-
- .toolbar { display: flex; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; align-items: center; }
- .toolbar .btn { white-space: nowrap; }
-
-
- .toggle-wrap { display: flex; align-items: center; gap: 6px; font-size: 13px; color: #555; cursor: pointer; user-select: none; }
- .switch { position: relative; display: inline-block; width: 36px; height: 20px; }
- .switch input { opacity: 0; width: 0; height: 0; }
- .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background: #ccc; transition: .2s; border-radius: 20px; }
- .slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 2px; bottom: 2px; background: #fff; transition: .2s; border-radius: 50%; }
- .switch input:checked + .slider { background: #409eff; }
- .switch input:checked + .slider:before { transform: translateX(16px); }
- .switch input:disabled + .slider { opacity: 0.5; cursor: not-allowed; }
-
-
- .main-layout { display: flex; gap: 16px; align-items: stretch; }
- .left-panel { flex: 0 0 140px; min-width: 0; display: flex; flex-direction: column; }
- .right-panel { flex: 1; min-width: 0; display: flex; flex-direction: column; }
-
-
- .settings-panel {
- background: #f7f7fb; border-radius: 8px; padding: 16px 20px;
- margin-bottom: 14px; box-shadow: 0 1px 3px #eee;
- }
- .settings-panel h3 { font-size: 15px; margin-bottom: 10px; color: #333; }
- .form-row { display: flex; flex-wrap: nowrap; gap: 10px; align-items: flex-end; margin-bottom: 0; overflow-x: auto; }
- .form-group { display: flex; flex-direction: column; flex-shrink: 0; }
- .form-group label { font-size: 12px; color: #555; margin-bottom: 3px; }
- .form-group input, .form-group select {
- height: 34px; padding: 4px 10px; border: 1px solid #ccc; border-radius: 6px;
- font-size: 13px; min-width: 90px;
- }
- .form-group input[type="number"] { width: 80px; }
- .form-group input[type="range"] { min-width: 120px; height: auto; }
- .color-wrap { display: flex; align-items: center; gap: 6px; }
- .color-wrap input[type="color"] { height: 34px; width: 36px; border: 1px solid #ccc; border-radius: 6px; padding: 2px; cursor: pointer; }
- .color-wrap .color-hex { font-size: 12px; color: #666; }
-
-
- .btn { background: #409eff; color: #fff; border: none; border-radius: 6px; padding: 8px 16px; cursor: pointer; font-size: 13px; transition: background 0.2s; }
- .btn:hover { background: #3a8ee6; }
- .btn:active { background: #2d7cd6; }
- .btn-success { background: #67c23a; }
- .btn-success:hover { background: #5daf34; }
- .btn-warn { background: #e6a23c; }
- .btn-warn:hover { background: #cf9236; }
- .btn-danger { background: #f56c6c; }
- .btn-danger:hover { background: #e25c5c; }
- .btn-sm { padding: 6px 12px; font-size: 12px; }
-
-
- .thumb-section { flex: 1; overflow: hidden; display: flex; flex-direction: column; }
- .thumb-header {
- display: flex; justify-content: space-between; align-items: center;
- margin-bottom: 8px; padding-bottom: 8px; border-bottom: 1px solid #eee;
- }
- .thumb-header h3 { font-size: 15px; color: #333; }
- .thumb-header .count { font-size: 13px; color: #888; }
- .thumb-grid {
- display: flex; flex-direction: column;
- gap: 8px; overflow-y: auto; flex: 1; padding: 2px;
- max-height: calc(100vh - 260px);
- }
- .thumb-card {
- border: 2px solid #e8e8e8; border-radius: 6px; overflow: hidden;
- cursor: pointer; transition: border-color 0.2s, box-shadow 0.2s;
- background: #fafafa; position: relative; flex-shrink: 0;
- }
- .thumb-card:hover { border-color: #a0c4ff; }
- .thumb-card.active { border-color: #409eff; box-shadow: 0 0 0 2px rgba(64,158,255,0.3); }
- .thumb-card img { width: 100%; aspect-ratio: 1; object-fit: cover; display: block; }
- .thumb-card .thumb-name {
- font-size: 10px; color: #555; padding: 3px 4px;
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
- text-align: center; background: #f5f5f5;
- }
- .thumb-card .thumb-remove {
- position: absolute; top: 2px; right: 2px; width: 18px; height: 18px;
- border-radius: 50%; background: rgba(245,108,108,0.85); color: #fff;
- border: none; font-size: 11px; cursor: pointer; display: none;
- align-items: center; justify-content: center; line-height: 1;
- }
- .thumb-card:hover .thumb-remove { display: flex; }
- .thumb-empty {
- text-align: center; padding: 40px 10px;
- color: #bbb; font-size: 13px;
- }
-
-
- .preview-section {
- background: #f7f7fb; border-radius: 8px; padding: 16px;
- flex: 1; display: flex; flex-direction: column;
- }
- .preview-header {
- display: flex; justify-content: space-between; align-items: center;
- margin-bottom: 10px;
- }
- .preview-header h3 { font-size: 15px; color: #333; }
- .preview-header .preview-name { font-size: 12px; color: #888; max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
- .preview-canvas-wrap {
- flex: 1; display: flex; align-items: center; justify-content: center;
- background: repeating-conic-gradient(#e8e8e8 0% 25%, #fff 0% 50%) 0 0 / 16px 16px;
- border-radius: 6px; overflow: hidden; min-height: 400px;
- }
- .preview-canvas-wrap canvas {
- max-width: 100%; max-height: calc(100vh - 320px); display: block;
- box-shadow: 0 2px 12px rgba(0,0,0,0.1);
- }
- .preview-empty {
- text-align: center; color: #bbb; font-size: 15px; padding: 80px 20px;
- }
-
-
- .exporting-overlay {
- position: fixed; left: 50%; top: 30%; transform: translate(-50%, -50%);
- background: rgba(34,34,34,0.95); color: #fff; font-size: 18px;
- padding: 20px 36px; border-radius: 10px; z-index: 9999;
- box-shadow: 0 2px 16px rgba(0,0,0,0.3); display: none;
- }
-
-
- .opacity-val { font-size: 12px; color: #888; min-width: 30px; text-align: center; }
-
- [url=home.php?mod=space&uid=945662]@media[/url] (max-width: 900px) {
- .main-layout { flex-direction: column; }
- .left-panel { flex: none; }
- .right-panel { flex: none; }
- .thumb-grid { max-height: 200px; flex-direction: row; flex-wrap: wrap; }
- .thumb-card { width: 80px; flex-shrink: 0; }
- }
- @media (max-width: 700px) {
- .container { margin: 0; border-radius: 0; padding: 10px 3vw 60px 3vw; }
- .navbar { height: 44px; }
- .navbar .nav-left { margin-left: 120px; font-size: 16px; }
- .navbar .back-link { left: 10px; font-size: 13px; }
- }
- </style>
- </head>
- <body>
-
- <div class="container">
- <h2>批量文字水印工具</h2>
-
- <div class="toolbar">
- <button class="btn" onclick="document.getElementById('singleInput').click()">🖼️ 选择图片</button>
- <input type="file" id="singleInput" accept="image/*" multiple style="display:none;" />
- <button class="btn" onclick="document.getElementById('folderInput').click()">📁 选择文件夹</button>
- <input type="file" id="folderInput" webkitdirectory multiple style="display:none;" />
- <label class="toggle-wrap">
- <span class="switch"><input type="checkbox" id="moveWatermarkSwitch" onchange="toggleMove()" /><span class="slider"></span></span>
- 移动水印
- </label>
- <label class="toggle-wrap">
- <span class="switch"><input type="checkbox" id="individualSwitch" onchange="toggleIndividual()" /><span class="slider"></span></span>
- 单独调整
- </label>
- <span style="flex:1;"></span>
- <button class="btn btn-warn btn-sm" onclick="exportZip()">压缩包下载</button>
- <button class="btn btn-success btn-sm" onclick="exportAll()">导出全部</button>
- <button class="btn btn-danger btn-sm" onclick="clearAll()">清空</button>
- </div>
-
-
- <div class="main-layout">
-
- <div class="left-panel">
- <div class="thumb-section">
- <div class="thumb-header">
- <h3>图片列表</h3>
- <span class="count" id="thumbCount">共 0 张</span>
- </div>
- <div class="thumb-grid" id="thumbGrid">
- <div class="thumb-empty">暂无图片,请上传</div>
- </div>
- </div>
- </div>
-
-
- <div class="right-panel">
- <!-- 水印设置 -->
- <div class="settings-panel">
- <h3 style="margin-bottom:8px;">文字水印参数</h3>
- <div class="form-row">
- <div class="form-group">
- <label>水印文字</label>
- <input type="text" id="wmText" value="仅限办理***使用" placeholder="水印文字" style="width:180px;" />
- </div>
- <div class="form-group">
- <label>字体</label>
- <select id="wmFont">
- <option value="Microsoft YaHei" selected>微软雅黑</option>
- <option value="SimHei">黑体</option>
- <option value="SimSun">宋体</option>
- <option value="KaiTi">楷体</option>
- <option value="FangSong">仿宋</option>
- <option value="DengXian">等线</option>
- <option value="YouYuan">幼圆</option>
- <option value="LiSu">隶书</option>
- <option value="FZXiaoBiaoSong-B05S">方正小标宋</option>
- <option value="Arial">Arial</option>
- <option value="Times New Roman">Times New Roman</option>
- </select>
- </div>
- <div class="form-group">
- <label>字号</label>
- <input type="number" id="wmSize" value="60" min="8" max="300" />
- </div>
- <div class="form-group">
- <label>颜色</label>
- <div class="color-wrap">
- <input type="color" id="wmColor" value="#000000" />
- </div>
- </div>
- <div class="form-group">
- <label>透明度</label>
- <div style="display:flex;align-items:center;gap:4px;">
- <input type="range" id="wmOpacity" min="5" max="100" value="50" style="min-width:80px;" />
- <span class="opacity-val" id="wmOpacityVal">40%</span>
- </div>
- </div>
- <div class="form-group">
- <label>旋转</label>
- <div style="display:flex;align-items:center;gap:4px;">
- <input type="range" id="wmRotate" min="-180" max="180" value="-30" style="min-width:80px;" />
- <span class="opacity-val" id="wmRotateVal">-30°</span>
- </div>
- </div>
- <div class="form-group">
- <label>行</label>
- <input type="number" id="wmRows" value="3" min="1" max="20" style="width:48px;" />
- </div>
- <div class="form-group">
- <label>列</label>
- <input type="number" id="wmCols" value="3" min="1" max="20" style="width:48px;" />
- </div>
- </div>
- </div>
-
-
-
- <div class="preview-section">
- <div class="preview-header">
- <h3>水印预览</h3>
- <span class="preview-name" id="previewName"></span>
- </div>
- <div class="preview-canvas-wrap" id="previewWrap">
- <div class="preview-empty" id="previewEmpty">请上传图片后查看预览</div>
- <canvas id="previewCanvas" style="display:none;"></canvas>
- </div>
- </div>
- </div>
- </div>
- </div>
-
-
- <div class="exporting-overlay" id="exportingOverlay">正在导出中</div>
-
- <script src="./js/jszip.min.js"></script>
- <script>
-
- let imageFiles = [];
- let selectedIdx = -1;
- let previewTimer = null;
- let moveWatermark = false;
- let individualAdjust = false;
- let isGlobalMode = false;
- let isDragging = false;
- let dragStartX = 0;
- let dragStartY = 0;
- let dragInitialOffsetX = 0;
- let dragInitialOffsetY = 0;
-
-
- const singleInput = document.getElementById('singleInput');
- const folderInput = document.getElementById('folderInput');
-
- singleInput.addEventListener('change', function(e) {
- addFiles(Array.from(e.target.files));
- singleInput.value = '';
- });
- folderInput.addEventListener('change', function(e) {
- addFiles(Array.from(e.target.files));
- folderInput.value = '';
- });
-
- function addFiles(files) {
- const imgFiles = files.filter(f => f.type.startsWith('image/'));
- if (imgFiles.length === 0) { alert('未检测到图片文件'); return; }
- let loaded = 0;
- imgFiles.forEach(file => {
- const reader = new FileReader();
- reader.onload = function(e) {
- const imgEl = new Image();
- imgEl.onload = function() {
- imageFiles.push({ file, name: file.name, dataUrl: e.target.result, imgEl, offsetX: 0, offsetY: 0 });
- loaded++;
- if (loaded === imgFiles.length) {
- renderThumbnails();
-
- if (selectedIdx < 0 || selectedIdx >= imageFiles.length) {
- selectImage(imageFiles.length - imgFiles.length);
- } else {
- updatePreview();
- }
- }
- };
- imgEl.src = e.target.result;
- };
- reader.readAsDataURL(file);
- });
- }
-
-
- function renderThumbnails() {
- const grid = document.getElementById('thumbGrid');
- const countEl = document.getElementById('thumbCount');
- countEl.textContent = '共 ' + imageFiles.length + ' 张';
-
- if (imageFiles.length === 0) {
- grid.innerHTML = '<div class="thumb-empty">暂无图片,请上传</div>';
- return;
- }
-
- grid.innerHTML = '';
- imageFiles.forEach((item, idx) => {
- const card = document.createElement('div');
- card.className = 'thumb-card' + (idx === selectedIdx ? ' active' : '');
- card.onclick = function() { selectImage(idx); };
-
- const img = document.createElement('img');
- img.src = item.dataUrl;
- img.alt = item.name;
-
- const nameDiv = document.createElement('div');
- nameDiv.className = 'thumb-name';
- nameDiv.title = item.name;
- nameDiv.textContent = item.name;
-
- const removeBtn = document.createElement('button');
- removeBtn.className = 'thumb-remove';
- removeBtn.textContent = '✕';
- removeBtn.onclick = function(e) { e.stopPropagation(); removeImage(idx); };
-
- card.appendChild(img);
- card.appendChild(nameDiv);
- card.appendChild(removeBtn);
- grid.appendChild(card);
- });
- }
-
- function selectImage(idx) {
- if (idx < 0 || idx >= imageFiles.length) return;
- selectedIdx = idx;
-
- const cards = document.querySelectorAll('.thumb-card');
- cards.forEach((c, i) => c.classList.toggle('active', i === idx));
-
- updatePreview();
- }
-
- function removeImage(idx) {
- imageFiles.splice(idx, 1);
- if (imageFiles.length === 0) {
- selectedIdx = -1;
- document.getElementById('previewCanvas').style.display = 'none';
- document.getElementById('previewEmpty').style.display = '';
- document.getElementById('previewName').textContent = '';
- } else if (selectedIdx >= imageFiles.length) {
- selectedIdx = imageFiles.length - 1;
- } else if (selectedIdx === idx) {
- selectedIdx = Math.min(idx, imageFiles.length - 1);
- } else if (selectedIdx > idx) {
- selectedIdx--;
- }
- renderThumbnails();
- if (imageFiles.length > 0) updatePreview();
- }
-
- function clearAll() {
- imageFiles = [];
- selectedIdx = -1;
- renderThumbnails();
- document.getElementById('previewCanvas').style.display = 'none';
- document.getElementById('previewEmpty').style.display = '';
- document.getElementById('previewName').textContent = '';
- }
-
-
- function toggleMove() {
- moveWatermark = document.getElementById('moveWatermarkSwitch').checked;
-
- isGlobalMode = moveWatermark;
-
- if (moveWatermark && individualAdjust) {
- individualAdjust = false;
- document.getElementById('individualSwitch').checked = false;
- }
- updateCanvasCursor();
- }
- function toggleIndividual() {
- individualAdjust = document.getElementById('individualSwitch').checked;
-
- if (individualAdjust && moveWatermark) {
- moveWatermark = false;
- document.getElementById('moveWatermarkSwitch').checked = false;
- isGlobalMode = false; // 切换到单独调整模式
- } else {
-
- isGlobalMode = moveWatermark;
- }
- updateCanvasCursor();
- updatePreview();
- }
- function updateCanvasCursor() {
- const canvas = document.getElementById('previewCanvas');
- canvas.style.cursor = (moveWatermark || individualAdjust) ? 'move' : '';
- }
- function getImageOffset(item) {
-
- return { x: item.offsetX || 0, y: item.offsetY || 0 };
- }
-
-
- function initCanvasDrag() {
- const canvas = document.getElementById('previewCanvas');
-
-
- function startDrag(e) {
- if (!moveWatermark && !individualAdjust) return;
- isDragging = true;
- const rect = canvas.getBoundingClientRect();
- const clientX = e.touches ? e.touches[0].clientX : e.clientX;
- const clientY = e.touches ? e.touches[0].clientY : e.clientY;
- dragStartX = clientX - rect.left;
- dragStartY = clientY - rect.top;
- if (individualAdjust) {
- const item = imageFiles[selectedIdx];
- dragInitialOffsetX = item.offsetX || 0;
- dragInitialOffsetY = item.offsetY || 0;
- } else {
- dragInitialOffsetX = globalOffsetX;
- dragInitialOffsetY = globalOffsetY;
- }
- if (e.touches) e.preventDefault();
- }
-
-
- function doDrag(e) {
- if (!isDragging) return;
- const rect = canvas.getBoundingClientRect();
- const scaleX = canvas.width / rect.width;
- const scaleY = canvas.height / rect.height;
- const clientX = e.touches ? e.touches[0].clientX : e.clientX;
- const clientY = e.touches ? e.touches[0].clientY : e.clientY;
- const currentX = clientX - rect.left;
- const currentY = clientY - rect.top;
- const dx = (currentX - dragStartX) * scaleX;
- const dy = (currentY - dragStartY) * scaleY;
-
-
- const newOffsetX = dragInitialOffsetX + dx;
- const newOffsetY = dragInitialOffsetY + dy;
-
-
- imageFiles[selectedIdx].offsetX = newOffsetX;
- imageFiles[selectedIdx].offsetY = newOffsetY;
-
-
- if (isGlobalMode) {
- imageFiles.forEach((item, idx) => {
- if (idx !== selectedIdx) {
- item.offsetX = newOffsetX;
- item.offsetY = newOffsetY;
- }
- });
- }
-
- updatePreview();
- if (e.touches) e.preventDefault();
- }
-
-
- function endDrag() {
- isDragging = false;
- }
-
-
- canvas.addEventListener('mousedown', startDrag);
- canvas.addEventListener('mousemove', doDrag);
- canvas.addEventListener('mouseup', endDrag);
- canvas.addEventListener('mouseleave', endDrag);
-
-
- canvas.addEventListener('touchstart', startDrag, { passive: false });
- canvas.addEventListener('touchmove', doDrag, { passive: false });
- canvas.addEventListener('touchend', endDrag);
- canvas.addEventListener('touchcancel', endDrag);
- }
-
-
- function getWatermarkParams() {
- return {
- text: document.getElementById('wmText').value || '水印',
- font: document.getElementById('wmFont').value,
- size: parseInt(document.getElementById('wmSize').value) || 40,
- color: document.getElementById('wmColor').value,
- opacity: parseInt(document.getElementById('wmOpacity').value) / 100,
- rotate: parseInt(document.getElementById('wmRotate').value) || 0,
- rows: Math.max(1, parseInt(document.getElementById('wmRows').value) || 3),
- cols: Math.max(1, parseInt(document.getElementById('wmCols').value) || 3),
- };
- }
-
- function drawWatermark(ctx, img, width, height, params, offsetX, offsetY) {
- ctx.clearRect(0, 0, width, height);
- ctx.drawImage(img, 0, 0, width, height);
-
- const { text, font, size, color, opacity, rotate, rows, cols } = params;
- const scale = Math.min(width, height) / 800;
- const fontSize = Math.round(size * scale);
- const ox = offsetX || 0;
- const oy = offsetY || 0;
-
- ctx.save();
- ctx.globalAlpha = opacity;
- ctx.fillStyle = color;
- ctx.font = fontSize + 'px "' + font + '"';
- ctx.textAlign = 'center';
- ctx.textBaseline = 'middle';
-
- const cellW = width / cols;
- const cellH = height / rows;
- for (let r = 0; r < rows; r++) {
- for (let c = 0; c < cols; c++) {
- const cx = cellW * c + cellW / 2 + ox;
- const cy = cellH * r + cellH / 2 + oy;
- ctx.save();
- ctx.translate(cx, cy);
- ctx.rotate(rotate * Math.PI / 180);
- ctx.fillText(text, 0, 0);
- ctx.restore();
- }
- }
- ctx.restore();
- }
-
-
- function updatePreview() {
- if (selectedIdx < 0 || selectedIdx >= imageFiles.length) return;
- const item = imageFiles[selectedIdx];
- const canvas = document.getElementById('previewCanvas');
- const empty = document.getElementById('previewEmpty');
- const nameEl = document.getElementById('previewName');
-
- empty.style.display = 'none';
- canvas.style.display = '';
- nameEl.textContent = item.name;
- nameEl.title = item.name;
-
- canvas.width = item.imgEl.naturalWidth;
- canvas.height = item.imgEl.naturalHeight;
- const ctx = canvas.getContext('2d');
- const offset = getImageOffset(item);
- drawWatermark(ctx, item.imgEl, canvas.width, canvas.height, getWatermarkParams(), offset.x, offset.y);
- }
-
-
- function schedulePreview() {
- clearTimeout(previewTimer);
- previewTimer = setTimeout(updatePreview, 80);
- }
-
-
- ['wmText', 'wmFont', 'wmSize', 'wmColor', 'wmOpacity', 'wmRotate', 'wmRows', 'wmCols'].forEach(function(id) {
- const el = document.getElementById(id);
- el.addEventListener('input', function() {
- if (id === 'wmOpacity') document.getElementById('wmOpacityVal').textContent = el.value + '%';
- if (id === 'wmRotate') document.getElementById('wmRotateVal').textContent = el.value + '°';
- schedulePreview();
- });
- el.addEventListener('change', function() {
- schedulePreview();
- });
- });
-
-
- function getExportFileName(originalName) {
- const nameNoExt = originalName.replace(/\.[^.]+$/, '');
- const now = new Date();
- const pad = n => n < 10 ? '0' + n : n;
- const ts = '' + now.getFullYear() + pad(now.getMonth() + 1) + pad(now.getDate()) +
- '_' + pad(now.getHours()) + pad(now.getMinutes());
- return nameNoExt + '_水印_' + ts + '.png';
- }
-
- function renderWatermarkedImage(item, callback) {
- const canvas = document.createElement('canvas');
- canvas.width = item.imgEl.naturalWidth;
- canvas.height = item.imgEl.naturalHeight;
- const ctx = canvas.getContext('2d');
- const offset = getImageOffset(item);
- drawWatermark(ctx, item.imgEl, canvas.width, canvas.height, getWatermarkParams(), offset.x, offset.y);
- canvas.toBlob(function(blob) { callback(blob); }, 'image/png');
- }
-
- function exportAll() {
- if (imageFiles.length === 0) { alert('请先上传图片'); return; }
- const overlay = document.getElementById('exportingOverlay');
- overlay.textContent = '正在导出中';
- overlay.style.display = '';
- let idx = 0;
- function processNext() {
- if (idx >= imageFiles.length) { overlay.style.display = 'none'; return; }
- renderWatermarkedImage(imageFiles[idx], function(blob) {
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = getExportFileName(imageFiles[idx].name);
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- setTimeout(function() { URL.revokeObjectURL(url); }, 100);
- idx++;
- setTimeout(processNext, 200);
- });
- }
- processNext();
- }
-
- function exportZip() {
- if (imageFiles.length === 0) { alert('请先上传图片'); return; }
- const overlay = document.getElementById('exportingOverlay');
- overlay.textContent = '正在压缩中';
- overlay.style.display = '';
- const zip = new JSZip();
- let pending = imageFiles.length;
- const results = [];
- imageFiles.forEach(function(item, idx) {
- renderWatermarkedImage(item, function(blob) {
- results[idx] = { name: getExportFileName(item.name), blob: blob };
- pending--;
- if (pending === 0) {
- results.forEach(function(r) { zip.file(r.name, r.blob); });
- const now = new Date();
- const pad = n => n < 10 ? '0' + n : n;
- const ts = '' + now.getFullYear() + pad(now.getMonth() + 1) + pad(now.getDate()) +
- '_' + pad(now.getHours()) + pad(now.getMinutes());
- zip.generateAsync({ type: 'blob' }).then(function(zipBlob) {
- const url = URL.createObjectURL(zipBlob);
- const a = document.createElement('a');
- a.href = url;
- a.download = '水印图片_' + ts + '.zip';
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- setTimeout(function() { URL.revokeObjectURL(url); }, 100);
- overlay.style.display = 'none';
- });
- }
- });
- });
- }
-
-
- document.addEventListener('DOMContentLoaded', function() {
- initCanvasDrag();
- });
- </script>
- </body>
- </html>[/hide]
复制代码
|
-
|