JavaScript/File

Fileオブジェクト

はじめに

ウェブアプリケーションにおいて、ユーザーからファイルを受け取り処理することは非常に一般的な要件です。JavaScriptのFileオブジェクトは、ユーザーのローカルファイルシステムのファイルを表すオブジェクトであり、ファイルのアップロードやファイル内容の読み取りなどの操作を可能にします。

本章では、Fileオブジェクトの基本から応用まで、実用的なコード例を交えながら詳しく解説します。

Fileオブジェクトの基本

Fileオブジェクトは、Web APIの一部として提供されており、Blobオブジェクトを拡張したものです。ユーザーがファイル選択フォームやドラッグ&ドロップでファイルを提供したときに、JavaScriptでこれを取得できます。

Fileオブジェクトのプロパティ

Fileオブジェクトには以下の主要なプロパティがあります。

プロパティ名 説明
name ファイルの名前(パスを含まない) "document.pdf"
size ファイルのサイズ(バイト単位) 1024
type ファイルのMIMEタイプ "application/pdf"
lastModified 最終更新日時(Unix時間、ミリ秒) 1609459200000
lastModifiedDate 最終更新日時(Dateオブジェクト)※非推奨 Wed Dec 31 2020 15:00:00

Fileオブジェクトの取得方法

Fileオブジェクトを取得する主な方法は2つあります。

  1. input要素からの取得
<input type="file" id="fileInput" multiple>
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', (event) => {
  const fileList = event.target.files;
  const file = fileList[0]; // 最初のファイルを取得
  
  console.log('ファイル名:', file.name);
  console.log('ファイルサイズ:', file.size, 'バイト');
  console.log('ファイルタイプ:', file.type);
  console.log('最終更新日:', new Date(file.lastModified).toLocaleString());
});
  1. ドラッグ&ドロップからの取得
<div id="dropArea" style="width: 300px; height: 200px; border: 2px dashed #ccc; padding: 20px;">
  ここにファイルをドロップしてください
</div>
const dropArea = document.getElementById('dropArea');

dropArea.addEventListener('dragover', (event) => {
  event.preventDefault(); // デフォルトの動作をキャンセル
  event.stopPropagation();
  dropArea.style.background = '#f0f0f0';
});

dropArea.addEventListener('dragleave', () => {
  dropArea.style.background = 'transparent';
});

dropArea.addEventListener('drop', (event) => {
  event.preventDefault(); // デフォルトの動作をキャンセル
  event.stopPropagation();
  dropArea.style.background = 'transparent';
  
  const fileList = event.dataTransfer.files;
  if (fileList.length > 0) {
    const file = fileList[0];
    console.log('ドロップされたファイル:', file.name);
  }
});

FileReaderを使ったファイルの読み込み

Fileオブジェクトを取得したら、FileReaderオブジェクトを使用してファイルの内容を読み込むことができます。FileReaderは非同期でファイルを読み込み、様々な形式でデータを提供します。

テキストファイルの読み込み

const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', (event) => {
  const file = event.target.files[0];
  if (file) {
    const reader = new FileReader();
    
    reader.onload = (e) => {
      const content = e.target.result;
      console.log('ファイルの内容:', content);
      // 内容をテキストエリアなどに表示
      document.getElementById('output').textContent = content;
    };
    
    reader.onerror = (e) => {
      console.error('ファイルの読み込みエラー:', e);
    };
    
    reader.readAsText(file); // テキストとして読み込み
  }
});

画像ファイルの読み込みとプレビュー

const imageInput = document.getElementById('imageInput');
const preview = document.getElementById('imagePreview');

imageInput.addEventListener('change', (event) => {
  const file = event.target.files[0];
  
  if (file && file.type.startsWith('image/')) {
    const reader = new FileReader();
    
    reader.onload = (e) => {
      // 画像をプレビュー表示
      preview.src = e.target.result;
      preview.style.display = 'block';
    };
    
    reader.readAsDataURL(file); // Data URLとして読み込み
  } else {
    alert('画像ファイルを選択してください');
  }
});

FileReaderのメソッド

FileReaderには、ファイルをさまざまな形式で読み込むための複数のメソッドがあります。

メソッド 説明 結果の形式
readAsText(file, [encoding]) テキストとして読み込む 文字列
readAsDataURL(file) Data URLとして読み込む data:URLスキーマの文字列
readAsArrayBuffer(file) ArrayBufferとして読み込む ArrayBuffer
readAsBinaryString(file) バイナリ文字列として読み込む※非推奨 文字列

FileReaderのイベント

FileReaderには以下のイベントがあります。

イベント 説明
onload 読み込みが正常に完了したとき
onerror エラーが発生したとき
onprogress 読み込み中の進捗状況が変化したとき
onabort 読み込みが中断されたとき
onloadstart 読み込みが開始されたとき
onloadend 読み込みが成功・失敗にかかわらず完了したとき

実践的なファイル操作例

複数ファイルの処理

<input type="file" id="multipleFiles" multiple>
<div id="fileList"></div>
const multipleFiles = document.getElementById('multipleFiles');
const fileList = document.getElementById('fileList');

multipleFiles.addEventListener('change', (event) => {
  fileList.innerHTML = ''; // リストをクリア
  
  const files = event.target.files;
  for (let i = 0; i < files.length; i++) {
    const file = files[i];
    const fileInfo = document.createElement('div');
    
    // ファイル情報を表示
    fileInfo.innerHTML = `
      <strong>${file.name}</strong> (${formatFileSize(file.size)})
      <br>タイプ: ${file.type || '不明'}
      <br>更新日: ${new Date(file.lastModified).toLocaleString()}
    `;
    
    fileList.appendChild(fileInfo);
  }
});

// ファイルサイズをフォーマットする関数
function formatFileSize(bytes) {
  if (bytes === 0) return '0 Bytes';
  
  const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
  const i = Math.floor(Math.log(bytes) / Math.log(1024));
  
  return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + ' ' + sizes[i];
}

ファイルアップロード機能の実装

<form id="uploadForm">
  <input type="file" id="fileUpload" multiple>
  <div id="progressBar" style="width: 0%; height: 20px; background-color: #4CAF50; display: none;"></div>
  <button type="submit">アップロード</button>
</form>
const uploadForm = document.getElementById('uploadForm');
const fileUpload = document.getElementById('fileUpload');
const progressBar = document.getElementById('progressBar');

uploadForm.addEventListener('submit', (event) => {
  event.preventDefault();
  
  const files = fileUpload.files;
  if (files.length === 0) {
    alert('ファイルを選択してください');
    return;
  }
  
  const formData = new FormData();
  for (let i = 0; i < files.length; i++) {
    formData.append('files', files[i]);
  }
  
  // プログレスバー表示
  progressBar.style.display = 'block';
  progressBar.style.width = '0%';
  
  // XMLHttpRequestでアップロード
  const xhr = new XMLHttpRequest();
  xhr.open('POST', '/upload-endpoint', true);
  
  // 進捗状況の追跡
  xhr.upload.onprogress = (event) => {
    if (event.lengthComputable) {
      const percentComplete = (event.loaded / event.total) * 100;
      progressBar.style.width = percentComplete + '%';
    }
  };
  
  xhr.onload = () => {
    if (xhr.status === 200) {
      alert('アップロード完了!');
    } else {
      alert('アップロードエラー: ' + xhr.statusText);
    }
  };
  
  xhr.onerror = () => {
    alert('ネットワークエラーが発生しました');
  };
  
  xhr.send(formData);
});

画像のリサイズと圧縮

<input type="file" id="imageInput" accept="image/*">
<canvas id="canvas" style="display: none;"></canvas>
<img id="output" style="max-width: 100%;">
<div>
  <label>画質: <input type="range" id="quality" min="0" max="1" step="0.1" value="0.7"></label>
  <button id="download">ダウンロード</button>
</div>
const imageInput = document.getElementById('imageInput');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const output = document.getElementById('output');
const quality = document.getElementById('quality');
const download = document.getElementById('download');

let originalImage = null;

imageInput.addEventListener('change', (event) => {
  const file = event.target.files[0];
  
  if (file && file.type.startsWith('image/')) {
    const reader = new FileReader();
    
    reader.onload = (e) => {
      originalImage = new Image();
      originalImage.onload = () => {
        // 画像を最大幅800pxにリサイズ
        const maxWidth = 800;
        let width = originalImage.width;
        let height = originalImage.height;
        
        if (width > maxWidth) {
          height = (maxWidth / width) * height;
          width = maxWidth;
        }
        
        canvas.width = width;
        canvas.height = height;
        
        // キャンバスに描画
        ctx.drawImage(originalImage, 0, 0, width, height);
        
        // 圧縮した画像を表示
        resizeAndCompress();
      };
      originalImage.src = e.target.result;
    };
    
    reader.readAsDataURL(file);
  }
});

quality.addEventListener('input', resizeAndCompress);

function resizeAndCompress() {
  if (!originalImage) return;
  
  // 選択した品質で圧縮
  const dataUrl = canvas.toDataURL('image/jpeg', parseFloat(quality.value));
  output.src = dataUrl;
}

download.addEventListener('click', () => {
  if (!output.src) return;
  
  const link = document.createElement('a');
  link.download = 'resized-image.jpg';
  link.href = output.src;
  link.click();
});

ファイルの種類に応じた処理

MIME型による判別

ファイルのtype属性を使って、適切な処理を行うことができます。

function handleFile(file) {
  // MIMEタイプによる処理の振り分け
  if (file.type.startsWith('image/')) {
    // 画像ファイルの処理
    previewImage(file);
  } else if (file.type === 'application/pdf') {
    // PDFファイルの処理
    previewPDF(file);
  } else if (file.type.startsWith('text/')) {
    // テキストファイルの処理
    readTextFile(file);
  } else if (file.type.startsWith('audio/')) {
    // 音声ファイルの処理
    playAudio(file);
  } else if (file.type.startsWith('video/')) {
    // 動画ファイルの処理
    playVideo(file);
  } else {
    // その他のファイル
    console.log(`${file.name}は処理できないタイプです: ${file.type}`);
  }
}

// 各種ファイルごとの処理関数
function previewImage(file) {
  const reader = new FileReader();
  reader.onload = (e) => {
    const img = document.createElement('img');
    img.src = e.target.result;
    img.style.maxWidth = '100%';
    document.getElementById('preview').appendChild(img);
  };
  reader.readAsDataURL(file);
}

function previewPDF(file) {
  const reader = new FileReader();
  reader.onload = (e) => {
    const embed = document.createElement('embed');
    embed.src = e.target.result;
    embed.style.width = '100%';
    embed.style.height = '500px';
    document.getElementById('preview').appendChild(embed);
  };
  reader.readAsDataURL(file);
}

function readTextFile(file) {
  const reader = new FileReader();
  reader.onload = (e) => {
    const pre = document.createElement('pre');
    pre.textContent = e.target.result;
    document.getElementById('preview').appendChild(pre);
  };
  reader.readAsText(file);
}

function playAudio(file) {
  const reader = new FileReader();
  reader.onload = (e) => {
    const audio = document.createElement('audio');
    audio.src = e.target.result;
    audio.controls = true;
    document.getElementById('preview').appendChild(audio);
  };
  reader.readAsDataURL(file);
}

function playVideo(file) {
  const reader = new FileReader();
  reader.onload = (e) => {
    const video = document.createElement('video');
    video.src = e.target.result;
    video.controls = true;
    video.style.maxWidth = '100%';
    document.getElementById('preview').appendChild(video);
  };
  reader.readAsDataURL(file);
}

ファイル拡張子による判別

MIMEタイプが適切に設定されていない場合は、ファイル名から拡張子を取得して判別することもできます。

function getFileExtension(filename) {
  return filename.slice((filename.lastIndexOf('.') - 1 >>> 0) + 2).toLowerCase();
}

function handleFileByExtension(file) {
  const extension = getFileExtension(file.name);
  
  switch (extension) {
    case 'jpg':
    case 'jpeg':
    case 'png':
    case 'gif':
    case 'webp':
      previewImage(file);
      break;
    case 'pdf':
      previewPDF(file);
      break;
    case 'txt':
    case 'md':
    case 'js':
    case 'html':
    case 'css':
    case 'json':
      readTextFile(file);
      break;
    case 'mp3':
    case 'wav':
    case 'ogg':
      playAudio(file);
      break;
    case 'mp4':
    case 'webm':
    case 'mov':
      playVideo(file);
      break;
    default:
      console.log(`未知の拡張子: ${extension}`);
  }
}

Blobとの連携

FileオブジェクトはBlobを継承しているため、Blobで使用できるメソッドやプロパティを使用できます。

ファイルの一部だけを読み込む

Blobのsliceメソッドを使用すると、ファイルの一部だけを読み込むことができます。

const file = fileInput.files[0];
// ファイルの最初の1KBだけを取得
const chunk = file.slice(0, 1024);

const reader = new FileReader();
reader.onload = (e) => {
  console.log('ファイルの先頭1KB:', e.target.result);
};
reader.readAsText(chunk);

Blobからファイルを作成

Blobオブジェクトから新しいFileオブジェクトを作成することもできます。

// テキストからBlobを作成
const text = 'Hello, World!';
const blob = new Blob([text], { type: 'text/plain' });

// BlobからFileオブジェクトを作成
const file = new File([blob], 'hello.txt', { type: 'text/plain', lastModified: Date.now() });

console.log(file.name); // "hello.txt"
console.log(file.size); // 13
console.log(file.type); // "text/plain"

セキュリティの考慮事項

ファイル操作を行う際には、セキュリティ上の考慮事項があります。

ファイルサイズの制限

大きすぎるファイルは処理に時間がかかり、メモリ不足を引き起こす可能性があります。

function validateFileSize(file, maxSizeMB) {
  const maxSizeBytes = maxSizeMB * 1024 * 1024;
  
  if (file.size > maxSizeBytes) {
    alert(`ファイルサイズが大きすぎます。${maxSizeMB}MB以下のファイルを選択してください。`);
    return false;
  }
  
  return true;
}

// 使用例
const file = fileInput.files[0];
if (validateFileSize(file, 5)) { // 5MB制限
  // ファイル処理
}

ファイル型の検証

潜在的な脆弱性を減らすために、期待するファイル型のみを受け入れるようにしましょう。

function validateFileType(file, allowedTypes) {
  // allowedTypes は ['image/jpeg', 'image/png'] などの配列
  if (!allowedTypes.includes(file.type)) {
    alert('許可されていないファイル形式です。');
    return false;
  }
  
  return true;
}

// 使用例
const file = fileInput.files[0];
const allowedImageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];

if (validateFileType(file, allowedImageTypes)) {
  // 画像ファイル処理
}

モダンなFileSystem Access API

最新のブラウザでは、FileSystem Access APIを使用して、より強力なファイル操作が可能になっています。このAPIはまだ実験的な機能ですが、ユーザーがファイルを保存したり、ディレクトリ全体にアクセスしたりすることができます。

async function openFile() {
  try {
    // ファイル選択ダイアログを表示
    const [fileHandle] = await window.showOpenFilePicker();
    // ファイルのハンドルからFileオブジェクトを取得
    const file = await fileHandle.getFile();
    
    console.log('選択されたファイル:', file.name);
    return { file, fileHandle };
  } catch (err) {
    console.error('ファイルを開けませんでした:', err);
    return null;
  }
}

async function saveFile(content, suggestedName = 'document.txt') {
  try {
    // ファイル保存ダイアログを表示
    const options = {
      types: [{
        description: 'Text Files',
        accept: { 'text/plain': ['.txt'] }
      }],
      suggestedName: suggestedName
    };
    
    const fileHandle = await window.showSaveFilePicker(options);
    // 書き込み可能なストリームを取得
    const writable = await fileHandle.createWritable();
    // コンテンツを書き込む
    await writable.write(content);
    // ストリームを閉じる
    await writable.close();
    
    console.log('ファイルが保存されました');
    return true;
  } catch (err) {
    console.error('ファイルを保存できませんでした:', err);
    return false;
  }
}

// 使用例
document.getElementById('openBtn').addEventListener('click', async () => {
  const result = await openFile();
  if (result) {
    const { file } = result;
    const text = await file.text();
    document.getElementById('editor').value = text;
  }
});

document.getElementById('saveBtn').addEventListener('click', async () => {
  const content = document.getElementById('editor').value;
  await saveFile(content, 'document.txt');
});

まとめ

Fileオブジェクトは、ウェブアプリケーションにおいてユーザーがローカルファイルを操作するための強力なインターフェースを提供します。本章では、以下の内容を学びました。

  • Fileオブジェクトの基本的なプロパティとメソッド
  • ファイル選択フォームやドラッグ&ドロップからのファイル取得方法
  • FileReaderを使ったファイル内容の読み込み
  • 様々なファイル形式(テキスト、画像、PDF、音声、動画など)の処理
  • ファイルのリサイズや圧縮などの実践的な操作
  • セキュリティ上の考慮事項
  • モダンなFileSystem Access APIの概要

これらの知識を活用することで、ファイルアップロード、プレビュー、編集機能を備えた高機能なウェブアプリケーションを構築することができます。


附録

静的プロパティ

File.arguments
File.caller
File.length
File.name
File.prototype

静的アクセサ

静的メソッド

Fileのインスタンスプロパティ

File.prototype [ Symbol.toStringTag ]

Fileのインスタンスアクセサ

get File.prototype.lastModified
get File.prototype.lastModifiedDate
get File.prototype.name
get File.prototype.webkitRelativePath

Fileのインスタンスメソッド

File.prototype.constructor()

Blobのインスタンスプロパティ

Blob.prototype [ Symbol.toStringTag ]

Blobのインスタンスアクセサ

get Blob.prototype.size
get Blob.prototype.type

Blobのインスタンスメソッド

Blob.prototype.arrayBuffer()
Blob.prototype.constructor()
Blob.prototype.slice()
Blob.prototype.stream()
Blob.prototype.text()
カテゴリ:JavaScript
カテゴリ:JavaScript