special

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

images-to-webp-script.png

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

Використання WebP допомагає зменшити час завантаження сторінок, що позитивно впливає на користувацький досвід та SEO вашого сайту.

У цьому посібнику ви дізнаєтеся, як правильно налаштувати та використовувати наш скрипт для досягнення найкращих результатів з переводу сайта на webp.

Короткий опис WebP

images-to-webp-script3.jpg

WebP — це формат файлу, розроблений компанією Google у 2010 році. Його основною характеристикою є вдосконалений алгоритм стиснення, який дозволяє зменшити розмір зображення без помітних втрат якості.

Хоча інші формати також підтримують стиснення, технології, на яких базується WebP, є більш прогресивними. У порівнянні з конкурентами, WebP демонструє вищу ефективність у відношенні стиснення до якості зображення.

У середньому, розмір зображень зменшується на 25–35%, що дає вебмайстрам можливість завантажувати більше зображень на сайти, економлячи простір на жорсткому диску.

При розробці формату Google використовував ті ж методи стиснення, що й у кодеках VP8.>

Переваги WebP в порівнянні з іншими форматами

Головна перевага WebP — це зменшений розмір файлів. Це позитивно впливає на кілька аспектів роботи в інтернеті:

  1. Сайти з стиснутими WebP-зображеннями працюють швидше. Менше часу витрачається на обробку невеликих файлів, навіть якщо в статті міститься безліч зображень.
  2. Завантажуючи невеликі зображення на VDS, можна зекономити місце на жорсткому диску.
  3. Користувачі витрачатимуть менше мобільного трафіку, відвідуючи сайт зі смартфона.
  4. Завантаження каналу до сервера буде меншим, якщо передавати менші медіа-файли, що також підвищує продуктивність.

Таким чином, переваги WebP стають очевидними в порівнянні з іншими форматами.

Опис функціоналу

Даний скрипт призначений для автоматичної обробки зображень на веб-сайті. Він конвертує зображення у формат WebP, що забезпечує менший розмір файлів без значної втрати якості.

Це прискорює завантаження сторінок та покращує продуктивність сайту.

Скрипт підтримує кешування, щоб не створювати одні й ті ж зображення повторно, а також виводить лог-повідомлення в консоль для зручності налагодження.

Основні функції

  • Конвертація зображень у формат WebP.
  • Кешування вже створених зображень для запобігання повторної обробки.
  • Обробка зображень в атрибутах srcset.
  • Паралельна обробка кількох зображень для підвищення продуктивності.
  • Валідація MIME-типу та розміру завантажуваних зображень.

Принцип роботи

  1. Завантаження сторінки: Скрипт запускається під час завантаження сторінки та шукає всі зображення () на сторінці.
  2. Перевірка кешу: Для кожного зображення скрипт перевіряє наявність кешованого файлу WebP. Якщо файл існує і термін його дії не закінчився, зображення замінюється на кешоване.
  3. Створення WebP: Якщо кешованого файлу немає або термін дії закінчився, оригінальне зображення завантажується, конвертується у WebP, а потім завантажується на сервер.
  4. Оновлення srcset: Якщо у зображення є атрибут srcset, скрипт обробляє його аналогічно основному зображенню, створюючи та кешуючи нові версії у форматі WebP.
  5. Логування: Усі дії скрипта логуються в консолі браузера для зручності налагодження.

Структура скрипта

  • Основний скрипт (script.js): Включає в себе всю логіку обробки зображень і взаємодії з сервером.
  • Файл для завантаження зображень (upload.php): PHP-скрипт на сервері, який приймає завантажувані зображення та зберігає їх у вказаній директорії.

Мінімальні вимоги для роботи

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

Опис параметрів, які можна змінити

CACHE_DURATION

  • Тип: Число (в мілісекундах)
  • Опис: Час дії кешу для зображень (за замовчуванням 30 днів).
  • Приклад: const CACHE_DURATION = 30 * 24 * 60 * 60 * 1000;

DEBUG

  • Тип: Логічне значення
  • Опис: Увімкнення режиму налагодження, який відключає кешування.
  • Приклад: const DEBUG = false;

LOG_TO_CONSOLE

  • Тип: Логічне значення
  • Опис: Увімкнення виводу логів у консоль браузера.
  • Приклад: const LOG_TO_CONSOLE = true;

MAX_IMAGE_SIZE

  • Тип: Число (в байтах)
  • Опис: Максимально допустимий розмір зображення для обробки (за замовчуванням 5 MB).
  • Приклад: const MAX_IMAGE_SIZE = 5 * 1024 * 1024;

VALID_MIME_TYPES

  • Тип: Масив рядків
  • Опис: Дозволені MIME-типи для завантажуваних зображень.
  • Приклад: const VALID_MIME_TYPES = ['image/jpeg', 'image/png'];

COMPRESSION_QUALITY

  • Тип: Число (від 0.0 до 1.0)
  • Опис: Якість стиснення для зображень WebP (за замовчуванням 0.8).
  • Приклад: const COMPRESSION_QUALITY = 0.8;

MAX_CONCURRENT_REQUESTS

  • Тип: Число
  • Опис: Максимальна кількість паралельних запитів на обробку зображень.
  • Приклад: const MAX_CONCURRENT_REQUESTS = 5;

Встановлення та використання

images-to-webp-script2.jpg

  1. Завантажте файл script.js та upload.php.
  2. Завантажте їх на ваш сервер, у директорію, де знаходяться ваші зображення.
  3. Переконайтеся, що у вас є файл .htaccess у директорії завантаження для обмеження доступу.
  4. Створіть теку "uploads" з правами 777 для загрузки створених зображень формату webp.
  5. Файл upload_log.txt створиться автоматично, якщо буде включено логування.
  6. Додайте посилання на script.js у ваш HTML-код перед закриваючим тегом :

<script src="path/to/script.js"></script>

Структура:

images-to-webp-script2.jpg

Тепер скрипт готовий до використання, і ваші зображення будуть автоматично конвертуватися у формат WebP під час завантаження сторінки.

Встановлення та використання

 Завантажити скрипт архивом із сайту www.shram.kiev.ua

Пароль на архів: shram.kiev.ua

Якщо у вас з'являється повідомлення Virus or Unsafe Browsing detected! будь ласка, зверніть увагу, що це не вірус, а хибне спрацювання на js код. Можно перепровірити будь-яким антівірусом.

Код script.js

/**
* Основной скрипт для обработки изображений и их конвертации в формат WebP.
* Скрипт поддерживает кеширование и вывод логов в консоль для отладки.
*/

const DEBUG = false; // Режим отладки: true отключает кеширование
const LOG_TO_CONSOLE = true; // Вывод логов в консоль
const CACHE_DURATION = 1 * 24 * 60 * 60 * 1000; // Длительность кеша (1 сутки)
const MAX_IMAGE_SIZE = 5 * 1024 * 1024; // Максимальный размер изображения (5 MB)
const COMPRESSION_QUALITY = 0.8; // Качество сжатия (0.0 - 1.0)
const MAX_CONCURRENT_REQUESTS = 5; // Максимальное количество параллельных запросов

const CHECK_MIME_TYPE = false; // Включение/выключение проверки MIME-типа
const VALID_MIME_TYPES = ['image/*']; // Разрешенные MIME-типы для всех изображений

// Конфигурационные переменные
const UPLOAD_SCRIPT_URL = 'https://www.site.com/webp/upload.php';
const CACHE_BASE_URL = 'https://www.site.com/webp/uploads/';

window.addEventListener('load', async function() {
 const images = document.querySelectorAll('img'); // Получаем все изображения на странице
 const baseUrl = window.location.origin; // Базовый URL сайта
 const processedImages = new Set(); // Множество для отслеживания обработанных изображений

 // Функция для обработки изображений в батчах
 const processBatch = async (batch) => {
 await Promise.all(batch.map(async img => {
 // Проверка условий для обработки изображения
 if (img.classList.contains('no-webp')) {
 return;
 }

 // Обработка srcset, если он существует
 const srcset = img.getAttribute('srcset');
 if (srcset) {
 const newSrcset = await processSrcset(srcset);
 img.setAttribute('srcset', newSrcset); // Обновляем srcset
 }

 // Проверяем, что img.src существует
 if (!img.src) {
 return;
 }

 const imgSrcUrl = new URL(img.src, window.location.href);
 if (!imgSrcUrl.href.startsWith(baseUrl)) {
 return;
 }

 // Проверка на повторную обработку
 if (processedImages.has(imgSrcUrl.href)) {
 return;
 }
 processedImages.add(imgSrcUrl.href);

 const filename = imgSrcUrl.pathname.split('/').pop().split('.')[0]; // Имя файла без расширения
 const webpFilename = `${filename}.webp`; // Имя файла WebP
 const cachedUrl = `${CACHE_BASE_URL}${webpFilename}`; // URL кешированного файла

 // Проверка наличия кешированного файла
 const cachedResponse = await fetch(cachedUrl, { method: 'HEAD' });
 if (cachedResponse.ok && !DEBUG) {
 const lastModified = new Date(cachedResponse.headers.get('Last-Modified'));
 if (Date.now() - lastModified.getTime() < CACHE_DURATION) {
 img.src = cachedUrl; // Заменяем src на кешированный URL
 if (LOG_TO_CONSOLE) {
 console.log(`Используется кешированный файл: ${cachedUrl}`);
 }
 return;
 }
 }

 try {
 if (LOG_TO_CONSOLE) {
 console.log(`Создание нового WebP файла для: ${img.src}`);
 }
 const response = await fetch(imgSrcUrl.href); // Получаем оригинальное изображение
 if (!response.ok) throw new Error('Network response was not ok');

 const blob = await response.blob(); // Получаем Blob из ответа
 if (CHECK_MIME_TYPE && !VALID_MIME_TYPES.includes(blob.type)) {
 console.error(`Неверный MIME-тип: ${blob.type}`);
 throw new Error(`Неверный MIME-тип: ${blob.type}`);
 }
 if (blob.size > MAX_IMAGE_SIZE) {
 throw new Error('Изображение превышает максимальный размер');
 }

 const webpBlob = await createWebP(blob, COMPRESSION_QUALITY); // Создаем WebP изображение
 const uploadData = await uploadToServer(webpBlob, webpFilename); // Загружаем WebP

 if (uploadData && uploadData.url) {
 img.onerror = () => {
 if (LOG_TO_CONSOLE) {
 console.error(`Ошибка загрузки изображения: ${img.src}`);
 }
 img.src = imgSrcUrl.href; // Возвращаем исходный URL
 };
 img.src = uploadData.url; // Заменяем src на URL сохраненного изображения
 } else {
 console.error('Error uploading image:', uploadData);
 }
 } catch (error) {
 console.error('Error processing image:', error);
 img.onerror = () => {
 if (LOG_TO_CONSOLE) {
 console.error(`Ошибка загрузки изображения: ${img.src}`);
 }
 img.src = imgSrcUrl.href; // Возвращаем исходный URL
 };
 }
 }));
 };

 // Разделение изображений на батчи для параллельной обработки
 const batches = [];
 const imagesArray = Array.from(images);
 for (let i = 0; i < imagesArray.length; i += MAX_CONCURRENT_REQUESTS) {
 batches.push(imagesArray.slice(i, i + MAX_CONCURRENT_REQUESTS));
 }

 for (const batch of batches) {
 await processBatch(batch); // Обрабатываем каждый батч
 }
});

/**
* Функция для проверки поддержки WebP в браузере.
* @returns {boolean} - true, если WebP поддерживается.
*/
function isWebPSupported() {
 const elem = document.createElement('canvas');
 if (!!(elem.getContext && elem.getContext('2d'))) {
 return elem.toDataURL('image/webp').indexOf('data:image/webp') === 0;
 }
 return false;
}

/**
* Функция для создания WebP изображения из Blob.
* @param {Blob} blob - Исходное изображение в формате Blob.
* @param {number} quality - Качество сжатия (0.0 - 1.0).
* @returns {Promise<Blob>} - Promise, возвращающий созданное WebP изображение в формате Blob.
*/
async function createWebP(blob, quality = 0.8) {
 return new Promise((resolve, reject) => {
 const canvas = document.createElement('canvas');
 const ctx = canvas.getContext('2d');
 const imgElement = new Image();
 const url = URL.createObjectURL(blob); // Создаем URL для Blob

 imgElement.onload = () => {
 canvas.width = imgElement.width;
 canvas.height = imgElement.height;
 ctx.drawImage(imgElement, 0, 0); // Рисуем изображение на холсте
 canvas.toBlob((webpBlob) => {
 URL.revokeObjectURL(url);
 if (webpBlob) {
 resolve(webpBlob); // Возвращаем созданный Blob
 } else {
 reject(new Error('Failed to create WebP blob'));
 }
 }, 'image/webp', quality); // Сжимаем в WebP
 };
 imgElement.onerror = () => reject(new Error('Failed to load image'));
 imgElement.src = url; // Устанавливаем источник для изображения
 });
}

/**
* Функция для загрузки файла на сервер.
* @param {Blob} blob - WebP изображение в формате Blob.
* @param {string} filename - Имя файла для сохранения на сервере.
* @returns {Promise<Object>} - Promise, возвращающий ответ сервера с URL загруженного изображения.
*/
async function uploadToServer(blob, filename) {
 const response = await fetch(UPLOAD_SCRIPT_URL, {
 method: 'POST',
 body: blob,
 headers: {
 'X-Filename': filename // Передаем имя файла в заголовке
 },
 });

 if (!response.ok) {
 const errorText = await response.text();
 throw new Error(`Upload failed: ${errorText}`);
 }

 return await response.json(); // Возвращаем ответ в формате JSON
}

/**
* Функция для обработки srcset атрибута.
* @param {string} srcset - Строка srcset с изображениями.
* @returns {Promise<string>} - Promise, возвращающий обновленную строку srcset.
*/
async function processSrcset(srcset) {
 const sources = srcset.split(',').map(source => source.trim());
 const processedSrcset = new Set(); // Множество для отслеживания обработанных URL

 const promises = sources.map(async source => {
 const [url, size] = source.split(' ');
 const resolvedUrl = new URL(url, window.location.href).href; // Преобразуем в абсолютный URL

 if (processedSrcset.has(resolvedUrl)) {
 return `${resolvedUrl} ${size}`; // Уже обработано
 }
 processedSrcset.add(resolvedUrl);

 const filename = url.split('/').pop().split('.')[0];
 const webpFilename = `${filename}.webp`;
 const cachedUrl = `${CACHE_BASE_URL}${webpFilename}`;
 const cachedResponse = await fetch(cachedUrl, { method: 'HEAD' });

 if (cachedResponse.ok && !DEBUG) {
 return `${cachedUrl} ${size}`; // Используем кешированный файл
 }

 const response = await fetch(resolvedUrl); // Используем абсолютный URL
 const blob = await response.blob();
 if (CHECK_MIME_TYPE && !VALID_MIME_TYPES.includes(blob.type)) {
 console.error(`Неверный MIME-тип для ${resolvedUrl}: ${blob.type}`);
 throw new Error(`Неверный MIME-тип для ${resolvedUrl}: ${blob.type}`);
 }
 if (blob.size > MAX_IMAGE_SIZE) {
 throw new Error(`Изображение ${resolvedUrl} превышает максимальный размер`);
 }

 const webpBlob = await createWebP(blob, COMPRESSION_QUALITY);
 const uploadResponse = await uploadToServer(webpBlob, webpFilename);
 return `${uploadResponse.url} ${size}`;
 });

 const newSources = await Promise.all(promises);
 return newSources.join(', ');
}

Код upload.php

 <?php
 // Конфигурационные переменные
 $uploadDir = '/var/www/USER/data/www/site.com/webp/uploads/';
 $realUploadDir = 'webp/uploads/';

 // Заголовки для CORS и методов доступа
 header("Access-Control-Allow-Origin: *");
 header("Access-Control-Allow-Methods: POST");
 header("Access-Control-Allow-Headers: X-Filename, Content-Type");

 // Обработка POST-запроса
 if ($_SERVER['REQUEST_METHOD'] === 'POST') {
 // Получаем имя файла из заголовка
 $filename = uniqid() . '.webp'; // Генерируем уникальное имя по умолчанию
 if (isset($_SERVER['HTTP_X_FILENAME'])) {
 $headerFilename = basename($_SERVER['HTTP_X_FILENAME']); // Извлекаем только имя файла
 // Проверяем, не добавлено ли уже .webp
 if (pathinfo($headerFilename, PATHINFO_EXTENSION) !== 'webp') {
 $filename = $headerFilename . '.webp'; // Добавляем .webp, если его нет
 } else {
 $filename = $headerFilename; // Используем имя без изменений, если уже .webp
 }
 }

 // Логирование имени файла (для отладки)
 // file_put_contents('upload_log.txt', $filename . "\n", FILE_APPEND);

 // Получаем бинарные данные из POST-запроса
 $data = file_get_contents('php://input');

 // Проверяем и создаем директорию, если она не существует
 if (!is_dir($uploadDir)) {
 mkdir($uploadDir, 0755, true);
 }

 // Сохраняем файл на сервере
 if (file_put_contents($uploadDir . $filename, $data)) {
 // Возвращаем полный URL сохраненного файла
 echo json_encode(['url' => 'https://' . $_SERVER['HTTP_HOST'] . '/' . $realUploadDir . '/' . $filename]);
 } else {
 http_response_code(500);
 echo json_encode(['error' => 'Failed to save file']);
 }
 }
 ?>

Via shram.kiev.ua & ChatGPT


Created/Updated: 30.09.2024