Как реализовать пагинацию с помощью HTML, CSS и JavaScript
Хотя пагинация может быть реализована с помощью таких фреймворков, как React и Angular, цель данной статьи — предоставить простое пошаговое руководство по настройке пагинации, чтобы мы могли понять основные концепции, связанные с ней.
Создание базовой веб-страницы
Прежде чем приступить к реализации системы пагинации, давайте создадим HTML-структуру, хранящую содержимое, которое мы хотим отображать. Это может быть любое содержимое, но в данном учебном пособии мы будем использовать таблицу из пяти столбцов и 15 строк, в которой будут храниться имена учеников разных классов. Вот фрагмент нашего HTML:
<article class="content">
<table>
<thead>
<tr>
<th>Grade 1</th>
<th>Grade 2</th>
<th>Grade 3</th>
<th>Grade 4</th>
<th>Grade 5</th>
</tr>
</thead>
<tbody>
<tr>
<td>Faith Andrew</td>
<td>Angela Christopher`</td>
<td>David Elias</td>
<td>Samuel Thomas</td>
<td>Richard Elias</td>
</tr>
⋮
</tbody>
</table>
</article>
Мы обернули таблицу в элемент-контейнер (<article class="content">
). Хотя элемент-контейнер нам не нужен, его удобно иметь, особенно если на странице есть другие элементы. (Он создаёт полезный контекст для кнопок пагинации, которые мы будем добавлять).
Полный HTML-код с некоторыми элементами оформления можно посмотреть на CodePen.
Реализация функциональности пагинации с помощью JavaScript
После того как HTML и CSS созданы, остаётся реализовать постраничную навигацию. Сначала мы воспользуемся JavaScript для разделения таблицы на различные "страницы" и добавим функциональность кнопок для перехода по этим страницам.
Создание функции, разбивающей таблицу на страницы
Вот наш код для разделения таблицы на отдельные части:
document.addEventListener('DOMContentLoaded', function () {
const content = document.querySelector('.content');
const itemsPerPage = 5;
let currentPage = 0;
const items = Array.from(content.getElementsByTagName('tr')).slice(1);
Первая строка создаёт слушатель событий, который обеспечивает выполнение JavaScript-кода после полной загрузки и разбора HTML-содержимого. Это делается для того, чтобы предотвратить любые манипуляции или взаимодействие с элементами до того, как содержимое станет доступным в DOM.
С помощью document.querySelector('.content')
мы выбираем обёртку <article class="content">
и инициализируем её как переменную.
С помощью const itemsPerPage = 5;
мы задаём количество строк для отображения на каждой странице.
С помощью let currentPage = 0;
мы создаём переменную, которая отслеживает номер текущей страницы. Она начинается с 0, что означает первую страницу. (Первый индекс в JavaScript равен 0, поэтому отсчёт ведётся от 0, а не от 1).
Последняя строка использует метод getElementsByTagName
для выбора всех элементов с тегом <tr>
внутри таблицы. Мы создаём массив (items
) из всех дочерних элементов и с помощью метода slice(1)
исключаем первую строку (заголовок) и создаём массив из оставшихся строк.
Это означает, что заголовок будет оставаться на месте при переключении страниц.
Отработка функциональности showPage()
Далее поработаем над кодом для показа страниц:
function showPage(page) {
const startIndex = page * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
items.forEach((item, index) => {
item.classList.toggle('hidden', index < startIndex || index >= endIndex);
});
updateActiveButtonStates();
}
Начнём с создания функции showPage()
, принимающей параметр page
. Эта функция отвечает за отображение элементов, связанных с данной страницей, при её вызове.
Далее мы вычисляем startIndex
, являющийся первым элементом, который должен быть отображён на текущей странице, путём умножения параметра page
на itemsPerPage
. Также вычисляется endIndex
, находящийся сразу после последнего элемента, который должен быть отображён на текущей странице.
Таким образом, мы создаём диапазон отображаемых элементов. Например, допустим, у нас есть десять элементов, и мы хотим отображать по пять элементов на странице. Если мы находимся на первой странице (page = 0
), то startIndex
будет равен 0, а endIndex
будет равен 0 + 5 = 5. Этот диапазон ([0, 5]
) включает в себя первые пять элементов. На следующей странице (page = 1
) startIndex
будет равен 5, а endIndex
— 5 + 5 = 10. Этот диапазон ([5, 10]
) включает оставшиеся элементы.
С помощью функции items.forEach()
мы создаём цикл, перебирающий каждую строку и проверяющий, попадает ли её индекс в диапазон элементов, которые будут отображаться на текущей странице, т.е. находится ли он до startIndex
или после/равно endIndex
. Если индекс находится в этом диапазоне, то ключевое слово toggle
применяет к элементу класс hidden
(который мы определим в нашем CSS-коде), эффективно скрывая его. Если индекс не удовлетворяет ни одному из условий, класс hidden
удаляется, делая элемент видимым.
Наш класс hidden
перемещает элементы за пределы экрана, скрывая их от глаз, но сохраняя доступность для пользователей программ чтения с экрана:
.hidden {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
Добавление кнопок
Теперь рассмотрим, как добавить кнопки навигации. В приведённом ниже коде мы создадим и добавим функциональность кнопок на основе содержимого таблицы:
function createPageButtons() {
const totalPages = Math.ceil(items.length / itemsPerPage);
const paginationContainer = document.createElement('div');
const paginationDiv = document.body.appendChild(paginationContainer);
paginationContainer.classList.add('pagination');
Сначала мы создаём функцию createPageButtons()
, которая будет хранить логику создания наших кнопок. Затем мы вычисляем общее количество страниц, необходимых для отображения нашей таблицы. Для этого мы делим общее количество элементов на желаемое количество элементов на странице. Полученный результат округляется в большую сторону с помощью функции Math.ceil()
. Это гарантирует, что все строки элементов нашей таблицы будут заняты имеющимися страницами.
Затем мы создаём div
для размещения динамически генерируемых кнопок страницы (document.createElement('div')
). Далее мы добавляем элемент <div>
в тело нашей HTML-структуры с помощью document.body.appendChild(paginationDiv)
. (На самом деле мы ещё не указали ему место в структуре HTML. Сделаем это в ближайшее время.) Наконец, мы добавляем класс pagination
к контейнеру кнопки, чтобы можно было использовать для него стили.
Следующим шагом будет создание кнопок для каждой страницы с использованием цикла для перебора всех возможных индексов страниц:
for (let i = 0; i < totalPages; i++) {
const pageButton = document.createElement('button');
pageButton.textContent = i + 1;
pageButton.addEventListener('click', () => {
currentPage = i;
showPage(currentPage);
updateActiveButtonStates();
});
Цикл for
имеет диапазон от 0 (это первая страница) до общего количества страниц минус 1.
В каждой итерации страницы с помощью метода document.createElement()
создаётся новая отдельная кнопка страницы, увеличивающая номер страницы на 1 при каждом цикле.
Далее мы создаём слушатель события click
и прикрепляем его к кнопкам страницы. При нажатии на кнопку будет выполняться функция обратного вызова слушателя события.
Вот объяснение функции обратного вызова:
- Переменная
currentPage
обновляется до текущего значенияi
, которое соответствует индексу кликнутой страницы. - Функция
showPage()
вызывается с обновлённым значениемcurrentPage
, что приводит к отображению содержимого кликнутой страницы.
Завершая наш код создания кнопки, мы заканчиваем его следующим образом:
content.appendChild(paginationContainer);
paginationDiv.appendChild(pageButton);
Мы добавляем наш контейнер кнопок в конец нашей обёртки .content
, а затем помещаем наши кнопки внутрь контейнера button
.
Подсветка активных кнопок
Чтобы сделать наши кнопки более удобными в использовании, добавим отличительный стиль к активной
в данный момент кнопке. Создадим функцию, которая будет применять стили CSS-класса active
к кнопке, когда её страница становится активной:
function updateActiveButtonStates() {
const pageButtons = document.querySelectorAll('.pagination button');
pageButtons.forEach((button, index) => {
if (index === currentPage) {
button.classList.add('active');
} else {
button.classList.remove('active');
}
});
}
Сначала мы извлекаем все кнопки пагинации с помощью document.querySelectorAll
и присваиваем их переменной pageButtons
.
Затем функция updateActiveButtonStates()
с помощью цикла forEach
поочерёдно проходит по каждой из этих кнопок и сравнивает её индекс со значением переменной currentPage
.
Далее мы используем условный оператор if
для назначения стилей класса active
, если индекс кнопки совпадает с текущей страницей.
Если индекс кнопки не соответствует текущей странице, то класс active
удаляется. Это гарантирует, что другие кнопки не сохранят класс active
.
Для реализации этой возможности мы вызываем функцию updateActiveButtonStates()
при каждом изменении или отображении страницы.
Вызов скрипта
Наш скрипт пагинации заканчивается следующими двумя строками:
createPageButtons();
showPage(currentPage);
Мы вызываем функцию createPageButtons()
перед функцией showPage()
. Это гарантирует, что кнопки будут созданы сразу после загрузки страницы.
Теперь наш скрипт вычисляет соответствующий диапазон элементов для отображения на каждой странице, прослушивает нажатия кнопок и обновляет отображение страницы.
Итоговый результат
Следующий Pen показывает итоговый результат.
Адаптация кода к другим сценариям
Созданный нами скрипт удобен для разбиения таблицы на ряд страниц. Но что делать, если наше содержимое не является таблицей? Вместо таблиц попробуем применить наш скрипт к другим видам содержимого.
Пагинация для элементов section
Вместо элемента таблицы разместим внутри нашего контейнера несколько элементов <section>
и посмотрим, как адаптировать наш скрипт. Вот наш базовый HTML:
<article class="content">
<section></section>
<section></section>
<section></section>
<section></section>
<section></section>
</article>
Нам нужно внести всего три очень простых изменения в наш скрипт:
document.addEventListener('DOMContentLoaded', function () {
const content = document.querySelector('.content');
const itemsPerPage = 1;
let currentPage = 0;
const items = Array.from(content.getElementsByTagName('section')).slice(0);
Изменения заключаются в следующем:
- установить значение
itemsPerPage
равным 1, чтобы на каждой странице отображалась только одна секция - изменить имя целевого тега на
section
, поскольку теперь мы перебираем элементы<section>
, а не<tr>
- установить значение
slice()
равным 0, что ограничивает выборку первым элементом секции (который имеет индекс 0)
Приведённая ниже демонстрация CodePen показывает это в действии.
Пагинация для неупорядоченного списка
Мы можем легко адаптировать приведённую выше демонстрацию для работы со списком элементов. В приведённом ниже примере мы меняем элемент-обёртку с <article>
на <ul>
и меняем элементы <section>
на элементы <li>
:
<ul class="content">
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
В нашем JavaScript мы сделаем всего два изменения:
getElementsByTagName('section')
становитсяgetElementsByTagName('li')
- установим
const itemsPerPage
равной 2, чтобы показывать по два элемента списка на странице
После небольших изменений в CSS для учёта неупорядоченного списка мы получили результат, представленный ниже.
Заключение
В этом руководстве мы научились реализовывать пагинацию с помощью HTML, CSS и JavaScript. Для тех, у кого не включён JavaScript (по каким-либо причинам), полный текст страницы по-прежнему доступен — просто без пагинации. Благодаря использованию семантических элементов <button>
страница по-прежнему доступна для клавиатуры. Кроме того, мы скрыли неактивное содержимое, переместив его за пределы экрана, а не использовали display: none
, чтобы оно оставалось доступным для программ чтения с экрана.
Мы можем пойти дальше и добавить описательные метки и атрибуты ARIA, чтобы передать читателям экрана назначение и роль таких элементов, как кнопки пагинации.
Я надеюсь, что эта демонстрация заставит вас задуматься о простой функциональности пагинации без необходимости обращаться к фреймворку.