Селектор с изображениями

Работа с изображениями в выпадающих списках на базе Choices.js опирается на расширение стандартного шаблона рендеринга элементов и подключение пользовательских полей данных к объектам выбора. Библиотека изначально ориентирована на текстовые значения, однако архитектура позволяет полностью переопределять представление как выбранных элементов, так и элементов списка.

Основной принцип реализации селектора с изображениями заключается в том, что каждый элемент данных дополняется URL изображения, а затем отображение элемента формируется через пользовательские шаблоны.


Каждый элемент списка расширяется дополнительным полем, которое хранит путь к изображению. Стандартный формат данных для Choices.js допускает произвольные свойства:

const items = [
  {
    value: 'paris',
    label: 'Париж',
    image: 'https://example.com/images/paris.jpg'
  },
  {
    value: 'tokyo',
    label: 'Токио',
    image: 'https://example.com/images/tokyo.jpg'
  },
  {
    value: 'ny',
    label: 'Нью-Йорк',
    image: 'https://example.com/images/ny.jpg'
  }
];

Ключевой момент заключается в том, что библиотека не использует поле image автоматически — оно применяется исключительно в кастомных шаблонах отображения.


Инициализация базового экземпляра Choices.js

Подключение начинается с создания экземпляра селектора на основе существующего select элемента:

const element = document.querySelector('#city-select');

const choices = new Choices(element, {
  searchEnabled: true,
  itemSelectText: '',
  shouldSort: false
});

На этом этапе список остаётся стандартным текстовым, но уже готов к расширению визуального слоя.


Переопределение шаблонов отображения

Для вывода изображений используется механизм callbackOnCreateTemplates, который позволяет полностью заменить шаблоны элементов списка и выбранных значений.

Базовая структура кастомных шаблонов

const choices = new Choices(element, {
  searchEnabled: true,
  itemSelectText: '',
  shouldSort: false,

  callbackOnCreateTemplates: function (template) {
    return {
      choice: (classNames, data) => {
        return template(`
          <div class="${classNames.item} ${classNames.itemChoice}"
               data-select-text="${this.config.itemSelectText}"
               data-choice
               data-id="${data.id}"
               data-value="${data.value}"
               ${data.disabled ? 'data-choice-disabled aria-disabled="true"' : 'data-choice-selectable'}>

            <img class="choice-image" src="${data.customProperties.image}" alt="${data.label}">
            <span class="choice-label">${data.label}</span>

          </div>
        `);
      },

      item: (classNames, data) => {
        return template(`
          <div class="${classNames.item} ${classNames.itemSelectable}"
               data-item
               data-id="${data.id}"
               data-value="${data.value}">

            <img class="item-image" src="${data.customProperties.image}" alt="${data.label}">
            <span class="item-label">${data.label}</span>

          </div>
        `);
      }
    };
  }
});

Передача пользовательских свойств

Choices.js безопасно переносит дополнительные поля через customProperties. Поэтому изображения следует передавать именно через этот объект:

const items = [
  {
    value: 'paris',
    label: 'Париж',
    customProperties: {
      image: 'https://example.com/images/paris.jpg'
    }
  },
  {
    value: 'tokyo',
    label: 'Токио',
    customProperties: {
      image: 'https://example.com/images/tokyo.jpg'
    }
  }
];

Такой подход предотвращает конфликты с внутренними полями библиотеки и обеспечивает стабильность при обновлениях.


Стилизация изображения внутри селектора

После добавления <img> элементов требуется определить поведение визуальных компонентов через CSS.

Базовые стили

.choices__list--dropdown .choice-image {
  width: 32px;
  height: 32px;
  object-fit: cover;
  border-radius: 6px;
  margin-right: 10px;
  vertical-align: middle;
}

.choices__list--single .item-image {
  width: 24px;
  height: 24px;
  object-fit: cover;
  border-radius: 4px;
  margin-right: 8px;
  vertical-align: middle;
}

Выравнивание элементов

Для корректного отображения изображения и текста применяется flex-модель:

.choices__item--choice {
  display: flex;
  align-items: center;
  gap: 10px;
}

.choices__item--selectable {
  display: flex;
  align-items: center;
  gap: 8px;
}

Асинхронная загрузка данных с изображениями

При динамическом получении данных через API структура остаётся аналогичной, однако добавляется этап трансформации ответа.

fetch('/api/cities')
  .then(response => response.json())
  .then(data => {
    const formatted = data.map(city => ({
      value: city.id,
      label: city.name,
      customProperties: {
        image: city.image_url
      }
    }));

    choices.setChoices(formatted, 'value', 'label', true);
  });

Ключевой момент заключается в сохранении единого формата данных независимо от источника.


Управление поведением поиска

При использовании изображений часто требуется отключение сортировки, чтобы сохранить визуальную целостность интерфейса:

shouldSort: false,
searchEnabled: true,
searchFields: ['label']

Поиск работает исключительно по текстовому полю label, изображения не участвуют в индексации.


Оптимизация загрузки изображений

При большом количестве элементов список может содержать десятки или сотни изображений. В таких случаях применяются следующие подходы:

Ленивое отображение

<img loading="lazy" src="${data.customProperties.image}" alt="${data.label}">

Плейсхолдеры

<img src="${data.customProperties.image || '/placeholder.png'}">

Предзагрузка критических изображений

const preload = (items) => {
  items.forEach(item => {
    const img = new Image();
    img.src = item.customProperties.image;
  });
};

Обработка отсутствующих изображений

При отсутствии изображения важно предотвращать поломку интерфейса:

const safeImage = data.customProperties?.image || '/default.png';

Или через CSS-фоллбек:

img {
  background-color: #f0f0f0;
}

Интеграция с многоуровневыми данными

Choices.js допускает расширение логики до категорий с изображениями, где каждый элемент дополнительно группируется.

const grouped = [
  {
    label: 'Европа',
    choices: [
      {
        value: 'paris',
        label: 'Париж',
        customProperties: {
          image: 'https://example.com/paris.jpg'
        }
      }
    ]
  }
];

Расширенное поведение элементов выбора

Дополнительные визуальные эффекты часто добавляются через модификацию шаблонов:

  • индикатор выбранного состояния
  • overlay при hover
  • иконка загрузки

Пример:

<span class="choice-overlay"></span>
.choice-overlay {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 0.2s;
}

.choices__item--choice:hover .choice-overlay {
  opacity: 0.1;
}

Ограничения подхода

Использование изображений внутри Choices.js увеличивает нагрузку на DOM, так как каждый элемент создаёт дополнительные узлы с медиа-контентом. При больших списках наблюдаются следующие эффекты:

  • рост времени рендеринга dropdown
  • увеличение потребления памяти
  • задержки при открытии списка

Для компенсации применяется ограничение количества элементов:

maxItemCount: 10,
renderChoiceLimit: 20

Комбинирование изображений и тегов

В расширенных интерфейсах изображения часто сочетаются с тегами или дополнительными метаданными:

{
  value: 'tokyo',
  label: 'Токио',
  customProperties: {
    image: 'https://example.com/tokyo.jpg',
    subtitle: 'Япония'
  }
}

Рендер:

<div>
  <img src="...">
  <div>
    <div>Токио</div>
    <small>Япония</small>
  </div>
</div>

Стабильность и поддержка обновлений

При обновлении Choices.js важно учитывать, что внутренние классы и структура template могут изменяться. Поэтому все визуальные расширения должны опираться на:

  • classNames, передаваемые библиотекой
  • data-* атрибуты
  • customProperties

Жёсткая привязка к HTML-структуре без использования этих механизмов приводит к поломке при обновлениях.


Итоговая архитектура изображения в селекторе

Общая схема работы состоит из трёх слоёв:

  1. Данные — расширенные объекты с customProperties.image
  2. ЛогикаcallbackOnCreateTemplates для рендера
  3. Представление — HTML + CSS для визуализации

Такая модель позволяет превратить стандартный текстовый селектор в визуальный компонент с карточками, сохраняя при этом всю функциональность Choices.js, включая поиск, выбор и управление состоянием.