Leitor de arquivos JavaScript

Resumo : neste tutorial, você aprenderá sobre a API JavaScript FileReader e como usá-la para implementar o upload de arquivos.

Introdução à API JavaScript FileReader

Quando você arrasta e solta arquivos no navegador da web ou seleciona arquivos para upload por meio do elemento de entrada de arquivo, o JavaScript representa cada arquivo como um Fileobjeto.

O Fileobjeto permite acessar o arquivo selecionado em JavaScript. E o JavaScript usa o FileListobjeto para armazenar os Fileobjetos.

Para ler o conteúdo de um arquivo, você usa o FileReaderobjeto. Observe que o FileReaderúnico pode acessar os arquivos selecionados por meio de arrastar e soltar ou entrada de arquivo.

Para usar o FileReaderobjeto, siga estas etapas:

Primeiro, crie um novo FileObject:

const reader = new FileReader();Linguagem de código:  JavaScript  ( javascript )

Segundo, chame um dos métodos read para ler o conteúdo de um arquivo. Por exemplo:

reader.readAsDataURL(file);Linguagem de código:  JavaScript  ( javascript )

O readAsDataURL()método lê o conteúdo do arquivo, que você obtém do FileListobjeto.

O readAsDataURL()método retorna um objeto com a resultpropriedade que contém os dados como um arquivo data: URL. O data:URLrepresenta os dados do arquivo como uma string codificada em base64.

Por exemplo, você pode usar readAsDataURL()para ler uma imagem e mostrar sua string codificada em base64 em uma página da web.

Além do readAsDataURL()método, FileReaderpossui outros métodos para leitura de dados de arquivos como readAsText(), readAsBinaryString(), e readAsArrayBuffer().

Como todos esses métodos leem os dados do arquivo de forma assíncrona, você não pode simplesmente retornar o resultado assim:

const data = reader.readAsDataURL(file);Linguagem de código:  JavaScript  ( javascript )

Quando o readAsDataURL()método conclui a leitura do arquivo com sucesso, ele FileReaderdispara o loadevento.

Terceiro, adicione um manipulador de eventos para manipular o loadevento do FileReaderobjeto:

reader.addEventListener('load', (e) => {
    const data = e.target.result;
}Linguagem de código:  JavaScript  ( javascript )

Usando JavaScript FileReader para implementar um aplicativo de upload de imagens

Usaremos o FileReaderpara implementar um aplicativo de upload de imagens :

Demonstração da API FileReader JavaScript

Quando você arrasta e solta imagens na zona para soltar, o aplicativo usará o FileReaderpara ler as imagens e mostrá-las na página junto com o nome e o tamanho do arquivo:

Demonstração da API FileReader JavaScript

Além disso, o aplicativo usará a API Fetch para fazer upload dos arquivos para o servidor.

Para o lado do servidor, implementaremos um script PHP simples que carrega as imagens para a 'uploads'pasta no servidor.

Configurando a estrutura do projeto

Primeiro, crie a seguinte estrutura de arquivos e diretórios:

├── css
|  └── style.css
├── images
|  └── upload.svg
├── js
|  └── app.js
├── index.html
├── upload.php
└── uploadsLinguagem de código:  JavaScript  ( javascript )

index.html

O seguinte mostra o index.htmlarquivo:

<!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>Linguagem de código:  JavaScript  ( javascript )

No index.html, adicionamos o css/style.cssao headdo htmldocumento e js/app.jsantes da tag envolvente body.

O divelemento com a classe dropzonepermite arrastar e soltar imagens nele. Além disso, o elemento de entrada de arquivo permitirá que você selecione os arquivos para upload.

O elemento de entrada file aceita vários arquivos e permite apenas imagens jpeg e png:

<input type="file" class="files" id="images"
    accept="image/png, image/jpeg"
    multiple />Linguagem de código:  JavaScript  ( javascript )

Fornece style.cssos estilos que transformam o elemento de entrada do arquivo em um botão. Além disso, possui a activeclasse que destaca a zona de lançamento quando você arrasta o arquivo para ela.

O divelemento com a image-listclasse mostrará as imagens enviadas.

aplicativo.js

Primeiro, selecione dropzone, entrada de arquivo (arquivos) e elementos da lista de imagens usando o querySelector()método:

const imageList = document.querySelector('.image-list');
const fileInput = document.querySelector('.files');
const dropzone = document.querySelector('.dropzone');Linguagem de código:  JavaScript  ( javascript )

Segundo, defina uma função que adicione ou remova a classe ativa da zona de lançamento:

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');
    }
};Linguagem de código:  JavaScript  ( javascript )

Se você ligar setActive(dropzone), ele adicionará a classe ativa ao arquivo dropzone. Se você ligar setActive(dropzone, false), isso removerá a activeclasse do dropzone.

Terceiro, destaque dropzonequando os eventos dragentere dragoverocorrem e remova o destaque quando os eventos dragleavee dropocorrem:

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);
    // ..
});Linguagem de código:  JavaScript  ( javascript )

Quarto, obtenha o FileListobjeto no e.targetmanipulador e.target.filesde dropeventos de dropzone:

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

No manipulador de eventos drop, usamos a desestruturação de objetos para obter o FileListobjeto e chamar a handleImages()função para manipular as imagens carregadas:

Quinto, defina a handleImages()função:

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);
};Linguagem de código:  JavaScript  ( javascript )

A handleImages()função obtém as imagens válidas, mostra cada imagem válida na página usando a showImage()função e carrega todas as imagens para o servidor usando a uploadImages()função.

Sexto, defina a showImage()função que mostra cada imagem do validImagesarray:

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);
    });
};Linguagem de código:  JavaScript  ( javascript )

O showImage()usa FileReaderpara ler a imagem carregada como o URL de dados. Assim que FileReaderconcluir a leitura do arquivo, ele criará um novo divelemento para armazenar as informações da imagem.

Observe que a formatBytes()função converte o tamanho em bytes em um formato legível por humanos:

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];
}Linguagem de código:  JavaScript  ( javascript )

Sétimo, defina a uploadImages()função que carrega todas as imagens para o servidor:

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();
};Linguagem de código:  JavaScript  ( javascript )

A uploadImages()função usa a FormDataAPI para construir dados para envio:

const formData = new FormData();Linguagem de código:  JavaScript  ( javascript )

Para cada imagem, nós a adicionamos ao FormDataobjeto:

[...images].forEach((image) =>
    formData.append('images[]', image, image.name)
);Linguagem de código:  JavaScript  ( javascript )

Observe que a imagesvariável é um FileListobjeto, não um array. Para usar o forEach()método, você usa o operador spread ( ...) para converter o FileListobjeto em um array como este:

[...images]Linguagem de código:  JavaScript  ( javascript )

Todos os pares chave/valor nos dados do formulário têm a mesma chave que images[]; em PHP, você pode acessá-lo como um array ( $_FILES['images'])

A uploadImages()função usa a API Fetch para fazer upload das imagens (como FormDataobjeto) para o servidor:

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

return await response.json();Linguagem de código:  JavaScript  ( javascript )

Oitavo, adicione o manipulador de eventos change ao elemento de entrada do arquivo se os usuários selecionarem arquivos usando este elemento de entrada:

fileInput.addEventListener('change', (e) => {
    const { files } = e.target;
    handleImages(files);
});Linguagem de código:  JavaScript  ( javascript )

No manipulador de eventos change, você pode acessar o objeto FileList como arquivo e.target.files. A lógica para mostrar e fazer upload de imagens é a mesma de arrastar e soltar.

Observe que se você arrastar e soltar imagens fora da zona de lançamento, o navegador da web exibirá as imagens por padrão.

Para evitar isso, você chama o preventDefault()método dos objetos de evento dragovere dropdo documento assim:

// prevent the drag & drop on the page
document.addEventListener('dragover', (e) => e.preventDefault());
document.addEventListener('drop', (e) => e.preventDefault());Linguagem de código:  JavaScript  ( javascript )

A seguir mostra o app.jsarquivo completo:

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());Linguagem de código:  JavaScript  ( javascript )

Por fim, crie um upload.phpscript simples que mova as imagens enviadas para a uploadspasta:

<?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;
}
Linguagem de código:  JavaScript  ( javascript )

Leia mais sobre como fazer upload de vários arquivos em PHP para obter mais informações.

Resumo

  • Use a API JavaScript FileReader para ler os arquivos selecionados pelos usuários por meio de arrastar e soltar ou elemento de entrada de arquivo.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *