阿里云主机折上折
  • 微信号
Current Site:Index > The HTML5 file upload control ('<input type="file">' enhancement)

The HTML5 file upload control ('<input type="file">' enhancement)

Author:Chuan Chen 阅读数:22246人阅读 分类: HTML

HTML5 File Upload Control ('<input type="file">' Enhancements)

HTML5 has significantly enhanced the traditional file upload control by introducing new attributes and APIs. These improvements allow developers to better control the file selection process, enhance user experience, and implement more complex file handling logic.

Multiple File Selection

HTML5 allows users to select multiple files at once by simply adding the multiple attribute:

<input type="file" id="fileInput" multiple>

All selected files can be retrieved via JavaScript:

document.getElementById('fileInput').addEventListener('change', function(e) {
  const files = e.target.files;
  console.log(`Selected ${files.length} files`);
  
  for (let i = 0; i < files.length; i++) {
    console.log(`Filename: ${files[i].name}, Size: ${files[i].size} bytes`);
  }
});

File Type Restrictions

The accept attribute can be used to restrict users to selecting only specific file types:

<!-- Only accept images -->
<input type="file" accept="image/*">

<!-- Accept specific formats -->
<input type="file" accept=".pdf,.doc,.docx">

<!-- Video files -->
<input type="file" accept="video/*">

Note that this is only a client-side restriction; server-side validation is still required.

File Information Retrieval

The File API allows retrieving detailed file information:

const fileInput = document.getElementById('fileInput');

fileInput.addEventListener('change', function() {
  const file = this.files[0];
  
  if (file) {
    console.log('Filename:', file.name);
    console.log('File type:', file.type);
    console.log('File size:', file.size, 'bytes');
    console.log('Last modified:', new Date(file.lastModified));
  }
});

File Preview

Files selected by users can be previewed without actually uploading them:

Image Preview

<input type="file" id="imageInput" accept="image/*">
<img id="preview" src="#" alt="Image preview" style="max-width: 300px; display: none;">

<script>
  document.getElementById('imageInput').addEventListener('change', function(e) {
    const file = e.target.files[0];
    if (file) {
      const preview = document.getElementById('preview');
      preview.style.display = 'block';
      
      const reader = new FileReader();
      reader.onload = function(e) {
        preview.src = e.target.result;
      }
      reader.readAsDataURL(file);
    }
  });
</script>

Text File Preview

document.getElementById('textFileInput').addEventListener('change', function(e) {
  const file = e.target.files[0];
  if (file && file.type.match('text.*')) {
    const reader = new FileReader();
    reader.onload = function(e) {
      document.getElementById('textPreview').textContent = e.target.result;
    };
    reader.readAsText(file);
  }
});

Drag and Drop Upload

HTML5's Drag and Drop API can be combined with file upload:

<div id="dropZone" style="border: 2px dashed #ccc; padding: 20px; text-align: center;">
  Drag and drop files here to upload
</div>

<script>
  const dropZone = document.getElementById('dropZone');
  
  // Prevent default behavior
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
    dropZone.addEventListener(eventName, preventDefaults, false);
  });
  
  function preventDefaults(e) {
    e.preventDefault();
    e.stopPropagation();
  }
  
  // Highlight drop zone
  ['dragenter', 'dragover'].forEach(eventName => {
    dropZone.addEventListener(eventName, highlight, false);
  });
  
  ['dragleave', 'drop'].forEach(eventName => {
    dropZone.addEventListener(eventName, unhighlight, false);
  });
  
  function highlight() {
    dropZone.style.borderColor = 'blue';
  }
  
  function unhighlight() {
    dropZone.style.borderColor = '#ccc';
  }
  
  // Handle dropped files
  dropZone.addEventListener('drop', handleDrop, false);
  
  function handleDrop(e) {
    const dt = e.dataTransfer;
    const files = dt.files;
    
    if (files.length) {
      console.log('Dropped files:', files);
      // File upload can be handled here
    }
  }
</script>

File Upload Progress Monitoring

Both XMLHttpRequest and Fetch API can be used to monitor upload progress:

Using XMLHttpRequest

function uploadFile(file) {
  const xhr = new XMLHttpRequest();
  const formData = new FormData();
  formData.append('file', file);
  
  xhr.upload.addEventListener('progress', function(e) {
    if (e.lengthComputable) {
      const percentComplete = (e.loaded / e.total) * 100;
      console.log(`Upload progress: ${percentComplete.toFixed(2)}%`);
      document.getElementById('progressBar').value = percentComplete;
    }
  }, false);
  
  xhr.addEventListener('load', function() {
    console.log('Upload complete');
  });
  
  xhr.addEventListener('error', function() {
    console.error('Upload error');
  });
  
  xhr.open('POST', '/upload', true);
  xhr.send(formData);
}

Using Fetch API

async function uploadWithProgress(file) {
  const formData = new FormData();
  formData.append('file', file);
  
  const response = await fetch('/upload', {
    method: 'POST',
    body: formData,
    // Use AbortController to cancel upload
    signal: new AbortController().signal
  });
  
  if (!response.ok) {
    throw new Error('Upload failed');
  }
  
  const result = await response.json();
  console.log('Upload result:', result);
}

Chunked File Upload

For large files, chunked upload can be implemented:

async function uploadInChunks(file, chunkSize = 1024 * 1024) {
  const totalChunks = Math.ceil(file.size / chunkSize);
  let chunkNumber = 0;
  
  while (chunkNumber < totalChunks) {
    const start = chunkNumber * chunkSize;
    const end = Math.min(start + chunkSize, file.size);
    const chunk = file.slice(start, end);
    
    const formData = new FormData();
    formData.append('file', chunk);
    formData.append('chunkNumber', chunkNumber);
    formData.append('totalChunks', totalChunks);
    formData.append('originalFilename', file.name);
    
    try {
      await fetch('/upload-chunk', {
        method: 'POST',
        body: formData
      });
      
      console.log(`Uploaded chunk ${chunkNumber + 1}/${totalChunks}`);
      chunkNumber++;
    } catch (error) {
      console.error('Chunk upload failed:', error);
      break;
    }
  }
  
  if (chunkNumber === totalChunks) {
    console.log('All chunks uploaded');
    // Notify server to merge chunks
    await fetch('/merge-chunks', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        filename: file.name,
        totalChunks: totalChunks
      })
    });
  }
}

File Validation

Client-side validation before upload:

function validateFile(file) {
  // File size limit (5MB)
  const maxSize = 5 * 1024 * 1024;
  if (file.size > maxSize) {
    alert('File size cannot exceed 5MB');
    return false;
  }
  
  // File type validation
  const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
  if (!allowedTypes.includes(file.type)) {
    alert('Only JPEG, PNG images and PDF files are supported');
    return false;
  }
  
  // Filename validation
  if (/[^a-zA-Z0-9_\-\.]/.test(file.name)) {
    alert('Filename contains invalid characters');
    return false;
  }
  
  return true;
}

Custom File Input Styling

Native file input controls have limited styling options, which can be customized as follows:

<style>
  .custom-file-input {
    position: relative;
    overflow: hidden;
    display: inline-block;
  }
  
  .custom-file-input input[type="file"] {
    position: absolute;
    left: 0;
    top: 0;
    opacity: 0;
    width: 100%;
    height: 100%;
    cursor: pointer;
  }
  
  .custom-file-label {
    display: inline-block;
    padding: 8px 16px;
    background: #4CAF50;
    color: white;
    border-radius: 4px;
    cursor: pointer;
  }
</style>

<div class="custom-file-input">
  <input type="file" id="realFileInput">
  <label for="realFileInput" class="custom-file-label">Choose file</label>
</div>

<script>
  document.getElementById('realFileInput').addEventListener('change', function() {
    const fileName = this.files[0] ? this.files[0].name : 'No file chosen';
    document.querySelector('.custom-file-label').textContent = fileName;
  });
</script>

File System Access API

Modern browsers provide more powerful file system access capabilities:

// Request file handle
async function getFileHandle() {
  try {
    const handle = await window.showOpenFilePicker({
      types: [
        {
          description: 'Text Files',
          accept: {
            'text/plain': ['.txt']
          }
        }
      ],
      multiple: false
    });
    
    const file = await handle[0].getFile();
    console.log('Retrieved file:', file);
    return file;
  } catch (err) {
    console.error('User canceled selection or error occurred:', err);
  }
}

// Save file
async function saveFile(content) {
  try {
    const handle = await window.showSaveFilePicker({
      types: [{
        description: 'Text Files',
        accept: {
          'text/plain': ['.txt']
        }
      }]
    });
    
    const writable = await handle.createWritable();
    await writable.write(content);
    await writable.close();
    console.log('File saved successfully');
  } catch (err) {
    console.error('Save failed:', err);
  }
}

Mobile Adaptation Considerations

On mobile devices, the following issues need to be considered:

// Check if camera capture is supported
if ('capture' in document.createElement('input')) {
  // Use camera to directly capture and upload
  const cameraInput = document.createElement('input');
  cameraInput.type = 'file';
  cameraInput.accept = 'image/*';
  cameraInput.capture = 'camera';  // Can also be 'user' or 'environment'
  document.body.appendChild(cameraInput);
}

// Handle mobile orientation issues
function handleImageOrientation(file) {
  return new Promise((resolve) => {
    const img = new Image();
    const url = URL.createObjectURL(file);
    
    img.onload = function() {
      const width = this.naturalWidth;
      const height = this.naturalHeight;
      
      // Check if rotation is needed
      if (height > width) {
        console.log('Potentially portrait photo needing rotation');
      }
      
      URL.revokeObjectURL(url);
      resolve(file);
    };
    
    img.src = url;
  });
}

Performance Optimization Techniques

Optimization methods when handling large files:

// Use Web Worker to process large files
function processLargeFileInWorker(file) {
  const worker = new Worker('file-processor.js');
  
  worker.postMessage({
    file: file
  }, [file]);  // Use Transferable objects for better performance
  
  worker.onmessage = function(e) {
    console.log('Processing result:', e.data);
    worker.terminate();
  };
}

// File processor (file-processor.js)
self.onmessage = function(e) {
  const file = e.data.file;
  const reader = new FileReaderSync();  // Synchronous reading, only available in Worker
  
  try {
    const content = reader.readAsText(file);
    // Perform time-consuming processing...
    const result = heavyProcessing(content);
    self.postMessage(result);
  } catch (error) {
    self.postMessage({ error: error.message });
  }
};

Security Considerations

Security issues to consider when uploading files:

// Check real file type
function checkFileType(file) {
  return new Promise((resolve) => {
    const reader = new FileReader();
    
    reader.onloadend = function(e) {
      const arr = new Uint8Array(e.target.result).subarray(0, 4);
      let header = '';
      
      for (let i = 0; i < arr.length; i++) {
        header += arr[i].toString(16);
      }
      
      let type = 'unknown';
      
      // Check file magic numbers
      switch (header) {
        case '89504e47': type = 'image/png'; break;
        case '47494638': type = 'image/gif'; break;
        case 'ffd8ffe0':
        case 'ffd8ffe1':
        case 'ffd8ffe2': type = 'image/jpeg'; break;
        case '25504446': type = 'application/pdf'; break;
      }
      
      resolve(type === file.type);
    };
    
    reader.readAsArrayBuffer(file.slice(0, 4));
  });
}

// Usage example
document.getElementById('fileInput').addEventListener('change', async function(e) {
  const file = e.target.files[0];
  if (file) {
    const isValid = await checkFileType(file);
    console.log('File type validation:', isValid ? 'Passed' : 'Failed');
  }
});

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.