图片切割工具:智能分割长图并控制文件大小
写在前面
这是一个直观易用的图片切割工具,用户上传长图后自动分割成多个不超过5MB的片段,保持原始宽度不变,自己用的,顺带发表一下,不喜勿喷!
设计思路
-
创建简洁的UI,包含上传区域和结果展示区
-
实现智能分割算法,保持原始宽度,动态计算高度
-
确保每个片段不超过5MB
-
使用Canvas进行图片处理和压缩
-
添加下载功能,支持下载所有切割后的图片
效果预览
最终实现代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>智能图片切割工具</title><style>* {box-sizing: border-box;margin: 0;padding: 0;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;}body {background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);color: #fff;min-height: 100vh;padding: 20px;}.container {max-width: 1000px;margin: 0 auto;padding: 20px;}header {text-align: center;padding: 30px 0;margin-bottom: 30px;background: rgba(0, 0, 0, 0.3);border-radius: 15px;box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);backdrop-filter: blur(10px);}h1 {font-size: 2.5rem;margin-bottom: 15px;text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);}.subtitle {font-size: 1.2rem;opacity: 0.9;max-width: 700px;margin: 0 auto;line-height: 1.6;}.card {background: rgba(255, 255, 255, 0.15);border-radius: 15px;padding: 30px;margin-bottom: 30px;box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);backdrop-filter: blur(10px);}.upload-area {border: 3px dashed rgba(255, 255, 255, 0.4);border-radius: 10px;padding: 40px 20px;text-align: center;cursor: pointer;transition: all 0.3s ease;position: relative;}.upload-area:hover {background: rgba(255, 255, 255, 0.1);border-color: rgba(255, 255, 255, 0.7);}.upload-area i {font-size: 60px;margin-bottom: 20px;display: block;color: rgba(255, 255, 255, 0.8);}.upload-area h2 {margin-bottom: 15px;}.upload-area p {margin-bottom: 20px;opacity: 0.8;}.btn {background: linear-gradient(to right, #ff416c, #ff4b2b);color: white;border: none;padding: 12px 30px;font-size: 1.1rem;border-radius: 50px;cursor: pointer;transition: all 0.3s ease;display: inline-block;box-shadow: 0 4px 15px rgba(255, 75, 43, 0.4);}.btn:hover {transform: translateY(-3px);box-shadow: 0 6px 20px rgba(255, 75, 43, 0.6);}.btn:active {transform: translateY(1px);}.btn-secondary {background: linear-gradient(to right, #2193b0, #6dd5ed);box-shadow: 0 4px 15px rgba(33, 147, 176, 0.4);}.btn-secondary:hover {box-shadow: 0 6px 20px rgba(33, 147, 176, 0.6);}.btn:disabled {background: #999;cursor: not-allowed;transform: none;box-shadow: none;}input[type="file"] {display: none;}.preview-container {display: none;}.image-info {display: flex;justify-content: space-between;margin-bottom: 20px;background: rgba(0, 0, 0, 0.2);padding: 15px;border-radius: 10px;}.image-info div {text-align: center;flex: 1;}.image-info h3 {font-size: 1.1rem;margin-bottom: 8px;opacity: 0.8;}.image-info p {font-size: 1.3rem;font-weight: bold;}.results-container {display: none;margin-top: 30px;}.results-header {display: flex;justify-content: space-between;align-items: center;margin-bottom: 20px;}.slices-container {display: grid;grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));gap: 20px;}.slice-card {background: rgba(255, 255, 255, 0.1);border-radius: 10px;overflow: hidden;box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);transition: transform 0.3s ease;}.slice-card:hover {transform: translateY(-5px);}.slice-img {width: 100%;display: block;border-bottom: 1px solid rgba(255, 255, 255, 0.1);}.slice-info {padding: 15px;text-align: center;}.slice-name {font-weight: bold;margin-bottom: 8px;font-size: 1.1rem;}.slice-size {font-size: 0.9rem;opacity: 0.8;}.progress-container {margin: 20px 0;height: 8px;background: rgba(255, 255, 255, 0.1);border-radius: 4px;overflow: hidden;}.progress-bar {height: 100%;background: linear-gradient(to right, #00b09b, #96c93d);width: 0%;transition: width 0.5s ease;}.status-text {text-align: center;margin: 10px 0;font-style: italic;opacity: 0.8;}.loading {text-align: center;padding: 30px;display: none;}.spinner {width: 50px;height: 50px;border: 5px solid rgba(255, 255, 255, 0.3);border-radius: 50%;border-top-color: #fff;animation: spin 1s ease-in-out infinite;margin: 0 auto 20px;}@keyframes spin {to { transform: rotate(360deg); }}.action-buttons {display: flex;justify-content: center;gap: 15px;margin-top: 20px;flex-wrap: wrap;}footer {text-align: center;margin-top: 40px;padding: 20px;font-size: 0.9rem;opacity: 0.7;}@media (max-width: 768px) {.container {padding: 10px;}.card {padding: 20px;}.image-info {flex-direction: column;gap: 15px;}.action-buttons {flex-direction: column;align-items: center;}.btn {width: 100%;max-width: 300px;}}</style>
</head>
<body><div class="container"><header><h1>智能图片切割工具</h1><p class="subtitle">上传长图,自动切割为多个不超过5MB的片段,保持原始宽度不变,尽可能减少切割数量</p></header><main><div class="card"><div id="upload-section"><div class="upload-area" id="drop-area"><i>📁</i><h2>上传您的图片</h2><p>点击或拖放图片到此处<br>支持JPG、PNG等格式,图片大小无限制</p><button class="btn" id="select-btn">选择图片</button><input type="file" id="file-input" accept="image/*"></div></div><div class="loading" id="loading"><div class="spinner"></div><p id="status-text">处理中,请稍候...</p><div class="progress-container"><div class="progress-bar" id="progress-bar"></div></div></div><div class="preview-container" id="preview-section"><div class="image-info"><div><h3>原始图片尺寸</h3><p id="original-dimensions">-</p></div><div><h3>原始文件大小</h3><p id="original-size">-</p></div><div><h3>切割片段数量</h3><p id="slice-count">-</p></div></div><div class="action-buttons"><button class="btn" id="process-btn">开始切割图片</button><button class="btn btn-secondary" id="reset-btn">重新选择图片</button></div></div></div><div class="card results-container" id="results-section"><div class="results-header"><h2>切割结果</h2><button class="btn" id="download-all">下载全部图片</button></div><div class="slices-container" id="slices-container"><!-- 切割后的图片将在这里展示 --></div></div></main><footer><p>© 2023 智能图片切割工具 | 使用Canvas技术实现 | 所有图片处理均在浏览器完成</p></footer></div><script>document.addEventListener('DOMContentLoaded', function() {// 获取DOM元素const fileInput = document.getElementById('file-input');const selectBtn = document.getElementById('select-btn');const dropArea = document.getElementById('drop-area');const processBtn = document.getElementById('process-btn');const resetBtn = document.getElementById('reset-btn');const downloadAllBtn = document.getElementById('download-all');const previewSection = document.getElementById('preview-section');const resultsSection = document.getElementById('results-section');const loadingSection = document.getElementById('loading');const statusText = document.getElementById('status-text');const progressBar = document.getElementById('progress-bar');const slicesContainer = document.getElementById('slices-container');// 存储原始图片和切割结果let originalImage = null;let imageSlices = [];// 事件绑定selectBtn.addEventListener('click', () => fileInput.click());fileInput.addEventListener('change', handleFileSelect);dropArea.addEventListener('dragover', handleDragOver);dropArea.addEventListener('drop', handleDrop);processBtn.addEventListener('click', processImage);resetBtn.addEventListener('click', resetApp);downloadAllBtn.addEventListener('click', downloadAllSlices);// 处理文件选择function handleFileSelect(e) {const file = e.target.files[0];if (file && file.type.match('image.*')) {loadImage(file);}}// 处理拖放function handleDragOver(e) {e.preventDefault();e.stopPropagation();dropArea.style.borderColor = 'rgba(255, 255, 255, 0.7)';dropArea.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';}function handleDrop(e) {e.preventDefault();e.stopPropagation();dropArea.style.borderColor = 'rgba(255, 255, 255, 0.4)';dropArea.style.backgroundColor = '';const file = e.dataTransfer.files[0];if (file && file.type.match('image.*')) {loadImage(file);}}// 加载图片function loadImage(file) {const reader = new FileReader();reader.onload = function(e) {const img = new Image();img.onload = function() {originalImage = {element: img,file: file,width: img.width,height: img.height,size: file.size};// 显示图片信息document.getElementById('original-dimensions').textContent = `${img.width} × ${img.height} 像素`;document.getElementById('original-size').textContent = formatFileSize(file.size);document.getElementById('slice-count').textContent = calculateSliceCount(img.height, file.size);// 显示预览区域previewSection.style.display = 'block';resultsSection.style.display = 'none';};img.src = e.target.result;};reader.readAsDataURL(file);}// 计算切割数量function calculateSliceCount(height, fileSize) {// 简单估算:假设文件大小与高度成正比const maxSize = 5 * 1024 * 1024; // 5MBconst minSlices = Math.ceil(fileSize / maxSize);// 高度切割的最小单位(至少保留100像素高度)const minHeight = 100;const maxSliceHeight = Math.max(minHeight, Math.floor(height / minSlices));// 实际切片数const sliceCount = Math.ceil(height / maxSliceHeight);return sliceCount;}// 处理图片切割async function processImage() {if (!originalImage) return;// 显示加载状态loadingSection.style.display = 'block';previewSection.style.display = 'none';resultsSection.style.display = 'none';// 清除之前的切割结果imageSlices = [];slicesContainer.innerHTML = '';try {// 计算切割参数const maxSize = 4.8 * 1024 * 1024; // 4.8MB (预留空间)const originalWidth = originalImage.width;const originalHeight = originalImage.height;// 初始切片高度(基于原始大小比例)let sliceHeight = Math.floor(originalHeight * (maxSize / originalImage.size));// 确保最小高度sliceHeight = Math.max(100, sliceHeight);// 计算切片数量const sliceCount = Math.ceil(originalHeight / sliceHeight);// 更新状态statusText.textContent = `准备切割图片为 ${sliceCount} 个片段...`;progressBar.style.width = '0%';// 创建临时canvasconst canvas = document.createElement('canvas');const ctx = canvas.getContext('2d');canvas.width = originalWidth;// 处理每个切片for (let i = 0; i < sliceCount; i++) {// 更新进度const progress = Math.floor((i / sliceCount) * 100);progressBar.style.width = `${progress}%`;statusText.textContent = `处理片段 ${i+1}/${sliceCount}...`;// 计算当前切片的y坐标和高度const y = i * sliceHeight;const h = Math.min(sliceHeight, originalHeight - y);// 设置canvas高度canvas.height = h;// 清除画布ctx.clearRect(0, 0, canvas.width, canvas.height);// 绘制切片ctx.drawImage(originalImage.element, 0, y, originalWidth, h,0, 0, originalWidth, h);// 获取Blob并调整质量let quality = 0.9;let blob = null;do {// 将canvas转为Blobblob = await new Promise(resolve => canvas.toBlob(resolve, 'image/jpeg', quality));// 如果文件太大,降低质量if (blob.size > maxSize && quality > 0.5) {quality -= 0.1;} else {break;}} while (quality > 0.5);// 创建切片对象const slice = {index: i,width: originalWidth,height: h,blob: blob,url: URL.createObjectURL(blob),size: blob.size};imageSlices.push(slice);}// 显示结果showResults();} catch (error) {console.error('图片处理失败:', error);statusText.textContent = '处理失败,请重试';} finally {// 隐藏加载状态loadingSection.style.display = 'none';}}// 显示切割结果function showResults() {// 更新切片数量显示document.getElementById('slice-count').textContent = imageSlices.length;// 显示结果区域resultsSection.style.display = 'block';previewSection.style.display = 'block';// 清空容器slicesContainer.innerHTML = '';// 添加每个切片imageSlices.forEach((slice, index) => {const sliceElement = document.createElement('div');sliceElement.className = 'slice-card';sliceElement.innerHTML = `<img src="${slice.url}" alt="图片片段 ${index+1}" class="slice-img"><div class="slice-info"><div class="slice-name">片段 #${index+1}</div><div class="slice-size">${formatFileSize(slice.size)} | ${slice.width}×${slice.height}</div></div>`;// 添加点击下载事件sliceElement.addEventListener('click', () => {downloadSlice(slice, index);});slicesContainer.appendChild(sliceElement);});}// 下载单个切片function downloadSlice(slice, index) {const a = document.createElement('a');a.href = slice.url;a.download = `image-slice-${index+1}.jpg`;document.body.appendChild(a);a.click();document.body.removeChild(a);}// 下载所有切片function downloadAllSlices() {imageSlices.forEach((slice, index) => {setTimeout(() => {downloadSlice(slice, index);}, index * 300); // 避免同时下载导致浏览器阻塞});}// 重置应用function resetApp() {// 清除所有状态originalImage = null;imageSlices = [];// 释放URL对象imageSlices.forEach(slice => URL.revokeObjectURL(slice.url));// 重置UIfileInput.value = '';previewSection.style.display = 'none';resultsSection.style.display = 'none';slicesContainer.innerHTML = '';}// 辅助函数:格式化文件大小function formatFileSize(bytes) {if (bytes < 1024) return bytes + ' B';else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';else return (bytes / 1048576).toFixed(1) + ' MB';}});</script>
</body>
</html>