网址:图片批量编辑(架设在github上,更新快)或 图片批量编辑
因为某些需要,找了半天也没在手机上找到能够批量编辑图片的软件或者是网站,有些网站只能上传几个图片,多了就得登陆或者开会员😅,所以就花了点时间手搓了个网站
优点:图片不会上传到服务器,所有操作均在本地进行,处理速度快,断网也能用
结束
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>图片批量编辑</title> <link href="https://unpkg.com/filepond/dist/filepond.css" rel="stylesheet"> <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script> <style> header { background-color: #2c3e50; color: white; padding: 10px 0; text-align: center; font-size: 1.5rem; } .image-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; width: 100%; margin-top: 20px; } .image-grid img { width: 100%; height: auto; border-radius: 4px; object-fit: cover; } /* 底部按钮布局 */ .navbar-bottom { position: fixed; bottom: 0; width: 100%; background-color: #f8f9fa; border-top: 1px solid #dee2e6; padding: 10px 0; text-align: center; display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; } .navbar-bottom button { background: none; border: none; color: gray; font-size: 1rem; cursor: pointer; } .navbar-bottom button.active { color: lightblue; } /* 水印设置弹窗样式 */ #watermark-modal { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border: 1px solid #ccc; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); z-index: 1000; } #watermark-modal h3 { margin-top: 0; } #watermark-modal label { display: block; margin-top: 10px; } #watermark-modal input[type="range"] { width: 100%; } #watermark-modal button { margin-top: 20px; padding: 10px 20px; cursor: pointer; } </style> </head> <body> <header> <h1>图片批量编辑</h1> </header> <main id="main-container"> <input type="file" id="image-input" accept="image/*" multiple style="display:none;"> <div class="image-grid" id="image-grid"></div> </main> <div class="navbar-bottom"> <button class="btn-default" id="rotate-button"> <span class="icon">↻</span><br>旋转 </button> <button class="btn-default" id="mirror-button"> <span class="icon">⇆</span><br>镜像 </button> <button class="btn-default" id="rotate-mirror-button"> <span class="icon">↔️</span><br>旋转镜像 </button> <button class="btn-default" id="watermark-button"> <span class="icon">💧</span><br>水印 </button> <button class="btn-save" id="save-button"> <span class="icon">💾</span><br>保存图片 </button> <button class="btn-clear" id="clear-button"> <span class="icon">🗑️</span><br>清空 </button> </div> <div id="watermark-modal" style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border: 1px solid #ccc; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); z-index: 1000;"> <h3>水印设置</h3> <a href="https://github.com/joyqi/sfz">Github地址</a></article> <label for="text">输入需要打水印的文字</label> <p> <input type="text" id="text" value="经供参考" autocomplete="off" maxlength="30"> </p> <label for="color">颜色</label> <p> <input type="color" id="color" pattern="#[0-9A-Fa-f]{6}" autocomplete="off" value="#ffffff"> </p> <label for="alpha">透明度</label> <p> <input type="range" id="alpha" min="0" max="1" step="0.05" autocomplete="off" value="0.15"> </p> <label for="angle">角度</label> <p> <input type="range" id="angle" min="-90" max="90" step="3" autocomplete="off" value="45"> </p> <label for="space">间隔</label> <p> <input type="range" id="space" min="1" max="8" step="0.2" autocomplete="off" value="2"> </p> <label for="size">字号</label> <p> <input type="range" id="size" min="0.5" max="3" step="0.05" autocomplete="off" value="1"> </p> <button id="apply-watermark">应用水印</button> <button id="close-modal">关闭</button> </div> <script src="https://unpkg.com/filepond/dist/filepond.js"></script> <script> const pond = FilePond.create(document.querySelector('input[type="file"]'), { allowMultiple: true, // 允许多选 acceptedFileTypes: ['image/*'], labelIdle: '拖放图片或点击选择图片', allowRemove: true, }); pond.on('addfile', (error, file) => { if (error) { console.error('文件添加失败:', error); return; } const imageGrid = document.getElementById('image-grid'); const existingImage = Array.from(imageGrid.querySelectorAll('img')).find( img => img.dataset.fileId === file.id ); if (!existingImage) { const img = document.createElement('img'); img.src = URL.createObjectURL(file.file); img.dataset.rotate = 0; // 初始化旋转角度 img.dataset.mirror = 'false'; // 初始化镜像状态 img.dataset.fileId = file.id; // 为图片添加唯一标识 imageGrid.appendChild(img); document.getElementById('image-grid').style.display = 'grid'; } }); // 监听文件删除事件 pond.on('removefile', (error, file) => { if (error) { console.error('文件删除失败:', error); return; } const imageGrid = document.getElementById('image-grid'); const images = imageGrid.querySelectorAll('img'); images.forEach(img => { if (img.dataset.fileId === file.id) { // 通过唯一标识匹配 img.remove(); } }); if (imageGrid.children.length === 0) { imageGrid.style.display = 'none'; } }); // 绑定按钮事件 document.getElementById('rotate-button').addEventListener('click', rotateImages); document.getElementById('mirror-button').addEventListener('click', mirrorImages); document.getElementById('rotate-mirror-button').addEventListener('click', rotateMirrorImages); document.getElementById('save-button').addEventListener('click', saveImages); document.getElementById('clear-button').addEventListener('click', clearImages); // 旋转图片(每次旋转90°) function rotateImages() { const images = document.querySelectorAll('.image-grid img'); images.forEach(img => { let currentRotate = parseInt(img.dataset.rotate) || 0; currentRotate = (currentRotate + 90) % 360; img.dataset.rotate = currentRotate; // 更新旋转角度 applyTransform(img); }); } // 镜像图片 function mirrorImages() { const images = document.querySelectorAll('.image-grid img'); images.forEach(img => { const isMirrored = img.dataset.mirror === 'true'; img.dataset.mirror = !isMirrored; // 切换镜像状态 applyTransform(img); }); } // 旋转并镜像图片 function rotateMirrorImages() { const images = document.querySelectorAll('.image-grid img'); images.forEach(img => { let currentRotate = parseInt(img.dataset.rotate) || 0; currentRotate = (currentRotate + 180) % 360; img.dataset.rotate = currentRotate; const isMirrored = img.dataset.mirror === 'true'; img.dataset.mirror = !isMirrored; applyTransform(img); }); } // 旋转 + 镜像 function applyTransform(img) { const rotate = parseInt(img.dataset.rotate) || 0; const isMirrored = img.dataset.mirror === 'true'; img.style.transform = `rotate(${rotate}deg) ${isMirrored ? 'scaleX(-1)' : ''}`; } document.getElementById('watermark-button').addEventListener('click', () => { document.getElementById('watermark-modal').style.display = 'block'; }); document.getElementById('close-modal').addEventListener('click', () => { document.getElementById('watermark-modal').style.display = 'none'; }); document.getElementById('apply-watermark').addEventListener('click', () => { const watermarkText = document.getElementById('text').value; const watermarkColor = document.getElementById('color').value; const watermarkAlpha = parseFloat(document.getElementById('alpha').value); const watermarkAngle = parseFloat(document.getElementById('angle').value); const watermarkSpace = parseFloat(document.getElementById('space').value); const watermarkSize = parseFloat(document.getElementById('size').value); addWatermark(watermarkText, watermarkColor, watermarkAlpha, watermarkAngle, watermarkSpace, watermarkSize); document.getElementById('watermark-modal').style.display = 'none'; }); // 添加水印功能 function addWatermark(text, color, alpha, angle, space, size) { const images = document.querySelectorAll('.image-grid img'); if (images.length === 0) { alert('请先导入图片!'); return; } images.forEach(img => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; ctx.drawImage(img, 0, 0, canvas.width, canvas.height); // 设置水印样式 ctx.font = `${canvas.width * 0.05 * size}px Arial`; // 根据图片宽度和字号比例设置字体大小 ctx.fillStyle = `rgba(${parseInt(color.slice(1, 3), 16)}, ${parseInt(color.slice(3, 5), 16)}, ${parseInt(color.slice(5, 7), 16)}, ${alpha})`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.translate(canvas.width / 2, canvas.height / 2); ctx.rotate((angle * Math.PI) / 180); const textWidth = ctx.measureText(text).width; const textHeight = parseInt(ctx.font, 10); for (let x = -canvas.width; x < canvas.width * 2; x += textWidth * space) { for (let y = -canvas.height; y < canvas.height * 2; y += textHeight * space) { ctx.fillText(text, x, y); } } img.src = canvas.toDataURL('image/png'); }); } //打包为ZIP文件 async function saveImages() { const images = document.querySelectorAll('.image-grid img'); if (images.length === 0) { alert('请先导入图片!'); return; } const zip = new JSZip(); const folder = zip.folder('edited-images'); await Promise.all(Array.from(images).map(async (img, index) => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); //限制最大宽度或高度为 1024px const maxSize = 1024; let width = img.naturalWidth; let height = img.naturalHeight; if (width > maxSize || height > maxSize) { const ratio = Math.min(maxSize / width, maxSize / height); width = Math.floor(width * ratio); height = Math.floor(height * ratio); } canvas.width = width; canvas.height = height; ctx.save(); ctx.translate(canvas.width / 2, canvas.height / 2); ctx.rotate((parseInt(img.dataset.rotate) || 0) * Math.PI / 180); if (img.dataset.mirror === 'true') { ctx.scale(-1, 1); } ctx.drawImage(img, -canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height); ctx.restore(); // 将 canvas 转换为 Blob 并添加到 ZIP return new Promise(resolve => { canvas.toBlob(blob => { folder.file(`image-${index + 1}.png`, blob); resolve(); }, 'image/png', 0.8); }); })); zip.generateAsync({ type: 'blob' }).then(content => { saveAs(content, 'edited-images.zip'); }); } // 清空图片 function clearImages() { const imageGrid = document.getElementById('image-grid'); imageGrid.innerHTML = ''; document.getElementById('image-grid').style.display = 'none'; pond.removeFiles(); } </script> </body> </html>