文件API(File API)提供了在Web浏览器中处理文件的能力,允许JavaScript异步读取文件内容、获取文件信息,以及处理用户选择的文件。这是现代Web应用实现文件上传、预览和处理的基础。
东巴文(db-w.cn) 认为:文件API让Web应用能够更好地处理用户文件,实现丰富的文件操作功能。
| 特点 | 说明 |
|---|---|
| 异步读取 | 使用FileReader异步读取文件 |
| 安全机制 | 只能访问用户明确选择的文件 |
| 多种格式 | 支持文本、DataURL、ArrayBuffer等格式 |
| 文件信息 | 提供文件名、大小、类型等信息 |
| 拖放支持 | 与拖放API结合使用 |
// 文件API核心接口
const FileInterfaces = {
File: '表示一个文件对象',
FileList: '文件对象的集合',
FileReader: '异步读取文件',
Blob: '表示二进制大对象',
URL: '创建文件临时URL'
};
<!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>
<!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>
<!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()">×</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>
// 推荐:验证文件类型和大小
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);
}
// 推荐:完善的错误处理
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);
});
// 推荐:大文件分片读取
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。
问题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> <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) - 让编程学习更有趣、更高效!