Экспертная верстка на React, Next.js и Astro.build от профессионала! 750 рублей в час. Свяжитесь сo мной прямо сейчас!

Реализация live search на JavaScript

Реализация live search на JavaScript

В современном динамичном мире веб-разработок реализация мощной поисковой функциональности является ключевым моментом, позволяющим значительно повысить удобство работы пользователей и упростить навигацию по большим массивам данных. Если вы хотите добавить возможности живого поиска на свой сайт или в веб-приложение, вы попали по адресу. В этой статье мы рассмотрим все тонкости реализации функции живого поиска с помощью JavaScript.

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

Для эффективной работы с данным руководством рекомендуется хорошо знать основы HTML, CSS и JavaScript. Знакомство с манипуляциями с DOM и обработкой событий будет полезным, когда мы погрузимся в детали реализации. Однако даже если вы относительно недавно знакомы с JavaScript или веб-разработкой, руководство построено таким образом, чтобы обеспечить четкие объяснения и пошаговые инструкции, что делает его доступным для учащихся с различным уровнем подготовки.

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

Ознакомиться с живой реализацией можно здесь.

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

Важность функции живого поиска

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

  1. Улучшение качества работы пользователей: Живой поиск значительно повышает удобство работы пользователей, обеспечивая интуитивно понятный процесс поиска. Как только пользователь начинает вводить свой запрос, результат поиска обновляется в реальном времени, обеспечивая мгновенную обратную связь и устраняя необходимость в ручном вводе или перезагрузке страницы. Такая интерактивность позволяет сэкономить время и силы пользователей, что делает поиск более эффективным и приятным.
  2. Ускоренный поиск информации: Благодаря «живому» поиску пользователи могут быстро находить нужную информацию, не переходя по нескольким страницам и не ожидая загрузки результатов поиска. По мере ввода текста результаты поиска мгновенно сужаются, отображая соответствующие предложения и избавляя от необходимости вводить полный текст поискового запроса. Благодаря такой скорости и оперативности пользователи могут найти то, что им нужно, за долю времени, которое требуется при использовании традиционных методов поиска.
  3. Увеличение вовлеченности и конверсии: Бесперебойная и оперативная работа живого поиска стимулирует пользователей к более активному взаимодействию с веб-сайтом или веб-приложением. Предоставление мгновенной обратной связи и релевантных предложений поддерживает пользователей, сводя к минимуму вероятность отказов и разочарований. Повышение вовлеченности может привести к повышению конверсии, поскольку пользователи с большей вероятностью будут изучать сайт дальше и конвертировать свои поисковые намерения в действия.
  4. Усовершенствованная фильтрация и уточнение: Функциональность живого поиска часто включает дополнительные возможности, такие как фильтры, предложения и автозаполнение. Эти функции помогают пользователям уточнить поиск и сузить результаты, что позволяет им найти то, что они ищут. Предоставляя такие инструменты, «живой» поиск улучшает качество поиска, а также помогает пользователям обнаружить связанный контент или продукты, о которых они, возможно, даже не подозревали.
  5. Ценные сведения для владельцев сайтов: Функциональность живого поиска позволяет получить ценные сведения о поведении и предпочтениях пользователей. Анализируя поисковые запросы и закономерности, владельцы сайтов могут лучше понять, что ищут их пользователи, выявить популярные тенденции или темы, а также принять обоснованные решения по созданию контента, предложению продуктов или улучшению пользовательского интерфейса. Эти данные позволяют владельцам сайтов адаптировать свои предложения к потребностям пользователей, что приводит к повышению удовлетворенности клиентов и росту бизнеса.

Настройка структуры HTML

Теперь, когда мы имеем полное представление о том, что такое функциональность живого поиска и какова ее важность, давайте рассмотрим, как можно реализовать ее и в своем проекте.

Прежде всего, давайте определим структуру проекта. Для этого проекта нам понадобится всего три файла: HTML, CSS и JavaScript.

Начнем с настройки HTML-структуры нашего проекта: В HTML-файл сначала нужно включить стандартный HTML-шаблон, включающий ссылку и скрипт на наши CSS- и JS-файлы:

<!doctype html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<link rel="stylesheet" href="./live-search.css" />
		<title> </title>
		.
	</head>
	<body>
		<script src="./live-search.js"></script>
	</body>
</html>

Теперь в тег body мы включаем семантические теги header и main. Внутри тега header мы задаем заголовок нашего проекта, который в данном случае представляет собой просто название приложения и иконку видео.

<header>
	<ion-icon name="videocam"></ion-icon>
	<h1>Поиск фильмов</h1>
</header>

Прежде чем перейти к тегу main, в конце тега body включим необходимые теги script для того, чтобы можно было использовать иконки:

<script type="module" src="https://unpkg.com/[email protected]/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/[email protected]/dist/ionicons/ionicons.js"></script>

Иконки можно найти на сайте Ionicons.

Теперь внутри тега main мы включим наш первый тег div, который будет нашим контейнером строки поиска, а внутри него мы разместим наш тег ввода поиска и иконку поиска:

<div id="search-container">
	<ion-icon name="search-outline"></ion-icon>
	<input type="search" id="search-bar" placeholder="Поиск фильмов..." />
</div>

Затем мы создадим еще один тег div под этим «div». В нем будут храниться все результаты просмотра фильмов:

<div id="results-container"></div>

Пока оставим его пустым, так как его содержимое будет сгенерировано в разделе JavaScript.

Наконец, в тег main мы включим тег p. Этот тег предназначен для последующего отображения ответа на сообщение об ошибке или пустом сообщении.

<p id="movie-unavailable-txt"></p>

Это все для HTML-файла, а общий код должен выглядеть следующим образом:

<!doctype html>
<html lang="en">
 <head>
   <meta charset="UTF-8" />
   <meta http-equiv="X-UA-Compatible" content="IE=edge" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <link rel="stylesheet" href="./live-search.css" />
   <title>Функциональные возможности живого поиска</title>
 </head>
 <body>
   <Заголовок>
     <ion-icon name="videocam"></ion-icon>
     <h1>Поиск фильмов</h1>
   </header>

   <main>
     <div id="search-container">
       <ion-icon name="search-outline"></ion-icon>
       <input type="search" id="search-bar" placeholder="Поиск фильмов..." />
     </div>

     <div id="results-container"></div>

     <p id="movie-unavailable-txt"></p>
   </main>

   <script src="./live-search.js"></script>
   <script
     type="module"
     src="https://unpkg.com/[email protected]/dist/ionicons/ionicons.esm.js"
   ></script>
   <script
     nomodule
     src="https://unpkg.com/[email protected]/dist/ionicons/ionicons.js"
   ></script>
 </body>
</html>

Теперь, когда мы закончили реализацию HTML-структуры проекта, давайте добавим несколько стилей на страницу.

Добавление стилей на страницу

В этом разделе мы добавим основные стили к различным частям страницы. Поэтому давайте сразу же приступим к работе.

Сначала добавим несколько общих стилей к общей части страницы:

html{ scroll-behavior: smooth; background-color: #111; цвет: белый дым; } *{ margin: 0; padding: 0;
box-sizing: border-box; }

Теперь добавим некоторые стили к разделу заголовка и его содержимому:

header {
	display: flex;
	justify-content: center;
	padding: 25px;
	letter-spacing: 2px;
	position: sticky;
	top: 0%;
	z-index: 2;
	border-bottom: 2px solid;
	background-color: black;
	text-shadow: 3px 3px 5px #fd1d6b;
	box-shadow: 10px 10px 20px -10px #fd1d6b;
}

header > ion-icon {
	color: #fd1d6b;
	font-size: 60px;
	position: absolute;
	left: 5%;
}

Далее мы переходим к стилизации контейнера поиска и его содержимого:

#search-container {
	display: flex;
	justify-content: center;
	padding: 20px;
	margin-bottom: 20px;
	position: sticky;
	top: 100px;
}

#search-bar {
	border: none;
	ширина: 60%;
	padding: 15px;
	padding-left: 40px;
	border-radius: 15px;
	font-size: 15px;
}

#search-container > ion-icon {
	цвет: серый;
	position: relative;
	left: 30px;
	top: 13px;
	z-index: 3;
	font-size: 19px;
}

После этого мы приступаем к созданию стиля results-container, в котором будут храниться все фильмы, полученные из базы данных фильмов:

#results-container {
	border-right: 5px solid #fd1d6b;
	border-left: 5px solid #fd1d6b;
	border-radius: 25px;
	display: flex;
	justify-content: center;
	flex-wrap: wrap;
	width: 90vw;
}

Далее мы добавим стили к movie-unavailable-txt, установив display в none, поскольку пока не хотим, чтобы он был виден:

#movie-unavailable-txt {
	text-align: center;
	интервалмеждубуквами: 2px;
	display: none;
	margin-top: 15%;
	text-shadow: 3px 3px 5px #fd1d6b;
}

Далее мы добавим стили к нескольким элементам, которые еще не были объявлены, но которые мы создадим с помощью javascript. Это карточка фильма, которая будет отображать подробную информацию о фильме, содержащую изображение и название фильма:

.movie-cards {
	padding: 25px;
	max-width: 250px;
	border-radius: 15px;
	display: grid;
	place-items: center;
	box-shadow: 1px 1px 20px -1px #fd1d6b;
	margin: 50px;
}

.title {
	margin: 20px auto;
	text-align: center;
	font-size: 1.2rem;
	text-shadow: 3px 3px 5px #fd1d6b;
}

.date {
	margin-top: 15px;
	font-size: 0.8rem;
	text-shadow: 3px 3px 5px #fd1d6b;
}

.movie-image {
	width: 90%;
	max-width: 400px;
	object-fit: contain;
	border-radius: 5px;
}

Теперь, когда мы закончили со стилизацией страницы, перейдем к самому интересному и важному разделу — реализации Javascript.

Отправка асинхронных поисковых запросов к API базы данных фильмов.

В этом разделе мы будем выполнять API-вызовы к выбранному нами API базы данных фильмов, чтобы наполнить нашу страницу различными фильмами. В данном случае я буду использовать API бесплатных фильмов IMDb Top 100 Movies в RapidAPI Hub. На странице API мы выбираем конкретные данные, которые хотим использовать, а затем копируем код javascript (fetch), приведенный в правой части страницы для этих данных, как показано ниже:Страница учетных данных API

Но прежде чем использовать API, необходимо подписаться на него (без использования кредитной карты), чтобы для вас был сгенерирован API-ключ. Это можно сделать на странице [pricing page] (https://rapidapi.com/rapihub-rapihub-default/api/imdb-top-100-movies/pricing) API.

Далее мы переходим в наш пустой файл javascript и вставляем туда скопированный нами код:

const url = 'https://imdb-top-100-movies.p.rapidapi.com/';
const options = {
	method: 'GET',
	headers: {
		'X-RapidAPI-Key': 'СГЕНЕРИРОВАННЫЙ ВАМИ API КЛЮЧ',
		'X-RapidAPI-Host': 'imdb-top-100-movies.p.rapidapi.com',
	},
};

try {
	const response = await fetch(url, options);
	const result = await response.text();
	console.log(result);
} catch (error) {
	console.error(error);
}

Теперь, когда мы получили API от базы данных фильмов в наш проект, мы можем использовать ее данные. Для этого объявим несколько необходимых нам переменных и поместим их чуть выше блока try в скопированном нами коде:

const SearchBar = document.getElementById('search-bar');
const resultsContainer = document.getElementById('results-container');
const movieUnavailableTxt = document.getElementById('movie-unavailable-txt');
let movieList;
let searchValue;
let searchedMovies;

Мы подходим к назначению этих переменных, которые мы только что создали, держитесь.

Далее мы вносим некоторые изменения в блок try из скопированного кода, поскольку хотим полностью интегрировать его в наш проект. Итак, для начала нам нужно создать асинхронную функцию:

const fetchMovies = async () => {
	// блок try catch помещается сюда.
};

Внутри этой функции мы поместим весь блок try catch из скопированного нами кода, чтобы иметь возможность осуществлять асинхронные вызовы API.

Внутри блока try мы избавимся от строки console.log(result) и изменим переменную result на переменную movieList, которую мы объявили ранее, а также изменим response.text() в этой же строке на response.json(). Это необходимо для того, чтобы данные, полученные в результате вызова API, были представлены в нужном нам формате JSON. Теперь эта строка должна выглядеть следующим образом:

movieList = await response.json();

Теперь, когда мы успешно извлекли фильм из API и вернули набор данных JSON, нам необходимо заполнить этими данными нашу страницу. Для этого мы вызовем функцию renderMovies() и установим в качестве аргумента данные, полученные в результате обращения к API. Не волнуйтесь, мы скоро создадим эту функцию:

renderMovies(movieList);

Теперь создадим функцию renderMovies, которую мы только что вызвали в функции fetchMovies(). С помощью этой функции мы создадим динамический шаблон карточки фильма, стили для которого мы задали ранее в нашем CSS-файле, а в каждом из элементов шаблона мы зададим их содержимое данными, полученными из API, что позволит нам отображать разные фильмы с помощью одного и того же шаблона. Затем мы поместим карточку фильма внутрь элемента resultsContainer. При каждом вызове функции необходимо очищать resultsContainer, устанавливать moviesUnavailableTxt в значение display="none", поскольку мы хотим, чтобы текст не был виден при выводе фильмов на страницу, а также очищать массив moviesReturnedOnSearch, прежде чем поместить в него новые данные, возвращенные из поля ввода поиска:

const renderMovies = (movies) => {
	resultsContainer.innerHTML = ''; // Очищаем существующие фильмы
	movieUnavailableTxt.style.display = 'none'; // Скрыть сообщение "Фильмы не найдены"
	moviesReturnedOnSearch = []; // Очистить массив фильмов, возвращенных при поиске

	movies.forEach((movie) => {
		resultsContainer.innerHTML += `
     <div class="movie-cards">
       <img src="${movie.image}" alt="изображение фильма" class="movie-image" />
       <h2 class="title">${movie.title}</h2>
       <p class="plot">${movie.description}</p>
       <p class="date">${movie.year}</p>
     </div>
   `;

		moviesReturnedOnSearch.push(movie); // Добавляем фильмы, ставшие результатом поиска, во входное значение поиска
	});
};

Перехват пользовательского ввода и отображение результатов поиска фильмов в реальном времени

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

Для перехвата пользовательского ввода мы будем использовать слушатель событий input и привяжем его к элементу searchBar. Мы используем именно этот приемник событий, поскольку он фиксирует все действия внутри поисковой строки — от ввода до очистки и вставки, что как раз то, что нам нужно. Поэтому давайте создадим его:

searchBar.addEventListener('input', (event) => {
	// живой код функциональности
});

Итак, мы подключили к строке поиска слушатель события input, который будет воспринимать любой ввод от пользователя. Во втором параметре мы добавили обработчик события — функцию, которая будет вызываться каждый раз, когда в строку поиска будет введен хоть один элемент. Теперь внутри этой функции мы создадим код, который будет обрабатывать живой поиск.

Первое, что нам нужно сделать внутри функции поиска, — это отредактировать значение ввода, полученное от пользователя, и установить его во все строчные буквы, а также избавиться от лишних пробелов:

searchValue = event.target.value.trim().toLowerCase();

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

const filteredMovies = movieList.filter((movie) => movie.title.toLowerCase().includes(searchValue));

Далее мы выводим результаты поиска фильмов в реальном времени, отображая название фильма, соответствующее символам, которые пользователь набрал в строке поиска, еще раз вызывая функцию renderMovies() и устанавливая в качестве аргументов значения переменной filtered Movies.

renderMovies(filteredMovies);

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

Обработка пустых или ошибочных ответов

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

Для обработки ошибок или пустых ответов необходимо обеспечить четкую обратную связь с пользователем. При этом, поскольку это довольно легкое приложение, нам не нужно беспокоиться о большом количестве ошибок, поскольку мы будем иметь дело только с ошибками, возникающими при работе с API. Например, сервисы API могут быть временно отключены, или приложение просто превысило лимит запросов. Для обработки этой ошибки нам достаточно установить display элемента movieUnavailableTxt в block и установить innerHTML для отображения сообщения об ошибке пользователю и поместить его внутри блока catch функции fetchMovies(). Теперь блок catch выглядит следующим образом:

catch (error) {
 movieUnavailableTxt.innerHTML = 'При получении фильмов произошла ошибка. <br /> Пожалуйста, повторите попытку позже.'
 movieUnavailableTxt.style.display = "block";
 console.error(error);
}

Теперь, когда мы закончили работу с ответом на ошибку, перейдем к работе с пустым ответом. Если фильм, который ищет пользователь, не совпадает ни с одним из фильмов на странице, то необходимо предупредить пользователя о том, что искомый фильм недоступен. Для этого сначала нужно проверить содержимое массива moviesReturnedOnSearch, который мы объявили ранее, и если длина массива меньше или равна 0, то установить display элемента movieUnavailableTxt в block и установить innerHTML в пустое сообщение ответа, которое мы хотим отобразить, как показано ниже:

if (moviesReturnedOnSearch.length <= 0) {
	movieUnavailableTxt.innerHTML = 'OOPS! <br/><br/> Фильм недоступен';
	movieUnavailableTxt.style.display = 'block'; // Показать сообщение "Фильмы не найдены", если ни один фильм не соответствует поиску
}

Этот блок if мы поместим непосредственно перед закрывающей скобкой обработчика события searchBar.

Повышение производительности поиска с помощью кэширования

При реализации функции живого поиска с помощью API одним из эффективных методов повышения производительности является кэширование. Кэширование подразумевает хранение ранее полученных результатов поиска и их повторное использование при повторном запросе того же поискового запроса. Это позволяет значительно сократить количество обращений к API, что поможет избежать превышения лимита запросов API и в целом улучшить отзывчивость функции поиска, а также время загрузки сайта.

Чтобы реализовать кэширование в нашем проекте, сначала нужно определить, какой элемент необходимо кэшировать, и в данном случае это будет значение переменной movieList, которая представляет собой данные, полученные нами в результате API-запроса fetch в формате JSON. Кэширование этого элемента позволит нам использовать данные API без дополнительного запроса fetch, даже при перезагрузке страницы. Но для данного проекта мы установим срок действия кэшированных данных в 6 часов, то есть страница будет запрашивать API только раз в 6 часов, а не при каждой перезагрузке страницы. Это необходимо для того, чтобы страница могла сохранять свежесть и актуальность своих данных, а количество запросов к API было минимальным.

Возвращаясь к нашему коду, отметим, что теперь нам необходимо сохранить данные JSON в локальном хранилище браузера, но для этого нужно сначала превратить их в строку и задать имя ключа, который будет использоваться для идентификации данных в локальном хранилище. Зададим его как movieData, как показано ниже:

localStorage.setItem('moviedata', JSON.stringify(movieList));

Следующее, что нам нужно сделать, это сохранить текущую дату и время в локальном хранилище:

localStorage.setItem('cacheTimestamp', Date.now());

Здесь хранится текущая дата и время в миллисекундах с ключевым именем cacheTimeStamp.

Эти две строки кода мы поместим в блок try функции fetchMovies(), прямо под переменной movieList.

Далее, вне функции fetchMovies(), чуть ниже функции renderMovies(), мы установим время истечения кэшированных данных равным 6 часам в миллисекундах:

const expirationDuration = 21600000; // 6 часов в миллисекундах

После этого нам необходимо получить обратно cacheTimestamp, который мы ранее установили в локальном хранилище:

const cacheTimestamp = localStorage.getItem('cacheTimestamp');

Теперь мы проверим, не истек ли срок действия кэшированных данных или они недоступны, т. е. еще не были сохранены. Если это так, то мы сделаем новый запрос fetch к API, вызвав функцию fetchMovies(). С другой стороны, если кэшированные данные присутствуют и срок их хранения еще не истек, мы используем их для отображения фильмов на странице, а не делаем новый запрос fetch. Для этого мы извлекаем кэшированные данные о фильмах и разбираем их в формат JSON, а затем вызываем функцию render с аргументом, равным данным, полученным из кэша.

// Проверяем, не истек ли срок действия кэша или не доступны ли данные
if (!cacheTimestamp || Date.now() - parseInt(cacheTimestamp) > expirationDuration) {
	// Срок действия кэша истек или данные недоступны, выполните повторную выборку фильмов
	fetchMovies();
} else {
	// Используем кэшированные данные о фильмах
	movieList = JSON.parse(localStorage.getItem('moviedata'));
	renderMovies(movieList);
}

В операторе if переменная !cacheTimestamp проверяет, является ли переменная cacheTimestamp фальшивой, то есть равна ли она null, undefined, 0, false или пустой строке. Если cacheTimestamp равна false, то это означает, что в кэше не хранится ни одной существующей временной метки. Функция Date.now() - parseInt(cacheTimestamp) вычисляет разницу во времени между текущей временной меткой и разобранным целочисленным значением cacheTimestamp. Таким образом, мы просто говорим: «Значение текущего времени минус значение времени, которое мы ранее хранили в кэше, больше ли оно, чем установленное нами время истечения? Если да, то давайте снова получать фильмы из API, а если нет, то просто воспользуйтесь кэшированными данными».

Вот так мы кэшируем данные для повторного использования, а не выполняем запрос fetch при каждом вводе пользователя или при каждой перезагрузке страницы. Как видите, это значительно оптимизирует работу приложения, так как предотвращает медленный рендеринг фильма, который может происходить из-за медленной сети.

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

const url = 'https://imdb-top-100-movies.p.rapidapi.com/';
const options = {
	method: 'GET',
	headers: {
		'X-RapidAPI-Key': 'Ваш сгенерированный API-ключ',
		'X-RapidAPI-Host': 'imdb-top-100-movies.p.rapidapi.com',
	},
};

const SearchBar = document.getElementById('search-bar');
const resultsContainer = document.getElementById('results-container');
const movieUnavailableTxt = document.getElementById('movie-unavailable-txt');
let movieList;
let searchValue;
let moviesReturnedOnSearch;

// Функция для получения фильмов из API
const fetchMovies = async () => {
	try {
		const response = await fetch(url, options);
		movieList = await response.json();

		// Хранение данных о фильмах в хранилище браузера
		localStorage.setItem('moviedata', JSON.stringify(movieList));
		localStorage.setItem('cacheTimestamp', Date.now()); // Обновление временной метки кэша

		// Рендеринг фильмов на странице
		renderMovies(movieList);
	} catch (error) {
		movieUnavailableTxt.innerHTML =
			'Произошла ошибка при получении фильмов. <br /> Пожалуйста, повторите попытку позже.';
		movieUnavailableTxt.style.display = 'block';
		console.error(error);
	}
};

// Функция для вывода фильмов на страницу
const renderMovies = (movies) => {
	resultsContainer.innerHTML = ''; // Очистка существующих фильмов
	movieUnavailableTxt.style.display = 'none'; // Скрыть сообщение "Фильмы не найдены"
	moviesReturnedOnSearch = []; // Очистить массив фильмов, возвращенных при поиске

	movies.forEach((movie) => {
		resultsContainer.innerHTML += `
     <div class="movie-cards">
       <img src="${movie.image}" alt="изображение фильма" class="movie-image" />
       <h2 class="title">${movie.title}</h2>
       <p class="plot">${movie.description}</p>
       <p class="date">${movie.year}</p>
     </div>
   `;

		moviesReturnedOnSearch.push(movie); // Добавляем фильмы, ставшие результатом поиска, во входное значение поиска
	});
};

const cacheTimestamp = localStorage.getItem('cacheTimestamp');
const expirationDuration = 21600000; // 6 часов в миллисекундах

// Проверяем, истек ли срок действия кэша или данные недоступны
if (!cacheTimestamp || Date.now() - parseInt(cacheTimestamp) > expirationDuration) {
	// Срок действия кэша истек или данные недоступны, выполните повторную выборку фильмов
	fetchMovies();
} else {
	// Используем кэшированные данные о фильмах
	movieList = JSON.parse(localStorage.getItem('moviedata'));
	renderMovies(movieList);
}

// Слушатель и обработчик событий для ввода строки поиска
searchBar.addEventListener('input', (event) => {
	searchValue = event.target.value.trim().toLowerCase();

	// Отфильтровать фильмы на основе поисковых данных
	const filteredMovies = movieList.filter((movie) =>
		movie.title.toLowerCase().includes(searchValue),
	);

	// Отображение отфильтрованных фильмов на странице
	renderMovies(filteredMovies);

	if (moviesReturnedOnSearch.length <= 0) {
		movieUnavailableTxt.style.display = 'block'; // Показываем сообщение "Фильмы не найдены", если ни один фильм не соответствует поиску
	}
});

Заключение

В этом руководстве мы рассмотрели реализацию функциональности живого поиска на JavaScript с использованием API. Выполнив описанные шаги, можно создать динамический поиск, который будет выдавать результаты в реальном времени по мере ввода пользователем текста в строке поиска.

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

Благодаря знаниям, полученным из этого руководства, вы сможете эффективно реализовать функциональность живого поиска на JavaScript. Воспользуйтесь возможностями динамического поиска и создайте удобную систему, которая произведет неизгладимое впечатление на пользователя.

Счастливого кодирования!