Câu trả lời: CÓ
HTML5 cung cấp **File API** cho phép web app đọc, xử lý file local từ máy người dùng **sau khi người dùng cho phép** (qua input file hoặc drag & drop). Không thể truy cập file hệ thống tùy ý vì bảo mật.
Các API chính
| API | Mục đích | Hỗ trợ |
|---|---|---|
| File API | Đọc file, lấy metadata (name, size, type, lastModified) | Tất cả trình duyệt hiện đại |
| FileReader API | Đọc nội dung file asynchronously (text, DataURL, ArrayBuffer) | Tất cả trình duyệt hiện đại |
| Blob / File Constructor | Tạo file/blob mới từ dữ liệu | Tất cả trình duyệt hiện đại |
| File System Access API | Đọc/ghi file/thư mục thực sự trên ổ cứng (cần quyền) | Chrome 86+, Edge 86+ (Origin Trial) |
| File Writer API | Ghi file (đã deprecated, thay bằng File System Access API) | Deprecated |
| Drag & Drop API | Kéo thả file từ OS vào trình duyệt | Tất cả trình duyệt hiện đại |
Ví dụ 1: Đọc file qua <input type="file">
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<title>File API Demo</title>
<style>
.file-info { background: #f8f9fa; padding: 1rem; border-radius: 8px; margin: 1rem 0; font-family: monospace; white-space: pre-wrap; }
.preview { max-width: 300px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
.file-list { list-style: none; padding: 0; }
.file-list li { padding: 0.5rem; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; }
</style>
</head>
<body>
<h2>Đọc nhiều file cùng lúc</h2>
<input type="file" id="multiFileInput" multiple accept="image/*,.pdf,.txt">
<div id="fileList" class="file-list"></div>
<div id="previewArea"></div>
<script>
const input = document.getElementById('multiFileInput');
const list = document.getElementById('fileList');
const preview = document.getElementById('previewArea');
input.addEventListener('change', async (e) => {
const files = Array.from(e.target.files);
list.innerHTML = '';
preview.innerHTML = '';
for (const file of files) {
// Metadata
const info = `
File: ${file.name}
Kích thước: ${formatBytes(file.size)}
Loại: ${file.type || 'Unknown'}
Sửa đổi: ${new Date(file.lastModified).toLocaleString('vi-VN')}
`.trim();
const li = document.createElement('li');
li.innerHTML = `${file.name} (${formatBytes(file.size)})`;
li.title = info;
list.appendChild(li);
// Preview nếu là ảnh
if (file.type.startsWith('image/')) {
const img = document.createElement('img');
img.className = 'preview';
img.src = URL.createObjectURL(file); // Tạo Object URL (hiệu quả hơn DataURL)
img.onload = () => URL.revokeObjectURL(img.src); // Giải phóng memory
preview.appendChild(img);
}
// Đọc nội dung text file
if (file.type.startsWith('text/') || file.name.endsWith('.json')) {
const text = await readFileAsText(file);
console.log(`Nội dung ${file.name}:`, text.substring(0, 200) + '...');
}
}
});
function readFileAsText(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => resolve(e.target.result);
reader.onerror = (e) => reject(e.target.error);
reader.readAsText(file);
});
}
function formatBytes(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>
Ví dụ 2: Drag & Drop file
<div id="dropZone" style="border: 2px dashed #ccc; padding: 3rem; text-align: center; border-radius: 8px; transition: all 0.3s;">
Kéo thả file vào đây hoặc click để chọn
</div>
<script>
const dropZone = document.getElementById('dropZone');
['dragenter', 'dragover'].forEach(event => {
dropZone.addEventListener(event, (e) => {
e.preventDefault();
e.stopPropagation();
dropZone.style.borderColor = '#3498db';
dropZone.style.background = '#ebf5fb';
});
});
['dragleave', 'drop'].forEach(event => {
dropZone.addEventListener(event, (e) => {
e.preventDefault();
e.stopPropagation();
dropZone.style.borderColor = '#ccc';
dropZone.style.background = 'transparent';
});
});
dropZone.addEventListener('drop', (e) => {
const files = Array.from(e.dataTransfer.files);
// Xử lý files giống như input change
console.log('Dropped files:', files);
});
dropZone.addEventListener('click', () => {
const input = document.createElement('input');
input.type = 'file';
input.multiple = true;
input.onchange = (e) => console.log('Selected:', Array.from(e.target.files));
input.click();
});
</script>
Ví dụ 3: File System Access API (Mới - Chrome/Edge)
<button id="openFileBtn">Mở file văn bản</button>
<button id="saveFileBtn" disabled>Lưu file</button>
<textarea id="editor" style="width: 100%; height: 300px; margin-top: 1rem;"></textarea>
<script>
let fileHandle = null;
document.getElementById('openFileBtn').addEventListener('click', async () => {
try {
[fileHandle] = await window.showOpenFilePicker({
types: [{
description: 'Text Files',
accept: { 'text/plain': ['.txt', '.md', '.js', '.css', '.html'] }
}],
multiple: false
});
const file = await fileHandle.getFile();
const content = await file.text();
document.getElementById('editor').value = content;
document.getElementById('saveFileBtn').disabled = false;
} catch (e) {
console.log('User cancelled');
}
});
document.getElementById('saveFileBtn').addEventListener('click', async () => {
if (!fileHandle) return;
try {
const writable = await fileHandle.createWritable();
await writable.write(document.getElementById('editor').value);
await writable.close();
alert('Đã lưu!');
} catch (e) {
console.error('Lỗi lưu:', e);
}
});
// Tạo file mới
async function saveAsNewFile(content) {
try {
const handle = await window.showSaveFilePicker({
suggestedName: 'new-file.txt',
types: [{ description: 'Text', accept: { 'text/plain': ['.txt'] } }]
});
const writable = await handle.createWritable();
await writable.write(content);
await writable.close();
return handle;
} catch (e) {
console.log('Cancelled');
}
}
</script>
Ứng dụng thực tế
- Image Editor: Đọc ảnh → xử lý canvas → xuất file
- CSV/JSON Processor: Đọc file → parse → hiển thị bảng → xuất kết quả
- Offline-first App: Lưu file reference (FileSystemHandle) để đọc khi offline
- Thumbnail Preview: Tạo preview ảnh trước khi upload server
- Code Editor: Mở/lưu file code trực tiếp từ IDE web
Giới hạn bảo mật
- Không thể liệt kê thư mục (trừ File System Access API với quyền)
- Không thể ghi file tùy ý (chỉ qua Save File Picker)
- File API chỉ đọc file user chọn, không scan ổ cứng
- CORS không áp dụng cho file local (file:// protocol bị hạn chế)