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(), а яким діалектом її записати, вирішує конкретний проєкт.