Этап 3: Фильтрация и поиск
Free PreviewКарточки загружаются и отображаются. Теперь добавим возможность искать и фильтровать — пользователь должен быстро найти то, что ищет.
Требования
Фильтрация по категории
- Клик по кнопке категории показывает только карточки с соответствующей категорией.
- Кнопка «Все» сбрасывает фильтр и показывает все карточки.
- Активная кнопка визуально выделена (класс, который вы уже стилизовали в предыдущем этапе).
- В один момент времени активна только одна кнопка категории.
Как переключать активную кнопку. Снимите класс у всех кнопок, затем добавьте нужной:
document.querySelectorAll('.filter-btn').forEach(btn => btn.classList.remove('active')); clickedButton.classList.add('active');js
Поиск по тексту
- Ввод текста в строку поиска фильтрует карточки по полям
nameиdescription. - Поиск нечувствителен к регистру.
- Поиск работает одновременно с фильтром категории: если выбрана категория «Музей» и введён текст «эрм», показываются только музеи, в названии или описании которых есть «эрм».
- При очистке поля поиска карточки возвращаются к предыдущему состоянию (с учётом активного фильтра категории).
Состояние «ничего не найдено»
- Если ни одна карточка не подходит под текущие фильтры, в сетке должно отображаться сообщение «Ничего не найдено» с подсказкой сбросить поиск.
- Рядом с сообщением — кнопка «Сбросить», которая очищает поле поиска и снимает фильтр категории.
Архитектура
- Не удаляйте и не добавляйте карточки при каждом изменении фильтра. Лучший подход — держать все карточки в DOM и управлять их видимостью через CSS. Это быстрее и проще.
- Вся логика фильтрации — одна функция, которая вызывается и при смене категории, и при вводе в поиск. Не дублируйте код.
Как связать всё вместе. Центральная идея — одна функция
applyFilters, которая читает текущее состояние и решает, какие карточки показать:function applyFilters() { const query = state.searchQuery.toLowerCase(); const category = state.activeCategory; document.querySelectorAll('.card').forEach(card => { const id = Number(card.dataset.id); const attraction = state.attractions.find(a => a.id === id); const matchesCategory = category === 'Все' || attraction.category === category; const matchesSearch = !query || attraction.name.toLowerCase().includes(query) || attraction.description.toLowerCase().includes(query); card.hidden = !(matchesCategory && matchesSearch); }); // показать/скрыть состояние "ничего не найдено" const anyVisible = [...document.querySelectorAll('.card')].some(c => !c.hidden); emptyState.hidden = anyVisible; }jsОбработчики событий только обновляют
stateи вызываютapplyFilters():searchInput.addEventListener('input', (e) => { state.searchQuery = e.target.value; applyFilters(); }); filtersContainer.addEventListener('click', (e) => { const btn = e.target.closest('.filter-btn'); if (!btn) return; state.activeCategory = btn.dataset.category; // обновить активную кнопку applyFilters(); });jsОбратите внимание на
btn.dataset.category— при создании кнопок фильтрации добавляйте атрибутdata-category="Музей", чтобы потом легко его читать.
Продвинутое задание — debounce
Поиск запускается при каждом нажатии клавиши. Если данных много, это может тормозить браузер. Реальное решение — debounce: функция-обёртка, которая откладывает выполнение до тех пор, пока пользователь не перестанет печатать (обычно 300–400 мс).
Реализуйте debounce самостоятельно — это небольшая функция, которую полезно уметь писать:
Подсказка.
debounceпринимает функцию и задержку, возвращает новую функцию. Каждый вызов новой функции сбрасывает предыдущий таймер и запускает новый. Выполнение происходит только когда таймер «добежал» до конца.function debounce(fn, delay) { let timer; return function(...args) { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); }; } // Использование: searchInput.addEventListener('input', debounce((e) => { state.searchQuery = e.target.value; applyFilters(); }, 300));js
Ожидаемый результат
- клик по категории показывает только карточки этой категории
- поиск работает по имени и описанию, нечувствителен к регистру
- поиск и фильтр по категории работают одновременно
- при отсутствии результатов видно сообщение и кнопка сброса
- активная кнопка категории визуально выделена