# Битрикс: ресайз изображений на лету

URL: https://dmeshcheryakov.ru/blog/bitrix-image-resize/
Раздел: 1С-Битрикс
Теги: 1С-Битрикс, Изображения, PHP, Оптимизация
Опубликовано: 2019-03-13
Обновлено: 2026-09-07
Автор: Дмитрий Мещеряков (https://dmeshcheryakov.ru)

> CFile::ResizeImageGet для создания миниатюр. Типы масштабирования, кэширование, оптимизация производительности.

`CFile::ResizeImageGet()` — метод, который знают все и почти никто не читал до конца. Отсюда две типовые проблемы: непонимание, что означает четвёртый аргумент, и директория `upload/resize_cache/` весом в сорок гигабайт, которую однажды находят по алерту о свободном месте.

Разберём метод по аргументам, а потом — как не превратить кэш ресайза в свалку.

## Зачем нужен ресайз на лету

При загрузке изображения в инфоблок часто нужны разные размеры:
- Превью для списка (300×200)
- Миниатюра для корзины (80×80)
- Средний размер для карточки (600×400)
- Полный размер для галереи

`CFile::ResizeImageGet()` создаёт уменьшенные копии автоматически и кэширует их в `/upload/resize_cache/`.

## Базовое использование

```php
<?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

```php
// BX_RESIZE_IMAGE_PROPORTIONAL — вписывает в 300×200
// Результат: 300×200 (пропорции сохранены, может быть меньше)

// BX_RESIZE_IMAGE_EXACT — обрезает до 300×200
// Результат: ровно 300×200 (лишнее обрезано по центру)
```

## Хелпер-функция

```php
<?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
<?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
<?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
<?php
// Очистка кэша ресайза для конкретного изображения
$imageId = 123;
\CFile::ResizeImageDelete($imageId);

// Очистка всего кэша (осторожно!)
// Удаление папки /upload/resize_cache/
```

```bash
# Сначала посмотреть объём и распределение по датам
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 принесёт больше, чем подбор размеров.