Думаю, не стоит объяснять необходимость оптимизации картинок на сайте. Хоть сейчас низкая скорость интернета встречается куда реже, всё же объём веб-страницы влияет как на позиции сайта в поисковых системах, так и на конверсию в общем.

Условно подходы к оптимизации изображений можно разделить на два типа: обработка исходников и оптимизация «на лету».

Первый подход использует, например, ms2Gallery. Сначала исходник уменьшается до размера 1980x1080 (по умолчанию) ещё до физической загрузки на сайт — прямо в браузере контент-менеджера. А потом на сервере создаются превьюшки нужного размера.

Так же первый подход близок автору компонента Tinycompressor. Этот компонент отправляет картинки в сервис TinyJpg сразу после загрузки на сайт.

В некоторых случаях бывает удобнее выгружать все картинки к себе на компьютер, оптимизировать их локально (программ для этого существует множество) и заливать изображения обратно на сайт. Ну, и брать каждый раз за это денег с клиента =)

У второго подхода — оптимизации «на лету» есть существенные минусы: во-первых, высокая нагрузка на сервер. Нужно следить, чтобы оптимизированные изображения сохранялись, чтобы не обрабатывать картинки повторно. Во-вторых, конечно, гораздо бóльшие требования к свободному месту на диске — ведь нужно хранить и оригиналы, и оптимизированные картинки.

На недавном MODXpo Стивен МакЛин упомянул дополнение SmushIt — оно работает как фильтр ввода-вывода и отправляет картинки в сервис reSmush.it, который сжимает их без существенной потери качества.

Я попробовал это дополнение и немного в нём разочаровался. Сниппет сохраняет оптимизированную версию изображения поверх оригинала, соответственно определить, сжимался этот файл ранее или нет, невозможно. Так что файлы будут каждый раз при очистке кеша отправляться на обработку, что скажется на производительности сайта и нагрузке на сервер.

Я попробовал устранить этот минус. Вот код сниппета, который сохраняет обработанные картинки с префиксом op- в той же папке: https://github.com/ilyautkin/SmushIt/blob/patch-1/core/components/smushit/elements/snippets/snippet.smushit.php. Вот только после внедрения этого сниппета на сайт я обнаружил ещё один минус: хоть размер изображений действительно становится меньше, Google PageSpeed считает, что картинки можно сжать ещё сильнее.

На этом бесплатные варианты оптимизации, которые я нашёл, закончились. Среди платных решений есть сервис OptiPic. Сервиса предоставляет вам PHP-скрипт, который вы размещаете на сайте, после чего происходит индексация (составление списка) всех картинок. Потом, в течение некоторого времени, скрипт оптимизирует эти картинки небольшими порциями. В результате нагрузка на сервер минимальная, а все картинки (и оригиналы, и превьюшки) сжимаются до размера, который устроит алгоритмы PageSpeed.

Сервис берёт оплату за объём обработанных изображений. При регистрации вы получаете 10 Мб, но этого не хватит даже для сайта-визитки. Минимальный тариф — 100 руб. за 100 Мб тоже не внушает оптимизма. Проблема заключается в том, что изображения обрабатываются неизбирательно. Обычно на сайтах оригиналы изображений открываются в модальных окнах при клике на превьюшку и оптимизировать нужно только превьюшки и уменьшенные/обрезанные картинки. Да, скрипт можно настроить — указать, в каких папках картинки нужно обрабатывать, а в каких нет. Но мы любим MODX за его гибкость — иногда бывает сложно определить, в какой папке будут храниться оригиналы.

Поэтому я не использую стандартный скрипт OptiPic, а написал свой сниппет, идею которого подсмотрел у компонента SmushIt. Сниппет работает как фильтр ввода-вывода, а использовать его лучше после создания превьюшки (например, сниппетом phpthumbon, phpThumb и пр.) — тогда трафик будет расходоваться экономно.

Кроме того, обработанные картинки сохраняются и на повторную оптимизацию изображения не отправляются — этим мы снижаем нагрузку на сервер.

Вот код этого сниппета:
$optipicCfg = array(
    "secretkey" => "XXXXXXXXXXXXXXXXXXXXXXXXXX", // Укажите секретный ключ OptiPic
    "api_url" => "https://optipic.io/api/",
);

$file = ltrim($input, '/');

// Если CURL не установлен - возвращаем соответствующую ошибку
if(!function_exists('curl_init') || !is_callable('curl_init'))
{
    $modx->log(MODX_LOG_LEVEL_ERROR, "[OptiPic] Could not run Curl");
    return $file;
}

$postParams = array();
$compressedFiles = array();
$postParams["quality"] = 70;
$postParams["secretkey"] = $optipicCfg['secretkey'];

$fullpath = MODX_BASE_PATH . $file;
$path_arr = explode('/', $fullpath);
$file_name = array_pop($path_arr);
$optimized_file_name = 'op-' . $file_name;
$optimized_file = implode('/', $path_arr) . '/' . $optimized_file_name;
$output = str_replace(MODX_BASE_PATH, '', $optimized_file);

if (!file_exists($optimized_file)) {
    
    // Файла не существует - говорим об этом сервису (чтобы он удалил файл из индекса)
    if(!file_exists($fullpath))
    {
        $modx->log(MODX_LOG_LEVEL_ERROR, "[OptiPic] File {$file} does not exist");
        return $file;
    }
    
    // Если директория файла или сам файл недоступен для записи - выдаем ошибку записи
    if(!is_writable(dirname($fullpath)) || !is_writable($fullpath))
    {
        $modx->log(MODX_LOG_LEVEL_ERROR, "[OptiPic] File {$file} or directory is not writable");
        return $file;
    }
    
    // Прикрепляем файл
    if (function_exists('curl_file_create')) { // php 5.5+
        $cFile = curl_file_create($fullpath);
    } else { // 
        $cFile = '@' . realpath($fullpath);
    }
    $postParams["file"] = $cFile; 
    
    // Указываем внутренний путь к файлу от корня DOCUMENT_ROOT (это нужно для сохранения оригиналов в облеке optipic.io)
    $postParams['filepath'] = $file;
    
    //$imgData = file_get_contents($fullpath);
    //var_dump($optipicCfg["api_url"]."compress?".http_build_query($getParams));        
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $optipicCfg["api_url"]."compress");
    //curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
    //curl_setopt($ch, CURLOPT_USERPWD, $optipicCfg["api_login"] . ":" . $optipicCfg["api_pass"]);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $postParams);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    if(!ini_get("open_basedir"))
    {
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    }
    curl_setopt($ch, CURLOPT_POSTREDIR, 3); // чтобы POST-данные передавались и при редиректе
    
    $optiImgData = curl_exec($ch);
    $info = curl_getinfo($ch);
    
    if($info["http_code"]==200)
    {
        //var_dump($_SERVER['DOCUMENT_ROOT'].$file);
        
        // сохраняем результат сжатия во временный файл
        $tmpCompressedFile = $fullpath.".tmp"; // tempnam(sys_get_temp_dir(), 'optipic_');
        file_put_contents($tmpCompressedFile, $optiImgData);
        
        // сравниваем исходный размер картинки со сжатым
        $compressedSize = filesize($tmpCompressedFile); // сжатый размер
        $origSize = filesize($fullpath); // исходный размер
        
        $perms = fileperms($fullpath); // запоминаем исходный chmod
        
        // сжатая картинка меньше, чем исходная - сохраняем сжатую версию
        if($compressedSize>0 && $compressedSize<$origSize)
        {
            $saved = file_put_contents($optimized_file, $optiImgData);
        }
        // исходная картинка меньше сжатой - оставляем исходную
        else
        {
            $modx->log(MODX_LOG_LEVEL_ERROR, "[OptiPic] Original file size of {$file} less then optimized");
            $saved = file_put_contents($optimized_file, file_get_contents($fullpath));
        }
        @chmod($optimized_file, $perms); // ставим chmod у сжатого такой же как у исходного
        @chown($optimized_file, fileowner($fullpath)); // ставим владельца таким же, как у исходного файла
        @chgrp($optimized_file, filegroup($fullpath)); // ставим группу такой же, как у исходного файла
        
        @unlink($tmpCompressedFile); // удаляем временный файл
        
        // успешно сохранен сжатый файл
        if($saved!==false)
        {
            clearstatcache(true, $fullpath); // чистим кеш информации по файлу - иначе php выдает старый mtime и size
            $newsize = filesize($fullpath);
            $newmtime = filemtime($fullpath);
        }
        // сжатый файл не удалось сохранить - фиксируем ошибку записи файла
        else
        {
            $modx->log(MODX_LOG_LEVEL_ERROR, "[OptiPic] Could not save {$file}");
            return $file;
            
        }
    }
    @unlink($tmpCompressedFile); // удаляем временный файл
}
return $output;


На сервисе есть партнёрская программа, плюс они дают 100 Мб бесплатно, если вы разместите обзор на своём сайте. А если при регистрации указать купон ILYAUT, вы получите скидку 10%.

Расскажите и вы о том, как оптимизируете и сжимаете изображения. Может, я упустил какой-то интересный сервис или скрипт для оптимизации. Или есть какое-то бесплатное решение)