CFile::ResizeImageGet() — метод, который знают все и почти никто не читал до конца. Отсюда две типовые проблемы: непонимание, что означает четвёртый аргумент, и директория upload/resize_cache/ весом в сорок гигабайт, которую однажды находят по алерту о свободном месте.
Разберём метод по аргументам, а потом — как не превратить кэш ресайза в свалку.
Зачем нужен ресайз на лету
При загрузке изображения в инфоблок часто нужны разные размеры:
- Превью для списка (300×200)
- Миниатюра для корзины (80×80)
- Средний размер для карточки (600×400)
- Полный размер для галереи
CFile::ResizeImageGet() создаёт уменьшенные копии автоматически и кэширует их в /upload/resize_cache/.
Базовое использование
<?php
// ID изображения из инфоблока
$imageId = $arItem['PREVIEW_PICTURE'];
// Получаем ресайзнутое изображение
$resized = CFile::ResizeImageGet(
$imageId,
['width' => 300, 'height' => 200],
BX_RESIZE_IMAGE_PROPORTIONAL,
true // $bInitSizes: вернуть реальные width/height созданного файла
);
if ($resized) {
echo '<img src="' . $resized['src'] . '"
width="' . $resized['width'] . '"
height="' . $resized['height'] . '"
alt="' . htmlspecialchars($arItem['NAME']) . '">';
}Четвёртый аргумент — не про скорость. Это $bInitSizes, и он определяет, будут ли в ответе заполнены реальные width и height созданного изображения. Ничего не ускоряет — наоборот, требует прочитать размеры файла.
Ставить его в true при этом нужно почти всегда, и вот почему: без реальных размеров вы не сможете проставить атрибуты width и height в теге <img>, а без них браузер не знает, сколько места зарезервировать под картинку, и вёрстка прыгает при загрузке. Это напрямую метрика CLS в Core Web Vitals.
Полная сигнатура для справки: ResizeImageGet($file, $arSize, $resizeType, $bInitSizes, $arFilters, $bImmediate, $jpgQuality).
Типы масштабирования
| Константа | Описание | Когда использовать |
|---|---|---|
BX_RESIZE_IMAGE_PROPORTIONAL | Вписывает в размер с сохранением пропорций | Карточки, превью |
BX_RESIZE_IMAGE_EXACT | Обрезает до точного размера | Аватары, квадратные превью |
BX_RESIZE_IMAGE_PROPORTIONAL_ALT | Альтернативный пропорциональный | Вертикальные изображения |
Примеры результатов
Исходное изображение: 1200×800
// BX_RESIZE_IMAGE_PROPORTIONAL — вписывает в 300×200
// Результат: 300×200 (пропорции сохранены, может быть меньше)
// BX_RESIZE_IMAGE_EXACT — обрезает до 300×200
// Результат: ровно 300×200 (лишнее обрезано по центру)Хелпер-функция
<?php
/**
* Получение URL ресайзнутого изображения
*/
function getResizedImage(
$imageId,
int $width,
int $height,
int $resizeType = BX_RESIZE_IMAGE_PROPORTIONAL
): ?array {
if (empty($imageId)) {
return null;
}
// Если передан массив — извлекаем ID
if (is_array($imageId)) {
$imageId = $imageId['ID'] ?? $imageId['id'] ?? null;
}
if (!$imageId) {
return null;
}
$resized = \CFile::ResizeImageGet(
(int) $imageId,
['width' => $width, 'height' => $height],
$resizeType,
true,
false, // Не использовать фильтры
false, // Не выбрасывать исключения
85 // Качество JPEG
);
return $resized ?: null;
}
/**
* Получение srcset для responsive изображений
*/
function getResponsiveSrcset($imageId, array $sizes): string
{
$srcset = [];
foreach ($sizes as $width) {
$resized = getResizedImage($imageId, $width, 0, BX_RESIZE_IMAGE_PROPORTIONAL);
if ($resized) {
$srcset[] = $resized['src'] . ' ' . $resized['width'] . 'w';
}
}
return implode(', ', $srcset);
}
// Использование
$imageId = $arItem['PREVIEW_PICTURE'];
$preview = getResizedImage($imageId, 400, 300, BX_RESIZE_IMAGE_EXACT);
$srcset = getResponsiveSrcset($imageId, [400, 800, 1200]);
echo '<img
src="' . $preview['src'] . '"
srcset="' . $srcset . '"
sizes="(max-width: 600px) 100vw, 400px"
alt="' . htmlspecialchars($arItem['NAME']) . '"
loading="lazy"
>';Класс для работы с изображениями
<?php
namespace Local\Image;
class ImageResizer
{
/**
* Стандартные размеры
*/
public const SIZE_THUMB = ['width' => 100, 'height' => 100];
public const SIZE_PREVIEW = ['width' => 300, 'height' => 200];
public const SIZE_MEDIUM = ['width' => 600, 'height' => 400];
public const SIZE_LARGE = ['width' => 1200, 'height' => 800];
private int $quality = 85;
/**
* Ресайз до конкретного размера
*/
public function resize(
$imageId,
int $width,
int $height,
int $type = BX_RESIZE_IMAGE_PROPORTIONAL
): ?array {
$imageId = $this->normalizeImageId($imageId);
if (!$imageId) {
return null;
}
return \CFile::ResizeImageGet(
$imageId,
['width' => $width, 'height' => $height],
$type,
true,
false,
false,
$this->quality
) ?: null;
}
/**
* Квадратное превью (аватар)
*/
public function square($imageId, int $size = 100): ?array
{
return $this->resize($imageId, $size, $size, BX_RESIZE_IMAGE_EXACT);
}
/**
* Превью для списка
*/
public function preview($imageId): ?array
{
return $this->resize(
$imageId,
self::SIZE_PREVIEW['width'],
self::SIZE_PREVIEW['height']
);
}
/**
* Средний размер
*/
public function medium($imageId): ?array
{
return $this->resize(
$imageId,
self::SIZE_MEDIUM['width'],
self::SIZE_MEDIUM['height']
);
}
/**
* Набор размеров для srcset
*/
public function getSrcset($imageId, array $widths = [400, 800, 1200]): string
{
$srcset = [];
foreach ($widths as $width) {
$resized = $this->resize($imageId, $width, 0);
if ($resized) {
$srcset[] = $resized['src'] . ' ' . $resized['width'] . 'w';
}
}
return implode(', ', $srcset);
}
/**
* WebP версия (если поддерживается)
*/
public function resizeWebp($imageId, int $width, int $height): ?array
{
$resized = $this->resize($imageId, $width, $height);
if (!$resized) {
return null;
}
// Проверяем наличие WebP версии
$webpPath = preg_replace('/\.(jpe?g|png)$/i', '.webp', $resized['src']);
$webpFullPath = $_SERVER['DOCUMENT_ROOT'] . $webpPath;
if (file_exists($webpFullPath)) {
$resized['webp'] = $webpPath;
}
return $resized;
}
/**
* Picture элемент с WebP
*/
public function getPictureHtml($imageId, int $width, int $height, string $alt = ''): string
{
$image = $this->resizeWebp($imageId, $width, $height);
if (!$image) {
return '';
}
$html = '<picture>';
if (!empty($image['webp'])) {
$html .= '<source srcset="' . $image['webp'] . '" type="image/webp">';
}
$html .= '<img src="' . $image['src'] . '" ';
$html .= 'width="' . $image['width'] . '" ';
$html .= 'height="' . $image['height'] . '" ';
$html .= 'alt="' . htmlspecialchars($alt) . '" ';
$html .= 'loading="lazy">';
$html .= '</picture>';
return $html;
}
/**
* Установка качества
*/
public function setQuality(int $quality): self
{
$this->quality = max(1, min(100, $quality));
return $this;
}
/**
* Нормализация ID изображения
*/
private function normalizeImageId($imageId): ?int
{
if (is_array($imageId)) {
$imageId = $imageId['ID'] ?? $imageId['id'] ?? null;
}
return $imageId ? (int) $imageId : null;
}
}Использование в шаблоне
<?php
$resizer = new \Local\Image\ImageResizer();
foreach ($arResult['ITEMS'] as $item):
$preview = $resizer->preview($item['PREVIEW_PICTURE']);
$srcset = $resizer->getSrcset($item['PREVIEW_PICTURE']);
?>
<div class="product-card">
<img
src="<?= $preview['src'] ?>"
srcset="<?= $srcset ?>"
sizes="(max-width: 768px) 100vw, 300px"
width="<?= $preview['width'] ?>"
height="<?= $preview['height'] ?>"
alt="<?= htmlspecialchars($item['NAME']) ?>"
loading="lazy"
>
<h3><?= $item['NAME'] ?></h3>
</div>
<?php endforeach; ?>Сколько это стоит на списке из 50 товаров
Метод дёшев, когда файл уже создан, но «дёшев» не значит «бесплатен». На каждый вызов приходится как минимум проверка существования файла на диске, а если вы передали ID, а не готовый массив, — ещё и запрос к b_file.
Отсюда практический приём: если вы уже получили данные элемента через GetList и в выборке есть PREVIEW_PICTURE, передавайте в ResizeImageGet массив файла целиком, а не идентификатор. Пятьдесят товаров в списке — это пятьдесят сэкономленных запросов к базе на каждой отрисовке страницы.
Второй приём — не делать ресайз в шаблоне для того, что не видно. Картинки под loading="lazy" всё равно попадут в вёрстку, но если у вас есть блоки, отрисовывающиеся условно, ресайз для них считать не нужно.
Очистка кэша
<?php
// Очистка кэша ресайза для конкретного изображения
$imageId = 123;
\CFile::ResizeImageDelete($imageId);
// Очистка всего кэша (осторожно!)
// Удаление папки /upload/resize_cache/# Сначала посмотреть объём и распределение по датам
du -sh /home/bitrix/www/upload/resize_cache
find /home/bitrix/www/upload/resize_cache -type f -atime +90 | wc -l
# Удаление по времени последнего ОБРАЩЕНИЯ, а не изменения
find /home/bitrix/www/upload/resize_cache -type f -atime +90 -delete
find /home/bitrix/www/upload/resize_cache -type d -empty -delete-mtime и -atime — разные вещи, и здесь это принципиально. -mtime показывает, когда файл был создан ресайзом, и не имеет отношения к тому, нужен ли он сейчас: превью главного товара каталога, созданное год назад и запрашиваемое каждую минуту, попадёт под удаление наравне с мусором. -atime — время последнего обращения, что и есть искомый критерий «этой картинкой давно никто не пользовался».
Оговорка: многие серверы монтируют файловые системы с noatime или relatime ради производительности, и тогда -atime работает неточно или не работает вовсе. Проверьте вывод mount | grep atime перед тем, как полагаться на этот критерий.
И в любом случае — удалять пачками с проверкой: сначала | wc -l, потом -delete. Массовое удаление кэша ресайза на живом сайте означает, что все эти картинки начнут генерироваться заново по мере обращений, и вы получите всплеск нагрузки на CPU ровно в тот момент, когда пытались освободить диск.
Итоги
Ставьте $bInitSizes = true и проставляйте width/height в теге. Это одна строка, которая напрямую влияет на CLS.
Передавайте массив файла, а не ID, если данные уже выбраны, — экономия запросов на списках заметна.
Считайте кэш ресайза расходуемым ресурсом, а не вечным хранилищем. Директория растёт при каждом новом сочетании «файл + размер», а размеры добавляются при каждом редизайне: сменили сетку каталога — получили ещё один полный комплект превью для всего каталога, старый при этом никуда не делся.
Не удаляйте кэш целиком на живом трафике — регенерация даст всплеск нагрузки. Чистите порциями и по времени обращения.
Про формат. ResizeImageGet отдаёт тот же формат, что и исходник: JPEG остаётся JPEG. Современные форматы дают 25–35% экономии веса при том же качестве — если оптимизация картинок делается ради скорости загрузки, конвертация в WebP принесёт больше, чем подбор размеров.
Комментарии
Система комментариев скоро будет подключена. А пока вы можете написать мне в Telegram или на email.