Стилизация Input File

Сегодня будем стилизовать input type file. Т.к. стилизация тесно связана с обработкой input file на фронтенде, мы так же рассмотрим, как обработать выбранный файл и отправить на сервер. Скрипт будет работать для множества форм с возможностью не только загрузить файл при клике на кнопку, но так же перетаскиванием файла (drag-and-drop) в область загрузки. Если вам не нужен тот или иной функционал, вы легко сможете удалить часть кода.

Содержание:

  1. кнопка «Прикрепить файл»
    1. HTML, CSS
    2. JavaScript
  2. drag-and-drop загрузка файлов
    1. HTML, CSS
    2. JavaScript
  3. совместное использование кнопки «Прикрепить файл» и Drag-and-drop
  4. отправка множества input file multiple (PHP)

1. Стилизация input type file

1.1 Кнопка «Прикрепить файл» — HTML, CSS

Сначала нам необходимо создать html-разметку.

  • multiple — данный атрибут необходим, если вы хотите разрешить отправку более одного файла;
  • accept — в данный атрибут запишите типы файлов (MIME-типы), которые разрешены для выбора.
<form>
    <input type="text" name="Ваше имя">
    <input type="email" name="E-mail">

    <div class="upload-file__wrapper">
        <input
            type="file"
            name="files[]"
            id="upload-file__input_1"
            class="upload-file__input"
            accept=".jpg, .jpeg, .png, .gif, .bmp, .doc, .docx, .xls, .xlsx, .txt, .tar, .zip, .7z, .7zip"
            multiple
        >
        <label class="upload-file__label" for="upload-file__input_1">
            <svg class="upload-file__icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
                <path d="M286 384h-80c-14.2 1-23-10.7-24-24V192h-87.7c-17.8 0-26.7-21.5-14.1-34.1L242.3 5.7c7.5-7.5 19.8-7.5 27.3 0l152.2 152.2c11.6 11.6 3.7 33.1-13.1 34.1H320v168c0 13.3-10.7 24-24 24zm216-8v112c0 13.3-10.7 24-24 24H24c-13.3 0-24-10.7-23-23V366c0-13.3 10.7-24 24-24h136v8c0 31 24.3 56 56 56h80c30.9 0 55-26.1 57-55v-8h135c13.3 0 24 10.6 24 24zm-124 88c0-11-9-20-19-20s-19 9-20 20 9 19 20 20 21-9 20-20zm64 0c0-12-9-20-20-20s-20 9-19 20 9 20 20 20 21-9 20-20z"></path>
            </svg>
            <span class="upload-file__text">Прикрепить файл</span>
        </label>
    </div>

    <button type="submit">Отправить</button>
</form>

Скроем input и стилизуем кнопку для загрузки файла.

.upload-file__input {
    opacity: 0;
    position: absolute;
    z-index: -1;
    overflow: hidden;
    width: 0.4px;
    height: 0.4px;
}

.upload-file__label {
    display: flex;
    justify-content: center;
    align-items: center;
    max-width: 240px;
    border: 2px dashed #150B22;
    padding: 9px 49px;
    border-radius: 6px;
    cursor: pointer;
}

.upload-file__icon {
    width: 30px;
    height: auto;
    margin-right: 11px;
}

.upload-file__label .upload-file__text,
.upload-file__label .upload-file__icon path {
    transition: .25s ease;
}

.upload-file__label:hover .upload-file__text {
    color: #58862D;
}

.upload-file__label:hover .upload-file__icon path {
    fill: #58862D;
}

.upload-file__label:hover {
    border: 2px dashed #58862D;
}

1.2 Кнопка «Прикрепить файл» — JavaScript

document.addEventListener("DOMContentLoaded", () => {

    const forms = document.querySelectorAll("form");
    const inputFile = document.querySelectorAll(".upload-file__input");

    /////////// Кнопка «Прикрепить файл» ///////////
    inputFile.forEach(function(el) {
        let textSelector = document.querySelector(".upload-file__text");
        let fileList;

        // Событие выбора файла(ов)
        el.addEventListener("change", function (e) {

            // создаём массив файлов
            fileList = [];
            for (let i = 0; i < el.files.length; i++) {
                fileList.push(el.files[i]);
            }

            // вызов функции для каждого файла
            fileList.forEach(file => {
                uploadFile(file);
            });
        });

        // Проверяем размер файлов и выводим название
        const uploadFile = (file) => {

            // файла <5 Мб
            if (file.size > 5 * 1024 * 1024) {
                alert("Файл должен быть не более 5 МБ.");
                return;
            }

            // Показ загружаемых файлов
            if (file && file.length > 1) {
                if ( file.length <= 4 ) {
                    textSelector.textContent = `Выбрано ${file.length} файла`;
                }
                if ( file.length > 4 ) {
                    textSelector.textContent = `Выбрано ${file.length} файлов`;
                }
            } else {
                textSelector.textContent = file.name;
            }
        }

    });

    // Отправка формы на сервер
    const postData = async (url, fData) => { // имеет асинхронные операции

        // начало отправки
        // здесь можно оповестить пользователя о начале отправки

        // ждём ответ, только тогда наш код пойдёт дальше
        let fetchResponse = await fetch(url, {
            method: "POST",
            body: fData
        });

        // ждём окончания операции
        return await fetchResponse.text();
    };

    if (forms) {
        forms.forEach(el => {
            el.addEventListener("submit", function (e) {
                e.preventDefault();

                // создание объекта FormData
                let fData = new FormData(this);

                // Добавление файлов input type file
                let file = el.querySelector(".upload-file__input");
                for (let i = 0; i < (file.files.length); i++) {
                    fData.append("files[]", file.files[i]); // добавляем файлы в объект FormData()
                }

                // Отправка на сервер
                postData("./mail.php", fData)
                    .then(fetchResponse => {
                        console.log("Данные успешно отправлены!");
                        console.log(fetchResponse);
                    })
                    .catch(function (error) {
                        console.log("Ошибка!");
                        console.log(error);
                    });
            });
        });
    };

});

2. Drag-and-drop загрузка файлов

Структура останется прежней, т.к. наша первоначальная разметка вполне подойдёт для создания drag-and-drop области.

2.1 Drag-and-drop — HTML, CSS

<form>
    <input type="text" name="Ваше имя">
    <input type="email" name="E-mail">

    <div class="upload-file__wrapper">
        <input type="file" name="files" id="upload-file__input" class="upload-file__input" multiple  accept="image/jpeg,image/png,image/gif">
        <label class="upload-file__label" for="upload-file__input">
            <svg class="upload-file__icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
                <path d="M286 384h-80c-14.2 1-23-10.7-24-24V192h-87.7c-17.8 0-26.7-21.5-14.1-34.1L242.3 5.7c7.5-7.5 19.8-7.5 27.3 0l152.2 152.2c11.6 11.6 3.7 33.1-13.1 34.1H320v168c0 13.3-10.7 24-24 24zm216-8v112c0 13.3-10.7 24-24 24H24c-13.3 0-24-10.7-23-23V366c0-13.3 10.7-24 24-24h136v8c0 31 24.3 56 56 56h80c30.9 0 55-26.1 57-55v-8h135c13.3 0 24 10.6 24 24zm-124 88c0-11-9-20-19-20s-19 9-20 20 9 19 20 20 21-9 20-20zm64 0c0-12-9-20-20-20s-20 9-19 20 9 20 20 20 21-9 20-20z">
                </path>
            </svg>
            <span class="upload-file__text">Прикрепить файл</span>
        </label>
    </div>

    <button type="submit">Отправить</button>
</form>

Улучшим юсабилити путём добавления стилей для drag-and-drop.

/* drag and drop - "hover" */
.upload-file__label.hover .upload-file__text {
    color: #ffb300;
}
.upload-file__label.hover .upload-file__icon path {
    fill: #ffb300;
}
.upload-file__label.hover {
    border: 2px dashed #ffb300;
}

/* drag and drop - ошибка */
.upload-file__label.error .upload-file__text {
    color: #d32f2f;
}
.upload-file__label.error .upload-file__icon path {
    fill: #d32f2f;
}
.upload-file__label.error {
    border: 2px dashed #d32f2f;
}

/* drag and drop - файл(ы) успешно перетянут(ы) */
.upload-file__label.drop .upload-file__text {
    color: #388e3c;
}
.upload-file__label.drop .upload-file__icon path {
    fill: #388e3c;
}
.upload-file__label.drop {
    border: 2px dashed #388e3c;
}

2.2 Drag-and-drop загрузка файлов — JavaScript

Теперь напишем JavaScript для обработки событий перетаскивания файлов на веб-страницу. А уже в следующем пункте рассмотрим, как использовать кнопку добавления файла и область Drag-and-drop одновременно.

document.addEventListener("DOMContentLoaded", () => {
    const forms = document.querySelectorAll("form");

    /////////// Загрузка файлов при помощи «Drag-and-drop» ///////////
    const dropZone = document.querySelector(".upload-file__label");
    const dropZoneText = document.querySelector(".upload-file__text");
    const maxFileSize = 5000000; // максимальный размер файла - 5 мб.
    let uploadDragFile = "";

    // Проверка поддержки «Drag-and-drop»
    if (typeof (window.FileReader) == "undefined") {
        dropZone.textContent = "Drag&Drop Не поддерживается браузером!";
        dropZone.classList.add("error");
    }

    // Событие - перетаскивания файла
    dropZone.ondragover = function () {
        this.classList.add("hover");
        return false;
    };
    dropZone.ondragleave = function () {
        this.classList.remove("hover");
        return false;
    };

    let uploadDragFiles = "";
    dropZone.ondrop = function (e) { // Событие - файл перетащили
        e.preventDefault();
        this.classList.remove("hover");
        this.classList.add("drop");

        uploadDragFiles = e.dataTransfer.files;

        // Проверка размера файла
        if (uploadDragFiles[0].size > maxFileSize) {
            dropZoneText.textContent = "Размер превышает допустимое значение!";
            this.addClass("error");
            return false;
        }

        // Показ загружаемых файлов
        if (uploadDragFiles.length > 1) {
            if (uploadDragFiles.length <= 4) {
                dropZoneText.textContent = `Выбрано ${uploadDragFiles.length} файла`;
            } else {
                dropZoneText.textContent = `Выбрано ${uploadDragFiles.length} файлов`;
            }
        } else {
            dropZoneText.textContent = e.dataTransfer.files[0].name;
        }
    }

    // Отправка формы на сервер
    const postData = async (url, fData) => { // имеет асинхронные операции

        // начало отправки
        // здесь можно оповестить пользователя о начале отправки

        // ждём ответ, только тогда наш код пойдёт дальше
        let fetchResponse = await fetch(url, {
            method: "POST",
            body: fData
        });

        // ждём окончания операции
        return await fetchResponse.text();
    };

    if (forms) {
        forms.forEach(el => {
            el.addEventListener("submit", function (e) {
                e.preventDefault();

                // создание объекта FormData
                let fData = new FormData(this);

                // Добавление файлов input type file
                let file = el.querySelector(".upload-file__input");
                for (let i = 0; i < (file.files.length); i++) {
                    fData.append("files[]", file.files[i]); // добавляем файлы в объект FormData()
                }

                // Отправка на сервер
                postData("./mail.php", fData)
                    .then(fetchResponse => {
                        console.log("Данные успешно отправлены!");
                        console.log(fetchResponse);
                    })
                    .catch(function (error) {
                        console.log("Ошибка!");
                        console.log(error);
                    });
            });
        });
    };

});

3. Совместное использование кнопки «Прикрепить файл» и Drag-and-drop

Теперь рассмотрим случай, когда нам необходимо чтобы пользователь имел возможность прикрепить файл любым из этих способов.

HTML-структура и CSS останутся без изменений. Объеденим JavaScript код.

document.addEventListener("DOMContentLoaded", () => {

    const forms = document.querySelectorAll("form");

    for (let i = 1; i <= 4; i++) { // сюда будем помещать drug-&-drop файлы (4)
        window["uploadDragFiles_"+i] = new Object();
    }

    document.querySelectorAll(".upload-file__wrapper").forEach(function (current_item, index) {

        const inputFile = current_item.querySelector(".upload-file__input");

        // создаём массив файлов
        let fileList = [];

        /////////// Кнопка «Прикрепить файл» ///////////
        let textSelector = current_item.querySelector(".upload-file__text");

        // Событие выбора файла(ов)
        inputFile.addEventListener("change", function () {
            fileList.push(...inputFile.files);
            // console.log(inputFile.files);
            // вызов функции для каждого файла
            fileList.forEach(file => {
                uploadFile(file);
            });
        });

        // Проверяем размер файлов и выводим название
        const uploadFile = (file) => {

            // размер файла <5 Мб
            if (file.size > 5 * 1024 * 1024) {
                alert("Файл должен быть не более 5 МБ.");
                return;
            }

            // Показ загружаемых файлов
            if (file && fileList.length > 1) {
                if (fileList.length <= 4) {
                    textSelector.textContent = `Выбрано ${fileList.length} файла`;
                } else {
                    textSelector.textContent = `Выбрано ${fileList.length} файлов`;
                }
            } else {
                textSelector.textContent = file.name;
            }
            fileList = [];
        }


        /////////// Загрузка файлов при помощи «Drag-and-drop» ///////////
        // const dropZones = document.querySelectorAll(".upload-file__label");
        const dropZone = current_item.querySelector(".upload-file__label");
        const dropZoneText = current_item.querySelector(".upload-file__text");
        const maxFileSize = 5000000; // максимальный размер файла - 5 мб.

        // Проверка поддержки «Drag-and-drop»
        if (typeof (window.FileReader) == "undefined") {
            dropZone.textContent = "Drag&Drop Не поддерживается браузером!";
            dropZone.classList.add("error");
        }
        // Событие - перетаскивания файла
        dropZone.ondragover = function () {
            this.classList.add("hover");
            return false;
        };
        // Событие - отмена перетаскивания файла
        dropZone.ondragleave = function () {
            this.classList.remove("hover");
            return false;
        };
        // Событие - файл перетащили
        dropZone.addEventListener("drop", function (e) {
            e.preventDefault();
            this.classList.remove("hover");
            this.classList.add("drop");

            uploadDragFiles = e.dataTransfer.files[0]; // один файл

            // Проверка размера файла
            if (uploadDragFiles.size > maxFileSize) {
                dropZoneText.textContent = "Размер превышает допустимое значение!";
                this.addClass("error");
                return false;
            }

            // Показ загружаемых файлов
            if (uploadDragFiles.length > 1) {
                if (uploadDragFiles.length <= 4) {
                    dropZoneText.textContent = `Выбрано ${uploadDragFiles.length} файла`;
                } else {
                    dropZoneText.textContent = `Выбрано ${uploadDragFiles.length} файлов`;
                }
            } else {
                dropZoneText.textContent = e.dataTransfer.files[0].name;
            }

            // добавляем файл в объект "uploadDragFiles_i"
            window["uploadDragFiles_"+index] = uploadDragFiles;
        });

    });


    // Отправка формы на сервер
    const postData = async (url, fData) => { // имеет асинхронные операции

        // начало отправки
        // здесь можно сообщить пользователю о начале отправки

        // ждём ответ, только тогда наш код пойдёт дальше
        let fetchResponse = await fetch(url, {
            method: "POST",
            body: fData
        });

        // ждём окончания операции
        return await fetchResponse.text();
    };

    if (forms) {
        forms.forEach(el => {
            el.addEventListener("submit", function (e) {
                e.preventDefault();

                // создание объекта FormData
                let fData = new FormData(this);

                // Добавление файлов input type file
                el.querySelectorAll(".upload-file__input").forEach((one_file, index) => {
                    for (let i = 0; i < (one_file.files.length); i++) {
                        fData.append("files[]", one_file.files[i]); // добавляем файлы, добавленные кнопкой
                    }
                    fData.append("files[]", window["uploadDragFiles_"+index]); // добавляем drug-&-drop файлы
                });

                // Отправка на сервер
                postData("./mail-files.php", fData)
                    .then(fetchResponse => {
                        swal({
                            title: "Спасибо!",
                            text: "Данные отправлены.",
                            icon: "success",
                            button: "Ok"
                        });
                        // console.table("Данные успешно отправлены!", fetchResponse);
                        el.reset(); // Очистка полей формы
                        document.querySelectorAll(".upload-file__text").forEach(this_text => {
                            this_text.textContent = "Выберите файл или перетащите в поле";
                        });
                    })
                    .catch(function (error) {
                        swal({
                            title: error,
                            icon: "error",
                            button: "Ok"
                        });
                        // console.table("Ошибка!", error);
                    });
            });
        });
    };

});

4. Отправка множества input file multiple

Ранее мы успешно отправили все даннные формы (текстовые поля и файлы) на сервер.

Рассмотрим пример отправки multiple формы с множеством input file на почту.

<?php

$project_name = trim($_POST["project_name"]);
$admin_email  = trim($_POST["admin_email"]);
$form_subject = trim($_POST["form_subject"]);

$file_attach = array();

// Если поле выбора вложения не пустое - закачиваем его на сервер
if (!empty($_FILES)) {
    foreach ($_FILES["files"]["name"] as $key => $file) {
        $path = __DIR__ . "/upload-files/" . $file; // путь загрузки файла

        if (copy($_FILES["files"]["tmp_name"][$key], $path)) {
            $file_attach[] = $path;
        }
    }
}

$c = true;
foreach ($_POST as $key => $value) {
    if (is_array($value)) {
        $value = implode(", ", $value);
    }
    if (
        $value != "" &&
        $key != "project_name" &&
        $key != "admin_email" &&
        $key != "form_subject" &&
        $key != "file_attach"
    ) {
        $message .= (($c = !$c) ? "<tr>" : "<tr style='background-color: #f8f8f8;'>") . "
            <td style='padding: 10px; border: #e9e9e9 1px solid;'><b>$key</b></td>
            <td style='padding: 10px; border: #e9e9e9 1px solid;'>$value</td>
        </tr>";
    }
}

$message = '<table style="width: 100%;">
    <tr>
        <td style="padding:10px; border:#e9e9e9 1px solid; text-align:center" colspan="2">
            <big>$project_name</big>. $form_subject
        </td>
    </tr>
    ' . $message . '
</table>';

// Отправляем сообщение
if (empty($file_attach)) {
    $headers = "MIME-Version: 1.0" . PHP_EOL .
        "Content-Type: text/html; charset=utf-8" . PHP_EOL .
        "From: " . $project_name . " <" . $admin_email . ">" . PHP_EOL .
        "Reply-To: " . $admin_email . "" . PHP_EOL;
    mail($admin_email, $form_subject, $message, $headers); # отправка текста
} else {
    send_mail($admin_email, $form_subject, $message, $file_attach); # отправка файлов
}

// Функция для отправки сообщения с вложением
function send_mail($to, $form_subject, $html, $paths)
{
    $boundary = "--" . md5(uniqid(time())); // генерируем разделитель

    $headers = "MIME-Version: 1.0\n";
    $headers .= "Content-Type: multipart/mixed; boundary=\"$boundary\"\n";

    $multipart = "--$boundary\n";

    $multipart .= "Content-Type: text/html; charset='utf-8'\n";
    $multipart .= "Content-Transfer-Encoding: Quot-Printed\n\n";
    $multipart .= "$html\n\n";

    $message_part = "";

    foreach ($paths as $path) {
        $fp = fopen($path, "r");

        if (!$fp) {
            echo "Файл $path не может быть прочитан";
            exit();
        }

        $file = fread($fp, filesize($path));
        fclose($fp);

        $message_part .= "--$boundary\n";
        $message_part .= "Content-Type: application/octet-stream\n";
        $message_part .= "Content-Transfer-Encoding: base64\n";
        $message_part .= "Content-Disposition: attachment; filename = \"" . $path . "\"\n\n";
        $message_part .= chunk_split(base64_encode($file)) . "\n";
    }

    $multipart .= $message_part . "--$boundary--\n";

    if (!mail($to, $form_subject, $multipart, $headers)) {
        echo "К сожалению, письмо не отправлено";
        exit();
    }
}