Стилизация 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;
}

SASS

.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__icon path

        transition: .25s ease



    &:hover

        border: 2px dashed #58862D



        .upload-file__text

            color: #58862D



        .upload-file__icon path

            fill: #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();

                // Добавление всех input, кроме type="file"

                el.querySelectorAll('input:not([type="file"])').forEach(input => {
                    fData.append(input.name, input.value);
                });

                // Добавление файлов 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;
}

SASS

.upload-file__label 

    &.hover

        border: 2px dashed #ffb300 

        .upload-file__text 

            color: #ffb300 

        .upload-file__icon 

            path 

                fill: #ffb300 

    &.error 

        border: 2px dashed #d32f2f 

        .upload-file__text 

            color: #d32f2f 

        .upload-file__icon 

            path 

                fill: #d32f2f 

    &.drop 

        border: 2px dashed #388e3c 

        .upload-file__text 

            color: #388e3c 

        .upload-file__icon 

            path 

                fill: #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();

                // Добавление всех input, кроме type="file"

                el.querySelectorAll('input:not([type="file"])').forEach(input => {
                    fData.append(input.name, input.value);
                });

                // Добавление файлов 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();

                // Добавление всех input, кроме type="file"

                el.querySelectorAll('input:not([type="file"])').forEach(input => {
                    fData.append(input.name, input.value);
                });

                // Добавление файлов 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();
    }
}
admin