Скрипт для обробки зображень у формат 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
|