JavaScript FileReader

概要: このチュートリアルでは、JavaScript FileReader API について学び、ファイルアップロードの実装方法を理解します。

JavaScript FileReader API入門

ファイルをウェブブラウザにドラッグアンドドロップしたり、ファイル入力要素を使ってアップロードするファイルを選択したりすると、JavaScript は各ファイルをFileオブジェクトとして表現します。

Fileオブジェクトを使用すると、JavaScript で選択したファイルにアクセスできます。 また、JavaScript はFileListオブジェクトを使用してFileオブジェクトを保持します。

ファイルの内容を読み取るには、FileReaderオブジェクトを使用します。 FileReaderは、ドラッグアンドドロップまたはファイル入力によって選択したファイルにのみアクセスできることに注意してください。

FileReaderオブジェクトを使用するには、次の手順に従います。

まず、新しいFileObjectを作成します。

const reader = new FileReader();Code language: JavaScript (javascript)

次に、読み取りメソッドのいずれかを呼び出して、ファイルの内容を読み取ります。 例えば

reader.readAsDataURL(file);Code language: JavaScript (javascript)

readAsDataURL()メソッドは、FileListオブジェクトから取得したファイルの内容を読み取ります。

readAsDataURL()メソッドは、データをdata: URLとして含む`result`プロパティを持つオブジェクトを返します。 data:URLは、ファイルのデータをbase64エンコードされた文字列として表します。

たとえば、readAsDataURL()を使用して画像を読み取り、そのbase64エンコードされた文字列をWebページに表示できます。

readAsDataURL()メソッドに加えて、FileReaderには、readAsText()readAsBinaryString()readAsArrayBuffer()など、ファイルデータを読み取るための他のメソッドがあります。

これらのメソッドはすべてファイルのデータを非同期的に読み取るため、次のように結果を返すことはできません。

const data = reader.readAsDataURL(file);Code language: JavaScript (javascript)

readAsDataURL()メソッドがファイルの読み取りに成功すると、FileReaderloadイベントを発生させます。

3 番目に、FileReaderオブジェクトのloadイベントを処理するイベントハンドラーを追加します。

reader.addEventListener('load', (e) => {
    const data = e.target.result;
}Code language: JavaScript (javascript)

JavaScript FileReader を使用した画像アップロードアプリケーションの実装

FileReaderを使用して、画像アップロードアプリケーションを実装します。

JavaScript FileReader API Demo

ドロップゾーンに画像をドラッグアンドドロップすると、アプリケーションはFileReaderを使用して画像を読み取り、ファイル名とファイルサイズとともにページに表示します。

JavaScript FileReader API Demo

また、アプリケーションはFetch APIを使用してファイルをサーバーにアップロードします。

サーバー側では、画像をサーバーの「uploads」フォルダにアップロードする簡単なPHPスクリプトを実装します。

プロジェクト構造の設定

まず、次のファイルとディレクトリ構造を作成します。

├── css
|  └── style.css
├── images
|  └── upload.svg
├── js
|  └── app.js
├── index.html
├── upload.php
└── uploadsCode language: JavaScript (javascript)

index.html

以下は、`index.html`ファイルの内容です。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="css/style.css" />
    <title>FileReader API Demo - Image Upload Application</title>
</head>
<body>
    <main>
        <div class="dropzone">
            <img src="images/upload.svg" alt="upload" width="60" />

            <input type="file" class="files" id="images"
                    accept="image/png, image/jpeg"
                    multiple />
             <label for="images">Choose multiple images</label>

            <h3>or drag & drop your PNG or JPEG files here</h3>
        </div>
        <div class="image-list"></div>
    </main>
    <script src="js/app.js"></script>
</body>
</html>Code language: JavaScript (javascript)

`index.html`では、`css/style.css`を`html`ドキュメントの`head`に、`js/app.js`を`body`タグの終了前に追加します。

クラス`dropzone`を持つ`div`要素を使用すると、画像をドラッグアンドドロップできます。 また、ファイル入力要素を使用すると、アップロードするファイルを選択できます。

ファイル入力要素は複数のファイルを受け入れ、jpegおよびpng画像のみを許可します。

<input type="file" class="files" id="images"
    accept="image/png, image/jpeg"
    multiple />Code language: JavaScript (javascript)

`style.css`は、ファイル入力要素をボタンに変換するスタイルを提供します。 また、ファイルをドラッグしたときにドロップゾーンを強調表示する`active`クラスも含まれています。

クラス`image-list`を持つ`div`要素には、アップロードされた画像が表示されます。

app.js

まず、`querySelector()`メソッドを使用して、ドロップゾーン、ファイル入力(ファイル)、および画像リスト要素を選択します。

const imageList = document.querySelector('.image-list');
const fileInput = document.querySelector('.files');
const dropzone = document.querySelector('.dropzone');Code language: JavaScript (javascript)

次に、ドロップゾーンに`active`クラスを追加または削除する関数を定義します。

const setActive = (dropzone, active = true) => {
    const hasActiveClass = dropzone.classList.contains('active');

    if (active && !hasActiveClass) {
        return dropzone.classList.add('active');
    }

    if (!active && hasActiveClass) {
        return dropzone.classList.remove('active');
    }
};Code language: JavaScript (javascript)

`setActive(dropzone)`を呼び出すと、`dropzone`に`active`クラスが追加されます。 `setActive(dropzone, false)`を呼び出すと、`dropzone`から`active`クラスが削除されます。

3 番目に、`dragenter`および`dragover`イベントが発生したときにドロップゾーンを強調表示し、`dragleave`および`drop`イベントが発生したときに強調表示を削除します。

dropzone.addEventListener('dragenter', (e) => {
    e.preventDefault();
    setActive(dropzone);
});

dropzone.addEventListener('dragover', (e) => {
    e.preventDefault();
    setActive(dropzone);
});

dropzone.addEventListener('dragleave', (e) => {
    e.preventDefault();
    setActive(dropzone, false);
});

dropzone.addEventListener('drop', (e) => {
    e.preventDefault();
    setActive(dropzone, false);
    // ..
});Code language: JavaScript (javascript)

4番目に、`dropzone`の`drop`イベントハンドラーで、`e.target`内の`FileList`オブジェクトを`e.target.files`として取得します。

dropzone.addEventListener('drop', (e) => {
    e.preventDefault();
    setActive(dropzone, false);
    // get the FileList
    const { files } = e.dataTransfer;
    handleImages(files);
});Code language: JavaScript (javascript)

ドロップイベントハンドラーでは、オブジェクトの分割代入を使用して`FileList`オブジェクトを取得し、`handleImages()`関数を呼び出してアップロードされた画像を処理します。

5番目に、`handleImages()`関数を定義します。

const handleImages = (files) => {
    // get valid images
    let validImages = [...files].filter((file) =>
        ['image/jpeg', 'image/png'].includes(file.type)
    );
    //  show the image
    validImages.forEach(showImage);

    // upload all images
    uploadImages(validImages);
};Code language: JavaScript (javascript)

`handleImages()`関数は、有効な画像を取得し、`showImage()`関数を使用して各有効な画像をページに表示し、`uploadImages()`関数を使用してすべての画像をサーバーにアップロードします。

6番目に、`validImages`配列の各画像を表示する`showImage()`関数を定義します。

const showImage = (image) => {
    const reader = new FileReader();
    reader.readAsDataURL(image);
    reader.addEventListener('load', (e) => {
        const div = document.createElement('div');
        div.classList.add('image');
        div.innerHTML = `
            <img src="${e.target.result}" alt="${image.name}">
            <p>${image.name}</p>
            <p>${formatBytes(image.size)}</p>
        `;
        imageList.appendChild(div);
    });
};Code language: JavaScript (javascript)

`showImage()`は、`FileReader`を使用して、アップロードされた画像をデータURLとして読み取ります。 `FileReader`がファイルの読み取りを完了すると、画像情報を保持する新しい`div`要素を作成します。

`formatBytes()`関数は、バイト単位のサイズを人間が読める形式に変換することに注意してください。

function formatBytes(size, decimals = 2) {
    if (size === 0) return '0 bytes';
    const k = 1024;
    const dm = decimals < 0 ? 0 : decimals;
    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
    const i = Math.floor(Math.log(size) / Math.log(k));
    return parseFloat((size / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}Code language: JavaScript (javascript)

7番目に、すべての画像をサーバーにアップロードする`uploadImages()`関数を定義します。

const uploadImages = async (images) => {

    const formData = new FormData();

    [...images].forEach((image) =>
        formData.append('images[]', image, image.name)
    );

    const response = await fetch('upload.php', {
        method: 'POST',
        body: formData,
    });

    return await response.json();
};Code language: JavaScript (javascript)

`uploadImages()`関数は、`FormData` APIを使用して送信用のデータを作成します。

const formData = new FormData();Code language: JavaScript (javascript)

画像ごとに、`FormData`オブジェクトに追加します.

[...images].forEach((image) =>
    formData.append('images[]', image, image.name)
);Code language: JavaScript (javascript)

`images`変数は配列ではなく`FileList`オブジェクトであることに注意してください。 `forEach()`メソッドを使用するには、スプレッド演算子(`...`)を使用して、`FileList`オブジェクトを次のように配列に変換します。

[...images]Code language: JavaScript (javascript)

フォームデータのすべてのキーと値のペアは、`images[]`と同じキーを持ちます。 PHPでは、配列(`$_FILES['images']`)としてアクセスできます。

`uploadImages()`関数は、Fetch APIを使用して画像(`FormData`オブジェクトとして)をサーバーにアップロードします。

const response = await fetch('upload.php', {
    method: 'POST',
    body: formData,
});

return await response.json();Code language: JavaScript (javascript)

8番目に、ユーザーがこの入力要素を使用してファイルを選択した場合に備えて、ファイル入力要素にchangeイベントハンドラーを追加します。

fileInput.addEventListener('change', (e) => {
    const { files } = e.target;
    handleImages(files);
});Code language: JavaScript (javascript)

変更イベントハンドラーでは、`FileList`オブジェクトに`e.target.files`としてアクセスできます。 画像の表示とアップロードのロジックは、ドラッグアンドドロップと同じです。

ドロップゾーンの外に画像をドラッグアンドドロップすると、Webブラウザはデフォルトで画像を表示することに注意してください。

これを防ぐには、ドキュメントの`dragover`および`drop`イベントオブジェクトの`preventDefault()`メソッドを次のように呼び出します.

// prevent the drag & drop on the page
document.addEventListener('dragover', (e) => e.preventDefault());
document.addEventListener('drop', (e) => e.preventDefault());Code language: JavaScript (javascript)

`app.js`ファイルの完全なコードは次のとおりです。

const imageList = document.querySelector('.image-list');
const fileInput = document.querySelector('.files');
const dropzone = document.querySelector('.dropzone');

const setActive = (dropzone, active = true) => {
    // active class
    const hasActiveClass = dropzone.classList.contains('active');

    if (active && !hasActiveClass) {
        return dropzone.classList.add('active');
    }

    if (!active && hasActiveClass) {
        return dropzone.classList.remove('active');
    }
};

dropzone.addEventListener('dragenter', (e) => {
    e.preventDefault();
    setActive(dropzone);
});

dropzone.addEventListener('dragover', (e) => {
    e.preventDefault();
    setActive(dropzone);
});

dropzone.addEventListener('dragleave', (e) => {
    e.preventDefault();
    setActive(dropzone, false);
});

dropzone.addEventListener('drop', (e) => {
    e.preventDefault();
    setActive(dropzone, false);
    // get the valid files
    const { files } = e.dataTransfer;
    // hand images
    handleImages(files);
});

const handleImages = (files) => {
    // get valid images
    let validImages = [...files].filter((file) =>
        ['image/jpeg', 'image/png'].includes(file.type)
    );
    //  show the image
    validImages.forEach(showImage);
    // upload files
    uploadImages(validImages);
};

const showImage = (image) => {
    const reader = new FileReader();
    reader.readAsDataURL(image);
    reader.addEventListener('load', (e) => {
        const div = document.createElement('div');
        div.classList.add('image');
        div.innerHTML = `
            <img src="${e.target.result}" alt="${image.name}">
            <p>${image.name}</p>
            <p>${formatBytes(image.size)}</p>
        `;
        imageList.appendChild(div);
    });
};

const uploadImages = async (images) => {
    const formData = new FormData();

    [...images].forEach((image) =>
        formData.append('images[]', image, image.name)
    );

    const response = await fetch('upload.php', {
        method: 'POST',
        body: formData,
    });

    return await response.json();
};

function formatBytes(size, decimals = 2) {
    if (size === 0) return '0 bytes';
    const k = 1024;
    const dm = decimals < 0 ? 0 : decimals;
    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];

    const i = Math.floor(Math.log(size) / Math.log(k));

    return parseFloat((size / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}

fileInput.addEventListener('change', (e) => {
    const { files } = e.target;
    handleImages(files);
});

// prevent the drag & drop on the page
document.addEventListener('dragover', (e) => e.preventDefault());
document.addEventListener('drop', (e) => e.preventDefault());Code language: JavaScript (javascript)

最後に、アップロードされた画像を`uploads`フォルダに移動する簡単な`upload.php`スクリプトを作成します。

<?php

const APP_ROOT = 'http://localhost:8080/';

const UPLOAD_DIR = __DIR__ . '/uploads';

const MESSAGES = [
    UPLOAD_ERR_OK => 'File uploaded successfully',
    UPLOAD_ERR_INI_SIZE => 'File is too big to upload',
    UPLOAD_ERR_FORM_SIZE => 'File is too big to upload',
    UPLOAD_ERR_PARTIAL => 'File was only partially uploaded',
    UPLOAD_ERR_NO_FILE => 'No file was uploaded',
    UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder on the server',
    UPLOAD_ERR_CANT_WRITE => 'File is failed to save to disk.',
    UPLOAD_ERR_EXTENSION => 'File is not allowed to upload to this server',
];

const ALLOWED_FILES = [
    'image/png' => 'png',
    'image/jpeg' => 'jpg'
];

const MAX_SIZE = 5 * 1024 * 1024; //  5MB

const HTTP_STATUSES = [
    200 => 'OK',
    400 => 'Bad Request',
    404 => 'Not Found',
    405 => 'Method Not Allowed'
];


$is_post_request = strtolower($_SERVER['REQUEST_METHOD']) === 'post';
$has_files = isset($_FILES['images']);

if (!$is_post_request || !$has_files) {
    response(405, [
        'success' => false,
        'message' => ' Method not allowed or files do not exist'
    ]);
}

$files = $_FILES['images'];
$file_count = count($files['name']);

// validation
$errors = [];
for ($i = 0; $i < $file_count; $i++) {
    // get the uploaded file info
    $status = $files['error'][$i];
    $filename = $files['name'][$i];
    $tmp = $files['tmp_name'][$i];

    // an error occurs
    if ($status !== UPLOAD_ERR_OK) {
        $errors[$filename] = MESSAGES[$status];
        continue;
    }
    // validate the file size
    $filesize = filesize($tmp);

    if ($filesize > MAX_SIZE) {
        // construct an error message
        $message = sprintf(
            "The file %s is %s which is greater than the allowed size %s",
            $filename,
            format_filesize($filesize),
            format_filesize(MAX_SIZE)
        );

        $errors[$filesize] = $message;
        continue;
    }

    // validate the file type
    if (!in_array(get_mime_type($tmp), array_keys(ALLOWED_FILES))) {
        $errors[$filename] = "The file $filename is allowed to upload";
    }
}

if ($errors) {
    response(400, [
        'success' => false,
        'message' => $errors
    ]);
}

// move the files
for ($i = 0; $i < $file_count; $i++) {
    $filename = $files['name'][$i];
    $tmp = $files['tmp_name'][$i];
    $mime_type = get_mime_type($tmp);

    // set the filename as the basename + extension
    $uploaded_file = pathinfo($filename, PATHINFO_FILENAME) . '.' . ALLOWED_FILES[$mime_type];
    // new filepath
    $filepath = UPLOAD_DIR . '/' . $uploaded_file;

    // move the file to the upload dir
    $success = move_uploaded_file($tmp, $filepath);
    if (!$success) {
        $errors[$filename] = "The file $filename was failed to move.";
    }
}

if ($errors) {
    response(400, [
        'success' => false,
        'message' => $errors
    ]);
}

response(200, [
    'success' => true,
    'message' => 'The files uploaded successfully'
]);


/**
 * Return a mime type of file or false if an error occurred
 *
 * @param string $filename
 * @return string | bool
 */
function get_mime_type(string $filename)
{
    $info = finfo_open(FILEINFO_MIME_TYPE);
    if (!$info) {
        return false;
    }

    $mime_type = finfo_file($info, $filename);
    finfo_close($info);

    return $mime_type;
}

/**
 * Return a human-readable file size
 *
 * @param int $bytes
 * @param int $decimals
 * @return string
 */
function format_filesize(int $bytes, int $decimals = 2): string
{
    $units = 'BKMGTP';
    $factor = floor((strlen($bytes) - 1) / 3);

    return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . $units[(int)$factor];
}

/**
 * Response JSON to the client
 * @param int $status_code
 * @param array|null $data
 */
function response(int $status_code, array $data = null)
{
    header("HTTP/1.1 " . $status_code . " " . HTTP_STATUSES[$status_code]);
    header("Content-Type: application/json");
    echo json_encode($data);
    exit;
}
Code language: JavaScript (javascript)

詳細については、PHPで複数のファイルをアップロードする方法をご覧ください。

まとめ

  • JavaScript FileReader APIを使用して、ユーザーがドラッグアンドドロップまたはファイル入力要素で選択したファイルを読み取ります。
このチュートリアルは役に立ちましたか?