Подсветка совпадений в выпадающих списках автодополнения реализуется через модификацию рендера каждого элемента перед его вставкой в DOM. В Awesomplete базовая логика разделена на три ключевых уровня: фильтрация данных, сортировка результатов и отрисовка элемента списка. Именно последний этап отвечает за визуальное выделение совпадающих подстрок.
Каждый элемент списка в Awesomplete проходит через функцию
item, которая определяет структуру DOM-узла. По умолчанию
используется простая генерация элемента <li>,
содержащего текстовое значение. Однако этот механизм проектирован как
расширяемый, что позволяет внедрять дополнительную логику, включая
подсветку совпадений.
Функция item получает два ключевых параметра:
На основании этих данных формируется HTML-содержимое элемента, где
совпадающие фрагменты оборачиваются в специальные теги, чаще всего
<mark> или <strong>.
Наиболее прямолинейный подход заключается в использовании регулярных выражений для поиска совпадений и замены их на HTML-разметку.
awesomplete.item = function (text, input) {
const regex = new RegExp(input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi");
const highlighted = text.replace(regex, function (match) {
return "<mark>" + match + "</mark>";
});
const li = document.createElement("li");
li.innerHTML = highlighted;
return li;
};
В данном подходе ключевым моментом является экранирование специальных символов регулярных выражений. Без этого любое пользовательское значение может нарушить корректность шаблона поиска.
Использование <mark> позволяет делегировать
стилизацию браузеру по умолчанию, однако внешний вид может быть
переопределён через CSS:
mark {
background-color: #ffe58f;
padding: 0;
}
Подсветка совпадений обычно реализуется без учёта регистра символов.
Для этого применяется флаг i в регулярных выражениях.
Однако при более сложной обработке данных используется предварительная
нормализация строк.
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
Далее применяется единый шаблон сопоставления:
const pattern = new RegExp(escapeRegExp(input), "gi");
Такой подход гарантирует стабильную работу при вводе латиницы, кириллицы и смешанных наборов символов.
Прямая вставка HTML через innerHTML повышает риск
повреждения структуры данных при наличии пользовательских символов.
Более устойчивый подход заключается в разбиении строки на сегменты и
создании DOM-узлов вручную.
awesomplete.item = function (text, input) {
const li = document.createElement("li");
const regex = new RegExp(escapeRegExp(input), "gi");
let lastIndex = 0;
let match;
while ((match = regex.exec(text)) !== null) {
if (match.index > lastIndex) {
li.appendChild(
document.createTextNode(text.slice(lastIndex, match.index))
);
}
const mark = document.createElement("mark");
mark.textContent = match[0];
li.appendChild(mark);
lastIndex = regex.lastIndex;
}
if (lastIndex < text.length) {
li.appendChild(
document.createTextNode(text.slice(lastIndex))
);
}
return li;
};
Такой метод исключает необходимость работы с innerHTML и
снижает риск XSS при обработке внешних данных.
Awesomplete позволяет переопределять логику фильтрации через свойство
filter. В связке с подсветкой это даёт возможность
синхронизировать поведение поиска и визуального выделения.
Awesomplete.FILTER_CONTAINS = function (text, input) {
return text.toLowerCase().includes(input.toLowerCase());
};
awesomplete.filter = Awesomplete.FILTER_CONTAINS;
При таком подходе подсветка совпадений должна использовать ту же стратегию сравнения, чтобы визуальный результат соответствовал логике отбора элементов.
При наличии нескольких вхождений строки поиска в одном элементе
используется глобальный поиск (g-флаг). Это приводит к
последовательной обработке всех совпадений.
const regex = new RegExp(escapeRegExp(input), "gi");
Каждое совпадение обрабатывается независимо, что позволяет корректно выделять все участки строки:
При вводе нескольких слов (например, “new yor”) стандартная регулярная логика работает только с последовательным совпадением подстроки. Для более гибкой подсветки используется разбиение ввода на токены.
const tokens = input.trim().split(/\s+/);
const regex = new RegExp(tokens.map(escapeRegExp).join("|"), "gi");
Такой подход приводит к подсветке каждого совпадающего слова независимо, даже если они встречаются в разном порядке внутри строки.
При использовании innerHTML критическим становится
контроль входных данных. Любые значения, поступающие извне, должны
рассматриваться как потенциально небезопасные. Поэтому применяется
экранирование:
function escapeHtml(str) {
return str
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
В сочетании с подсветкой это позволяет комбинировать безопасный вывод и HTML-разметку:
const safeText = escapeHtml(text);
После чего подсветка применяется уже к безопасной строке.
При работе с интернациональными данными возникает необходимость учитывать различные формы записи символов. Например, символы с диакритическими знаками могут иметь несколько представлений в Unicode.
Используется нормализация строк:
const normalizedText = text.normalize("NFD");
const normalizedInput = input.normalize("NFD");
После этого сравнение становится более стабильным, особенно при обработке европейских языков.
Awesomplete допускает полную замену структуры элемента через
переопределение item. Это позволяет включать не только
подсветку, но и дополнительные визуальные элементы.
awesomplete.item = function (text, input) {
const li = document.createElement("li");
const container = document.createElement("div");
container.className = "aw-item";
const regex = new RegExp(escapeRegExp(input), "gi");
const highlighted = text.replace(regex, m => `<mark>${m}</mark>`);
container.innerHTML = highlighted;
li.appendChild(container);
return li;
};
Такой подход используется в интерфейсах, где требуется сложная структура: иконки, категории, дополнительные описания.
Визуальное оформление подсветки обычно отделяется от логики.
Использование классов вместо тега <mark> позволяет
гибко управлять стилями:
return "<span class='aw-highlight'>" + match + "</span>";
CSS-правило:
.aw-highlight {
background: #fff3a0;
font-weight: 600;
border-radius: 2px;
}
Такой подход облегчает интеграцию в дизайн-системы и позволяет избегать конфликтов с глобальными стилями браузера.
При значительных объёмах данных повторное создание регулярных выражений для каждого элемента списка становится затратным. Оптимизация заключается в предварительной компиляции шаблона при изменении ввода:
let currentRegex = null;
function updateRegex(input) {
currentRegex = new RegExp(escapeRegExp(input), "gi");
}
Далее item использует уже готовый объект регулярного
выражения, что снижает накладные расходы при рендеринге большого
количества элементов.
При отсутствии текста ввода подсветка отключается, а элементы отображаются в исходном виде. Это достигается через условную проверку:
if (!input) {
li.textContent = text;
return li;
}
Такой режим предотвращает лишние операции и обеспечивает предсказуемость интерфейса при начальном состоянии компонента.