Эффективная загрузка изображений: Полное руководство для веб-разработчиков

Andre Kowalsy
86 раз
10 мин чтения
Опубликовано: 30-11-2025
Обновлено: Не обновлялось
Категории: Разработка

В этом посте мы рассмотрим, как реализовать функциональность загрузки файлов в веб-приложении с использованием JavaScript, HTML и PHP. Мы сосредоточимся на загрузке изображения, которое будет служить ключевым изображением для новости. В дальнейшем мы также обсудим редактирование загруженного изображения и добавление дополнительных файлов.

Часть 1: HTML-разметка

Первым делом необходимо создать HTML-шаблон для загрузки изображений. Вот пример кода, который использует Bootstrap для создания пользовательского интерфейса:

<div class="text-field col-sm-4">
    <div class="card file-upload">
        <div class="card-header">
            <h3 class="card-title">Ключевое изображение</h3>
        </div>
        <div class="card-body">
            <input type="file" name="base" class="file-input" accept="image/*" />
            <label class="file-label">Перетащите файл сюда или нажмите для выбора</label>
            <div class="file-list"></div>
        </div>
    </div>
</div>

Этот шаблон содержит заголовок, область для загрузки файлов и список для отображения загруженных изображений.

Часть 2: JavaScript для загрузки файлов

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

// Работа с изображениями
const fileUploads = document.querySelectorAll('.file-upload');

fileUploads.forEach(upload => {
    const fileInput = upload.querySelector('.file-input');
    const uploadLabel = upload.querySelector('.file-label');
    const fileList = upload.querySelector('.file-list');
    const dataTransfer = new DataTransfer(); // Создаем новый объект DataTransfer

    // Создаем элемент прелоадера
    const preloader = document.createElement('div');
    preloader.className = 'preloader';
    preloader.style.display = 'none'; // Скрываем прелоадер по умолчанию
    upload.appendChild(preloader); // Добавляем прелоадер в DOM

    // Обработчик события перетаскивания
    upload.addEventListener('dragover', (event) => {
        event.preventDefault();
    });

    // Обработчик события сброса
    upload.addEventListener('drop', (event) => {
        event.preventDefault();
        const files = event.dataTransfer.files;
        addFilesToList(files);
    });

    // Обработчик выбора файлов
    fileInput.addEventListener('change', (event) => {
        const files = event.target.files;
        addFilesToList(files);
    });

    // Функция для добавления файлов в список
    function addFilesToList(files) {
        preloader.style.display = 'block'; // Показываем прелоадер при начале загрузки

        // Очищаем список и DataTransfer перед добавлением новых файлов
        fileList.innerHTML = '';
        dataTransfer.items.clear();

        for (let i = 0; i < files.length; i++) {
            const file = files[i];
            const fileItem = document.createElement('div');
            fileItem.className = 'file-item';

            // Проверяем, является ли файл изображением
            if (file.type.startsWith('image/')) {
                const img = document.createElement('img');
                img.src = URL.createObjectURL(file);
                img.className = 'file-preview';
                fileItem.appendChild(img);

                const fileName = document.createElement('div');
                fileName.className = 'file-name';
                fileName.textContent = file.name;
                fileItem.appendChild(fileName);

                const removeButton = document.createElement('button');
                removeButton.textContent = 'Удалить';
                removeButton.className = 'remove-button';
                removeButton.addEventListener('click', () => {
                    fileItem.remove();
                    // Обновляем DataTransfer и input.files
                });

                fileItem.appendChild(removeButton); // Разместим кнопку удаления
                dataTransfer.items.add(file);
                fileList.appendChild(fileItem); // Добавляем файл в список
            }
        }

        fileInput.files = dataTransfer.files; // Обновляем input файлов

        // Скрыть input и текст при загрузке хотя бы одного файла
        if (fileList.children.length > 0) {
            fileInput.classList.add('hidden');
            uploadLabel.style.display = 'none'; // Скрыть текст загрузки
        }

        // Скрыть прелоадер
        setTimeout(() => {
            preloader.style.display = 'none';
            const images = fileList.querySelectorAll('.file-preview');
            images.forEach(image => {
                image.classList.add('visible'); // Добавляем класс для плавного появления
            });
        }, 1000);
    }
});

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

Часть 3: Серверная часть на PHP

Теперь давайте рассмотрим серверный код на PHP, который будет обрабатывать загрузку фоновых файлов. Мы предполагаем, что у вас есть контроллер (Controller), обрабатывающий запросы.

public function addAction()
{
    if (isset($_POST['add-news'])) {
        $news = new Blog(); // Создает новый объект блога
        $data = $_POST;  // Получаем все данные из POST-запроса
        $news->load($data);  // Загружаем данные в объект

        // Устанавливаем атрибуты на основе состояния чекбоксов
        $news->attributes['trending_post'] = isset($data['trending_post']) ? 1 : 0;
        $news->attributes['visibility'] = isset($data['visibility']) ? 1 : 0;

        // Валидация данных
        if (!$news->validate($data)) {
            $_SESSION['response_admin'] = $data; // Сохранение данных в сессии
            redirect(); // Перенаправление
        }

        $sizes = [
            [870, 475, 'blog'],    // Размер для блога
            [360, 197, 'gallery'], // Размер для галереи
            [85, 47, 'small'],     // Размер для маленького изображения
        ];

        if ($getNewsID = $news->save('news')) {
            // Загружаем изображения с передачей ID новости
            $uploadedFiles = $this->uploadImg(['base'], $sizes, WWW . '/assets/img/', $getNewsID, $data);

            // Обработка загруженных изображений по типу
            // ...
        }
    }
}

uploadImg - это метод, который обрабатывает загрузку изображений. Он проверяет ошибки, проверяет размер, объединяет и сохраняет изображения.

Теперь перейдем к важному методу, который обрабатывает загрузку изображений.

public function uploadImg(array $inputNames, array $sizes, string $uploadDir, int $newsID = null, array $data = [], $duplicate = TRUE): array
{
    $uploadedFiles = [];
    $_SESSION['response']['error'] = '';

    // Проверка настройки file_uploads
    if (!ini_get('file_uploads')) {
        $_SESSION['response']['error'] .= 'Загрузка файлов отключена на сервере.';
        return $uploadedFiles;
    }

    $maxFileSize = ini_get('upload_max_filesize');
    $postMaxSize = ini_get('post_max_size');
    if ($maxFileSize === FALSE || $postMaxSize === FALSE) {
        $_SESSION['response']['error'] .= 'Не удалось получить настройки максимального размера загружаемого файла.';
        return $uploadedFiles;
    }

    $maxFileSizeBytes = $this->convertToBytes($maxFileSize);
    $postMaxSizeBytes = $this->convertToBytes($postMaxSize);

    // Генерация имени файла один раз
    $new_name = null; // Изначально сбрасываем переменную
    foreach ($inputNames as $inputName) {
        if (isset($_FILES[$inputName]) && !empty($_FILES[$inputName]['name'])) {
            // Генерация имени файла
            $ext = strtolower(pathinfo($_FILES[$inputName]['name'], PATHINFO_EXTENSION));
            $slug = AppModel::str2url($data['alias'] ?: $data['heading']);
            $new_name = "news_{$newsID}_{$slug}.{$ext}"; // Генерация имени файла только один раз
            break; // Выходим из цикла, так как имя файла сгенерировано
        }
    }

    if (empty($new_name)) {
        // Если ни один файл не загружен, просто выходим
        $_SESSION['response']['error'] .= 'Нет загруженных файлов.';
        return $uploadedFiles;
    }

    // Обработка каждого файла
    foreach ($inputNames as $index => $inputName) {
        if (!isset($_FILES[$inputName])) {
            continue; // Пропустим, если данные файла не существуют.
        }

        $fileData = $_FILES[$inputName];

        if (!empty($fileData['name']) && is_uploaded_file($fileData['tmp_name'])) {
            // Проверяем наличие ошибок загрузки
            if ($fileData['error'] !== UPLOAD_ERR_OK) {
                $errorMessage = $this->getUploadErrorMessage($fileData['error']);
                $_SESSION['response']['error'] .= "Ошибка при загрузке файла {$fileData['name']}: {$errorMessage}";
                continue;
            }

            // Проверка размера файла
            if ($fileData['size'] > $maxFileSizeBytes || $fileData['size'] > $postMaxSizeBytes) {
                $_SESSION['response']['error'] .= "Размер загружаемого файла для {$inputName} превышает допустимый предел.";
                continue;
            }

            // Указываем директорию для загрузки
            $targetDir = $uploadDir . $sizes[$index][2] . '/';
            if (!is_dir($targetDir)) {
                mkdir($targetDir, 0777, TRUE); // Создаем директорию, если она не существует
            }

            // Полный путь к файлу
            $targetFile = $targetDir . $new_name;

            // Перемещение загруженного файла
            if (!move_uploaded_file($fileData['tmp_name'], $targetFile)) {
                $_SESSION['response']['error'] .= "Не удалось загрузить файл {$fileData['name']} в {$sizes[$index][2]}.";
                continue;
            }

            // Изменение размера изображения
            $image = new ResizeImage();
            $image->load($targetFile);
            $image->resize($sizes[$index][0], $sizes[$index][1]);
            $image->save($targetFile);

            $uploadedFiles[$sizes[$index][2]][] = $new_name; // Сохраняем имя загруженного файла
        } else if ($duplicate) {
            // Если файла нет, используем базовую картинку
            $this->copyBaseImageToTarget($uploadDir, $inputName, $sizes[$index], $new_name);
        }
    }

    return $uploadedFiles;
}

Метод getUploadErrorMessage

// Функция для получения текстового описания ошибки загрузки
private function getUploadErrorMessage($errorCode)
{
    switch ($errorCode) {
        // Ошибка не произошла, файл успешно загружен
        case UPLOAD_ERR_OK:
            return 'Нет ошибки, файл был загружен успешно.';
        // Размер загруженного файла превышает максимальный размер, установленный в конфигурации PHP
        case UPLOAD_ERR_INI_SIZE:
            return 'Размер загруженного файла превышает директиву upload_max_filesize в php.ini.';
        // Размер загруженного файла превышает ограничение, указанное в HTML-форме
        case UPLOAD_ERR_FORM_SIZE:
            return 'Размер загруженного файла превышает директиву MAX_FILE_SIZE, указанную в HTML-форме.';
        // Файл был загружен только частично
        case UPLOAD_ERR_PARTIAL:
            return 'Файл был только частично загружен.';
        // Файл не был загружен
        case UPLOAD_ERR_NO_FILE:
            return 'Файл не был загружен.';
        // Временная папка для загрузки отсутствует
        case UPLOAD_ERR_NO_TMP_DIR:
            return 'Нет временной папки на сервере.';
        // Не удалось записать файл на диск
        case UPLOAD_ERR_CANT_WRITE:
            return 'Не удалось записать файл на диск.';
        // Загрузка файла была остановлена из-за расширения
        case UPLOAD_ERR_EXTENSION:
            return 'Загрузка файла остановлена из-за расширения.';
        // Неизвестная ошибка
        default:
            return 'Неизвестная ошибка при загрузке.';
    }
}

Метод copyBaseImageToTarget

private function copyBaseImageToTarget($uploadDir, $inputName, $size, $new_name)
{
    // Проверяем, относится ли текущий inputName к 'gallery' или 'small'
    if (in_array($inputName, ['gallery', 'small'])) {
        // Формируем путь к целевой директории
        $targetDir = $uploadDir . $size[2] . '/';

        // Создаем каталог, если он не существует
        if (!is_dir($targetDir)) {
            mkdir($targetDir, 0777, true); // Создаем директорию с правами 0777
        }

        // Полный путь к файлу, который будем копировать
        $targetFile = $targetDir . $new_name;
        $sourceFile = $uploadDir . 'blog/' . $new_name; // Путь к исходному файлу

        // Проверка существования исходного файла
        if (!file_exists($sourceFile)) {
            $_SESSION['response']['error'] .= "<li>Базовый файл не найден для копирования в {$size[2]}.</li>";
            return; // Выход из функции, если файл не существует
        }

        // Проверка, является ли исходный путь файловой системой
        if (!is_file($sourceFile)) {
            $_SESSION['response']['error'] .= "<li>Исходный путь {$sourceFile} не является файлом.</li>";
            return; // Выход, если путь к файлу не является файлом
        }

        // Копируем базовую картинку в нужный каталог
        if (!copy($sourceFile, $targetFile)) {
            $_SESSION['response']['error'] .= "<li>Не удалось скопировать базовый файл в {$size[2]}.</li>";
            return; // Выход, если копирование не удалось
        } else {
            // Пытаемся изменить размер изображения
            $image = new ResizeImage();
            $image->load($targetFile);
            $image->resize($size[0], $size[1]);
            $image->save($targetFile); // Сохраняем измененное изображение
        }
    }
}

Метод convertToBytes

// Функция для преобразования размера файла в байты
private function convertToBytes($size)
{
    $unit = strtolower(substr($size, -1)); // Получаем единицу измерения (G, M, K)
    $value = (int)$size; // Преобразуем значение размера в целое число

    // Преобразование значения в байты, в зависимости от единицы измерения
    switch ($unit) {
        case 'g':
            $value *= 1024; // Гигабайты в мегабайты
        // no break
        case 'm':
            $value *= 1024; // Мегабайты в килобайты
        // no break
        case 'k':
            $value *= 1024; // Килобайты в байты
        // no break
    }

    return $value; // Возвращаем значение в байтах
}

Этот код мы используем в своем блоге! В коде мы используем целый ряд функций для обработки ошибок, копирования изображений и преобразования размеров файлов. Комментарии помогают объяснить, что делает каждая часть кода, а также зачем нужны проверки и действия, когда дело касается работы с файлами. Если у вас есть дополнительные предложения по комментариям или другие подходы, дайте знать!

Заключение

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

В следующем посте мы будем редактировать загруженные изображения и добавлять дополнительные файлы. Если у вас есть вопросы или пожелания, оставляйте их в комментариях!