Часто при разработке и выводе контента появляется необходимость использования постраничной навигации. Кто-то скорее всего использует готовые решения от своего фреймворка. Кто-то, возможно, не заморачивается и лупит страницы просто циклом. У кого-то есть свои наработки в этом направлении. Вот я как раз и хочу поделиться своим решением данной задачи.
Существует множество вариаций расположения и отображения кнопок, лично я пришел к следующему решению, которое по моему мнению наиболее наглядно и удобно. Подходит как для 5 страниц так и для 5000.
Пример HTML кода
Не буду ходить вокруг да около, сразу приложу пример сформированного скриптом html кода:
<div class="navigation">
<a href="/playlist/1.html?page=6">Назад</a>
<a href="/playlist/1.html">1</a>
<i>...</i>
<a href="/playlist/1.html?page=4">4</a>
<a href="/playlist/1.html?page=5">5</a>
<a href="/playlist/1.html?page=6">6</a>
<span class="link_active">7</span>
<a href="/playlist/1.html?page=8">8</a>
<a href="/playlist/1.html?page=9">9</a>
<a href="/playlist/1.html?page=10">10</a>
<i>...</i>
<a href="/playlist/1.html?page=17">17</a>
<a href="/playlist/1.html?page=8">Вперед</a>
</div>
Логика построения
По настройкам параметров я остановлюсь чуть позже после приведения кода, сейчас опишу логику формирования самих номеров.
С кнопками «Назад» и «Вперед» думаю все понятно, к тому же их можно просто отключить, поэтому на них не буду заострять внимания.
Первый и последний номер страницы отображается всегда, своего рода кнопки «В начало» и «В конец».
Середина формируется уже по простому алгоритму. Отображается просматриваемая страница и по N страниц по бокам. На примере отображается по N=3 страницы. В принципе все просто и понятно, но особая хитрость используется при приближении к краям. Опишу на примерах:
Страница 1-3 (где 3 = N)
1 2 3 4 5 6… 17
Отображаются первые N*2 страниц и последняя.
Страница 4
1 2 3 4 5 6 7… 17
Отображается первая и дальше сформированная строка от 4-3=1 до 4+3=7. Первая страница зарезервирована поэтому формируются номера от 2 до 7.
Страница 5
1 2 3 4 5 6 7 8… 17
от 5-3=2 до 5+3=8.
Страница 6
1 2 3 4 5 6 7 8 9… 17
Пожалуй во всех навигациях что я видел (включая хабр) строка была бы сформирована с пропуском, т.е. 1… 3 4 5 6 7 8 9… 17
Но ведь это не логично, отображать многоточие вместо одного числа. При построении второго многоточия выполняется аналогичная проверка.
Страница 7
1… 4 5 6 7 8 9 10… 17
Середина уже стандартна.
Формирование окончания аналогично началу
Страница 12
1… 9 10 11 12 13 14 15 16 17
Страница 13
1… 10 11 12 13 14 15 16 17
Страница 14
1… 11 12 13 14 15 16 17
Страница 15-17
1… 12 13 14 15 16 17
Редиректы
Помимо этого из особенностей хочу выделить еще 2 момента, это проверка существования страницы и редирект на «правильный» адрес. Т.е. к примеру, тут же на хабре первая страница может быть доступна сразу по 2м адресам:
habrahabr.ru/sandbox/page1
habrahabr.ru/sandbox
Скрипт не дает зайти на адрес page/1/ и выполняет редирект на «чистый» адрес
Так же если указан слишком большой номер страницы будет выполнен редирект на последнюю существующую. К примеру были удалены материалы или изменено количество записей на страницу. Не могу правда однозначно сказать полезно ли это будет с точки зрения СЕО, но для пользователей мне кажется так будет удобнее.
PHP код и его использование
class navigation
{
/**
* @var string чистый URL по умолчанию
* В адресе может быть указано место для размещения блока с номером страницы, тег {page}
* Пример:
* /some_url{page}.html
* В итоге адрес будет:
* /some_url.html
* /some_url/page_2.html
* Если тег {page} не указан, то страницы будут дописываться в конец адреса
*/
var $base_url = '/';
/**
* @var string шаблон ссылки навигации
*/
var $tpl = 'page/{page}/';
/**
* @var string Обертка кнопок
*/
var $wrap = "n<div class="navigation">n{pages}</div>nn";
/**
* @var int сколько показывать кнопок страниц до и после актуальной
* Пример:
* $spread = 2
* Всего 9 страниц навигации и сейчас просматривают 5ю
* 1 ... 3 4 5 6 7 ... 9
*/
var $spread = 5;
/**
* @var string разрыв между номерами страниц
*/
var $separator = "t<i>...</i>n";
/**
* @var string имя класса активной страницы
*/
var $active_class = 'link_active';
/**
* @var int номер просматриваемой страницы
*/
var $current_page = 0;
/**
* @var bool показывать кнопки "Вперед" и "Назад"
*/
var $next_prev = true;
/**
* @var string текст кнопки "Назад"
*/
var $prev_title = 'Назад';
/**
* @var string текст кнопки "Вперед"
*/
var $next_title = 'Вперед';
/**
* Инициализация класса
* @param string $base_url URL в конец которого будет добавляться навигация
*/
public function __construct( $base_url = '/' )
{
$this->base_url = $base_url;
}
/**
* Строим навигации и формируем шаблон
* @param int $limit количество записей на 1 страницу
* @param int $count_all общее количество всех записей
* @param int $current_page номер просматриваемой страницы
* @return - Сформированный шаблон навигации готовый к выводу
*/
public function build( $limit, $count_all, $current_page = 1 )
{
if( $limit < 1 OR $count_all <= $limit ) return;
$count_pages = ceil( $count_all / $limit );
if( $current_page > $count_pages ) {
header( "HTTP/1.0 301 Moved Permanently" );
header( "Location: " . $this->get_url( $count_pages ) );
die( "Redirect" );
}
if( $current_page == 1 AND $_SERVER['REQUEST_URI'] != $this->get_url( $current_page ) )
{
header( "HTTP/1.0 301 Moved Permanently" );
header( "Location: " . $this->get_url( $current_page ) );
die( "Redirect" );
}
$this->current_page = intval( $current_page );
if( $this->current_page < 1 ) $this->current_page = 1;
$shift_start = max( $this->current_page - $this->spread, 2 );
$shift_end = min( $this->current_page + $this->spread, $count_pages-1 );
if( $shift_end < $this->spread*2 ) $shift_end = min( $this->spread*2, $count_pages-1 );
if( $shift_end == $count_pages - 1 AND $shift_start > 3 ) $shift_start = max( 3, min( $count_pages - $this->spread*2 + 1, $shift_start ) );
$list = $this->get_item( 1 );
if( $shift_start == 3 ) $list .= $this->get_item( 2 );
elseif( $shift_start > 3 ) $list .= $this->separator;
for( $i = $shift_start; $i <= $shift_end; $i++ ) $list .= $this->get_item( $i );
$last_page = $count_pages - 1;
if( $shift_end == $last_page-1 ) $list .= $this->get_item( $last_page );
elseif( $shift_end < $last_page ) $list .= $this->separator;
$list .= $this->get_item( $count_pages );
if( $this->next_prev ) $list = $this->get_item( $this->current_page > 1 ? $this->current_page - 1 : 1, $this->prev_title, true ) . $list . $this->get_item( $this->current_page < $count_pages ? $this->current_page + 1 : $count_pages, $this->next_title, true );
return str_replace( "{pages}", $list, $this->wrap );
}
/**
* Формирование адреса
* @param int $page_num номер страницы
* @return string сформированный адрес
*/
private function get_url( $page_num = 0 )
{
$page = $page_num > 1 ? str_replace( '{page}', $page_num, $this->tpl ) : '';
if( stripos( $this->base_url, '{page}' ) !== false ) return str_replace( '{page}', $page, $this->base_url );
else return $this->base_url . $page;
}
/**
* Формирование кнопки/ссылки
* @param int $page_num номер страницы
* @param string $page_name если указано, будет выводиться текст вместо номера страницы
* @param bool $noclass
* @return - span блок с активной страницей или ссылку.
*/
private function get_item( $page_num, $page_name = '', $noclass = false )
{
$page_name = $page_name ?: $page_num;
$class = $noclass ? '' : ' class="' . $this->active_class . '"';
if( $this->current_page == $page_num ) return "t<span" . $class . ">" . $page_name . "</span>n";
return "t<a href="" . $this->get_url( $page_num ) . "">" . $page_name . "</a>n";
}
}
Для наглядности, приведу пример построения навигации песочницы:
habrahabr.ru/sandbox/page12
$navi = new navigation( "/sandbox/" );
$navi->tpl = "page{page}/";
$navi->spread = 4;
$template = $navi->build( $limit, $count_all, $page_num );
Или же если номер страницы прописан внутри URL:
example.com/some_url/1.html — первая страница
example.com/some_url/1-page2.html — вторая страница
$navi = new navigation( "/some_url/1{page}.html" );
$navi->tpl = "-page{page}";
$template = $navi->build( $limit, $count_all, $page_num );
где
$limit — количество записей на страницу
$count_all — общее количество записей
$page_num — номер страницы на которой находится пользователь
На этом, пожалуй, всё. Буду рад любой конструктивной критике.
Автор: Sandev