WebP даёт 25–35% экономии веса при том же визуальном качестве — на сайте с большим каталогом это самый дешёвый способ ускорить загрузку. Способов внедрения несколько, и выбор между ними определяется не техническими предпочтениями, а тем, что у вас уже есть: доступ к конфигу nginx, объём каталога, наличие CDN.
Разберём варианты и то, где каждый из них ломается.
Зачем нужен WebP
WebP — современный формат изображений от Google:
- На 25-35% меньше размер по сравнению с JPEG
- Поддержка прозрачности (как PNG, но меньше размер)
- Поддерживается всеми современными браузерами
Google PageSpeed настоятельно рекомендует использовать WebP.
Способ 1: Nginx с модулем ngx_pagespeed
Самый эффективный способ — конвертация на уровне веб-сервера:
# Включение модуля pagespeed
pagespeed on;
pagespeed FileCachePath /var/cache/pagespeed;
# Конвертация изображений в WebP
pagespeed EnableFilters convert_jpeg_to_webp;
pagespeed EnableFilters convert_png_to_jpeg;
pagespeed EnableFilters convert_to_webp_lossless;
# Оптимизация качества
pagespeed JpegRecompressionQuality 85;
pagespeed WebpRecompressionQuality 80;Способ 2: Nginx с проверкой поддержки
Отдавая разное содержимое по одному адресу, обязательно добавьте Vary: Accept. Иначе промежуточный кэш — CDN, кэш провайдера, прокси в офисной сети — сохранит WebP-версию, полученную для одного посетителя, и отдаст её всем остальным, включая браузеры, которые формат не поддерживают. Пользователь увидит вместо картинок пустые места или предложение скачать файл.
Проявляется это выборочно и невоспроизводимо на вашей машине — то есть относится к худшему классу ошибок. Заголовок Vary сообщает кэшам, что ответ зависит от заголовка Accept, и снимает проблему.
Отдаём WebP если браузер поддерживает:
map $http_accept $webp_suffix {
default "";
"~*webp" ".webp";
}
server {
location ~* ^(.+)\.(jpe?g|png)$ {
# Проверяем существование WebP-версии
set $webp_file $1$webp_suffix;
if (-f $document_root$webp_file) {
rewrite ^ $webp_file break;
}
# Если WebP нет — отдаём оригинал
try_files $uri =404;
}
}Способ 3: PHP-конвертация
Установка библиотеки
composer require rosell-dk/webp-convertБазовая конвертация
<?php
use WebPConvert\WebPConvert;
require 'vendor/autoload.php';
$source = __DIR__ . '/images/photo.jpg';
$destination = __DIR__ . '/images/photo.webp';
WebPConvert::convert($source, $destination, [
'quality' => 80,
'max-quality' => 85,
'default-quality' => 75,
]);Класс для массовой конвертации
<?php
namespace Local\Media;
use WebPConvert\WebPConvert;
class WebpConverter
{
private array $options = [
'quality' => 'auto',
'max-quality' => 85,
'default-quality' => 75,
'encoding' => 'auto',
'near-lossless' => 60,
];
/**
* Конвертация одного файла
*/
public function convert(string $source, ?string $destination = null): ?string
{
if (!file_exists($source)) {
return null;
}
// Проверяем расширение
$ext = strtolower(pathinfo($source, PATHINFO_EXTENSION));
if (!in_array($ext, ['jpg', 'jpeg', 'png'])) {
return null;
}
$destination = $destination ?? $this->getWebpPath($source);
// Пропускаем если уже конвертирован
if (file_exists($destination) && filemtime($destination) >= filemtime($source)) {
return $destination;
}
try {
WebPConvert::convert($source, $destination, $this->options);
return $destination;
} catch (\Throwable $e) {
error_log("WebP conversion failed: {$source} - {$e->getMessage()}");
return null;
}
}
/**
* Конвертация директории
*/
public function convertDirectory(string $dir, bool $recursive = true): array
{
$converted = [];
$pattern = $recursive ? '*.{jpg,jpeg,png}' : '*.{jpg,jpeg,png}';
$flags = GLOB_BRACE | ($recursive ? 0 : 0);
if ($recursive) {
$files = $this->globRecursive($dir, $pattern);
} else {
$files = glob($dir . '/' . $pattern, GLOB_BRACE);
}
foreach ($files as $file) {
$result = $this->convert($file);
if ($result) {
$converted[] = $result;
}
}
return $converted;
}
/**
* Получение пути для WebP
*/
private function getWebpPath(string $source): string
{
return preg_replace('/\.(jpe?g|png)$/i', '.webp', $source);
}
/**
* Рекурсивный glob
*/
private function globRecursive(string $dir, string $pattern): array
{
$files = glob($dir . '/' . $pattern, GLOB_BRACE);
foreach (glob($dir . '/*', GLOB_ONLYDIR) as $subdir) {
$files = array_merge($files, $this->globRecursive($subdir, $pattern));
}
return $files;
}
}
// Использование
$converter = new WebpConverter();
$converter->convertDirectory('/upload/iblock/', true);Способ 4: Замена в HTML (Битрикс)
Обработчик OnEndBufferContent для замены путей:
<?php
// /local/php_interface/init.php
use Bitrix\Main\EventManager;
EventManager::getInstance()->addEventHandler(
'main',
'OnEndBufferContent',
'replaceImagesToWebp'
);
function replaceImagesToWebp(string &$content): void
{
// Пропускаем админку
if (defined('ADMIN_SECTION') && ADMIN_SECTION === true) {
return;
}
// Проверяем поддержку WebP браузером
if (!isWebpSupported()) {
return;
}
// Заменяем в тегах <img>
$content = preg_replace_callback(
'/<img([^>]+)src=["\']([^"\']+\.(jpe?g|png))["\']([^>]*)>/i',
function ($matches) {
$webpPath = preg_replace('/\.(jpe?g|png)$/i', '.webp', $matches[2]);
$fullPath = $_SERVER['DOCUMENT_ROOT'] . $webpPath;
if (file_exists($fullPath)) {
return '<img' . $matches[1] . 'src="' . $webpPath . '"' . $matches[4] . '>';
}
return $matches[0];
},
$content
);
// Заменяем в background-image
$content = preg_replace_callback(
'/background(-image)?:\s*url\(["\']?([^"\')\s]+\.(jpe?g|png))["\']?\)/i',
function ($matches) {
$webpPath = preg_replace('/\.(jpe?g|png)$/i', '.webp', $matches[2]);
$fullPath = $_SERVER['DOCUMENT_ROOT'] . $webpPath;
if (file_exists($fullPath)) {
return 'background' . $matches[1] . ': url(' . $webpPath . ')';
}
return $matches[0];
},
$content
);
}
function isWebpSupported(): bool
{
return strpos($_SERVER['HTTP_ACCEPT'] ?? '', 'image/webp') !== false;
}Способ 5: Picture element (универсальный)
Это самый предсказуемый из всех вариантов, и при прочих равных я выбираю его. Выбор формата делает браузер по разметке, а не сервер по заголовку: не нужен доступ к конфигу nginx, не возникает проблем с промежуточными кэшами, поведение одинаково на любом хостинге и с любым CDN.
Плата — разметка становится многословнее, и её нужно генерировать. На проекте с шаблонизатором это одна функция-хелпер, после которой вопрос закрыт.
HTML с fallback для старых браузеров:
<picture>
<source srcset="/images/photo.webp" type="image/webp">
<source srcset="/images/photo.jpg" type="image/jpeg">
<img src="/images/photo.jpg" alt="Описание">
</picture>Генерация picture в Битрикс
<?php
function getPictureHtml(int $fileId, string $alt = ''): string
{
$file = \CFile::GetFileArray($fileId);
if (!$file) {
return '';
}
$src = $file['SRC'];
$webpSrc = preg_replace('/\.(jpe?g|png)$/i', '.webp', $src);
$webpExists = file_exists($_SERVER['DOCUMENT_ROOT'] . $webpSrc);
$html = '<picture>';
if ($webpExists) {
$html .= '<source srcset="' . $webpSrc . '" type="image/webp">';
}
$html .= '<img src="' . $src . '" alt="' . htmlspecialchars($alt) . '" loading="lazy">';
$html .= '</picture>';
return $html;
}Консольный скрипт конвертации
<?php
// /local/scripts/convert_to_webp.php
$_SERVER['DOCUMENT_ROOT'] = realpath(__DIR__ . '/../../');
require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';
use Local\Media\WebpConverter;
$directories = [
'/upload/iblock/',
'/upload/medialibrary/',
'/local/templates/main/images/',
];
$converter = new WebpConverter();
$totalConverted = 0;
foreach ($directories as $dir) {
$fullPath = $_SERVER['DOCUMENT_ROOT'] . $dir;
if (!is_dir($fullPath)) {
echo "Директория не найдена: {$dir}\n";
continue;
}
echo "Обработка: {$dir}\n";
$converted = $converter->convertDirectory($fullPath);
echo " Конвертировано: " . count($converted) . " файлов\n";
$totalConverted += count($converted);
}
echo "\nИтого конвертировано: {$totalConverted} файлов\n";Запуск:
php /home/bitrix/www/local/scripts/convert_to_webp.phpCron для автоматической конвертации
# Каждую ночь в 3:00
0 3 * * * /usr/bin/php /home/bitrix/www/local/scripts/convert_to_webp.php >> /var/log/webp_convert.log 2>&1Что учесть при внедрении
Не конвертируйте по запросу без ограничений. Схема «пришёл запрос на картинку — конвертируем на лету» превращается в вектор нагрузки: бот, перебирающий адреса изображений, заставит сервер молотить конвертацию непрерывно. Конвертируйте заранее пакетно, а на лету — только с сохранением результата и с ограничением на одновременные операции.
Проверяйте, что конвертация выгодна. Для небольших PNG с прозрачностью и для уже оптимизированных JPEG WebP иногда даёт файл больше исходного. Логика должна сравнивать размеры и оставлять оригинал, если выигрыша нет, — иначе оптимизация местами работает в минус.
Прозрачность и анимация переносятся не всегда. Проверьте на реальных изображениях каталога: логотипы с альфа-каналом и анимированные GIF — те случаи, где результат стоит посмотреть глазами, а не довериться коду.
Держите оригиналы. WebP — производный формат; исходники нужны для повторной генерации при смене размеров, для выгрузок и для маркетплейсов, которые его не принимают.
Место на диске удвоится. Каталог с картинками весом 20 ГБ после конвертации займёт около 35. Это нужно посчитать до запуска пакетной обработки, а не в момент, когда диск закончится посередине.
Про качество: разумный диапазон для WebP — 75–85, ниже 70 на фотографиях появляются заметные артефакты. Но подбирать значение стоит на своих изображениях: для фотографий товаров и для схематичной графики оптимум разный, а разница между 80 и 85 по весу заметнее, чем по виду.
Перед запуском проверьте поддержку формата в PHP: php -m | grep -iE "gd|imagick", а затем — что сборка GD действительно умеет WebP (gd_info() покажет соответствующий флаг). Наличие модуля само по себе ничего не гарантирует: сборки без поддержки WebP встречаются, и обнаруживается это на первой же конвертации.
Комментарии
Система комментариев скоро будет подключена. А пока вы можете написать мне в Telegram или на email.