文件API

文件API概述

文件API(File API)提供了在Web浏览器中处理文件的能力,允许JavaScript异步读取文件内容、获取文件信息,以及处理用户选择的文件。这是现代Web应用实现文件上传、预览和处理的基础。

东巴文(db-w.cn) 认为:文件API让Web应用能够更好地处理用户文件,实现丰富的文件操作功能。

文件API特点

核心特点

特点 说明
异步读取 使用FileReader异步读取文件
安全机制 只能访问用户明确选择的文件
多种格式 支持文本、DataURL、ArrayBuffer等格式
文件信息 提供文件名、大小、类型等信息
拖放支持 与拖放API结合使用

核心接口

// 文件API核心接口
const FileInterfaces = {
    File: '表示一个文件对象',
    FileList: '文件对象的集合',
    FileReader: '异步读取文件',
    Blob: '表示二进制大对象',
    URL: '创建文件临时URL'
};

File对象

获取文件

<!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>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 50px auto;
            padding: 20px;
        }
        
        .file-container {
            background: #f9f9f9;
            padding: 30px;
            border-radius: 10px;
            margin: 20px 0;
        }
        
        .file-input-wrapper {
            position: relative;
            margin: 20px 0;
        }
        
        .file-input-wrapper input[type="file"] {
            display: none;
        }
        
        .file-input-label {
            display: inline-block;
            padding: 15px 30px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border-radius: 10px;
            cursor: pointer;
            transition: transform 0.3s;
        }
        
        .file-input-label:hover {
            transform: translateY(-3px);
        }
        
        .file-info {
            background: white;
            padding: 20px;
            border-radius: 10px;
            margin-top: 20px;
        }
        
        .file-item {
            display: flex;
            align-items: center;
            padding: 15px;
            background: #f5f5f5;
            border-radius: 5px;
            margin-bottom: 10px;
        }
        
        .file-icon {
            font-size: 32px;
            margin-right: 15px;
        }
        
        .file-details {
            flex: 1;
        }
        
        .file-name {
            font-weight: bold;
            margin-bottom: 5px;
        }
        
        .file-meta {
            font-size: 14px;
            color: #666;
        }
        
        .drop-zone {
            border: 3px dashed #ddd;
            border-radius: 10px;
            padding: 40px;
            text-align: center;
            margin: 20px 0;
            transition: border-color 0.3s, background 0.3s;
        }
        
        .drop-zone.dragover {
            border-color: #667eea;
            background: #f0f0ff;
        }
        
        .drop-zone-icon {
            font-size: 48px;
            margin-bottom: 10px;
        }
    </style>
</head>
<body>
    <h1>获取文件示例</h1>
    
    <div class="file-container">
        <h2>选择文件</h2>
        
        <div class="file-input-wrapper">
            <input type="file" id="fileInput" multiple accept="image/*,.pdf,.txt">
            <label for="fileInput" class="file-input-label">📁 选择文件</label>
        </div>
        
        <div class="drop-zone" id="dropZone">
            <div class="drop-zone-icon">📂</div>
            <p>拖放文件到这里</p>
            <p style="color: #999; font-size: 14px;">支持多文件</p>
        </div>
        
        <div class="file-info" id="fileInfo" style="display: none;">
            <h3>文件信息</h3>
            <div id="fileList"></div>
        </div>
    </div>
    
    <script>
        const fileInput = document.getElementById('fileInput');
        const dropZone = document.getElementById('dropZone');
        const fileInfo = document.getElementById('fileInfo');
        const fileList = document.getElementById('fileList');
        
        // 监听文件选择
        fileInput.addEventListener('change', function(e) {
            handleFiles(e.target.files);
        });
        
        // 拖放处理
        dropZone.addEventListener('dragover', function(e) {
            e.preventDefault();
            dropZone.classList.add('dragover');
        });
        
        dropZone.addEventListener('dragleave', function(e) {
            e.preventDefault();
            dropZone.classList.remove('dragover');
        });
        
        dropZone.addEventListener('drop', function(e) {
            e.preventDefault();
            dropZone.classList.remove('dragover');
            handleFiles(e.dataTransfer.files);
        });
        
        // 处理文件
        function handleFiles(files) {
            if (files.length === 0) return;
            
            fileInfo.style.display = 'block';
            fileList.innerHTML = '';
            
            Array.from(files).forEach(file => {
                const fileItem = createFileItem(file);
                fileList.appendChild(fileItem);
            });
        }
        
        // 创建文件项
        function createFileItem(file) {
            const item = document.createElement('div');
            item.className = 'file-item';
            
            const icon = getFileIcon(file.type);
            const size = formatFileSize(file.size);
            
            item.innerHTML = `
                <div class="file-icon">${icon}</div>
                <div class="file-details">
                    <div class="file-name">${file.name}</div>
                    <div class="file-meta">
                        类型: ${file.type || '未知'} | 
                        大小: ${size} | 
                        修改时间: ${new Date(file.lastModified).toLocaleDateString()}
                    </div>
                </div>
            `;
            
            return item;
        }
        
        // 获取文件图标
        function getFileIcon(type) {
            if (type.startsWith('image/')) return '🖼️';
            if (type.startsWith('video/')) return '🎬';
            if (type.startsWith('audio/')) return '🎵';
            if (type === 'application/pdf') return '📄';
            if (type.includes('word')) return '📝';
            if (type.includes('excel') || type.includes('spreadsheet')) return '📊';
            if (type === 'text/plain') return '📃';
            return '📁';
        }
        
        // 格式化文件大小
        function formatFileSize(bytes) {
            if (bytes === 0) return '0 Bytes';
            
            const k = 1024;
            const sizes = ['Bytes', 'KB', 'MB', 'GB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
        }
    </script>
</body>
</html>

FileReader读取文件

读取文本文件

<!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>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 50px auto;
            padding: 20px;
        }
        
        .read-container {
            background: #f9f9f9;
            padding: 30px;
            border-radius: 10px;
            margin: 20px 0;
        }
        
        .file-input-wrapper {
            margin: 20px 0;
        }
        
        input[type="file"] {
            padding: 10px;
            background: white;
            border-radius: 5px;
        }
        
        .file-content {
            background: #1e1e1e;
            color: #d4d4d4;
            padding: 20px;
            border-radius: 10px;
            margin-top: 20px;
            font-family: 'Courier New', monospace;
            white-space: pre-wrap;
            word-break: break-all;
            max-height: 400px;
            overflow-y: auto;
        }
        
        .progress-bar {
            width: 100%;
            height: 20px;
            background: #e0e0e0;
            border-radius: 10px;
            margin: 20px 0;
            overflow: hidden;
        }
        
        .progress-fill {
            height: 100%;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            width: 0%;
            transition: width 0.3s;
        }
        
        .encoding-selector {
            margin: 10px 0;
        }
        
        select {
            padding: 8px;
            border-radius: 5px;
            border: 1px solid #ddd;
        }
        
        .stats {
            display: flex;
            gap: 20px;
            margin-top: 15px;
            font-size: 14px;
            color: #666;
        }
        
        .stat-item {
            background: white;
            padding: 10px 15px;
            border-radius: 5px;
        }
    </style>
</head>
<body>
    <h1>读取文本文件示例</h1>
    
    <div class="read-container">
        <h2>选择文本文件</h2>
        
        <div class="file-input-wrapper">
            <input type="file" id="fileInput" accept=".txt,.md,.json,.js,.css,.html,.xml,.csv">
        </div>
        
        <div class="encoding-selector">
            <label>编码格式:</label>
            <select id="encoding">
                <option value="UTF-8">UTF-8</option>
                <option value="GBK">GBK</option>
                <option value="GB2312">GB2312</option>
                <option value="ISO-8859-1">ISO-8859-1</option>
            </select>
        </div>
        
        <div class="progress-bar">
            <div class="progress-fill" id="progressFill"></div>
        </div>
        
        <div class="file-content" id="fileContent">
            文件内容将显示在这里...
        </div>
        
        <div class="stats" id="stats" style="display: none;">
            <div class="stat-item">字符数: <span id="charCount">0</span></div>
            <div class="stat-item">行数: <span id="lineCount">0</span></div>
            <div class="stat-item">文件大小: <span id="fileSize">0</span></div>
        </div>
    </div>
    
    <script>
        const fileInput = document.getElementById('fileInput');
        const encoding = document.getElementById('encoding');
        const progressFill = document.getElementById('progressFill');
        const fileContent = document.getElementById('fileContent');
        const stats = document.getElementById('stats');
        
        fileInput.addEventListener('change', function(e) {
            const file = e.target.files[0];
            if (!file) return;
            
            readTextFile(file);
        });
        
        function readTextFile(file) {
            const reader = new FileReader();
            
            // 读取开始
            reader.onloadstart = function() {
                progressFill.style.width = '0%';
                fileContent.textContent = '读取中...';
            };
            
            // 读取进度
            reader.onprogress = function(e) {
                if (e.lengthComputable) {
                    const percent = (e.loaded / e.total) * 100;
                    progressFill.style.width = percent + '%';
                }
            };
            
            // 读取完成
            reader.onload = function(e) {
                const content = e.target.result;
                fileContent.textContent = content;
                progressFill.style.width = '100%';
                
                // 显示统计信息
                showStats(content, file.size);
            };
            
            // 读取错误
            reader.onerror = function() {
                fileContent.textContent = '读取文件时发生错误: ' + reader.error;
            };
            
            // 开始读取
            reader.readAsText(file, encoding.value);
        }
        
        function showStats(content, size) {
            stats.style.display = 'flex';
            
            document.getElementById('charCount').textContent = content.length;
            document.getElementById('lineCount').textContent = content.split('\n').length;
            document.getElementById('fileSize').textContent = formatFileSize(size);
        }
        
        function formatFileSize(bytes) {
            if (bytes === 0) return '0 Bytes';
            const k = 1024;
            const sizes = ['Bytes', 'KB', 'MB', 'GB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
        }
    </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>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 50px auto;
            padding: 20px;
        }
        
        .image-container {
            background: #f9f9f9;
            padding: 30px;
            border-radius: 10px;
            margin: 20px 0;
        }
        
        .file-input-wrapper {
            margin: 20px 0;
        }
        
        input[type="file"] {
            padding: 10px;
            background: white;
            border-radius: 5px;
        }
        
        .image-preview {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 20px;
            margin-top: 20px;
        }
        
        .preview-item {
            background: white;
            border-radius: 10px;
            overflow: hidden;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
        }
        
        .preview-image {
            width: 100%;
            height: 200px;
            object-fit: cover;
        }
        
        .preview-info {
            padding: 15px;
        }
        
        .preview-name {
            font-weight: bold;
            margin-bottom: 5px;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        
        .preview-meta {
            font-size: 14px;
            color: #666;
        }
        
        .preview-actions {
            margin-top: 10px;
        }
        
        .preview-actions button {
            padding: 8px 15px;
            margin-right: 5px;
            background: #007bff;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 14px;
        }
        
        .preview-actions button:hover {
            background: #0056b3;
        }
    </style>
</head>
<body>
    <h1>读取图片文件示例</h1>
    
    <div class="image-container">
        <h2>选择图片</h2>
        
        <div class="file-input-wrapper">
            <input type="file" id="fileInput" multiple accept="image/*">
        </div>
        
        <div class="image-preview" id="imagePreview"></div>
    </div>
    
    <script>
        const fileInput = document.getElementById('fileInput');
        const imagePreview = document.getElementById('imagePreview');
        
        fileInput.addEventListener('change', function(e) {
            const files = Array.from(e.target.files);
            imagePreview.innerHTML = '';
            
            files.forEach(file => {
                if (file.type.startsWith('image/')) {
                    readImageFile(file);
                }
            });
        });
        
        function readImageFile(file) {
            const reader = new FileReader();
            
            reader.onload = function(e) {
                const dataURL = e.target.result;
                
                // 创建图片对象获取尺寸
                const img = new Image();
                img.onload = function() {
                    const previewItem = document.createElement('div');
                    previewItem.className = 'preview-item';
                    
                    previewItem.innerHTML = `
                        <img src="${dataURL}" class="preview-image" alt="${file.name}">
                        <div class="preview-info">
                            <div class="preview-name">${file.name}</div>
                            <div class="preview-meta">
                                尺寸: ${img.width} × ${img.height}<br>
                                大小: ${formatFileSize(file.size)}<br>
                                类型: ${file.type}
                            </div>
                            <div class="preview-actions">
                                <button onclick="downloadImage('${dataURL}', '${file.name}')">下载</button>
                                <button onclick="copyImage('${dataURL}')">复制</button>
                            </div>
                        </div>
                    `;
                    
                    imagePreview.appendChild(previewItem);
                };
                
                img.src = dataURL;
            };
            
            reader.readAsDataURL(file);
        }
        
        function formatFileSize(bytes) {
            if (bytes === 0) return '0 Bytes';
            const k = 1024;
            const sizes = ['Bytes', 'KB', 'MB', 'GB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
        }
        
        function downloadImage(dataURL, filename) {
            const link = document.createElement('a');
            link.href = dataURL;
            link.download = filename;
            link.click();
        }
        
        async function copyImage(dataURL) {
            try {
                const response = await fetch(dataURL);
                const blob = await response.blob();
                
                await navigator.clipboard.write([
                    new ClipboardItem({
                        [blob.type]: blob
                    })
                ]);
                
                alert('图片已复制到剪贴板');
            } catch (err) {
                alert('复制失败: ' + err.message);
            }
        }
    </script>
</body>
</html>

Blob对象

创建和操作Blob

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Blob对象示例</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 50px auto;
            padding: 20px;
        }
        
        .blob-container {
            background: #f9f9f9;
            padding: 30px;
            border-radius: 10px;
            margin: 20px 0;
        }
        
        .demo-section {
            background: white;
            padding: 20px;
            border-radius: 10px;
            margin: 15px 0;
        }
        
        .demo-section h3 {
            margin-bottom: 15px;
            color: #333;
        }
        
        textarea {
            width: 100%;
            min-height: 100px;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
            font-size: 14px;
            resize: vertical;
        }
        
        button {
            padding: 10px 20px;
            background: #007bff;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            margin: 5px;
        }
        
        button:hover {
            background: #0056b3;
        }
        
        .result {
            margin-top: 15px;
            padding: 15px;
            background: #f5f5f5;
            border-radius: 5px;
            font-family: 'Courier New', monospace;
            word-break: break-all;
        }
        
        .blob-info {
            background: #e7f3ff;
            padding: 15px;
            border-radius: 5px;
            margin-top: 10px;
        }
        
        .blob-info span {
            display: block;
            margin: 5px 0;
        }
    </style>
</head>
<body>
    <h1>Blob对象示例</h1>
    
    <div class="blob-container">
        <h2>Blob操作</h2>
        
        <div class="demo-section">
            <h3>创建文本Blob</h3>
            <textarea id="textInput">这是一段示例文本,将被转换为Blob对象。</textarea>
            <button onclick="createTextBlob()">创建Blob</button>
            <button onclick="downloadTextBlob()">下载</button>
            <div class="result" id="textResult"></div>
        </div>
        
        <div class="demo-section">
            <h3>创建JSON Blob</h3>
            <button onclick="createJsonBlob()">创建JSON Blob</button>
            <button onclick="downloadJsonBlob()">下载JSON</button>
            <div class="result" id="jsonResult"></div>
        </div>
        
        <div class="demo-section">
            <h3>创建图片Blob</h3>
            <button onclick="createImageBlob()">创建图片Blob</button>
            <div class="result" id="imageResult"></div>
            <img id="blobImage" style="max-width: 100%; margin-top: 10px; display: none;">
        </div>
        
        <div class="demo-section">
            <h3>合并Blob</h3>
            <button onclick="mergeBlobs()">合并多个Blob</button>
            <div class="result" id="mergeResult"></div>
        </div>
    </div>
    
    <script>
        let textBlob = null;
        let jsonBlob = null;
        let imageBlob = null;
        
        // 创建文本Blob
        function createTextBlob() {
            const text = document.getElementById('textInput').value;
            textBlob = new Blob([text], { type: 'text/plain;charset=utf-8' });
            
            showBlobInfo('textResult', textBlob);
        }
        
        // 下载文本Blob
        function downloadTextBlob() {
            if (!textBlob) {
                alert('请先创建Blob');
                return;
            }
            
            const url = URL.createObjectURL(textBlob);
            const link = document.createElement('a');
            link.href = url;
            link.download = 'text.txt';
            link.click();
            
            URL.revokeObjectURL(url);
        }
        
        // 创建JSON Blob
        function createJsonBlob() {
            const data = {
                name: '东巴文',
                url: 'https://db-w.cn',
                items: [
                    { id: 1, title: 'HTML教程' },
                    { id: 2, title: 'CSS教程' },
                    { id: 3, title: 'JavaScript教程' }
                ],
                time: new Date().toISOString()
            };
            
            const json = JSON.stringify(data, null, 2);
            jsonBlob = new Blob([json], { type: 'application/json' });
            
            showBlobInfo('jsonResult', jsonBlob, json);
        }
        
        // 下载JSON Blob
        function downloadJsonBlob() {
            if (!jsonBlob) {
                alert('请先创建Blob');
                return;
            }
            
            const url = URL.createObjectURL(jsonBlob);
            const link = document.createElement('a');
            link.href = url;
            link.download = 'data.json';
            link.click();
            
            URL.revokeObjectURL(url);
        }
        
        // 创建图片Blob
        function createImageBlob() {
            // 创建Canvas
            const canvas = document.createElement('canvas');
            canvas.width = 400;
            canvas.height = 300;
            const ctx = canvas.getContext('2d');
            
            // 绘制渐变背景
            const gradient = ctx.createLinearGradient(0, 0, 400, 300);
            gradient.addColorStop(0, '#667eea');
            gradient.addColorStop(1, '#764ba2');
            ctx.fillStyle = gradient;
            ctx.fillRect(0, 0, 400, 300);
            
            // 绘制文字
            ctx.fillStyle = 'white';
            ctx.font = 'bold 32px Arial';
            ctx.textAlign = 'center';
            ctx.fillText('东巴文 db-w.cn', 200, 150);
            
            // 转换为Blob
            canvas.toBlob(function(blob) {
                imageBlob = blob;
                
                const url = URL.createObjectURL(blob);
                const img = document.getElementById('blobImage');
                img.src = url;
                img.style.display = 'block';
                
                showBlobInfo('imageResult', blob);
            }, 'image/png');
        }
        
        // 合并Blob
        function mergeBlobs() {
            const blob1 = new Blob(['第一部分内容\n'], { type: 'text/plain' });
            const blob2 = new Blob(['第二部分内容\n'], { type: 'text/plain' });
            const blob3 = new Blob(['第三部分内容'], { type: 'text/plain' });
            
            const mergedBlob = new Blob([blob1, blob2, blob3], { type: 'text/plain' });
            
            showBlobInfo('mergeResult', mergedBlob, '合并了3个Blob');
        }
        
        // 显示Blob信息
        function showBlobInfo(elementId, blob, extra = '') {
            const result = document.getElementById(elementId);
            
            const url = URL.createObjectURL(blob);
            
            result.innerHTML = `
                <div class="blob-info">
                    <span><strong>Blob信息:</strong></span>
                    <span>大小: ${formatFileSize(blob.size)}</span>
                    <span>类型: ${blob.type}</span>
                    <span>URL: ${url}</span>
                    ${extra ? `<span>${extra}</span>` : ''}
                </div>
            `;
        }
        
        function formatFileSize(bytes) {
            if (bytes === 0) return '0 Bytes';
            const k = 1024;
            const sizes = ['Bytes', 'KB', 'MB', 'GB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
        }
    </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>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }
        
        .container {
            max-width: 800px;
            margin: 0 auto;
        }
        
        .header {
            text-align: center;
            color: white;
            padding: 30px 0;
        }
        
        .header h1 {
            font-size: 36px;
            margin-bottom: 10px;
        }
        
        .upload-card {
            background: white;
            border-radius: 15px;
            padding: 30px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
        }
        
        .drop-zone {
            border: 3px dashed #ddd;
            border-radius: 15px;
            padding: 60px 20px;
            text-align: center;
            transition: all 0.3s;
            cursor: pointer;
        }
        
        .drop-zone:hover,
        .drop-zone.dragover {
            border-color: #667eea;
            background: #f0f0ff;
        }
        
        .drop-zone-icon {
            font-size: 64px;
            margin-bottom: 20px;
        }
        
        .drop-zone-title {
            font-size: 24px;
            margin-bottom: 10px;
            color: #333;
        }
        
        .drop-zone-subtitle {
            color: #999;
            margin-bottom: 20px;
        }
        
        .file-input {
            display: none;
        }
        
        .browse-btn {
            padding: 12px 30px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            border-radius: 25px;
            cursor: pointer;
            font-size: 16px;
        }
        
        .file-list {
            margin-top: 30px;
        }
        
        .file-item {
            display: flex;
            align-items: center;
            padding: 15px;
            background: #f9f9f9;
            border-radius: 10px;
            margin-bottom: 10px;
        }
        
        .file-icon {
            font-size: 32px;
            margin-right: 15px;
        }
        
        .file-info {
            flex: 1;
        }
        
        .file-name {
            font-weight: bold;
            margin-bottom: 5px;
        }
        
        .file-meta {
            font-size: 14px;
            color: #666;
        }
        
        .progress-bar {
            width: 100%;
            height: 6px;
            background: #e0e0e0;
            border-radius: 3px;
            margin-top: 10px;
            overflow: hidden;
        }
        
        .progress-fill {
            height: 100%;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            width: 0%;
            transition: width 0.3s;
        }
        
        .file-status {
            padding: 5px 15px;
            border-radius: 15px;
            font-size: 14px;
        }
        
        .file-status.uploading {
            background: #fff3cd;
            color: #856404;
        }
        
        .file-status.success {
            background: #d4edda;
            color: #155724;
        }
        
        .file-status.error {
            background: #f8d7da;
            color: #721c24;
        }
        
        .upload-actions {
            display: flex;
            gap: 10px;
            margin-top: 20px;
        }
        
        .upload-btn {
            flex: 1;
            padding: 15px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            border-radius: 10px;
            cursor: pointer;
            font-size: 16px;
        }
        
        .upload-btn:hover {
            opacity: 0.9;
        }
        
        .upload-btn:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }
        
        .clear-btn {
            padding: 15px 30px;
            background: #f5f5f5;
            border: none;
            border-radius: 10px;
            cursor: pointer;
            font-size: 16px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>📁 文件上传</h1>
            <p>拖放或选择文件上传</p>
        </div>
        
        <div class="upload-card">
            <div class="drop-zone" id="dropZone">
                <div class="drop-zone-icon">📂</div>
                <div class="drop-zone-title">拖放文件到这里</div>
                <div class="drop-zone-subtitle"></div>
                <button class="browse-btn" onclick="document.getElementById('fileInput').click()">
                    浏览文件
                </button>
                <input type="file" id="fileInput" class="file-input" multiple>
            </div>
            
            <div class="file-list" id="fileList"></div>
            
            <div class="upload-actions" id="uploadActions" style="display: none;">
                <button class="upload-btn" onclick="uploadFiles()">开始上传</button>
                <button class="clear-btn" onclick="clearFiles()">清空列表</button>
            </div>
        </div>
    </div>
    
    <script>
        const dropZone = document.getElementById('dropZone');
        const fileInput = document.getElementById('fileInput');
        const fileList = document.getElementById('fileList');
        const uploadActions = document.getElementById('uploadActions');
        
        let selectedFiles = [];
        
        // 拖放处理
        dropZone.addEventListener('dragover', function(e) {
            e.preventDefault();
            dropZone.classList.add('dragover');
        });
        
        dropZone.addEventListener('dragleave', function(e) {
            e.preventDefault();
            dropZone.classList.remove('dragover');
        });
        
        dropZone.addEventListener('drop', function(e) {
            e.preventDefault();
            dropZone.classList.remove('dragover');
            handleFiles(e.dataTransfer.files);
        });
        
        // 文件选择
        fileInput.addEventListener('change', function(e) {
            handleFiles(e.target.files);
        });
        
        // 处理文件
        function handleFiles(files) {
            selectedFiles = Array.from(files);
            renderFileList();
        }
        
        // 渲染文件列表
        function renderFileList() {
            if (selectedFiles.length === 0) {
                fileList.innerHTML = '';
                uploadActions.style.display = 'none';
                return;
            }
            
            uploadActions.style.display = 'flex';
            
            fileList.innerHTML = selectedFiles.map((file, index) => `
                <div class="file-item">
                    <div class="file-icon">${getFileIcon(file.type)}</div>
                    <div class="file-info">
                        <div class="file-name">${file.name}</div>
                        <div class="file-meta">
                            ${formatFileSize(file.size)} | ${file.type || '未知类型'}
                        </div>
                        <div class="progress-bar">
                            <div class="progress-fill" id="progress-${index}"></div>
                        </div>
                    </div>
                    <div class="file-status" id="status-${index}">等待上传</div>
                </div>
            `).join('');
        }
        
        // 上传文件
        async function uploadFiles() {
            for (let i = 0; i < selectedFiles.length; i++) {
                await uploadFile(selectedFiles[i], i);
            }
        }
        
        // 上传单个文件
        async function uploadFile(file, index) {
            const progressFill = document.getElementById(`progress-${index}`);
            const status = document.getElementById(`status-${index}`);
            
            status.className = 'file-status uploading';
            status.textContent = '上传中...';
            
            // 模拟上传
            return new Promise(resolve => {
                let progress = 0;
                const interval = setInterval(() => {
                    progress += Math.random() * 30;
                    
                    if (progress >= 100) {
                        progress = 100;
                        clearInterval(interval);
                        
                        progressFill.style.width = '100%';
                        status.className = 'file-status success';
                        status.textContent = '上传成功';
                        
                        resolve();
                    } else {
                        progressFill.style.width = progress + '%';
                    }
                }, 200);
            });
        }
        
        // 清空文件
        function clearFiles() {
            selectedFiles = [];
            renderFileList();
            fileInput.value = '';
        }
        
        // 获取文件图标
        function getFileIcon(type) {
            if (type.startsWith('image/')) return '🖼️';
            if (type.startsWith('video/')) return '🎬';
            if (type.startsWith('audio/')) return '🎵';
            if (type === 'application/pdf') return '📄';
            if (type.includes('word')) return '📝';
            if (type.includes('excel') || type.includes('spreadsheet')) return '📊';
            return '📁';
        }
        
        // 格式化文件大小
        function formatFileSize(bytes) {
            if (bytes === 0) return '0 Bytes';
            const k = 1024;
            const sizes = ['Bytes', 'KB', 'MB', 'GB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
        }
    </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>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: #f5f5f5;
            min-height: 100vh;
        }
        
        .container {
            display: flex;
            min-height: 100vh;
        }
        
        .sidebar {
            width: 250px;
            background: #2c3e50;
            color: white;
            padding: 20px;
        }
        
        .sidebar h2 {
            margin-bottom: 20px;
            padding-bottom: 10px;
            border-bottom: 2px solid #34495e;
        }
        
        .sidebar-item {
            padding: 12px;
            margin: 5px 0;
            border-radius: 5px;
            cursor: pointer;
            transition: background 0.3s;
        }
        
        .sidebar-item:hover {
            background: #34495e;
        }
        
        .sidebar-item.active {
            background: #3498db;
        }
        
        .main-content {
            flex: 1;
            padding: 30px;
        }
        
        .header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 30px;
        }
        
        .header h1 {
            color: #2c3e50;
        }
        
        .toolbar {
            display: flex;
            gap: 10px;
        }
        
        button {
            padding: 10px 20px;
            background: #3498db;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            transition: background 0.3s;
        }
        
        button:hover {
            background: #2980b9;
        }
        
        button.success {
            background: #27ae60;
        }
        
        button.success:hover {
            background: #229954;
        }
        
        button.danger {
            background: #e74c3c;
        }
        
        button.danger:hover {
            background: #c0392b;
        }
        
        .file-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
            gap: 20px;
        }
        
        .file-card {
            background: white;
            border-radius: 10px;
            padding: 20px;
            text-align: center;
            cursor: pointer;
            transition: transform 0.3s, box-shadow 0.3s;
        }
        
        .file-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
        }
        
        .file-card.selected {
            border: 2px solid #3498db;
        }
        
        .file-icon {
            font-size: 48px;
            margin-bottom: 15px;
        }
        
        .file-name {
            font-weight: bold;
            margin-bottom: 5px;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        
        .file-meta {
            font-size: 12px;
            color: #999;
        }
        
        .drop-zone {
            border: 3px dashed #ddd;
            border-radius: 15px;
            padding: 60px 20px;
            text-align: center;
            margin-bottom: 30px;
            transition: all 0.3s;
        }
        
        .drop-zone.dragover {
            border-color: #3498db;
            background: #ecf0f1;
        }
        
        .drop-zone-icon {
            font-size: 48px;
            margin-bottom: 10px;
        }
        
        .modal {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.5);
            z-index: 1000;
        }
        
        .modal.show {
            display: flex;
            align-items: center;
            justify-content: center;
        }
        
        .modal-content {
            background: white;
            border-radius: 15px;
            padding: 30px;
            max-width: 600px;
            width: 90%;
            max-height: 80vh;
            overflow-y: auto;
        }
        
        .modal-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 20px;
        }
        
        .modal-close {
            font-size: 24px;
            cursor: pointer;
            color: #999;
        }
        
        .preview-image {
            max-width: 100%;
            border-radius: 10px;
            margin-bottom: 20px;
        }
        
        .preview-text {
            background: #f5f5f5;
            padding: 20px;
            border-radius: 10px;
            font-family: 'Courier New', monospace;
            white-space: pre-wrap;
            word-break: break-all;
            max-height: 400px;
            overflow-y: auto;
        }
        
        .file-info-table {
            width: 100%;
            border-collapse: collapse;
        }
        
        .file-info-table td {
            padding: 10px;
            border-bottom: 1px solid #eee;
        }
        
        .file-info-table td:first-child {
            font-weight: bold;
            width: 120px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="sidebar">
            <h2>📁 文件管理器</h2>
            
            <div class="sidebar-item active" onclick="filterFiles('all')">
                📂 全部文件
            </div>
            <div class="sidebar-item" onclick="filterFiles('image')">
                🖼️ 图片
            </div>
            <div class="sidebar-item" onclick="filterFiles('document')">
                📄 文档
            </div>
            <div class="sidebar-item" onclick="filterFiles('video')">
                🎬 视频
            </div>
            <div class="sidebar-item" onclick="filterFiles('audio')">
                🎵 音频
            </div>
        </div>
        
        <div class="main-content">
            <div class="header">
                <h1>我的文件</h1>
                <div class="toolbar">
                    <button onclick="document.getElementById('fileInput').click()">
                        📤 上传文件
                    </button>
                    <button class="success" onclick="createFolder()">
                        📁 新建文件夹
                    </button>
                    <button class="danger" onclick="deleteSelected()">
                        🗑️ 删除
                    </button>
                </div>
            </div>
            
            <div class="drop-zone" id="dropZone">
                <div class="drop-zone-icon">📂</div>
                <p>拖放文件到这里上传</p>
            </div>
            
            <input type="file" id="fileInput" style="display: none;" multiple>
            
            <div class="file-grid" id="fileGrid"></div>
        </div>
    </div>
    
    <div class="modal" id="previewModal">
        <div class="modal-content">
            <div class="modal-header">
                <h3 id="modalTitle">文件预览</h3>
                <span class="modal-close" onclick="closeModal()">&times;</span>
            </div>
            <div id="modalBody"></div>
        </div>
    </div>
    
    <script>
        const dropZone = document.getElementById('dropZone');
        const fileInput = document.getElementById('fileInput');
        const fileGrid = document.getElementById('fileGrid');
        const previewModal = document.getElementById('previewModal');
        
        let files = [];
        let selectedFiles = new Set();
        
        // 拖放处理
        dropZone.addEventListener('dragover', function(e) {
            e.preventDefault();
            dropZone.classList.add('dragover');
        });
        
        dropZone.addEventListener('dragleave', function(e) {
            e.preventDefault();
            dropZone.classList.remove('dragover');
        });
        
        dropZone.addEventListener('drop', function(e) {
            e.preventDefault();
            dropZone.classList.remove('dragover');
            addFiles(e.dataTransfer.files);
        });
        
        // 文件选择
        fileInput.addEventListener('change', function(e) {
            addFiles(e.target.files);
        });
        
        // 添加文件
        function addFiles(fileList) {
            Array.from(fileList).forEach(file => {
                files.push({
                    id: Date.now() + Math.random(),
                    file: file,
                    name: file.name,
                    size: file.size,
                    type: file.type,
                    lastModified: file.lastModified
                });
            });
            
            renderFiles();
        }
        
        // 渲染文件
        function renderFiles(filter = 'all') {
            let filteredFiles = files;
            
            if (filter === 'image') {
                filteredFiles = files.filter(f => f.type.startsWith('image/'));
            } else if (filter === 'document') {
                filteredFiles = files.filter(f => 
                    f.type.includes('pdf') || 
                    f.type.includes('document') ||
                    f.type.includes('text')
                );
            } else if (filter === 'video') {
                filteredFiles = files.filter(f => f.type.startsWith('video/'));
            } else if (filter === 'audio') {
                filteredFiles = files.filter(f => f.type.startsWith('audio/'));
            }
            
            fileGrid.innerHTML = filteredFiles.map(item => `
                <div class="file-card ${selectedFiles.has(item.id) ? 'selected' : ''}" 
                     onclick="toggleSelect(${item.id})"
                     ondblclick="previewFile(${item.id})">
                    <div class="file-icon">${getFileIcon(item.type)}</div>
                    <div class="file-name">${item.name}</div>
                    <div class="file-meta">${formatFileSize(item.size)}</div>
                </div>
            `).join('');
        }
        
        // 切换选择
        function toggleSelect(id) {
            if (selectedFiles.has(id)) {
                selectedFiles.delete(id);
            } else {
                selectedFiles.add(id);
            }
            renderFiles();
        }
        
        // 预览文件
        function previewFile(id) {
            const item = files.find(f => f.id === id);
            if (!item) return;
            
            document.getElementById('modalTitle').textContent = item.name;
            
            const modalBody = document.getElementById('modalBody');
            
            if (item.type.startsWith('image/')) {
                const reader = new FileReader();
                reader.onload = function(e) {
                    modalBody.innerHTML = `
                        <img src="${e.target.result}" class="preview-image">
                        ${getFileInfoTable(item)}
                    `;
                };
                reader.readAsDataURL(item.file);
            } else if (item.type.startsWith('text/')) {
                const reader = new FileReader();
                reader.onload = function(e) {
                    modalBody.innerHTML = `
                        <div class="preview-text">${e.target.result}</div>
                        ${getFileInfoTable(item)}
                    `;
                };
                reader.readAsText(item.file);
            } else {
                modalBody.innerHTML = getFileInfoTable(item);
            }
            
            previewModal.classList.add('show');
        }
        
        // 获取文件信息表格
        function getFileInfoTable(item) {
            return `
                <table class="file-info-table">
                    <tr>
                        <td>文件名</td>
                        <td>${item.name}</td>
                    </tr>
                    <tr>
                        <td>类型</td>
                        <td>${item.type || '未知'}</td>
                    </tr>
                    <tr>
                        <td>大小</td>
                        <td>${formatFileSize(item.size)}</td>
                    </tr>
                    <tr>
                        <td>修改时间</td>
                        <td>${new Date(item.lastModified).toLocaleString()}</td>
                    </tr>
                </table>
            `;
        }
        
        // 关闭模态框
        function closeModal() {
            previewModal.classList.remove('show');
        }
        
        // 过滤文件
        function filterFiles(filter) {
            document.querySelectorAll('.sidebar-item').forEach(item => {
                item.classList.remove('active');
            });
            event.target.classList.add('active');
            
            renderFiles(filter);
        }
        
        // 删除选中
        function deleteSelected() {
            if (selectedFiles.size === 0) {
                alert('请先选择要删除的文件');
                return;
            }
            
            if (confirm(`确定要删除 ${selectedFiles.size} 个文件吗?`)) {
                files = files.filter(f => !selectedFiles.has(f.id));
                selectedFiles.clear();
                renderFiles();
            }
        }
        
        // 新建文件夹
        function createFolder() {
            alert('新建文件夹功能演示');
        }
        
        // 获取文件图标
        function getFileIcon(type) {
            if (type.startsWith('image/')) return '🖼️';
            if (type.startsWith('video/')) return '🎬';
            if (type.startsWith('audio/')) return '🎵';
            if (type === 'application/pdf') return '📄';
            if (type.includes('word')) return '📝';
            if (type.includes('excel') || type.includes('spreadsheet')) return '📊';
            if (type.startsWith('text/')) return '📃';
            return '📁';
        }
        
        // 格式化文件大小
        function formatFileSize(bytes) {
            if (bytes === 0) return '0 Bytes';
            const k = 1024;
            const sizes = ['Bytes', 'KB', 'MB', 'GB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
        }
        
        // 初始化
        renderFiles();
    </script>
</body>
</html>

最佳实践

1. 文件类型验证

// 推荐:验证文件类型和大小
function validateFile(file, options = {}) {
    const {
        maxSize = 10 * 1024 * 1024, // 10MB
        allowedTypes = [],
        allowedExtensions = []
    } = options;
    
    // 检查文件大小
    if (file.size > maxSize) {
        throw new Error(`文件大小超过限制(最大 ${formatFileSize(maxSize)})`);
    }
    
    // 检查文件类型
    if (allowedTypes.length > 0 && !allowedTypes.includes(file.type)) {
        throw new Error('文件类型不允许');
    }
    
    // 检查文件扩展名
    if (allowedExtensions.length > 0) {
        const ext = file.name.split('.').pop().toLowerCase();
        if (!allowedExtensions.includes(ext)) {
            throw new Error('文件扩展名不允许');
        }
    }
    
    return true;
}

// 使用示例
try {
    validateFile(file, {
        maxSize: 5 * 1024 * 1024, // 5MB
        allowedTypes: ['image/jpeg', 'image/png'],
        allowedExtensions: ['jpg', 'jpeg', 'png']
    });
} catch (err) {
    alert(err.message);
}

2. 错误处理

// 推荐:完善的错误处理
function readFile(file) {
    return new Promise((resolve, reject) => {
        // 检查文件
        if (!file) {
            reject(new Error('未选择文件'));
            return;
        }
        
        // 检查FileReader支持
        if (!window.FileReader) {
            reject(new Error('浏览器不支持FileReader'));
            return;
        }
        
        const reader = new FileReader();
        
        reader.onload = () => resolve(reader.result);
        reader.onerror = () => reject(new Error('文件读取失败'));
        reader.onabort = () => reject(new Error('文件读取被中止'));
        
        // 根据文件类型选择读取方式
        if (file.type.startsWith('image/')) {
            reader.readAsDataURL(file);
        } else {
            reader.readAsText(file);
        }
    });
}

// 使用示例
readFile(file)
    .then(result => {
        console.log('读取成功:', result);
    })
    .catch(err => {
        console.error('读取失败:', err.message);
    });

3. 性能优化

// 推荐:大文件分片读取
function readLargeFile(file, chunkSize = 1024 * 1024) {
    return new Promise((resolve, reject) => {
        const chunks = [];
        let offset = 0;
        
        function readNextChunk() {
            const reader = new FileReader();
            const blob = file.slice(offset, offset + chunkSize);
            
            reader.onload = function(e) {
                chunks.push(e.target.result);
                offset += chunkSize;
                
                if (offset < file.size) {
                    readNextChunk();
                } else {
                    resolve(chunks);
                }
            };
            
            reader.onerror = reject;
            reader.readAsArrayBuffer(blob);
        }
        
        readNextChunk();
    });
}

// 使用示例
readLargeFile(largeFile)
    .then(chunks => {
        console.log(`文件已分成 ${chunks.length} 个块`);
    });

东巴文点评:文件API使用要注重安全验证和性能优化,提供良好的用户体验。

学习检验

知识点测试

问题1:以下哪个对象用于异步读取文件内容?

A. File B. FileList C. FileReader D. Blob

<details> <summary>点击查看答案</summary>

答案:C

东巴文解释FileReader对象用于异步读取文件内容,支持多种读取方式:readAsText()读取文本、readAsDataURL()读取为DataURL、readAsArrayBuffer()读取为ArrayBuffer。

</details>

问题2:如何创建一个文件的临时URL?

A. URL.createObjectURL() B. URL.createBlobURL() C. File.getURL() D. Blob.getURL()

<details> <summary>点击查看答案</summary>

答案:A

东巴文解释:使用URL.createObjectURL(blob)可以创建一个指向Blob或File对象的临时URL,使用后应调用URL.revokeObjectURL()释放资源。

</details>

实践任务

任务:创建一个图片压缩工具,支持选择图片、调整质量、预览和下载。

<details> <summary>点击查看参考答案</summary>
<!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>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }
        
        .container {
            max-width: 1000px;
            margin: 0 auto;
        }
        
        .header {
            text-align: center;
            color: white;
            padding: 30px 0;
        }
        
        .header h1 {
            font-size: 36px;
            margin-bottom: 10px;
        }
        
        .card {
            background: white;
            border-radius: 15px;
            padding: 30px;
            margin: 20px 0;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
        }
        
        .upload-area {
            border: 3px dashed #ddd;
            border-radius: 15px;
            padding: 60px 20px;
            text-align: center;
            cursor: pointer;
            transition: all 0.3s;
        }
        
        .upload-area:hover {
            border-color: #667eea;
            background: #f0f0ff;
        }
        
        .upload-icon {
            font-size: 64px;
            margin-bottom: 20px;
        }
        
        .upload-text {
            font-size: 18px;
            color: #666;
        }
        
        input[type="file"] {
            display: none;
        }
        
        .preview-container {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 20px;
            margin-top: 30px;
        }
        
        .preview-box {
            background: #f9f9f9;
            border-radius: 10px;
            padding: 20px;
        }
        
        .preview-box h3 {
            margin-bottom: 15px;
            color: #333;
        }
        
        .preview-image {
            width: 100%;
            border-radius: 10px;
            margin-bottom: 15px;
        }
        
        .preview-info {
            font-size: 14px;
            color: #666;
        }
        
        .preview-info span {
            display: block;
            margin: 5px 0;
        }
        
        .controls {
            margin-top: 30px;
        }
        
        .control-item {
            margin-bottom: 20px;
        }
        
        .control-item label {
            display: block;
            margin-bottom: 10px;
            font-weight: bold;
            color: #333;
        }
        
        .quality-slider {
            width: 100%;
            height: 8px;
            border-radius: 4px;
            background: #ddd;
            outline: none;
            -webkit-appearance: none;
        }
        
        .quality-slider::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 20px;
            height: 20px;
            border-radius: 50%;
            background: #667eea;
            cursor: pointer;
        }
        
        .quality-value {
            display: inline-block;
            margin-left: 10px;
            padding: 5px 15px;
            background: #667eea;
            color: white;
            border-radius: 15px;
        }
        
        .button-group {
            display: flex;
            gap: 10px;
            margin-top: 20px;
        }
        
        button {
            flex: 1;
            padding: 15px;
            background: #667eea;
            color: white;
            border: none;
            border-radius: 10px;
            cursor: pointer;
            font-size: 16px;
            transition: background 0.3s;
        }
        
        button:hover {
            background: #5568d3;
        }
        
        button:disabled {
            background: #ccc;
            cursor: not-allowed;
        }
        
        button.success {
            background: #27ae60;
        }
        
        button.success:hover {
            background: #229954;
        }
        
        .compression-info {
            background: #e7f3ff;
            padding: 20px;
            border-radius: 10px;
            margin-top: 20px;
            text-align: center;
        }
        
        .compression-info h3 {
            color: #667eea;
            margin-bottom: 10px;
        }
        
        .compression-ratio {
            font-size: 36px;
            font-weight: bold;
            color: #27ae60;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>🖼️ 图片压缩工具</h1>
            <p>快速压缩图片,减小文件大小</p>
        </div>
        
        <div class="card">
            <div class="upload-area" id="uploadArea" onclick="document.getElementById('fileInput').click()">
                <div class="upload-icon">📁</div>
                <div class="upload-text">点击选择图片或拖放图片到这里</div>
            </div>
            
            <input type="file" id="fileInput" accept="image/*">
            
            <div class="preview-container" id="previewContainer" style="display: none;">
                <div class="preview-box">
                    <h3>原始图片</h3>
                    <img class="preview-image" id="originalImage">
                    <div class="preview-info">
                        <span>文件名: <strong id="originalName"></strong></span>
                        <span>大小: <strong id="originalSize"></strong></span>
                        <span>尺寸: <strong id="originalDimensions"></strong></span>
                    </div>
                </div>
                
                <div class="preview-box">
                    <h3>压缩后图片</h3>
                    <img class="preview-image" id="compressedImage">
                    <div class="preview-info">
                        <span>大小: <strong id="compressedSize"></strong></span>
                        <span>尺寸: <strong id="compressedDimensions"></strong></span>
                    </div>
                </div>
            </div>
            
            <div class="controls" id="controls" style="display: none;">
                <div class="control-item">
                    <label>
                        压缩质量: 
                        <span class="quality-value" id="qualityValue">80%</span>
                    </label>
                    <input type="range" class="quality-slider" id="qualitySlider" 
                           min="10" max="100" value="80" oninput="updateQuality()">
                </div>
                
                <div class="compression-info">
                    <h3>压缩比例</h3>
                    <div class="compression-ratio" id="compressionRatio">0%</div>
                </div>
                
                <div class="button-group">
                    <button onclick="compressImage()">重新压缩</button>
                    <button class="success" onclick="downloadImage()">下载图片</button>
                </div>
            </div>
        </div>
    </div>
    
    <script>
        const uploadArea = document.getElementById('uploadArea');
        const fileInput = document.getElementById('fileInput');
        const previewContainer = document.getElementById('previewContainer');
        const controls = document.getElementById('controls');
        
        let originalFile = null;
        let compressedBlob = null;
        
        // 拖放处理
        uploadArea.addEventListener('dragover', function(e) {
            e.preventDefault();
            uploadArea.style.borderColor = '#667eea';
            uploadArea.style.background = '#f0f0ff';
        });
        
        uploadArea.addEventListener('dragleave', function(e) {
            e.preventDefault();
            uploadArea.style.borderColor = '#ddd';
            uploadArea.style.background = 'white';
        });
        
        uploadArea.addEventListener('drop', function(e) {
            e.preventDefault();
            uploadArea.style.borderColor = '#ddd';
            uploadArea.style.background = 'white';
            
            const files = e.dataTransfer.files;
            if (files.length > 0) {
                handleFile(files[0]);
            }
        });
        
        // 文件选择
        fileInput.addEventListener('change', function(e) {
            if (e.target.files.length > 0) {
                handleFile(e.target.files[0]);
            }
        });
        
        // 处理文件
        function handleFile(file) {
            if (!file.type.startsWith('image/')) {
                alert('请选择图片文件');
                return;
            }
            
            originalFile = file;
            
            // 显示原始图片
            const reader = new FileReader();
            reader.onload = function(e) {
                const img = new Image();
                img.onload = function() {
                    document.getElementById('originalImage').src = e.target.result;
                    document.getElementById('originalName').textContent = file.name;
                    document.getElementById('originalSize').textContent = formatFileSize(file.size);
                    document.getElementById('originalDimensions').textContent = 
                        `${img.width} × ${img.height}`;
                    
                    previewContainer.style.display = 'grid';
                    controls.style.display = 'block';
                    
                    // 自动压缩
                    compressImage();
                };
                img.src = e.target.result;
            };
            reader.readAsDataURL(file);
        }
        
        // 更新质量显示
        function updateQuality() {
            const quality = document.getElementById('qualitySlider').value;
            document.getElementById('qualityValue').textContent = quality + '%';
        }
        
        // 压缩图片
        function compressImage() {
            const quality = document.getElementById('qualitySlider').value / 100;
            
            const reader = new FileReader();
            reader.onload = function(e) {
                const img = new Image();
                img.onload = function() {
                    // 创建Canvas
                    const canvas = document.createElement('canvas');
                    canvas.width = img.width;
                    canvas.height = img.height;
                    
                    const ctx = canvas.getContext('2d');
                    ctx.drawImage(img, 0, 0);
                    
                    // 压缩
                    canvas.toBlob(function(blob) {
                        compressedBlob = blob;
                        
                        // 显示压缩后的图片
                        const url = URL.createObjectURL(blob);
                        document.getElementById('compressedImage').src = url;
                        document.getElementById('compressedSize').textContent = 
                            formatFileSize(blob.size);
                        document.getElementById('compressedDimensions').textContent = 
                            `${img.width} × ${img.height}`;
                        
                        // 计算压缩比例
                        const ratio = ((1 - blob.size / originalFile.size) * 100).toFixed(1);
                        document.getElementById('compressionRatio').textContent = 
                            ratio > 0 ? `-${ratio}%` : `+${Math.abs(ratio)}%`;
                        
                    }, 'image/jpeg', quality);
                };
                img.src = e.target.result;
            };
            reader.readAsDataURL(originalFile);
        }
        
        // 下载图片
        function downloadImage() {
            if (!compressedBlob) {
                alert('请先压缩图片');
                return;
            }
            
            const url = URL.createObjectURL(compressedBlob);
            const link = document.createElement('a');
            link.href = url;
            link.download = 'compressed_' + originalFile.name;
            link.click();
            
            URL.revokeObjectURL(url);
        }
        
        // 格式化文件大小
        function formatFileSize(bytes) {
            if (bytes === 0) return '0 Bytes';
            const k = 1024;
            const sizes = ['Bytes', 'KB', 'MB', 'GB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
        }
    </script>
</body>
</html>
</details>

东巴文(db-w.cn) - 让编程学习更有趣、更高效!