图片切割工具:智能分割长图并控制文件大小

写在前面

这是一个直观易用的图片切割工具,用户上传长图后自动分割成多个不超过5MB的片段,保持原始宽度不变,自己用的,顺带发表一下,不喜勿喷!

主要有两个小工具,一个针对长图智能切割,一个直接上传pdf之后导出切割后的图片;

长图智能切割并导出

设计思路

  1. 创建简洁的UI,包含上传区域和结果展示区

  2. 实现智能分割算法,保持原始宽度,动态计算高度

  3. 确保每个片段不超过5MB

  4. 使用Canvas进行图片处理和压缩

  5. 添加下载功能,支持下载所有切割后的图片

长图切割效果预览

图片切割最终实现代码

<!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>© 2025 智能图片切割工具 | 使用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; // 5MB
                const 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%';
                    
                    // 创建临时canvas
                    const 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转为Blob
                            blob = 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));
                
                // 重置UI
                fileInput.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>

 PDF智能转换并切割图片

自己不想办理wps会员,所以不想使用wps转换成长图再使用以上的代码分割,所以补充了一下,直接上传pdf,可以直接下载,也可以打包下载!

前端库直接使用cdn链接,当然下载下来放在本地也可以!

功能说明

  1. PDF文件上传:支持点击上传或拖放PDF文件

  2. 智能图片分割

    • 将PDF转换为高质量图片

    • 自动分割图片确保每张图片不超过5MB

    • 保持原始宽度不变,只调整高度

    • 尽量使图片数量最小化

  3. 结果展示

    • 显示生成的所有图片预览

    • 展示每张图片的尺寸和大小信息

  4. 下载功能

    • 可下载单张图片

    • 可打包下载所有图片(ZIP格式)

  5. 状态显示

    • 显示文件处理状态

    • 显示生成图片数量和总大小

使用说明

  1. 点击"选择PDF文件"按钮或拖放PDF文件到上传区域

  2. 等待处理完成(处理时间取决于PDF大小和页数)

  3. 查看生成的图片预览

  4. 点击单张图片下方的"下载"按钮下载该图片

  5. 点击"下载全部图片"按钮打包下载所有图片

  6. 点击"重置工具"按钮重新开始

技术实现

  • 使用pdf.js库处理PDF文件

  • 使用Canvas渲染PDF页面

  • 智能分割算法确保图片不超过5MB

  • 使用JSZip库打包下载所有图片

  • 响应式设计,适配各种设备

这个工具完全在浏览器端运行,无需服务器支持,确保您的文件隐私安全。

  PDF/Word智能转换并切割图片效果预览

 PDF/Word智能转换并切割图片全代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PDF/Word转图片智能分割工具</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }

        body {
            background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
            min-height: 100vh;
            padding: 20px;
            color: #333;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        .container {
            width: 100%;
            max-width: 1200px;
            background: rgba(255, 255, 255, 0.95);
            border-radius: 15px;
            box-shadow: 0 15px 50px rgba(0, 0, 0, 0.4);
            overflow: hidden;
        }

        header {
            background: linear-gradient(to right, #2c3e50, #4a6491);
            color: white;
            padding: 25px 40px;
            text-align: center;
            position: relative;
            overflow: hidden;
        }

        header::before {
            content: "";
            position: absolute;
            top: -50%;
            left: -50%;
            width: 200%;
            height: 200%;
            background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 70%);
            transform: rotate(30deg);
        }

        h1 {
            font-size: 2.8rem;
            margin-bottom: 15px;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 15px;
            position: relative;
            text-shadow: 0 2px 4px rgba(0,0,0,0.3);
        }

        .header-subtitle {
            font-size: 1.2rem;
            opacity: 0.9;
            max-width: 800px;
            margin: 0 auto;
            line-height: 1.6;
            position: relative;
        }

        .main-content {
            padding: 30px;
            display: flex;
            flex-direction: column;
            gap: 30px;
        }

        .upload-section {
            background: linear-gradient(135deg, #f8f9ff, #eef2f9);
            border-radius: 12px;
            padding: 40px 30px;
            text-align: center;
            border: 3px dashed #4a6491;
            transition: all 0.4s ease;
            position: relative;
            overflow: hidden;
        }

        .upload-section:hover {
            background: linear-gradient(135deg, #edf2ff, #e0e9ff);
            transform: translateY(-5px);
            box-shadow: 0 10px 25px rgba(74, 100, 145, 0.2);
        }

        .upload-icon {
            font-size: 5rem;
            color: #4a6491;
            margin-bottom: 25px;
            text-shadow: 0 3px 6px rgba(0,0,0,0.1);
            position: relative;
        }

        .upload-text {
            font-size: 1.6rem;
            margin-bottom: 25px;
            color: #2c3e50;
            font-weight: 600;
        }

        .upload-subtext {
            font-size: 1.1rem;
            color: #5a6b8c;
            max-width: 700px;
            margin: 0 auto 30px;
            line-height: 1.7;
        }

        .file-input {
            display: none;
        }

        .upload-btn {
            background: linear-gradient(to right, #4a6491, #3a5680);
            color: white;
            border: none;
            padding: 18px 45px;
            font-size: 1.3rem;
            border-radius: 50px;
            cursor: pointer;
            transition: all 0.3s ease;
            display: inline-flex;
            align-items: center;
            gap: 15px;
            box-shadow: 0 6px 20px rgba(74, 100, 145, 0.4);
            position: relative;
            overflow: hidden;
            z-index: 1;
        }

        .upload-btn::before {
            content: "";
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: linear-gradient(to right, #3a5680, #2c3e50);
            z-index: -1;
            opacity: 0;
            transition: opacity 0.3s ease;
        }

        .upload-btn:hover {
            transform: translateY(-5px);
            box-shadow: 0 8px 25px rgba(74, 100, 145, 0.6);
        }

        .upload-btn:hover::before {
            opacity: 1;
        }

        .stats-container {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
            gap: 20px;
            margin-top: 30px;
        }

        .stat-card {
            background: linear-gradient(135deg, #eef2f7, #f8f9ff);
            border-radius: 12px;
            padding: 25px;
            text-align: center;
            box-shadow: 0 5px 15px rgba(0,0,0,0.05);
            transition: all 0.3s ease;
            border: 1px solid #e0e6f0;
        }

        .stat-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 8px 25px rgba(0,0,0,0.1);
        }

        .stat-title {
            font-size: 1.1rem;
            color: #5a6b8c;
            margin-bottom: 15px;
            font-weight: 600;
        }

        .stat-value {
            font-size: 2.2rem;
            font-weight: 700;
            color: #4a6491;
            margin-bottom: 5px;
        }

        .stat-subtext {
            font-size: 1rem;
            color: #7a8aa3;
        }

        .results-section {
            display: none;
            flex-direction: column;
            gap: 25px;
            margin-top: 20px;
        }

        .section-title {
            font-size: 1.8rem;
            color: #2c3e50;
            padding-bottom: 15px;
            border-bottom: 2px solid #e0e6f0;
            margin-bottom: 25px;
            display: flex;
            align-items: center;
            gap: 15px;
        }

        .images-container {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
            gap: 30px;
        }

        .image-card {
            background: white;
            border-radius: 15px;
            overflow: hidden;
            box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
            transition: all 0.4s ease;
            position: relative;
            border: 1px solid #e0e6f0;
        }

        .image-card:hover {
            transform: translateY(-8px);
            box-shadow: 0 15px 35px rgba(0, 0, 0, 0.15);
        }

        .image-preview {
            width: 100%;
            height: 280px;
            object-fit: contain;
            background: #f8f9ff;
            border-bottom: 1px solid #e0e6f0;
            padding: 15px;
        }

        .image-info {
            padding: 20px;
            background: #f8f9ff;
        }

        .image-title {
            font-weight: 700;
            margin-bottom: 10px;
            color: #2c3e50;
            font-size: 1.3rem;
        }

        .image-details {
            display: flex;
            justify-content: space-between;
            font-size: 1rem;
            color: #5a6b8c;
            margin-bottom: 15px;
        }

        .image-pages {
            background: #eef2f7;
            padding: 8px 15px;
            border-radius: 20px;
            font-size: 0.95rem;
            color: #4a6491;
            font-weight: 600;
        }

        .action-buttons {
            display: flex;
            justify-content: center;
            gap: 25px;
            margin-top: 30px;
            flex-wrap: wrap;
        }

        .action-btn {
            background: linear-gradient(to right, #4a6491, #3a5680);
            color: white;
            border: none;
            padding: 18px 40px;
            font-size: 1.2rem;
            border-radius: 50px;
            cursor: pointer;
            transition: all 0.3s ease;
            display: inline-flex;
            align-items: center;
            gap: 12px;
            min-width: 250px;
            justify-content: center;
            box-shadow: 0 6px 20px rgba(74, 100, 145, 0.4);
            position: relative;
            overflow: hidden;
            z-index: 1;
        }

        .action-btn::before {
            content: "";
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: linear-gradient(to right, #3a5680, #2c3e50);
            z-index: -1;
            opacity: 0;
            transition: opacity 0.3s ease;
        }

        .action-btn:hover {
            transform: translateY(-5px);
            box-shadow: 0 10px 25px rgba(74, 100, 145, 0.6);
        }

        .action-btn:hover::before {
            opacity: 1;
        }

        .download-all {
            background: linear-gradient(to right, #00b09b, #38ef7d);
        }

        .download-all::before {
            background: linear-gradient(to right, #009e8a, #28c76f);
        }

        .reset-btn {
            background: linear-gradient(to right, #ff416c, #ff4b2b);
        }

        .reset-btn::before {
            background: linear-gradient(to right, #e03a60, #e54224);
        }

        .loading {
            display: none;
            text-align: center;
            padding: 50px 30px;
            background: rgba(248, 249, 255, 0.8);
            border-radius: 12px;
            margin: 20px 0;
        }

        .spinner {
            width: 70px;
            height: 70px;
            border: 6px solid rgba(74, 100, 145, 0.2);
            border-top: 6px solid #4a6491;
            border-radius: 50%;
            animation: spin 1.2s linear infinite;
            margin: 0 auto 30px;
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }

        .loading-text {
            font-size: 1.4rem;
            color: #4a6491;
            font-weight: 600;
            margin-bottom: 15px;
        }

        .loading-subtext {
            font-size: 1.1rem;
            color: #7a8aa3;
            max-width: 600px;
            margin: 0 auto;
        }

        .progress-container {
            width: 100%;
            max-width: 600px;
            height: 12px;
            background: #e0e6f0;
            border-radius: 10px;
            overflow: hidden;
            margin: 25px auto;
        }

        .progress-bar {
            height: 100%;
            background: linear-gradient(to right, #4a6491, #38ef7d);
            border-radius: 10px;
            width: 0%;
            transition: width 0.4s ease;
        }

        footer {
            text-align: center;
            padding: 25px;
            background: #f0f4fa;
            color: #5a6b8c;
            font-size: 1rem;
            border-top: 1px solid #e0e6f0;
        }

        .note {
            background: #edf7ff;
            border-left: 4px solid #4a90e2;
            padding: 20px;
            border-radius: 0 10px 10px 0;
            margin-top: 25px;
            font-size: 1.05rem;
            line-height: 1.7;
        }

        .highlight {
            background: linear-gradient(120deg, #f6d365 0%, #fda085 100%);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            font-weight: 700;
        }

        .file-type-tag {
            display: inline-block;
            padding: 5px 12px;
            border-radius: 20px;
            font-size: 0.85rem;
            font-weight: 600;
            margin-left: 10px;
            vertical-align: middle;
        }

        .pdf-tag {
            background: linear-gradient(to right, #ff416c, #ff4b2b);
            color: white;
        }

        .word-tag {
            background: linear-gradient(to right, #1e3c72, #2a5298);
            color: white;
        }

        .download-options {
            display: flex;
            gap: 15px;
            justify-content: center;
            margin-top: 20px;
            flex-wrap: wrap;
        }

        .download-option-btn {
            background: linear-gradient(to right, #4a6491, #3a5680);
            color: white;
            border: none;
            padding: 12px 25px;
            font-size: 1rem;
            border-radius: 50px;
            cursor: pointer;
            transition: all 0.3s ease;
            display: inline-flex;
            align-items: center;
            gap: 10px;
            box-shadow: 0 4px 15px rgba(74, 100, 145, 0.3);
            position: relative;
            overflow: hidden;
            z-index: 1;
        }

        .download-option-btn::before {
            content: "";
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: linear-gradient(to right, #3a5680, #2c3e50);
            z-index: -1;
            opacity: 0;
            transition: opacity 0.3s ease;
        }

        .download-option-btn:hover {
            transform: translateY(-3px);
            box-shadow: 0 6px 20px rgba(74, 100, 145, 0.5);
        }

        .download-option-btn:hover::before {
            opacity: 1;
        }

        .download-all-btn {
            background: linear-gradient(to right, #00b09b, #38ef7d);
        }

        .download-all-btn::before {
            background: linear-gradient(to right, #009e8a, #28c76f);
        }

        .download-individual-btn {
            background: linear-gradient(to right, #ff8c00, #ffb142);
        }

        .download-individual-btn::before {
            background: linear-gradient(to right, #e07b00, #e69d2e);
        }

        @media (max-width: 768px) {
            .images-container {
                grid-template-columns: 1fr;
            }

            h1 {
                font-size: 2.2rem;
            }

            .upload-text {
                font-size: 1.4rem;
            }

            .action-buttons {
                flex-direction: column;
                gap: 15px;
            }

            .action-btn {
                width: 100%;
            }

            .download-options {
                flex-direction: column;
                align-items: center;
            }
        }
    </style>
</head>
<body>
<div class="container">
    <header>
        <h1><i class="fas fa-file-pdf"></i> PDF/Word转图片智能分割工具</h1>
        <p class="header-subtitle">上传PDF或Word文件,自动转换为图片并智能分割成大小不超过5MB的片段。保持原始宽度不变,图片数量最小化</p>
    </header>

    <div class="main-content">
        <div class="upload-section" id="uploadArea">
            <div class="upload-icon">
                <i class="fas fa-cloud-upload-alt"></i>
            </div>
            <p class="upload-text">拖放PDF或Word文件到此处或点击上传</p>
            <p class="upload-subtext">智能算法将自动合并页面,生成接近5MB的图片片段,减少图片数量</p>
            <button class="upload-btn" id="uploadBtn">
                <i class="fas fa-file-upload"></i> 选择文件 (PDF/Word)
            </button>
            <input type="file" id="fileInput" class="file-input" accept=".pdf,.docx">

            <div class="stats-container">
                <div class="stat-card">
                    <div class="stat-title">支持文件类型</div>
                    <div class="stat-value">PDF & Word</div>
                    <div class="stat-subtext">智能识别文件格式</div>
                </div>
                <div class="stat-card">
                    <div class="stat-title">目标图片大小</div>
                    <div class="stat-value">< 5 MB</div>
                    <div class="stat-subtext">每张图片最大不超过5MB</div>
                </div>
                <div class="stat-card">
                    <div class="stat-title">智能合并</div>
                    <div class="stat-value">自动优化</div>
                    <div class="stat-subtext">多页合并为单张图片</div>
                </div>
            </div>
        </div>

        <div class="note">
            <p><i class="fas fa-lightbulb"></i> <strong>智能分割说明:</strong> 本工具会智能合并PDF/Word页面,每张图片包含尽可能多的页面(接近5MB)。例如,100页文档(每页0.5MB)会被合并为10张图片(每张约5MB),而不是生成100张图片。</p>
        </div>

        <div class="loading" id="loading">
            <div class="spinner"></div>
            <p class="loading-text">正在处理文件</p>
            <p class="loading-subtext" id="loadingSubtext">智能合并页面中,请稍候... 处理时间取决于文件大小和页数</p>
            <div class="progress-container">
                <div class="progress-bar" id="progressBar"></div>
            </div>
        </div>

        <div class="results-section" id="resultsSection">
            <h2 class="section-title"><i class="fas fa-images"></i> 智能分割结果</h2>
            <div class="images-container" id="imagesContainer">
                <!-- 生成的图片卡片将在这里显示 -->
            </div>

            <div class="action-buttons">
                <button class="action-btn reset-btn" id="resetBtn">
                    <i class="fas fa-redo"></i> 处理新文件
                </button>
            </div>

            <div class="download-options">
                <button class="download-option-btn download-all-btn" id="downloadAll">
                    <i class="fas fa-download"></i> 逐个下载全部图片
                </button>
                <button class="download-option-btn download-individual-btn" id="downloadIndividual">
                    <i class="fas fa-images"></i> 查看所有图片下载链接
                </button>
            </div>
        </div>
    </div>

    <footer>
        <p>© 2025 PDF/Word转图片智能分割工具 | 基于pdf.js和mammoth构建 | 智能合并算法 | 本地处理无需上传服务器</p>
    </footer>
</div>

<script>
    // 设置pdf.js worker路径
    pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.worker.min.js';

    // DOM元素引用
    const fileInput = document.getElementById('fileInput');
    const uploadBtn = document.getElementById('uploadBtn');
    const uploadArea = document.getElementById('uploadArea');
    const resultsSection = document.getElementById('resultsSection');
    const imagesContainer = document.getElementById('imagesContainer');
    const downloadAllBtn = document.getElementById('downloadAll');
    const downloadIndividualBtn = document.getElementById('downloadIndividual');
    const resetBtn = document.getElementById('resetBtn');
    const loading = document.getElementById('loading');
    const progressBar = document.getElementById('progressBar');
    const loadingSubtext = document.getElementById('loadingSubtext');

    // 存储生成的图片数据
    let generatedImages = [];

    // 上传按钮点击事件
    uploadBtn.addEventListener('click', () => {
        fileInput.click();
    });

    // 拖放功能
    uploadArea.addEventListener('dragover', (e) => {
        e.preventDefault();
        uploadArea.style.borderColor = '#38ef7d';
        uploadArea.style.background = 'linear-gradient(135deg, #e6f7f5, #ddf3ec)';
    });

    uploadArea.addEventListener('dragleave', () => {
        uploadArea.style.borderColor = '#4a6491';
        uploadArea.style.background = 'linear-gradient(135deg, #f8f9ff, #eef2f9)';
    });

    uploadArea.addEventListener('drop', (e) => {
        e.preventDefault();
        uploadArea.style.borderColor = '#4a6491';
        uploadArea.style.background = 'linear-gradient(135deg, #f8f9ff, #eef2f9)';

        if (e.dataTransfer.files.length) {
            const file = e.dataTransfer.files[0];
            if (file.type === 'application/pdf' || file.name.endsWith('.docx')) {
                processFile(file);
            } else {
                alert('请上传PDF或Word(.docx)文件');
            }
        }
    });

    // 文件选择事件
    fileInput.addEventListener('change', (e) => {
        if (e.target.files.length) {
            const file = e.target.files[0];
            if (file.type === 'application/pdf' || file.name.endsWith('.docx')) {
                processFile(file);
            } else {
                alert('请上传PDF或Word(.docx)文件');
                fileInput.value = '';
            }
        }
    });

    // 处理文件 - 智能合并页面
    async function processFile(file) {
        // 重置状态
        generatedImages = [];
        imagesContainer.innerHTML = '';
        resultsSection.style.display = 'none';
        loading.style.display = 'block';
        progressBar.style.width = '0%';

        // 更新加载提示
        if (file.type === 'application/pdf') {
            loadingSubtext.textContent = `正在处理PDF文件: ${file.name}...`;
        } else {
            loadingSubtext.textContent = `正在处理Word文件: ${file.name}...`;
        }

        try {
            // 根据文件类型选择处理方式
            if (file.type === 'application/pdf') {
                await processPDF(file);
            } else if (file.name.endsWith('.docx')) {
                await processWord(file);
            } else {
                throw new Error('不支持的文件类型');
            }

            // 更新进度
            progressBar.style.width = '98%';

            // 显示结果
            setTimeout(() => {
                displayResults();
                loading.style.display = 'none';
                resultsSection.style.display = 'flex';
                progressBar.style.width = '100%';
            }, 500);

        } catch (error) {
            console.error('文件处理失败:', error);
            loading.style.display = 'none';
            alert('处理文件时发生错误: ' + error.message);
            resetBtn.click();
        }
    }

    // 处理PDF文件
    async function processPDF(file) {
        // 读取PDF文件
        const arrayBuffer = await file.arrayBuffer();
        const pdfData = new Uint8Array(arrayBuffer);

        // 加载PDF文档
        const pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise;
        const numPages = pdfDoc.numPages;

        // 更新进度
        progressBar.style.width = '10%';
        loadingSubtext.textContent = `正在处理PDF文件: ${file.name} (共${numPages}页)...`;

        // 智能分组算法
        const targetSizeMB = 4.8; // 目标大小4.8MB(留0.2MB缓冲)
        const maxSizeBytes = targetSizeMB * 1024 * 1024;
        let currentGroup = [];
        let currentGroupSize = 0;
        const groups = [];

        // 第一轮:估算每页大小并分组
        for (let i = 1; i <= numPages; i++) {
            // 更新进度
            progressBar.style.width = `${10 + (i / numPages * 60)}%`;
            loadingSubtext.textContent = `处理PDF页面: ${i}/${numPages} (分组中)...`;

            const page = await pdfDoc.getPage(i);
            const viewport = page.getViewport({ scale: 2.0 });

            // 创建临时canvas
            const canvas = document.createElement('canvas');
            const context = canvas.getContext('2d');
            canvas.width = viewport.width;
            canvas.height = viewport.height;

            // 渲染页面
            await page.render({
                canvasContext: context,
                viewport: viewport
            }).promise;

            // 估算页面大小(使用JPEG质量0.85)
            const dataUrl = canvas.toDataURL('image/jpeg', 0.85);
            const pageSize = Math.round((dataUrl.length * 3) / 4); // 近似字节数

            // 智能分组逻辑
            if (currentGroupSize + pageSize > maxSizeBytes && currentGroup.length > 0) {
                // 当前组已接近5MB,保存并开始新组
                groups.push([...currentGroup]);
                currentGroup = [];
                currentGroupSize = 0;
            }

            // 将页面添加到当前组
            currentGroup.push({
                pageNum: i,
                viewport: viewport,
                canvas: canvas,
                size: pageSize
            });
            currentGroupSize += pageSize;

            // 释放资源
            page.cleanup();
        }

        // 添加最后一组
        if (currentGroup.length > 0) {
            groups.push([...currentGroup]);
        }

        // 第二轮:生成合并后的图片
        progressBar.style.width = '70%';
        loadingSubtext.textContent = `正在生成${groups.length}个图片片段...`;

        for (let groupIndex = 0; groupIndex < groups.length; groupIndex++) {
            const group = groups[groupIndex];

            // 更新进度
            progressBar.style.width = `${70 + (groupIndex / groups.length * 25)}%`;

            // 计算组的总高度和最大宽度
            let totalHeight = 0;
            let maxWidth = 0;

            for (const pageData of group) {
                totalHeight += pageData.viewport.height;
                if (pageData.viewport.width > maxWidth) maxWidth = pageData.viewport.width;
            }

            // 创建大canvas
            const groupCanvas = document.createElement('canvas');
            const groupCtx = groupCanvas.getContext('2d');
            groupCanvas.width = maxWidth;
            groupCanvas.height = totalHeight;

            // 绘制所有页面到canvas
            let currentY = 0;
            for (const pageData of group) {
                groupCtx.drawImage(pageData.canvas, 0, currentY);
                currentY += pageData.viewport.height;
            }

            // 转换为Blob
            const blob = await new Promise(resolve => {
                groupCanvas.toBlob(blob => {
                    resolve(blob);
                }, 'image/jpeg', 0.85);
            });

            // 存储合并后的图片
            generatedImages.push({
                blob: blob,
                name: `${file.name.replace('.pdf', '')}-${groupIndex + 1}.jpg`,
                width: groupCanvas.width,
                height: groupCanvas.height,
                size: blob.size,
                pages: group.map(p => p.pageNum),
                type: 'pdf'
            });
        }
    }

    // 处理Word文件
    async function processWord(file) {
        // 读取Word文件
        const arrayBuffer = await file.arrayBuffer();

        // 更新进度
        progressBar.style.width = '20%';
        loadingSubtext.textContent = `正在解析Word文档: ${file.name}...`;

        // 使用mammoth将Word转换为HTML
        const result = await mammoth.convertToHtml({ arrayBuffer: arrayBuffer });
        const html = result.value;

        // 创建临时容器
        const tempContainer = document.createElement('div');
        tempContainer.style.position = 'absolute';
        tempContainer.style.left = '-9999px';
        tempContainer.style.top = '-9999px';
        tempContainer.style.width = '800px';
        tempContainer.style.padding = '40px';
        tempContainer.style.background = 'white';
        tempContainer.style.boxSizing = 'border-box';
        tempContainer.style.fontFamily = 'Arial, sans-serif';
        tempContainer.style.fontSize = '16px';
        tempContainer.style.lineHeight = '1.6';
        tempContainer.innerHTML = html;
        document.body.appendChild(tempContainer);

        // 获取所有段落和表格
        const elements = tempContainer.querySelectorAll('p, table, h1, h2, h3, h4, h5, h6');

        // 智能分组算法
        const targetSizeMB = 4.8; // 目标大小4.8MB(留0.2MB缓冲)
        const maxSizeBytes = targetSizeMB * 1024 * 1024;
        let currentGroup = [];
        let currentGroupSize = 0;
        const groups = [];
        let pageCounter = 1;

        // 更新进度
        progressBar.style.width = '30%';
        loadingSubtext.textContent = `正在分析Word内容 (${elements.length}个元素)...`;

        // 估算每个元素的大小并分组
        for (let i = 0; i < elements.length; i++) {
            // 更新进度
            progressBar.style.width = `${30 + (i / elements.length * 40)}%`;
            loadingSubtext.textContent = `分析Word内容: ${i+1}/${elements.length} (分组中)...`;

            // 创建临时div来测量元素高度
            const tempElement = elements[i].cloneNode(true);
            tempContainer.appendChild(tempElement);

            // 估算元素大小(基于文本长度)
            const textContent = tempElement.textContent || '';
            const elementSize = textContent.length * 100; // 近似估算大小

            // 智能分组逻辑
            if (currentGroupSize + elementSize > maxSizeBytes && currentGroup.length > 0) {
                groups.push([...currentGroup]);
                currentGroup = [];
                currentGroupSize = 0;
                pageCounter++;
            }

            // 将元素添加到当前组
            currentGroup.push({
                element: tempElement,
                size: elementSize,
                pageNum: pageCounter
            });
            currentGroupSize += elementSize;
        }

        // 添加最后一组
        if (currentGroup.length > 0) {
            groups.push([...currentGroup]);
        }

        // 第三轮:生成合并后的图片
        progressBar.style.width = '70%';
        loadingSubtext.textContent = `正在生成${groups.length}个图片片段...`;

        for (let groupIndex = 0; groupIndex < groups.length; groupIndex++) {
            const group = groups[groupIndex];

            // 更新进度
            progressBar.style.width = `${70 + (groupIndex / groups.length * 25)}%`;

            // 创建临时容器来渲染当前组的内容
            const groupContainer = document.createElement('div');
            groupContainer.style.width = '800px';
            groupContainer.style.padding = '40px';
            groupContainer.style.background = 'white';
            groupContainer.style.boxSizing = 'border-box';
            groupContainer.style.fontFamily = 'Arial, sans-serif';
            groupContainer.style.fontSize = '16px';
            groupContainer.style.lineHeight = '1.6';

            // 添加所有元素到容器
            group.forEach(item => {
                groupContainer.appendChild(item.element.cloneNode(true));
            });

            document.body.appendChild(groupContainer);

            // 获取容器高度
            const height = groupContainer.offsetHeight;

            // 创建canvas
            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
            canvas.width = 800;
            canvas.height = height;

            // 绘制容器到canvas
            ctx.fillStyle = 'white';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.fillStyle = 'black';

            // 绘制文本(简化版,实际应用可能需要更复杂的渲染)
            const lines = groupContainer.innerText.split('\n');
            let y = 40;
            for (const line of lines) {
                ctx.fillText(line, 40, y);
                y += 20;
            }

            // 移除临时容器
            document.body.removeChild(groupContainer);

            // 转换为Blob
            const blob = await new Promise(resolve => {
                canvas.toBlob(blob => {
                    resolve(blob);
                }, 'image/jpeg', 0.85);
            });

            // 存储合并后的图片
            generatedImages.push({
                blob: blob,
                name: `${file.name.replace('.docx', '')}-${groupIndex + 1}.jpg`,
                width: canvas.width,
                height: canvas.height,
                size: blob.size,
                pages: [...new Set(group.map(p => p.pageNum))],
                type: 'word'
            });
        }

        // 移除临时容器
        document.body.removeChild(tempContainer);
    }

    // 显示生成的结果
    function displayResults() {
        imagesContainer.innerHTML = '';

        // 计算总大小
        let totalSize = 0;

        generatedImages.forEach((img, index) => {
            const url = URL.createObjectURL(img.blob);
            const sizeMB = (img.size / (1024 * 1024)).toFixed(2);
            totalSize += img.size;

            const pagesText = img.pages.length > 1 ?
                `第 ${Math.min(...img.pages)}-${Math.max(...img.pages)} 页` :
                `第 ${img.pages[0]} 页`;

            const imageCard = document.createElement('div');
            imageCard.className = 'image-card';
            imageCard.innerHTML = `
          <img src="${url}" alt="文档片段 ${index+1}" class="image-preview">
          <div class="image-info">
            <div class="image-title">片段 #${index+1}
              <span class="file-type-tag ${img.type === 'pdf' ? 'pdf-tag' : 'word-tag'}">
                ${img.type === 'pdf' ? 'PDF' : 'Word'}
              </span>
            </div>
            <div class="image-details">
              <span>尺寸: ${img.width} × ${img.height} px</span>
              <span>大小: ${sizeMB} MB</span>
            </div>
            <div class="image-details">
              <span>包含: ${img.pages.length} 页</span>
              <span class="image-pages">${pagesText}</span>
            </div>
            <button class="action-btn" style="padding: 12px 20px; margin-top: 15px; width: 100%; font-size: 1rem;" data-index="${index}">
              <i class="fas fa-download"></i> 下载此片段
            </button>
          </div>
        `;

            imagesContainer.appendChild(imageCard);
        });

        // 添加下载按钮事件
        document.querySelectorAll('.action-btn[data-index]').forEach(btn => {
            btn.addEventListener('click', (e) => {
                const index = e.target.closest('button').getAttribute('data-index');
                downloadImage(generatedImages[index].blob, generatedImages[index].name);
            });
        });

        // 更新总大小显示
        const totalSizeMB = (totalSize / (1024 * 1024)).toFixed(2);
        document.querySelector('.stat-card:nth-child(2) .stat-value').textContent = generatedImages.length;
        document.querySelector('.stat-card:nth-child(3) .stat-value').textContent = `${totalSizeMB} MB`;
    }

    // 下载单张图片
    function downloadImage(blob, filename) {
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = filename;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
    }

    // 逐个下载所有图片
    downloadAllBtn.addEventListener('click', async () => {
        if (generatedImages.length === 0) {
            alert('没有可下载的图片');
            return;
        }

        // 保存原始按钮状态
        const originalText = downloadAllBtn.innerHTML;
        downloadAllBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 正在下载...';
        downloadAllBtn.disabled = true;

        // 延迟函数
        const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

        // 循环下载每张图片
        for (let i = 0; i < generatedImages.length; i++) {
            const img = generatedImages[i];
            // 更新按钮文本显示进度
            downloadAllBtn.innerHTML = `<i class="fas fa-spinner fa-spin"></i> 正在下载 (${i+1}/${generatedImages.length})...`;
            // 下载当前图片
            downloadImage(img.blob, img.name);
            // 等待1秒,避免浏览器阻止连续下载
            await delay(1000);
        }

        // 恢复按钮状态
        downloadAllBtn.innerHTML = originalText;
        downloadAllBtn.disabled = false;

        // 显示完成提示
        // alert(`所有${generatedImages.length}张图片已开始下载!`);
    });

    // 显示所有图片下载链接
    downloadIndividualBtn.addEventListener('click', () => {
        if (generatedImages.length === 0) {
            alert('没有可下载的图片');
            return;
        }

        // 创建下载列表容器
        const downloadList = document.createElement('div');
        downloadList.style.background = '#f8f9ff';
        downloadList.style.borderRadius = '10px';
        downloadList.style.padding = '20px';
        downloadList.style.marginTop = '20px';
        downloadList.style.maxHeight = '400px';
        downloadList.style.overflowY = 'auto';

        // 创建标题
        const title = document.createElement('h3');
        title.textContent = '所有图片下载链接';
        title.style.textAlign = 'center';
        title.style.marginBottom = '15px';
        title.style.color = '#2c3e50';
        downloadList.appendChild(title);

        // 添加每张图片的下载链接
        generatedImages.forEach((img, index) => {
            const downloadItem = document.createElement('div');
            downloadItem.style.display = 'flex';
            downloadItem.style.justifyContent = 'space-between';
            downloadItem.style.alignItems = 'center';
            downloadItem.style.padding = '10px';
            downloadItem.style.borderBottom = '1px solid #e0e6f0';

            const fileName = document.createElement('span');
            fileName.textContent = img.name;
            fileName.style.fontWeight = '600';
            fileName.style.color = '#4a6491';

            const downloadBtn = document.createElement('button');
            downloadBtn.className = 'download-option-btn';
            downloadBtn.innerHTML = '<i class="fas fa-download"></i> 下载';
            downloadBtn.style.padding = '8px 15px';
            downloadBtn.style.fontSize = '0.9rem';

            downloadBtn.addEventListener('click', () => {
                downloadImage(img.blob, img.name);
            });

            downloadItem.appendChild(fileName);
            downloadItem.appendChild(downloadBtn);
            downloadList.appendChild(downloadItem);
        });

        // 检查是否已存在下载列表,如果存在则替换
        const existingList = document.getElementById('downloadListContainer');
        if (existingList) {
            existingList.replaceWith(downloadList);
        } else {
            downloadList.id = 'downloadListContainer';
            resultsSection.insertBefore(downloadList, document.querySelector('.action-buttons'));
        }
    });

    // 重置按钮
    resetBtn.addEventListener('click', () => {
        fileInput.value = '';
        generatedImages = [];
        imagesContainer.innerHTML = '';
        resultsSection.style.display = 'none';
        loading.style.display = 'none';
        progressBar.style.width = '0%';

        // 移除下载列表(如果存在)
        const downloadList = document.getElementById('downloadListContainer');
        if (downloadList) downloadList.remove();

        // 重置统计信息
        document.querySelector('.stat-card:nth-child(2) .stat-value').textContent = '0';
        document.querySelector('.stat-card:nth-child(3) .stat-value').textContent = '0 MB';
    });
</script>
</body>
</html>

缩略图生成器

<!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 rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;700&display=swap">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Noto Sans SC', sans-serif;
            background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
            color: #e6e6e6;
            min-height: 100vh;
            padding: 30px;
            display: flex;
            flex-direction: column;
            align-items: center;
        }

        .container {
            max-width: 1200px;
            width: 100%;
            display: flex;
            flex-direction: column;
            align-items: center;
        }

        header {
            text-align: center;
            margin-bottom: 40px;
            padding: 20px;
            width: 100%;
        }

        h1 {
            font-size: 2.8rem;
            margin-bottom: 10px;
            background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
        }

        .subtitle {
            font-size: 1.2rem;
            color: #a0aec0;
            max-width: 600px;
            margin: 0 auto;
        }

        .input-container {
            width: 100%;
            max-width: 600px;
            background: rgba(30, 30, 50, 0.7);
            border-radius: 15px;
            padding: 25px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
            margin-bottom: 40px;
            backdrop-filter: blur(10px);
        }

        .input-group {
            display: flex;
            flex-direction: column;
            gap: 15px;
        }

        label {
            font-size: 1.2rem;
            font-weight: 500;
            color: #4facfe;
        }

        input {
            padding: 14px 20px;
            font-size: 1.1rem;
            border-radius: 10px;
            border: 2px solid #2c5282;
            background: rgba(20, 20, 40, 0.7);
            color: #f8f8f8;
            transition: all 0.3s ease;
        }

        input:focus {
            outline: none;
            border-color: #4facfe;
            box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.3);
        }

        .btn-generate {
            background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
            color: white;
            border: none;
            padding: 14px 25px;
            font-size: 1.1rem;
            border-radius: 10px;
            cursor: pointer;
            font-weight: 700;
            transition: all 0.3s ease;
            box-shadow: 0 4px 15px rgba(79, 172, 254, 0.4);
        }

        .btn-generate:hover {
            transform: translateY(-3px);
            box-shadow: 0 6px 20px rgba(79, 172, 254, 0.6);
        }

        .btn-generate:active {
            transform: translateY(1px);
        }

        .thumbnails-container {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
            gap: 30px;
            width: 100%;
            margin-top: 20px;
        }

        .thumbnail-card {
            background: rgba(30, 30, 50, 0.6);
            border-radius: 12px;
            overflow: hidden;
            box-shadow: 0 8px 25px rgba(0, 0, 0, 0.25);
            transition: transform 0.3s ease, box-shadow 0.3s ease;
        }

        .thumbnail-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 12px 30px rgba(0, 0, 0, 0.35);
        }

        .thumbnail-image {
            width: 100%;
            height: 180px;
            display: flex;
            align-items: center;
            justify-content: center;
            position: relative;
        }

        .thumbnail-text {
            font-weight: 700;
            text-align: center;
            padding: 10px;
            color: #ffffff;
            text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.3);
            font-family: 'Noto Sans SC', sans-serif;
            position: relative;
            z-index: 2;
            word-break: break-word;
        }

        .download-btn {
            display: block;
            width: 100%;
            padding: 12px;
            background: rgba(20, 20, 40, 0.9);
            color: #4facfe;
            border: none;
            font-size: 1rem;
            font-weight: 600;
            cursor: pointer;
            transition: background 0.3s ease;
            text-align: center;
            text-decoration: none;
        }

        .download-btn:hover {
            background: rgba(30, 30, 60, 0.9);
            color: #00f2fe;
        }

        footer {
            margin-top: 50px;
            text-align: center;
            color: #718096;
            font-size: 0.9rem;
            padding: 20px;
        }

        @media (max-width: 768px) {
            .thumbnails-container {
                grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
            }

            h1 {
                font-size: 2.2rem;
            }
        }
    </style>
</head>
<body>
<div class="container">
    <header>
        <h1>缩略图生成器</h1>
        <p class="subtitle">输入标题文字,自动生成10种不同风格的缩略图,点击下载按钮保存图片</p>
    </header>

    <div class="input-container">
        <div class="input-group">
            <label for="blog-title">标题:</label>
            <input type="text" id="blog-title" placeholder="输入您的标题..." value="JavaScript">
            <button id="generate-btn" class="btn-generate">生成缩略图</button>
        </div>
    </div>

    <div class="thumbnails-container" id="thumbnails-container">
        <!-- 缩略图将在这里动态生成 -->
    </div>

    <footer>
        <p>© 2025 缩略图生成器 | 为您的设计精美封面</p>
    </footer>
</div>

<script>
    document.addEventListener('DOMContentLoaded', function() {
        // 生成缩略图按钮
        const generateBtn = document.getElementById('generate-btn');
        // input输入框
        const inputDom = document.getElementById('blog-title');
        // 缩略图容器
        const thumbnailsContainer = document.getElementById('thumbnails-container');

        // 10种不同的渐变背景[可动态调整]
        const gradients = [
            'linear-gradient(135deg, #6a11cb 0%, #2575fc 100%)',
            'linear-gradient(135deg, #d4fc79 0%, #96e6a1 100%)',
            'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
            'linear-gradient(135deg, #5ee7df 0%, #b490ca 100%)',
            'linear-gradient(135deg, #c2e59c 0%, #64b3f4 100%)',
            'linear-gradient(135deg, #ff9a9e 0%, #fad0c4 100%)',
            'linear-gradient(135deg, #a1c4fd 0%, #c2e9fb 100%)',
            'linear-gradient(135deg, #f6d365 0%, #fda085 100%)',
            'linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%)',
            'linear-gradient(135deg, #a3bded 0%, #6991c7 100%)'
        ];

        generateBtn.addEventListener('click', generateThumbnails);

        // 初始生成缩略图
        generateThumbnails();

        /* 生成10张缩略图 */
        function generateThumbnails() {
            const text = inputDom.value.trim() || 'MuTu_Web';
            thumbnailsContainer.innerHTML = '';

            gradients.forEach((gradient, index) => {
                // 创建卡片容器
                const card = document.createElement('div');
                card.className = 'thumbnail-card';

                // 创建缩略图区域
                const thumbnail = document.createElement('div');
                thumbnail.className = 'thumbnail-image';
                thumbnail.style.background = gradient;

                // 创建文字元素
                const textElement = document.createElement('div');
                textElement.className = 'thumbnail-text';
                textElement.textContent = text;

                // 动态计算字体大小
                const fontSize = calculateFontSize(text);
                textElement.style.fontSize = `${fontSize}px`;
                textElement.style.lineHeight = `${fontSize}px`;

                // 创建下载按钮
                const downloadBtn = document.createElement('a');
                downloadBtn.className = 'download-btn';
                downloadBtn.textContent = '下载图片';
                downloadBtn.download = `blog-thumbnail-${index+1}.png`;

                // 添加事件监听器以生成和下载图片
                downloadBtn.addEventListener('click', function(e) {
                    e.preventDefault();
                    // 修复:传递事件对象和当前索引
                    generateImage(text, gradient, fontSize, index, e);
                });

                // 组装元素
                thumbnail.appendChild(textElement);
                card.appendChild(thumbnail);
                card.appendChild(downloadBtn);
                thumbnailsContainer.appendChild(card);
            });
        }

        /* 动态计算文字大小 */
        function calculateFontSize(text) {
            const BASE_SIZE = 42; // 基础尺寸
            const MIN_SIZE = 22;  // 最小尺寸
            const MAX_LENGTH = 15; // 最大长度
            const SCALE_FACTOR = 1.2; // 缩减系数

            // 计算逻辑:
            // 1. 长度≤MAX_LENGTH保持基础尺寸
            // 2. 超过部分每字符减少SCALE_FACTOR
            // 3. 最终不低于MIN_SIZE
            if (text.length <= MAX_LENGTH) {
                return BASE_SIZE;
            }
            const size = BASE_SIZE - (text.length - MAX_LENGTH) * SCALE_FACTOR;
            return Math.max(MIN_SIZE, Math.min(BASE_SIZE, size));
        }

        /* 下载缩略图 */
        function generateImage(text, gradient, fontSize, index, event) {
            console.info('text', text)
            // 创建离屏canvas
            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');

            // 设置canvas尺寸(实际图片尺寸)
            canvas.width = 800;
            canvas.height = 450;

            // 绘制背景
            const gradientParts = gradient.match(/linear-gradient\((.+)\)/)[1].split(',');
            const angle = gradientParts[0].trim();
            const colorStops = gradientParts.slice(1).map(stop => stop.trim());

            // 创建canvas渐变
            ctx.fillStyle = createCanvasGradient(ctx, angle, colorStops, canvas.width, canvas.height);
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            // 绘制文字
            ctx.font = `bold ${fontSize * 2}px 'Noto Sans SC', sans-serif`;
            ctx.fillStyle = '#ffffff';
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
            ctx.shadowBlur = 3;
            ctx.shadowOffsetX = 1;
            ctx.shadowOffsetY = 1;

            // 处理多行文本
            const maxWidth = canvas.width * 0.8;
            const words = text.split(' ');
            let line = '';
            let lines = [];
            let y = canvas.height / 2 - (Math.ceil(words.length / 3) - 1) * fontSize;

            for (let i = 0; i < words.length; i++) {
                const testLine = line + words[i] + ' ';
                const metrics = ctx.measureText(testLine);

                if (metrics.width > maxWidth && i > 0) {
                    lines.push(line);
                    line = words[i] + ' ';
                } else {
                    line = testLine;
                }
            }
            lines.push(line);

            // 绘制每一行
            lines.forEach((line, i) => {
                ctx.fillText(line.trim(), canvas.width / 2, y + i * (fontSize * 2.2));
            });

            // 转换为数据URL
            const dataURL = canvas.toDataURL('image/png');

            // 修复:使用临时链接下载,避免递归触发
            const link = document.createElement('a');
            link.href = dataURL;
            link.download = `${text}-${index+1}.png`;
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }

        /* 创建canvas渐变 */
        function createCanvasGradient(ctx, angle, colorStops, width, height) {
            // 解析角度
            const angleDeg = parseInt(angle) || 135;
            const angleRad = angleDeg * Math.PI / 180;

            // 计算渐变方向
            const cos = Math.cos(angleRad);
            const sin = Math.sin(angleRad);

            // 计算渐变起点和终点
            const length = Math.abs(width * cos) + Math.abs(height * sin);
            const startX = width / 2 - cos * length / 2;
            const startY = height / 2 - sin * length / 2;
            const endX = width / 2 + cos * length / 2;
            const endY = height / 2 + sin * length / 2;

            // 创建渐变
            const gradient = ctx.createLinearGradient(startX, startY, endX, endY);

            // 添加色标
            colorStops.forEach(stop => {
                const parts = stop.split(' ');
                const color = parts[0];
                const position = parts[1] ? parseFloat(parts[1]) / 100 : 0;
                gradient.addColorStop(position, color);
            });

            return gradient;
        }
    });
</script>
</body>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值