Skip to content

Всё для WordPress, веб-разработки — и не только

🔍 jQuery: как найти элемент с определенным текстом

🔍 jQuery: как найти элемент с определенным текстом

Разработчик верстает карточку товара в WooCommerce. Всё идёт по плану, пока из админки не прилетает задача: «убрать плашку Featured и запятую после неё, но только для товаров внутри категории-холдера». Ручной обход, не вариант, товаров сотни. Нужен скрипт, который сам найдёт блок с текстом «featured», доберётся до родителя, прихватит соседний span и удалит всю конструкцию.

И вот тут на сцену выходит jQuery с его цепочками: .each() для перебора, .find() для поиска, :contains() для фильтра по тексту, .closest() для подъёма к родителю, .next() для перехода к соседу и .remove() для зачистки. Пять методов, и задача решается в четыре строки. Ниже разбираю каждый шаг: от селектора до финальной зачистки, с пояснением логики и подводными камнями.

💡 Быстрый обзор:

  • Сначала находим целевой блок с текстом через contains-селектор и поднимаемся к его родителю через closest
  • Затем переходим к соседнему элементу через next и точечно удаляем оба блока через remove
  • В конце отлаживаем скрипт и разбираем типовые ошибки с потерей контекста и жадным next

Задача: что ищем и что удаляем

Исходная HTML-структура выглядит примерно так:

1<span class="tg-cats-holder">
2 <a class="product_visibility" href="...">
3 <span class="tg-item-term">featured</span>
4 </a>
5 <span>, </span>
6 <a class="product_visibility" href="...">
7 <span class="tg-item-term">sale</span>
8 </a>
9 <span>, </span>
10</span>

Ключевая цель, найти <span class="tg-item-term">, внутри которого есть текст featured, подняться к родительскому <a class="product_visibility">, затем перейти к следующему за ним <span> (с запятой) и удалить. После этого, удалить сам <a class="product_visibility"> с плашкой. Остальные метки (sale и т.д.) не трогаем.

Готовый скрипт

1jQuery(document).ready(function($) {
2 $('span.tg-cats-holder').each(function() {
3 $(this)
4 .find('span.tg-item-term:contains("featured")')
5 .closest('a.product_visibility')
6 .next('span:contains(",")')
7 .remove();
8
9 $(this)
10 .find('span.tg-item-term:contains("featured")')
11 .closest('a.product_visibility')
12 .remove();
13 });
14});

Код намеренно разбит на две цепочки, а не свёрнут в одну. Причина простая: после .remove() элемента цепочка обрывается, дальше идти не к чему. Поэтому сначала чистим соседний span с запятой (первая цепочка), затем удаляем сам блок (вторая).

Пошаговый разбор

Шаг 1: заходим в каждый холдер

1$('span.tg-cats-holder').each(function() {

Метод .each() проходит по каждому элементу коллекции, в нашем случае по всем <span class="tg-cats-holder"> на странице. Если холдеров несколько (разные категории товаров), скрипт отработает в каждом независимо. Внутри callback-функции this указывает на текущий холдер, весь дальнейший поиск идёт от него.

Шаг 2: ищем блок с текстом через:contains()

1.find('span.tg-item-term:contains("featured")')

Селектор :contains("featured") находит элемент, внутри которого (включая дочерние узлы) есть подстрока «featured». Три важных нюанса:

  • Чувствительность к регистру. Строки «Featured», «FEATURED» и «featured», разные. Если в админке плашка пишется по-разному, добавьте проверку через .filter() с .toLowerCase(), пример в разделе FAQ.

  • Поиск по всем потомкам. :contains() проверяет текстовое содержимое и самого элемента, и всех его детей. Если внутри <span class="tg-item-term"> лежит ещё один <strong>, текст внутри него тоже считается.

  • Это не селектор производительности. :contains() не ускоряется нативным querySelectorAll, jQuery проходит по всем кандидатам и читает .textContent. Для страниц с сотнями элементов лучше сузить выборку дополнительным классом или атрибутом.

Метод .find() ограничивает поиск потомками текущего холдера, а не всей страницы, это даёт и прирост скорости, и защиту от ложных срабатываний.

Шаг 3: поднимаемся к родителю через.closest()

1.closest('a.product_visibility')

.closest() идёт вверх по дереву от найденного элемента и возвращает ближайшего предка, подходящего под селектор. В отличие от .parent(), он не ограничен одним уровнем, будет подниматься до самого <html>, пока не найдёт совпадение или не упрётся в корень.

Если бы вместо .closest() взяли .parent(), скрипт сломался бы при любой вложенности: <span class="tg-item-term"><strong><a class="product_visibility">, .parent() вернул бы <strong>, а не нужную ссылку. .closest() страхует от таких сюрпризов.

Шаг 4: прыгаем к соседу через.next()

1.next('span:contains(",")')

.next() берёт сразу следующий соседний элемент того же родителя. Фильтр span:contains(",") гарантирует, что мы возьмём именно span с запятой, а не любой следующий элемент. Без фильтра .next() схватил бы первый попавшийся соседний узел, например, другой <a class="product_visibility"> со следующей меткой.

Разница между .next() и .nextAll(): первый берёт ровно один элемент, второй, всех последующих соседей. Здесь нужен точечный удар, поэтому .next().

Шаг 5: удаляем

1.remove();

.remove() вырезает элемент из DOM полностью, вместе с обработчиками событий и данными jQuery. Если нужно только спрятать (с перспективой вернуть), используйте .hide() или .detach(). Но для задачи «убрать плашку навсегда», только .remove().

Важен порядок: сначала удаляем соседний span (первая цепочка), только потом, сам блок (вторая). Если удалить блок первым, соседний span потеряет контекст, .next() отработает от другого элемента или вернёт пустую коллекцию.

Расширенный пример: нечувствительный к регистру поиск

В реальном проекте текст метки может приходить с разным регистром: featured, Featured, FEATURED. Штатный :contains() тут не справится. Решение, кастомный селектор на базе .filter():

1$('span.tg-cats-holder').each(function() {
2 $(this).find('span.tg-item-term').filter(function() {
3 return $(this).text().toLowerCase().indexOf('featured') !== -1;
4 }).closest('a.product_visibility').each(function() {
5 $(this).next('span:contains(",")').remove();
6 $(this).remove();
7 });
8});

Здесь .filter() с callback-функцией проверяет приведённый к нижнему регистру текст каждого кандидата. Строк стало чуть больше, зато скрипт не зависит от того, как именно вбили текст в админке.

Альтернатива на чистом JavaScript

Если jQuery на проекте не используется совсем, та же логика пишется на vanilla JS:

1document.querySelectorAll('span.tg-cats-holder').forEach(function(holder) {
2 holder.querySelectorAll('span.tg-item-term').forEach(function(term) {
3 if (term.textContent.includes('featured')) {
4 const link = term.closest('a.product_visibility');
5 const nextSpan = link.nextElementSibling;
6 if (nextSpan && nextSpan.matches('span') && nextSpan.textContent.includes(',')) {
7 nextSpan.remove();
8 }
9 link.remove();
10 }
11 });
12});

Тот же алгоритм: перебор холдеров → поиск по тексту через .textContent.includes() → подъём к родителю через нативный .closest() → проверка и удаление соседа → удаление блока. Нативная реализация длиннее, но не тянет зависимость от jQuery.

Короткое видео по теме, разбор :contains() и цепочек навигации в jQuery:

⁉️🤔 Частые вопросы

Почему :contains() не находит текст, который я вижу на странице?

Скорее всего, проблема в регистре или пробелах. :contains("Featured") не найдёт «featured», это case-sensitive селектор. Плюс браузер может нормализовать пробелы внутри HTML не так, как вы ожидаете. Проверьте точное содержимое через console.log($('селектор').text()) и скопируйте строку один-в-один в :contains().

Можно ли обойтись без .closest() и использовать фиксированную цепочку .parent().parent()?

Технически, да. Практически, не стоит. Жёсткая цепочка .parent().parent() сломается при любом изменении вложенности: добавили обёртку, переехали на другую тему, скрипт встал. .closest('a.product_visibility') гибче: он ищет ближайший подходящий элемент на любом уровне вверх. Один раз написали, работает независимо от структуры.

Скрипт удаляет не тот span. Как отладить?

Первый шаг, вставить console.log() перед каждым .remove() и проверить, что именно попадает в выборку. Второй, уточнить селектор для .next(). Без фильтра .next() берёт любой следующий элемент того же уровня, даже если это другой <a> или <div>. Добавьте span:contains(",") или более конкретный класс, и проверьте, что сосед действительно тот.

Нужно ли оборачивать скрипт в jQuery(document).ready()?

Да, если скрипт в <head> или подключается до рендера DOM. Конструкция jQuery(document).ready(function($){ ... }) гарантирует, что код выполнится только после полной загрузки DOM-дерева. Плюс $ передаётся явно в callback, это страхует от конфликтов с другими библиотеками, которые тоже могут претендовать на $. Если скрипт в футере после всех элементов, .ready() формально не обязателен, но дисциплина «всегда оборачивать» спасает от трудноуловимых багов при рефакторинге.

Что быстрее: jQuery или vanilla JS для такой задачи?

Нативный JavaScript выигрывает на холодном старте: не нужно загружать, парсить и компилировать jQuery, а это экономия десятков килобайт трафика и нескольких миллисекунд парсинга. На операциях с DOM разрыв скромнее, речь о долях миллисекунды на один вызов. Для типового сайта на WordPress, где jQuery уже подключён темой или плагинами, разница незаметна, ваш скрипт из четырёх строк не станет узким горлом. Если jQuery на проекте уже есть, пишите на jQuery, код компактнее и читаемее. Если нет, берите vanilla JS, не тащите библиотеку ради одного обработчика.

Стоит ли использовать jQuery для поиска по тексту в 2026 году?

jQuery никуда не делся. По статистике W3Techs на июнь 2026-го, библиотека установлена на 87,3% сайтов с известной JavaScript-библиотекой (68,1% всех сайтов), в основном благодаря WordPress, где jQuery идёт в ядре и автоматически подключается темами. Для WordPress-разработчика jQuery, родной инструмент, который уже загружен и не создаёт лишних запросов.

Прямые альтернативы вроде Alpine.js или HTMX решают другие задачи (реактивность, partial updates) и для разового DOM-скрипта избыточны. Нативный querySelectorAll + closest() + remove(), рабочий вариант без зависимостей, но цепочки вызова получаются длиннее. Выбор сводится к простому правилу: jQuery уже на проекте, берите jQuery; проект без jQuery, берите vanilla JS.

Главное, что вы теперь знаете точную механику связки :contains() + .closest() + .next() + .remove(), а на каком диалекте её записать, решает конкретный проект.