html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FileReader文件读取最佳实践</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background-color: darkorange;
color: #333;
min-height: 100vh;
padding: 30px;
display: flex;
flex-direction: column;
align-items: center;
}
.container {
max-width: 1000px;
width: 100%;
background: rgba(255, 255, 255, 0.95);
border-radius: 15px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
padding: 30px;
margin-top: 20px;
opacity: 0.95;
}
header {
text-align: center;
margin-bottom: 15px;
}
h1 {
color: #2c3e50;
font-size: 2.5rem;
margin-bottom: 5px;
}
.subtitle {
color: #7f8c8d;
font-size: 1.2rem;
}
.upload-area {
border: 3px dashed #3498db;
border-radius: 12px;
padding: 40px;
text-align: center;
margin: 25px 0;
background: #f8f9fa;
transition: all 0.3s;
cursor: pointer;
}
.upload-area:hover {
background: orange;
border-color: red;
}
.upload-icon {
font-size: 60px;
color: #3498db;
margin-bottom: 15px;
}
.file-input {
display: none;
}
.btn {
background: #3498db;
color: white;
border: none;
padding: 12px 25px;
border-radius: 6px;
cursor: pointer;
font-size: 1rem;
font-weight: bold;
transition: background 0.3s;
margin: 10px 5px;
}
.btn:hover {
background: pink;
}
.btn-secondary {
background: #2ecc71;
}
.btn-secondary:hover {
background: #1d9650;
}
.results {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-top: 30px;
}
.result-card {
background: white;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
padding: 20px;
overflow: hidden;
}
.result-card h3 {
color: #3498db;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid #f0f0f0;
}
.file-info {
background: #e8f4fc;
padding: 15px;
border-radius: 8px;
margin-bottom: 15px;
}
.preview {
max-height: 300px;
overflow: auto;
padding: 15px;
background: #f9f9f9;
border-radius: 8px;
border: 1px solid #eee;
white-space: pre-wrap;
font-family: 'Consolas', monospace;
}
.image-preview {
max-width: 100%;
max-height: 200px;
display: block;
margin: 0 auto;
border-radius: 6px;
}
.progress-bar {
height: 8px;
background: #f0f0f0;
border-radius: 4px;
margin: 15px 0;
overflow: hidden;
}
.progress {
height: 100%;
background: linear-gradient(90deg, #3498db, #2ecc71);
width: 0%;
transition: width 0.3s;
}
.status {
text-align: center;
padding: 10px;
font-weight: bold;
color: #7f8c8d;
}
.error {
color: #e74c3c;
padding: 15px;
background: #fdecea;
border-radius: 8px;
margin: 15px 0;
}
@media (max-width: 768px) {
.results {
grid-template-columns: 1fr;
}
.container {
padding: 20px;
}
h1 {
font-size: 2rem;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>FileReader文件读取最佳实践</h1>
<p class="subtitle">使用File API和FileReader读取本地文件内容</p>
</header>
<div class="upload-area" id="dropZone">
<div class="upload-icon">📁</div>
<h2>拖放文件到此处或点击选择文件</h2>
<p>支持文本文件、图像、JSON等格式</p>
<input type="file" id="fileInput" class="file-input" multiple>
<button class="btn" onclick="document.getElementById('fileInput').click()">选择文件</button>
</div>
<div style="text-align: center;">
<button class="btn btn-secondary" id="readAsTextBtn">读取为文本</button>
<button class="btn btn-secondary" id="readAsDataURLBtn">读取为DataURL</button>
<button class="btn btn-secondary" id="readAsArrayBufferBtn">读取为ArrayBuffer</button>
</div>
<div class="progress-bar">
<div class="progress" id="progressBar"></div>
</div>
<div class="status" id="status">准备就绪</div>
<div class="results">
<div class="result-card">
<h3>文件信息</h3>
<div class="file-info" id="fileInfo">
<p>尚未选择文件</p>
</div>
</div>
<div class="result-card">
<h3>文件内容预览</h3>
<div class="preview" id="filePreview">
<p>文件内容将显示在这里</p>
</div>
</div>
</div>
<div id="errorContainer"></div>
</div>
<script>
// 获取文件的
const fileInput = document.getElementById('fileInput');
const dropZone = document.getElementById('dropZone');
// 文件信息展示
const fileInfo = document.getElementById('fileInfo');
const filePreview = document.getElementById('filePreview');
const progressBar = document.getElementById('progressBar');
const status = document.getElementById('status');
const errorContainer = document.getElementById('errorContainer');
// 三个按钮
const readAsTextBtn = document.getElementById('readAsTextBtn');
const readAsDataURLBtn = document.getElementById('readAsDataURLBtn');
const readAsArrayBufferBtn = document.getElementById('readAsArrayBufferBtn');
// 当前选中的文件
let currentFile = null;
// 拖放功能
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.style.background = 'orange';
dropZone.style.borderColor = 'red';
});
dropZone.addEventListener('dragleave', (e) => {
e.preventDefault();
dropZone.style.background = '#f8f9fa';
dropZone.style.borderColor = '#3498db';
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.style.background = '#f8f9fa';
dropZone.style.borderColor = '#3498db';
console.log(e.dataTransfer);
console.log(e);
if (e.dataTransfer.files.length > 0) {
handleFileSelection(e.dataTransfer.files[0]);
}
});
// 文件选择处理
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
handleFileSelection(e.target.files[0]);
}
});
// 处理文件选择
function handleFileSelection(file) {
currentFile = file;
console.log(file);
console.log(typeof file);
console.log(file instanceof File);
// true 也就是说,在浏览器中读取的文件的类型就是File
displayFileInfo(file);
clearError();
updateStatus('文件已选择,请选择读取方式');
// 根据文件类型启用适当的按钮
if (file.type.startsWith('image/')) {
readAsDataURLBtn.disabled = false;
} else {
readAsDataURLBtn.disabled = false;
}
readAsTextBtn.disabled = false;
readAsArrayBufferBtn.disabled = false;
}
// 显示文件信息
function displayFileInfo(file) {
fileInfo.innerHTML = `
<p><strong>文件名:</strong> ${file.name}</p>
<p><strong>类型:</strong> ${file.type || '未知'}</p>
<p><strong>大小:</strong> ${formatFileSize(file.size)}</p>
<p><strong>最后修改:</strong> ${new Date(file.lastModified).toLocaleString()}</p>
`;
}
// 格式化文件大小
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];
}
// 读取为文本
readAsTextBtn.addEventListener('click', () => {
if (!currentFile) {
showError('请先选择文件');
return;
}
const reader = new FileReader();
reader.onloadstart = () => {
updateStatus('开始读取文件...');
progressBar.style.width = '30%';
};
reader.onprogress = (e) => {
if (e.lengthComputable) {
const percentLoaded = Math.round((e.loaded / e.total) * 100);
progressBar.style.width = percentLoaded + '%';
updateStatus(`读取中: ${percentLoaded}%`);
}
};
reader.onload = (e) => {
progressBar.style.width = '100%';
updateStatus('文件读取完成');
// 显示文本内容
filePreview.textContent = e.target.result;
// 如果是JSON,尝试格式化
if (currentFile.type === 'application/json' || currentFile.name.endsWith('.json')) {
try {
const json = JSON.parse(e.target.result);
filePreview.textContent = JSON.stringify(json, null, 2);
} catch (err) {
// 不是有效的JSON,保持原样
}
}
};
reader.onerror = () => {
showError('读取文件时发生错误: ' + reader.error.message);
progressBar.style.width = '0%';
updateStatus('读取失败');
};
reader.readAsText(currentFile);
});
// 读取为DataURL
readAsDataURLBtn.addEventListener('click', () => {
if (!currentFile) {
showError('请先选择文件');
return;
}
const reader = new FileReader();
reader.onloadstart = () => {
updateStatus('开始读取文件...');
progressBar.style.width = '30%';
};
reader.onprogress = (e) => {
if (e.lengthComputable) {
const percentLoaded = Math.round((e.loaded / e.total) * 100);
progressBar.style.width = percentLoaded + '%';
updateStatus(`读取中: ${percentLoaded}%`);
}
};
reader.onload = (e) => {
progressBar.style.width = '100%';
updateStatus('文件读取完成');
// 如果是图片,显示图片预览
if (currentFile.type.startsWith('image/')) {
filePreview.innerHTML = `<img src="${e.target.result}" alt="预览" class="image-preview">`;
} else {
// 否则显示DataURL的前100个字符
const dataURL = e.target.result;
filePreview.textContent = dataURL.substring(0, 100) + '...';
}
};
reader.onerror = () => {
showError('读取文件时发生错误: ' + reader.error.message);
progressBar.style.width = '0%';
updateStatus('读取失败');
};
reader.readAsDataURL(currentFile);
});
// 读取为ArrayBuffer
readAsArrayBufferBtn.addEventListener('click', () => {
if (!currentFile) {
showError('请先选择文件');
return;
}
const reader = new FileReader();
reader.onloadstart = () => {
updateStatus('开始读取文件...');
progressBar.style.width = '30%';
};
reader.onprogress = (e) => {
if (e.lengthComputable) {
const percentLoaded = Math.round((e.loaded / e.total) * 100);
progressBar.style.width = percentLoaded + '%';
updateStatus(`读取中: ${percentLoaded}%`);
}
};
reader.onload = (e) => {
progressBar.style.width = '100%';
updateStatus('文件读取完成');
// 显示ArrayBuffer信息
const arrayBuffer = e.target.result;
filePreview.innerHTML = `
<p><strong>ArrayBuffer大小:</strong> ${arrayBuffer.byteLength} 字节</p>
<p><strong>前16字节:</strong> ${getFirstBytes(arrayBuffer, 16)}</p>
`;
};
reader.onerror = () => {
showError('读取文件时发生错误: ' + reader.error.message);
progressBar.style.width = '0%';
updateStatus('读取失败');
};
reader.readAsArrayBuffer(currentFile);
});
// 获取ArrayBuffer的前几个字节
function getFirstBytes(arrayBuffer, count) {
const uint8Array = new Uint8Array(arrayBuffer);
const bytes = [];
for (let i = 0; i < Math.min(count, uint8Array.length); i++) {
bytes.push(uint8Array[i].toString(16).padStart(2, '0'));
}
return bytes.join(' ');
}
// 更新状态
function updateStatus(message) {
status.textContent = message;
}
// 显示错误
function showError(message) {
errorContainer.innerHTML = `<div class="error">${message}</div>`;
}
// 清除错误
function clearError() {
errorContainer.innerHTML = '';
}
// 初始化按钮状态
readAsTextBtn.disabled = true;
readAsDataURLBtn.disabled = true;
readAsArrayBufferBtn.disabled = true;
</script>
</body>
</html>