Скрипт для обробки зображень у формат WebP

Цей скрипт створений для автоматизації процесу обробки зображень на вашому веб-сайті - він конвертує зображення у сучасний формат WebP, що забезпечує зменшення розміру файлів при збереженні високої якості.
Використання WebP допомагає зменшити час завантаження сторінок, що позитивно впливає на користувацький досвід та SEO вашого сайту.
У цьому посібнику ви дізнаєтеся, як правильно налаштувати та використовувати наш скрипт для досягнення найкращих результатів з переводу сайта на webp.
Що таке WebP і для чого?

WebP — це формат файлу, розроблений компанією Google у 2010 році. Його основною характеристикою є вдосконалений алгоритм стиснення, який дозволяє зменшити розмір зображення без помітних втрат якості.
Хоча інші формати також підтримують стиснення, технології, на яких базується WebP, є більш прогресивними. У порівнянні з конкурентами, WebP демонструє вищу ефективність у відношенні стиснення до якості зображення.
У середньому, розмір зображень зменшується на 25–35%, що дає вебмайстрам можливість завантажувати більше зображень на сайти, економлячи простір на жорсткому диску.
При розробці формату Google використовував ті ж методи стиснення, що й у кодеках VP8.>
Переваги WebP в порівнянні з іншими форматами
Головна перевага WebP — це зменшений розмір файлів. Це позитивно впливає на кілька аспектів роботи в інтернеті:
- Сайти з стиснутими WebP-зображеннями працюють швидше. Менше часу витрачається на обробку невеликих файлів, навіть якщо в статті міститься безліч зображень.
- Завантажуючи невеликі зображення на VDS, можна зекономити місце на жорсткому диску.
- Користувачі витрачатимуть менше мобільного трафіку, відвідуючи сайт зі смартфона.
- Завантаження каналу до сервера буде меншим, якщо передавати менші медіа-файли, що також підвищує продуктивність.
Таким чином, переваги WebP стають очевидними в порівнянні з іншими форматами.
Опис функціоналу та Основні функції
Даний скрипт призначений для автоматичної обробки зображень на веб-сайті. Він конвертує зображення у формат WebP, що забезпечує менший розмір файлів без значної втрати якості.
Це прискорює завантаження сторінок та покращує продуктивність сайту.
Скрипт підтримує кешування, щоб не створювати одні й ті ж зображення повторно, а також виводить лог-повідомлення в консоль для зручності налагодження.
Функції:
- Конвертація зображень у формат WebP.
- Кешування вже створених зображень для запобігання повторної обробки.
- Обробка зображень в атрибутах srcset.
- Паралельна обробка кількох зображень для підвищення продуктивності.
- Валідація MIME-типу та розміру завантажуваних зображень.
Принцип роботи
-
Завантаження сторінки: Скрипт запускається під час завантаження сторінки та шукає всі зображення (
) на сторінці.
- Перевірка кешу: Для кожного зображення скрипт перевіряє наявність кешованого файлу WebP. Якщо файл існує і термін його дії не закінчився, зображення замінюється на кешоване.
- Створення WebP: Якщо кешованого файлу немає або термін дії закінчився, оригінальне зображення завантажується, конвертується у WebP, а потім завантажується на сервер.
- Оновлення srcset: Якщо у зображення є атрибут srcset, скрипт обробляє його аналогічно основному зображенню, створюючи та кешуючи нові версії у форматі WebP.
- Логування: Усі дії скрипта логуються в консолі браузера для зручності налагодження.
Опис параметрів, які можна змінити
CACHE_DURATION- Тип: Число (в мілісекундах)
- Опис: Час дії кешу для зображень (за замовчуванням 30 днів).
- Приклад: const CACHE_DURATION = 30 * 24 * 60 * 60 * 1000;
- Тип: Логічне значення
- Опис: Увімкнення режиму налагодження, який відключає кешування.
- Приклад: const DEBUG = false;
- Тип: Логічне значення
- Опис: Увімкнення виводу логів у консоль браузера.
- Приклад: const LOG_TO_CONSOLE = true;
- Тип: Число (в байтах)
- Опис: Максимально допустимий розмір зображення для обробки (за замовчуванням 5 MB).
- Приклад: const MAX_IMAGE_SIZE = 5 * 1024 * 1024;
- Тип: Масив рядків
- Опис: Дозволені MIME-типи для завантажуваних зображень.
- Приклад: const VALID_MIME_TYPES = ['image/jpeg', 'image/png'];
- Тип: Число (від 0.0 до 1.0)
- Опис: Якість стиснення для зображень WebP (за замовчуванням 0.8).
- Приклад: const COMPRESSION_QUALITY = 0.8;
- Тип: Число
- Опис: Максимальна кількість паралельних запитів на обробку зображень.
- Приклад: const MAX_CONCURRENT_REQUESTS = 5;
Встановлення та використання

Мінімальні вимоги для роботи
- Веб-сервер з підтримкою PHP (наприклад, Apache або Nginx).
- PHP версії 5.0 або вище.
- Підтримка формату WebP на сервері.
- JavaScript, що підтримує Promises (більшість сучасних браузерів).
Структура скрипта

- Основний скрипт (script.js): Включає в себе всю логіку обробки зображень і взаємодії з сервером.
- Файл для завантаження зображень (upload.php): PHP-скрипт на сервері, який приймає завантажувані зображення та зберігає їх у вказаній директорії.
Встановлення
- Завантажте script.js та upload.php на ваш сервер, у директорію, де знаходяться ваші зображення.
- Переконайтеся, що у вас є файл .htaccess у директорії завантаження для обмеження доступу.
- Створіть теку "uploads" з правами 777 для загрузки створених зображень та вкажіть її шлях у файлах.
- Додайте посилання на script.js у ваш HTML-код перед закриваючим тегом :
<script src="path/to/script.js"></script>
Тепер скрипт готовий до використання, і ваші зображення будуть автоматично конвертуватися у формат WebP під час завантаження сторінки.
Завантажити скрипт
Пароль на архів: shram.kiev.ua
Якщо у вас з'являється повідомлення Virus or Unsafe Browsing detected! будь ласка, зверніть увагу, що це не вірус, а хибне спрацювання на js код. Можно перепровірити будь-яким антівірусом.
Код script.js
/** * Улучшенный скрипт для динамической конвертации изображений в WebP. * Включает поддержку ленивой загрузки, кеширование в IndexedDB (с fallback на LocalStorage), обработку srcset и адаптивное качество. */ const DEBUG_MODE = false; // Режим отладки: отключение кеширования при true const LOG_LEVEL = 'info'; // Уровень логирования: 'info', 'warn', 'error' const CACHE_DURATION_MS = 24 * 60 * 60 * 1000; // Длительность кеширования (1 день) const MAX_FILE_SIZE_MB = 5 * 1024 * 1024; // Максимальный размер файла (5 MB) const BASE_WEBP_QUALITY = 0.8; // Базовое качество сжатия WebP (0.0 - 1.0) const MAX_PARALLEL_REQUESTS = 5; // Максимум параллельных запросов //const VALID_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml']; // Разрешенные типы изображений const VALID_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif']; // Разрешенные типы изображений const UPLOAD_ENDPOINT = 'https://www.shram.kiev.ua/mycode/webp/upload.php'; const IMAGE_CACHE_PATH = '/images/uploads/'; let useIndexedDB = false; // Флаг для определения, использовать ли IndexedDB let dbPromise; // Объявляем dbPromise в глобальной области // Проверка на поддержку IndexedDB с fallback на LocalStorage в случае ошибки try { if (window.indexedDB) { useIndexedDB = true; dbPromise = new Promise((resolve, reject) => { const request = indexedDB.open('imageCacheDB', 1); request.onupgradeneeded = event => { let db = event.target.result; db.createObjectStore('images', { keyPath: 'url' }); }; request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); console.log("IndexedDB доступен и будет использоваться для кэширования."); } else { throw new Error("IndexedDB не поддерживается."); } } catch (error) { console.warn("Ошибка при инициализации IndexedDB:", error.message); console.warn("Использование LocalStorage в качестве альтернативного кэширования."); useIndexedDB = false; } document.addEventListener('DOMContentLoaded', () => { const images = Array.from(document.querySelectorAll('img:not([data-no-webp])')); const observer = new IntersectionObserver(handleIntersection, { rootMargin: '0px', threshold: 0.1 }); images.forEach(img => observer.observe(img)); }); function handleIntersection(entries, observer) { entries.forEach(entry => { if (entry.isIntersecting) { observer.unobserve(entry.target); handleImageConversion(entry.target); } }); } async function handleImageConversion(img) { const src = img.getAttribute('src'); if (!src) return; await processSrcset(img); // Обрабатываем srcset атрибут, если он есть const cachedUrl = await checkCache(src); if (cachedUrl && !DEBUG_MODE) { img.src = cachedUrl; log('info', `Кешированное изображение используется: ${cachedUrl}`); return; } try { const response = await fetch(src); const blob = await response.blob(); validateImage(blob); const adaptiveQuality = calculateAdaptiveQuality(blob.size); // Использование адаптивного качества const webpBlob = await convertToWebP(blob, adaptiveQuality); const uploadUrl = await uploadWebP(webpBlob, src); if (uploadUrl) { img.src = uploadUrl; saveToCache(src, uploadUrl); } } catch (error) { log('error', `Ошибка при обработке изображения: ${error.message}`); } } /** * Функция для расчета адаптивного качества сжатия. * Чем больше изображение, тем выше качество сжатия. * @param {number} size - Размер изображения в байтах. * @returns {number} - Оптимальное качество сжатия. */ function calculateAdaptiveQuality(size) { return size > 2 * 1024 * 1024 ? 0.9 : BASE_WEBP_QUALITY; } /** * Функция для обработки srcset атрибута. */ async function processSrcset(img) { const srcset = img.getAttribute('srcset'); if (!srcset) return; const sources = srcset.split(',').map(src => src.trim()); const newSources = await Promise.all(sources.map(async source => { const [url, size] = source.split(' '); const cachedUrl = await checkCache(url); if (cachedUrl && !DEBUG_MODE) { return `${cachedUrl} ${size}`; } try { const response = await fetch(url); const blob = await response.blob(); validateImage(blob); const webpBlob = await convertToWebP(blob, BASE_WEBP_QUALITY); const uploadUrl = await uploadWebP(webpBlob, url); saveToCache(url, uploadUrl); return `${uploadUrl} ${size}`; } catch (error) { log('error', `Ошибка при обработке srcset: ${error.message}`); return `${url} ${size}`; // Возвращаем оригинальное изображение в случае ошибки } })); img.setAttribute('srcset', newSources.join(', ')); // Обновляем srcset атрибут } /** * Функция для загрузки WebP изображения на сервер. */ async function uploadWebP(blob, originalUrl) { const filename = getFileNameFromUrl(originalUrl) + '.webp'; const formData = new FormData(); formData.append('file', blob, filename); try { console.log('Попытка отправки данных через FormData:', filename); const response = await fetch(UPLOAD_ENDPOINT, { method: 'POST', body: formData, headers: { 'X-Filename': filename } }); if (!response.ok) { throw new Error(`Ошибка загрузки через FormData: ${response.statusText}`); } const result = await response.json(); return result.url; } catch (error) { console.warn(`Ошибка при загрузке через FormData: ${error.message}`); console.log('Попытка отправки данных напрямую через Blob.'); // Попытка отправить данные напрямую через Blob try { const response = await fetch(UPLOAD_ENDPOINT, { method: 'POST', body: blob, headers: { 'X-Filename': filename, 'Content-Type': 'application/octet-stream' } }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Ошибка загрузки через Blob: ${errorText}`); } const result = await response.json(); return result.url; } catch (blobError) { console.error(`Ошибка при загрузке через Blob: ${blobError.message}`); throw blobError; } } } /** * Функция для определения типа устройства (мобильное, планшет, десктоп). * @returns {string} - Тип устройства: 'mobile', 'tablet', 'desktop'. */ function detectDeviceType() { const width = window.innerWidth; if (width <= 480) { return 'mobile'; } else if (width <= 1024) { return 'tablet'; } else { return 'desktop'; } } /** * Функция для создания WebP изображения из Blob с адаптивным ресайзингом для мобильных и планшетных устройств. * @param {Blob} blob - Исходное изображение в формате Blob. * @param {number} quality - Качество сжатия (0.0 - 1.0). * @returns {Promise<Blob>} - Promise, возвращающий созданное WebP изображение в формате Blob. */ async function convertToWebP(blob, quality) { return new Promise((resolve, reject) => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const imgElement = new Image(); const url = URL.createObjectURL(blob); const deviceType = detectDeviceType(); // Определяем тип устройства imgElement.onload = () => { let targetWidth = imgElement.width; let targetHeight = imgElement.height; // Уменьшаем ширину изображения для мобильных устройств и планшетов if (deviceType === 'mobile' && imgElement.width > 480) { targetWidth = 480; targetHeight = (imgElement.height * 480) / imgElement.width; } else if (deviceType === 'tablet' && imgElement.width > 720) { targetWidth = 720; targetHeight = (imgElement.height * 720) / imgElement.width; } canvas.width = targetWidth; canvas.height = targetHeight; ctx.drawImage(imgElement, 0, 0, targetWidth, targetHeight); // Рисуем изображение с новой шириной canvas.toBlob((webpBlob) => { URL.revokeObjectURL(url); if (webpBlob) { resolve(webpBlob); } else { reject(new Error('Не удалось создать WebP blob')); } }, 'image/webp', quality); }; imgElement.onerror = () => reject(new Error('Не удалось загрузить изображение')); imgElement.src = url; }); } /** * Функция для проверки и валидации изображения перед конвертацией. */ function validateImage(blob) { if (blob.size > MAX_FILE_SIZE_MB) throw new Error('Изображение слишком большое'); if (!VALID_IMAGE_TYPES.includes(blob.type)) throw new Error('Неподдерживаемый тип изображения'); } /** * Функция для сохранения данных в кэш. */ async function saveToCache(url, cachedUrl) { try { if (useIndexedDB) { const db = await dbPromise; const tx = db.transaction('images', 'readwrite'); const store = tx.objectStore('images'); store.put({ url, cachedUrl }); await tx.complete; } else { localStorage.setItem(url, cachedUrl); } } catch (error) { console.warn("Ошибка сохранения в кэш:", error.message); localStorage.setItem(url, cachedUrl); } } /** * Функция для проверки кэша. */ async function checkCache(url) { try { if (useIndexedDB) { const db = await dbPromise; const tx = db.transaction('images', 'readonly'); const store = tx.objectStore('images'); const cachedImage = await store.get(url); return cachedImage ? cachedImage.cachedUrl : null; } else { return localStorage.getItem(url); } } catch (error) { console.warn("Ошибка при проверке кэша:", error.message); return localStorage.getItem(url); } } /** * Функция для получения имени файла из URL. */ function getFileNameFromUrl(url) { return url.split('/').pop().split('.')[0]; } /** * Функция логирования. */ function log(level, message) { if (LOG_LEVEL === 'info' && level === 'info') console.log(message); if ((LOG_LEVEL === 'warn' || LOG_LEVEL === 'info') && level === 'warn') console.warn(message); if (level === 'error') console.error(message); }
Код upload.php
<?php // Конфигурационные переменные $uploadDir = '/var/www/admin/data/www/shram.kiev.ua/images/uploads/'; $uploadsUrlPath = '/images/uploads'; // Заголовки для CORS и методов доступа header("Access-Control-Allow-Origin: *"); header("Access-Control-Allow-Methods: POST, GET, OPTIONS"); header("Access-Control-Allow-Headers: X-Filename, Content-Type"); // Добавляем заголовки для контроля кэширования header("Cache-Control: no-cache, must-revalidate"); header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); /** * Основная логика обработки POST-запроса на загрузку файла. * Принимает и обрабатывает загруженный файл, сохраняя его в заданную директорию. */ if ($_SERVER['REQUEST_METHOD'] === 'POST') { error_log("POST-запрос принят, начинаем обработку..."); $blobData = null; $filename = uniqid() . '.webp'; /** * Проверка и извлечение файла из запроса. * Если файл передан через FormData, получаем его через $_FILES, * в противном случае получаем бинарные данные из 'php://input'. */ if (isset($_FILES['file']) && $_FILES['file']['error'] === UPLOAD_ERR_OK) { $blobData = file_get_contents($_FILES['file']['tmp_name']); $headerFilename = basename($_SERVER['HTTP_X_FILENAME']); } else { $blobData = file_get_contents('php://input'); if (!$blobData) { error_log("Ошибка: не удалось получить данные файла."); http_response_code(400); echo json_encode(['error' => 'Не удалось получить данные файла']); exit; } $headerFilename = isset($_SERVER['HTTP_X_FILENAME']) ? basename($_SERVER['HTTP_X_FILENAME']) : uniqid(); } // Проверяем, заканчивается ли имя файла на '.webp', прежде чем добавить это расширение if (pathinfo($headerFilename, PATHINFO_EXTENSION) !== 'webp') { $filename = $headerFilename . '.webp'; } else { $filename = $headerFilename; } $filePath = $uploadDir . $filename; /** * Проверка и создание директории для загрузки файла. * Если директория не существует, пытаемся создать её. */ if (!is_dir($uploadDir)) { if (!mkdir($uploadDir, 0755, true)) { error_log("Ошибка: Не удалось создать директорию загрузки: $uploadDir"); http_response_code(500); echo json_encode(['error' => 'Не удалось создать директорию загрузки']); exit; } } /** * Сохранение файла в заданную директорию. * Применяем права доступа и возвращаем URL загруженного файла в случае успеха. */ if (file_put_contents($filePath, $blobData)) { chmod($filePath, 0644); $url = 'https://' . $_SERVER['HTTP_HOST'] . $uploadsUrlPath . '/' . $filename; echo json_encode(['url' => $url]); } else { error_log("Ошибка: не удалось сохранить файл на сервере."); http_response_code(500); echo json_encode(['error' => 'Failed to save file']); } } else { error_log("Ошибка: некорректный метод запроса. Ожидался POST-запрос."); http_response_code(405); echo json_encode(['error' => 'Метод запроса не поддерживается']); } ?>
Bonus: UI інтерфейс та конвектор у webp для сайту
Код index.php
Цей скрипт дозволяє зручно конвертувати зображення у формат WebP, забезпечуючи захист від зловживань і надаючи користувачу зрозумілий і функціональний інтерфейс.
Цей скрипт створює веб-інтерфейс для конвертації зображень у формат WebP. Інтерфейс дозволяє користувачам завантажувати кілька зображень, обробляти їх та завантажувати конвертовані файли у зручному форматі. Скрипт реалізує додаткові захисні заходи, такі як обмеження кількості завантажень і розміру файлів, а також генерує тимчасовий токен для обмеження доступу до функціональності. Інтерфейс має зручні повідомлення про помилки, які інформують користувача про можливі проблеми.
Інтерфейс завантаження та управління:
- Завантаження файлів: Користувач може завантажити кілька зображень одночасно у форматах, підтримуваних браузером.
- Кнопка "Скинути": Відображається після завантаження файлів, дозволяє очистити всі завантажені зображення і конвертовані дані.
- Кнопка "Завантажити всі": Дозволяє масово завантажити всі конвертовані зображення у форматі WebP.
Обмеження для захисту від зловживань:
- Максимальна кількість файлів за один раз: Скрипт обмежує кількість файлів, які можна завантажити за одне завантаження (за замовчуванням – 50). Якщо кількість файлів перевищена, користувач отримує повідомлення про помилку.
- Максимальний розмір файлу: Скрипт обмежує розмір одного завантаженого файлу (за замовчуванням – 5 МБ). Якщо файл перевищує цей розмір, виводиться повідомлення про помилку.
- Перевірка формату файлу: Перевіряється, чи є завантажений файл зображенням. Якщо завантажений файл не є зображенням, користувач отримує відповідне повідомлення.
Тимчасовий токен для обмеження доступу:
- Генерація токена: При відкритті сторінки генерується унікальний токен, який зберігається у sessionStorage і діє протягом 10 хвилин. Токен використовується для обмеження доступу до функцій завантаження і завантаження файлів.
- Перевірка токена: Перед кожною дією (завантаженням файлів або завантаженням зображень) перевіряється, чи не закінчився термін дії токена. Якщо токен недійсний, користувачу пропонується оновити сторінку.
Конвертація зображень у формат WebP:
- Конвертація за допомогою Canvas: Кожне завантажене зображення рендериться у Canvas, після чого конвертується у формат WebP. Конвертоване зображення відображається поряд з оригінальним.
- Індивідуальне завантаження конвертованих файлів: Для кожного конвертованого зображення є кнопка завантаження (значок стрілки), яка дозволяє завантажити окреме зображення.
- Масове завантаження всіх конвертованих файлів: Кнопка "Завантажити всі" дозволяє одночасно завантажити всі зображення, конвертовані у формат WebP.
Вивід повідомлень про помилки:
- Зрозумілі повідомлення: Скрипт включає систему сповіщень, яка інформує користувача про різні помилки, такі як неправильний формат файлу, перевищення ліміту розміру, помилки завантаження, проблеми при конвертації тощо.
- Типи повідомлень: Повідомлення виводяться в спеціальному блоці та мають різне форматування залежно від типу (наприклад, зелений фон для успішного завантаження та червоний для помилок).
Зручний інтерфейс:
- Кнопка "Вихід": Додана кнопка "Вихід" у правому верхньому куті, яка перенаправляє користувача на зовнішній сайт.
- Адаптивне розташування елементів: Всі елементи інтерфейсу, включаючи кнопки і блоки для зображень, організовані так, щоб було зручно працювати як на великих екранах, так і на мобільних пристроях.
<!DOCTYPE html> <html lang="uk"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Конвертація зображень у WebP</title> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/libwebp.min.js"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"> <style> body { font-family: Arial, sans-serif; margin: 20px; background-color: #f4f4f4; position: relative; } .logout { position: absolute; top: 20px; right: 20px; color: #dc3545; font-size: 1.5em; text-decoration: none; } h1 { text-align: center; color: #333; } .container { max-width: 800px; margin: 0 auto; text-align: center; } input[type="file"] { display: block; margin: 20px auto; } .buttons-container { display: flex; justify-content: space-between; margin-top: 10px; } .btn { padding: 10px 20px; color: white; border: none; cursor: pointer; font-size: 16px; border-radius: 5px; display: inline-flex; align-items: center; } .btn-download { background-color: #28a745; } .btn-download:hover { background-color: #218838; } .btn-reset { background-color: #dc3545; } .btn-reset:hover { background-color: #c82333; } .btn i { margin-right: 8px; } .images-wrapper { display: flex; flex-direction: column; gap: 20px; margin-top: 20px; } .image-container { display: flex; gap: 20px; align-items: center; border: 1px solid #ddd; padding: 10px; } .image-wrapper { width: 50%; text-align: center; } img { max-width: 100%; height: auto; border: 1px solid #ccc; padding: 5px; } .image-label { font-weight: bold; margin-top: 5px; padding: 5px; border-radius: 5px; } .original-label { color: red; border: 2px solid red; } .webp-label { color: green; border: 2px solid green; display: flex; align-items: center; justify-content: center; } .download-icon { margin-left: 10px; cursor: pointer; color: green; font-size: 1.2em; } .notification { display: none; margin: 10px 0; padding: 10px; border-radius: 5px; } .success { background-color: #d4edda; color: #155724; } .error { background-color: #f8d7da; color: #721c24; } </style> </head> <body> <a href="https://www.shram.kiev.ua" class="logout" title="Вихід"> <i class="fas fa-sign-out-alt"></i> </a> <div class="container"> <h1>Конвертація зображень у WebP</h1> <input type="file" id="fileInput" accept="image/*" multiple> <div class="buttons-container"> <button id="resetButton" class="btn btn-reset" style="display: none;"> <i class="fas fa-times"></i>Скинути </button> <button id="downloadAllButton" class="btn btn-download" style="display: none;"> <i class="fas fa-download"></i>Завантажити всі </button> </div> <div id="notification" class="notification"></div> <div class="images-wrapper" id="imagesWrapper"></div> </div> <script> const MAX_FILES_PER_UPLOAD = 50; // Максимальна кількість файлів за одне завантаження const MAX_FILE_SIZE_MB = 5; // Максимальний розмір одного файлу в мегабайтах const TOKEN_LIFETIME_MS = 10 * 60 * 1000; // Термін дії токена (10 хвилин в мілісекундах) const fileInput = document.getElementById('fileInput'); const resetButton = document.getElementById('resetButton'); const downloadAllButton = document.getElementById('downloadAllButton'); const imagesWrapper = document.getElementById('imagesWrapper'); const notification = document.getElementById('notification'); let convertedImages = []; generateToken(); fileInput.addEventListener('change', handleFileChange); resetButton.addEventListener('click', resetImages); downloadAllButton.addEventListener('click', downloadAllImages); function generateToken() { const token = Math.random().toString(36).substring(2) + Date.now().toString(36); const expirationTime = Date.now() + TOKEN_LIFETIME_MS; sessionStorage.setItem('uploadToken', JSON.stringify({ token, expirationTime })); } function validateToken() { const tokenData = JSON.parse(sessionStorage.getItem('uploadToken')); if (!tokenData || Date.now() > tokenData.expirationTime) { showNotification('Токен закінчився або відсутній. Оновіть сторінку.', 'error'); fileInput.disabled = true; return false; } return true; } function handleFileChange(event) { if (!validateToken()) return; const files = event.target.files; if (files.length > MAX_FILES_PER_UPLOAD) { showNotification(`Перевищено максимальну кількість файлів (${MAX_FILES_PER_UPLOAD}).`, 'error'); fileInput.value = ''; return; } for (const file of files) { if (file.size > MAX_FILE_SIZE_MB * 1024 * 1024) { showNotification(`Файл "${file.name}" перевищує допустимий розмір (${MAX_FILE_SIZE_MB} MB).`, 'error'); fileInput.value = ''; return; } if (!file.type.startsWith('image/')) { showNotification(`Файл "${file.name}" не є зображенням.`, 'error'); fileInput.value = ''; return; } } imagesWrapper.innerHTML = ''; convertedImages = []; Array.from(files).forEach(file => { const reader = new FileReader(); reader.onload = function(e) { const img = new Image(); img.src = e.target.result; img.onload = function() { try { const imageContainer = createImageContainer(img, file.name); imagesWrapper.appendChild(imageContainer); resetButton.style.display = 'inline-flex'; downloadAllButton.style.display = 'inline-flex'; showNotification('Зображення завантажені та оброблені.', 'success'); } catch (error) { showNotification('Помилка при створенні контейнера для зображення.', 'error'); } }; img.onerror = function() { showNotification(`Не вдалося завантажити зображення "${file.name}".`, 'error'); }; }; reader.onerror = function() { showNotification(`Помилка читання файлу "${file.name}".`, 'error'); }; reader.readAsDataURL(file); }); } function createImageContainer(img, filename) { const container = document.createElement('div'); container.className = 'image-container'; const originalWrapper = document.createElement('div'); originalWrapper.className = 'image-wrapper'; const originalImage = new Image(); originalImage.src = img.src; originalImage.alt = 'Original Image'; const originalLabel = document.createElement('div'); originalLabel.className = 'image-label original-label'; originalLabel.textContent = 'Оригінальне зображення'; originalWrapper.appendChild(originalImage); originalWrapper.appendChild(originalLabel); const webpWrapper = document.createElement('div'); webpWrapper.className = 'image-wrapper'; convertToWebP(img, webpWrapper, filename); container.appendChild(originalWrapper); container.appendChild(webpWrapper); return container; } function convertToWebP(img, container, filename) { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); try { const webpUrl = canvas.toDataURL('image/webp'); const webpImage = new Image(); webpImage.src = webpUrl; webpImage.alt = 'WebP Image'; const webpLabel = document.createElement('div'); webpLabel.className = 'image-label webp-label'; webpLabel.textContent = 'Конвертоване у WebP'; const downloadIcon = document.createElement('i'); downloadIcon.className = 'fas fa-download download-icon'; downloadIcon.title = 'Завантажити'; downloadIcon.onclick = () => downloadImage(webpUrl, filename.replace(/\.\w+$/, '.webp')); webpLabel.appendChild(downloadIcon); container.appendChild(webpImage); container.appendChild(webpLabel); convertedImages.push({ url: webpUrl, filename: filename.replace(/\.\w+$/, '.webp') }); } catch (error) { showNotification('Помилка конвертації у WebP: ' + error.message, 'error'); } } function downloadImage(url, filename) { if (!validateToken()) return; try { const link = document.createElement('a'); link.href = url; link.download = filename; link.click(); } catch (error) { showNotification('Помилка завантаження файлу.', 'error'); } } function downloadAllImages() { if (!validateToken()) return; try { convertedImages.forEach(image => { downloadImage(image.url, image.filename); }); } catch (error) { showNotification('Помилка при масовому завантаженні файлів.', 'error'); } } function resetImages() { try { fileInput.value = ''; imagesWrapper.innerHTML = ''; resetButton.style.display = 'none'; downloadAllButton.style.display = 'none'; convertedImages = []; hideNotification(); } catch (error) { showNotification('Помилка при скиданні.', 'error'); } } function showNotification(message, type) { notification.textContent = message; notification.className = `notification ${type}`; notification.style.display = 'block'; } function hideNotification() { notification.style.display = 'none'; } </script> </body> </html>
Via shram.kiev.ua & ChatGPT
Created/Updated: 29.10.2024