Внедрение формата изображений Webp на сайтах под управлением MODX Revolution

11 декабря 2021
assets/img/blog/730x430-webp.jpg Внедрение формата изображений Webp на сайтах под управлением MODX Revolution

Добрый день. Сегодня хочу рассказать о внедрении формата webp для сайтов написанных на MODX.


Webp – формат разработанный компанией Google специально для использования на сайтах. На данный момент инструменты оценки скорости работы вашего сайта будут требовать использования этого формата. Изначально поддерживаться только браузером Chrome, но на данный момент поддерживается большинством браузеров обновлённых до актуальной версии. Тем не менее остаётся ещё достаточно устройств, которые понимать его отказываются от слова совсем, а значит придётся изобретать некое универсальное решение.


Изначально я пользовался плагином phpThumbOn. Данный плагин может не только готовить превью заданного размера, но и конвертировать его в нужный формат, в том числе и в webp. Выглядит это так:


С использованием стандартного парсера:

 


[ [*image_tv_name:phpthumbon=`q=80&w=200&h=200&&zc=1&f=webp`]]

//не забудьте убрать пробел после открывающей скобки


C использованием fenom:

 


{ $_modx->resource. image_tv_name | phpthumbon: ' q=80w=200&h=200&zc=1&f=webp'}

//не забудьте убрать пробел после открывающей скобки


Или

 


{ 'phpThumbOn' | snippet : ['input' => 'assets/images/image_name.jpg, ', 'options' => ' q=80&w=200&h=200&zc=1&f=webp ']}
//не забудьте убрать пробел после открывающей скобки

q – качество сжатия изображения
w и h – ширина и высота генерируемого превью.
f – формат генерируемого превью.


Недостаток такого способа в том, что webp изображения будут отдаваться всем браузерам и в том числе которые его не понимают. Засучив рукава я прошерстил Google в поисках готового решения. Все найденные решения обладали своими недостатками, а значит настало время писать своё, постаравшись объединить их сильные стороны.

Задача была сформирована так:
•    Создавать копию в формате webp при загрузке нового jpg или png изображения.
•    Удалять созданную копию при удалении оригинала.
•    В случае если браузер пользователя поддерживает формат webp подменять изображения в картинках и фонах (кроме тех которые прописаны через css файлы).
•    При выводе страницы проверять есть ли webp копия изображения, если нет, то создавать ее.
•    Сделать работу совместимой с phpThumbOn, так как этот плагин позволяет автоматически делать подрезку картинок, не давая возможности вашему контент менеджеру ошибиться с их разрешением.

Итак, вот само решение:


Шаг 1. Устанавливаем phpthumbon.


Шаг 2 Создаём сниппет WebPConvertor:


<?php

$outFilePath=substr(MODX_BASE_PATH,0,-1).'/'.ltrim($url, '/');
$outFile=pathinfo($outFilePath);
$webpFile=$outFile['dirname'] . '/' . $outFile['basename'] . '.webp';
if (!file_exists($webpFile)){
    $ext=$outFile['extension'];
    if ($ext=="jpg" || $ext=="jpeg")
        $img = imageCreateFromJpeg($outFilePath);
    if ($ext=="png")
        $img = imageCreateFromPng($outFilePath);
    imagepalettetotruecolor($img);
    imagealphablending($img, true);
    imagesavealpha($img, true);
    imagewebp($img, $webpFile, 100);
    imagedestroy($img);
    return $webpFile;
}

 

На входе данный сниппет получает путь к изображению и конвертирует его создавая файл по принципу image_name.jpg -> image_name.jpg.webp. В первой версии сниппета родное разрешение обрезалось, но возникла путаница из-за jpg и png изображений с одинаковыми названиями.

Шаг 3. Создаём плагин WebpPlugin:
В плагине устанавливаем 4 события.

  1. OnFileManagerUpload – отвечает за конвертацию загружаемого изображения.
  2. OnFileManagerFileRemove – отвечает за удаление webp копии при удалении оригинала.
  3. OnFileManagerFileRename – отвечает за переименование копии при удалении оригинала.
  4. OnWebPagePrerender – за подмену исходных изображений на webp.

 


<?php
if ($modx->event->name =='OnFileManagerUpload') {
    $modx->runSnippet('WebPConverter',array(
        'url' => $directory.$files['file']['name']
     ));
}
if ($modx->event->name =='OnFileManagerFileRemove') {
    if(file_exists($path.".webp"))
        unlink($path.".webp");
}
if ($modx->event->name =='OnFileManagerFileRename') {
    if(file_exists($modx->getOption('base_path').$_POST['path'].".webp"))
        rename($modx->getOption('base_path').$_POST['path'].".webp", $path.".webp");
}
if($modx->event->name == 'OnWebPagePrerender' && stripos($_SERVER['HTTP_ACCEPT'], 'image/webp') !== false){
    $content = $modx->Event->params['documentOutput'];     
    $content = &$modx->resource->_output;
    $imgs = array();
    
    preg_match_all('/<source[^>]+>/i',$content, $result1);
        preg_match_all('/<img[^>]+>/i',$content, $result2);
        $result = array(array_merge($result1[0], $result2[0]));
    
    if (count($result))
    {
        foreach($result[0] as $img_tag)
        {        
            preg_match('/(src)=("[^"]*")/i',$img_tag, $img[$img_tag]);        
            if (!$img[$img_tag])
                preg_match('/(srcset)=("[^"]*")/i',$img_tag, $img[$img_tag]);
            $img_real = str_replace('"','',$img[$img_tag][2]);
            $img_real = str_replace('./','',$img_real);            
               if ((strpos($img_real, '.jpg')!==false) or (strpos($img_real, '.jpeg')!==false) or (strpos($img_real, '.png')!==false)) $imgs[] = $img_real;                     
        }
        $imgs = array_unique($imgs);
        foreach($imgs as $img_real)
        {
        if(($img_real) && (file_exists($modx->config['base_path'].$img_real)))
            {   
                $img_real = ltrim($img_real, '/');
                $img_file = pathinfo($img_real);
                $img_webp = $img_file['dirname'] . '/' . $img_file['basename'] . '.webp';
                if (file_exists($img_webp))
                    $content = str_replace($img_real, $img_webp, $content);
                else{
                    $img_webp = $modx->runSnippet('WebPConverter',array(
                    'url' => $img_real
                    ));
                }
            }
        }
    }
    $result='';
    
    
    preg_match_all('/url\(([^)]*)"?\)/iu', $content, $result);
    $imgs = array();
    if (count($result))
    {
        foreach($result[1] as $img_tag)
        {        
            $img_real = str_replace('./','',$img_tag);            
               if ((strpos($img_real, '.jpg')!==false) or (strpos($img_real, '.jpeg')!==false) or (strpos($img_real, '.png')!==false)) $imgs[] = $img_real;                     
        }
        $imgs = array_unique($imgs);
        foreach($imgs as $img_real)
        {
        if(($img_real) && (file_exists($modx->config['base_path'].$img_real)))
            {   
                $img_real = ltrim($img_real, '/');
                $img_file = pathinfo($img_real);
                $img_webp = $img_file['dirname'] . '/' . $img_file['basename'] . '.webp';
                
                if (file_exists($img_webp))
                    $content = str_replace($img_real, $img_webp, $content);
                else{
                    $img_webp = $modx->runSnippet('WebPConverter',array(
                    'url' => $img_real
                    ));
                }
            }
        }
    }
    $modx->Event->output($content);
}


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


Шаг 4. Финальный. Заменить содержимое сниппета phpThumbOn добавив туда строку


$modx->runSnippet('WebPConvertor',array('url' => $modx->phpThumbOn->run($scriptProperties)));

Так что бы получилоось:


<?php
/**
 * phpThumbOn
 * Создание превьюх картинок
 *
 * Copyright 2013 by Agel_Nash <Agel_Nash@xaker.ru>
 *
 * @category images
 * @license GNU General Public License (GPL), http://www.gnu.org/copyleft/gpl.html
 * @author Agel_Nash <Agel_Nash@xaker.ru>
 */
if(empty($modx) || !($modx instanceof modX)) return '';
$componentPath = (string)$modx->getOption('phpthumbon.core_path', null, $modx->getOption('core_path').'components/phpthumbon/');
if(!isset($modx->phpThumbOn)){
    $modx->phpThumbOn = $modx->getService("phpthumbon","phpThumbOn",$componentPath.'model/phpthumbon/', $scriptProperties);
}
if(!($flag = ($modx->phpThumbOn instanceof phpThumbOn))){
    $modx->phpThumbOn = null;
}
$modx->runSnippet('WebPConvertor',array('url' => $modx->phpThumbOn->run($scriptProperties)));
return $flag ? $modx->phpThumbOn->run($scriptProperties) : $modx->getOption('phpthumbon.noimage', $scriptProperties);

Когда превью уже сформировано путь к изображению будет передаваться в наш сниппет WebPConvertor для конвертации. Я предполагаю, что подобным образом можно подключиться и к другим аналогичным сниппетам, таким как phpThumb и phpThumbOf.


Актуальная версия на гитхаб.