Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> JavaScript

HTML5 có cho phép tương tác với file local client từ trình duyệt không?


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

APIMục đíchHỗ 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 ConstructorTạo file/blob mới từ dữ liệuTấ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 APIGhi file (đã deprecated, thay bằng File System Access API)Deprecated
Drag & Drop APIKéo thả file từ OS vào trình duyệtTấ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ế)