Бесконечный скролл с автоматической загрузкой контента

Бесконечный скролл с автоматической загрузкой контента

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

автор

Автор: Виктор Гавриленко

Меня зовут Виктор Гавриленко, по образованию я инженер электромеханик. Еще учась в институте, начал увлекаться компьютерными технологиями и программированием, на протяжении двух лет усиленно занимаюсь разработкой интернет сайтов и веб-программированием. Увлекаюсь написанием небольших скриптов на языке PHP, в связке базами данных MySQL, SQLite, неплохо знаю такие движки как WordPress, Drupal, Joomla.

скачать исходникискачать урок

1. Постановка задачи

Итак, давайте рассмотрим следующий пример. Есть вот такой сайт, на главной странице, которого выводятся обычные статьи:

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

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

2. Обзор работы тестового сайта

Итак, весь исходный код тестового сайта я в этой статье приводить не буду, так как он довольно большой, да и в исходниках к данном уроку Вы сможете их посмотреть. Главный файл данного сайта – это index.php, который, собственно и выводит на экран весь контент данного сайта. Давайте рассмотрим фрагмент кода данного сайта, а именно код PHP файла index.php:

<?php
include 'config.php';
include 'functions.php';

if($_GET['page']) {
	$page = (int)$_GET['page'];
	if(!$page) {
		$page = 1;
	}
}
else {
	$page = 1;
}

$result =  get_posts($page,COUNT_PER_PAGE);
?>

Как Вы видите, вначале подключаем конфигурационный файл, в котором выполняется соединение с базой данных и определяется константа COUNT_PER_PAGE – количество статей выводящихся на одной странице. И, файл, functions.php – в котором содержится функция получения статей из базы данных, в соответствии с выбранной страницей (к ней мы еще вернемся).

Далее, собственно определяем какую страницу необходимо загрузить (постраничной навигации), то есть, проверяем наличие в суперглобальном массиве GET ячейки page, если данная ячейка, есть и содержит в себе числовое значение – то мы создаем переменную $page в которую и записываем данное значение. Если же данной ячейки нет, или в ней содержится не числовой значение, то в переменную $page – сохраняем значение 1 (по умолчанию отображаем первую страницу постраничной навигации). Дальше собственно вызываем функцию get_posts(), которая возвращает массив со статьями.

Теперь давайте рассмотрим файл functions.php:

<?php
	function get_posts($page,$count_per_page) {
		
		$start = ($page - 1)*$count_per_page;

		$sql = "SELECT id,title,author,date,img_src,discription,view 
											FROM statti
											ORDER BY id
											LIMIT ".$start.",".$count_per_page;
		$result = mysql_query($sql);
		
		if (!$result){
			exit("<p>В базе данных не обнаружено таблицы проверте настройки</p>");
		}
		if(mysql_num_rows($result) == 0) {
			exit(false);
		}
		
		$row = array();
		for($i = 0; $i < mysql_num_rows($result); $i++) {
			$row[] = mysql_fetch_array($result,MYSQL_ASSOC);
		}				
		return $row;									
	}
?>

Как Вы видите функция принимает всего два параметра: $page – номер страницы, которую необходимо отобразить, и $count_per_page – количество статей отображаемых на одной странице (конечно данный параметр можно было и не передавать, а использовать константу из конфигурационного файла, но так будет универсальнее). Данная функция выполняет SQL запрос по выборке данных из базы данных и что бы реализовать механизм получения определенных статей, в соответствии со страницами, необходимо в запросе использовать инструкцию LIMIT, и задать собственно начало выборки и количество выбираемых статей. Для того что бы определить начало выборки мы воспользуемся следующим выражением:

$start = ($page - 1)*$count_per_page;

То есть от номера, который мы передаем параметром данной функции мы вычитаем 1 и умножаем на количество статей выводимых на одной странице постраничной навигации. К примеру, если мы хотим открыть вторую страницу, то что получается: $page = 2, минус 1, получаем 1, затем умножаем на 10 (количество статей на одной странице) и получаем 10, значит в инструкцию попадает два значения: LIMIT 10,10, что означает выбрать 10 статтей из базы данных, начиная с 10. Дальше выполняем данный SQL запрос и возвращаем массив со статьями.

3. Добавляем вспомогательный HTML код

Перед тем как начать писать код, для бесконечного скролла, давайте добавим вспомогательный HTML код:

<div class="load"></div>
<div class="pager"></div>

Блок div с классом load необходим, для индикации процесса отправки информации на сервер. То есть изначально, данный блок скрыт, но когда идет обращение к серверу, данный блок будет отображаться на экране. На фоне этого блока будет установлена анимационная картинка (gif). Сразу же давайте добавим стили данному блоку:

.load {
	width:100px;
	height:60px;
	background-image: url("images/loading2.gif");
	background-position: center bottom;
	background-repeat: no-repeat;
	text-align: center;
	position: fixed;
	top: 60%;
	left:15%;
	display: none;
}

Блок div с классом pager – информационный, он необходим, что бы мы с Вами видели, какая страница сейчас отображается. Сразу же давайте добавим стилей к данному блоку (данный блок я просто выведу в произвольном месте сайта, так как скорее всего его вообще не нужно показывать пользователю, но на этапе разработки скрипта он очень полезен).

.pager {
	width:30px;
	height:30px;
	text-align: center;
	position: fixed;
	top: 75%;
	left:80%;
	display: none;
	border: 1px solid red;
}

4. Логика на javaScript

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

<script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
<script src="js/script.js" type="text/javascript"></script>

Теперь откроем файл script.js и добавим в него следующий код:

$(document).ready(function () {
	var page;
	var param = location.
				search.
				slice(location.search.indexOf('?')+1).
				split('&');
	
	var result = [];
	for(var i = 0; i < param.length;i++) {
		var res = param.split('=');
		result[res[0]] = res[1];
	}
	
	if(result['page']) {
		page = result['page'];
	}
	else {
		page = 1;
	}
	$(".pager").show().text(page);
	
	var block = false;
	$(window).scroll(function () {
		
		if($(window).height() + $(window).scrollTop() >= $(document).height() && !block) {
			block = true;
			$(".load").fadeIn(500, function () {
				page++;
				$.ajax({
					url:"index.php",
					type:"GET",
					data:"page="+page+"&move=1",
					success:function(html) {
						if(html) {
							$(html).appendTo($("#posts")).hide().fadeIn(1000);
							$(".pager").text(page);
						}
						$(".load").fadeOut(500);
						block = false;
					}
				});
			});
		}
	});
});

Итак первым делом необходимо узнать нет ли в адресной строке параметра page с номером отображаемой страницы. Для этого мы используем свойство search объекта location, которое содержит в себе GET параметры передаваемые через адресную строку. Но если адресная строка имеет вид: http://localhost/lessons/pager/index.php?page=2&move=4, то свойство search содержит строку: ?page=2&move=4.

Значит, используя метод slice, мы с Вами исключим знак вопроса из полученной строки. И сразу же, используя метод split разобьем ее в массив, который сохраним в переменной param. Разбивать конечно же будем по символу &. То есть в переменно param содержится массив вида:

[0] = "page=2",
[1] = "move =4"

А нам необходимо получить значение параметра page. Значит, используя цикл for, создадим массив result, у которого названия ячеек – это названия GET параметров, а значения – это соответственно значения этих же GET параметров:

var result = [];
	for(var i = 0; i < param.length;i++) {
		var res = param.split('=');
		result[res[0]] = res[1];
	}

Теперь проверим, есть ли в массиве result ячейка, page, и если есть то ее значение запишем в переменную page. Если же данной ячейки нет, то в переменную page сохраняем значение 1 (по умолчанию). Далее, выберем при помощи jQuery блок с классом pager, отобразим его на экране, при помощи метода show() и добавим содержимое данному блоку (текст данного блока), значение переменной page, используя для этого метод text().

Дальше объявим переменную block и сохраним в ней значение FALSE. Данная переменная будет использоваться для блокировки скрипта. То есть если в данной переменно будет сохранено значение TRUE, значит, работа следующего участка кода будет блокирована (о котором мы с Вами поговорим дальше). Далее следует вот этот участок кода:

var block = false;
	$(window).scroll(function () {
		if($(window).height() + $(window).scrollTop() >= $(document).height() && !block) {
			block = true;
			$(".load").fadeIn(500, function () {
				page++;
				$.ajax({
					url:"index.php",
					type:"GET",
					data:"page="+page+"&move=1",
					success:function(html) {
						if(html) {
							$(html).appendTo($("#posts")).hide().fadeIn(1000);
							$(".pager").text(page);
						}
						$(".load").fadeOut(500);
						block = false;
					}
				});
			});
		}
	});

Сотрите выбираем элемент window и вызываем метод scroll(). Который задает функцию обработчик события scroll – перемещение ползунка скролла. То есть когда пользователь переместит ползунок скролла, выполнится функция, описанная в данном методе. Далее проверим, если ползунок скролла находится в самом низу страницы и блокировка скрипта не включена, то мы первым делом блокируем скрипт: block = true;

Затем показываем блок с классом load, используя метод fadeIn(), со скоростью 500 миллисекунд и после завершения всех анимационных эффектов, выполнится функция описанная во втором параметре метода fadeIn(). В этой функции мы первым делом увеличиваем значение переменой page на единицу, так как нам нужно отобразить следующую страницу. Затем обращаемся к методу ajax и передаем ему следующие параметры:

url — путь к фалу на сервере к которому мы обращаемся, в данном случае это index.php;

type – тип запроса, при помощи которого мы передаем данные, в нашем случае это тип GET;

data – данные, которые передаются на сервер. Передаем параметр page – значение новой отображаемой страницы и параметр move со значением 1, необходимый для формирования ответа от сервера.

success – функция которая выполнится после успешного выполнения запроса. Данная функция принимает параметром page переменную html – ответ от сервера, который мы с Вами сформируем далее. Если же ответ от сервера пришел и не равен false, то в данном ответе будет содержаться html код следующей страницы постраничной навигации. Поэтому используя метод appendTo(), мы добавим данный код в блок с идентификатором posts после его содержимого (как последний дочерний элемент). Далее мгновенно скроем данный код (значение переменной html) и сразу же покажем при помощи метода fadeIn(). Дальше изменяем содержимое блока с классом pager используя метод text(), то есть текущее значение переменной page выводим в данном блоке. Далше скрываем блок с классом load и разблокируем работу скрипта, то есть в переменную block записываем значение false.

Смотрите так как при перемещении скролла постоянно срабатывает обработчик события scroll, работу скрипта необходимо блокировать на время передачи данных на сервер, так как если пользователь быстро перемещает скролл и мы в это время отправляем данные на сервер, сразу же повторно может сработает обработчик данного события и так далее пока пользователь не остановит ползунок скролла. И получится что мы на сервер будем отправлять множество запросов, а это создаст серьезную нагрузку, да и визуально будет смотреться плохо, так как в одно мгновение отобразится несколько страниц подряд.

Для определения того что ползунок скролла находится в самом низу экрана, используем следующие значения:

$(window).height() – высота окна браузера;

$(document).height() – высота документа (другими словами высота нашего сайта).

$(window).scrollTop() – значение ползунка скрола. То есть высота на которую поднимается контент сайта вверх, за пределы видимой части браузера при перемещении ползунка скролла.

Значит если $(window).height() + $(window).scrollTop() больше либо равно $(document).height() (высоте документа), значит можно утверждать, то ползунок находится в самом низу браузера.

5. Формируем ответ от сервера

Когда мы обращаемся в файл index.php, используя AJAX и передаем параметр page со значением отображаемой страницы, сразу же сработает проверка:

if($_GET['page']) {
	$page = (int)$_GET['page'];
	if(!$page) {
		$page = 1;
	}
}
else {
	$page = 1;
}

И вызовется функция get_posts() уже с новым значением для отображаемой страницы и в переменную $result будет сохранен массив с новыми 10 статьями. Которые необходимо отобразить на сайте. Значит мы должны правильно сформировать ответ от сервера и данный ответ передать обратно в javaScript. Который попадет в переменную html, о которой мы с Вами говорил выше. Как Вы помните помимо параметра page, мы в файл index.php передаем параметр move, со значением 1. Значит в файл index.php после проверки наличия ячейки page в суперглобальном массиве GET, необходимо добавить следующий код:

if($_GET['move'] == 1) {
	 foreach($result as $row) {
	 
					printf("<div class='for_text'>
						<h3>%s</h3>
						<strong>%s | %s</strong>
						<br />
						<span>
						<img src='%s'>
						%s
						</span>					
						<p>%s</p>
					</div>",$row['title'], $row['author'], $row['date'], $row['img_src'], $row['discription'], $row['view']);
	}
	exit();
}

То есть проверяем есть ли в суперглобальном массиве GET ячейка move со значением 1 и если да, то в цикле проходим по массиву $result и как бы выводим его на экран. Но мы же обращаемся к данному файлу удаленно, используя AJAX поэтому на экран ничего не выведется, а наоборот, все что должно было вывестись на экран, будет являться ответом от сервера, который попадет в переменную html.

После цикла вызываем функцию exit(), что бы сразу же завершить работу скрипта после отработки цикла. Теперь можно проверять работу скрипта в целом.

Смотрите, при опускании скролла в самый низ, появляется вначале блок с классом load а затем появляется следующая страница, то есть дальше опуская скролл вниз мы увидим следующую страницу.

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

Практика HTML5 и CSS3 с нуля до результата!

Получите бесплатный пошаговый видеокурс по основам адаптивной верстки с полного нуля на HTML5 и CSS3

Получить

Метки: ,

Комментарии Вконтакте:

Комментарии Facebook:

Комментарии (27)

  1. Алексей

    Очень интересный скрипт. Прост в установке и удобен в использовании.

  2. Юрий

    отличный урок все получилось, а как сделать что бы при прокрутке вверх что бы страницы в квадрате правильно отображались а не оставалась последняя которую прокрутили?

  3. kni1951

    «Маленькая проблемка» !
    1) Запустили асинхронную операцию
    2) Как на фоне запуска асинхронной операции простыми средствами блокировать запуск синхронных операций
    (головная боль).
    3) При долгих асинхронных операциях вопрос НЕНАДУМАННЫЙ.

  4. Денис

    Поставил себе такую вещицу, но вот проблема

    не прогружается контент после первого запуска страницы — счетчик page растет (только в строке адресной не растет, а в блоке .pager растет), появляется и исчезает (при увеличении счетчика) анимация загрузки, но элементы не добавляются ни в какую, хоть до сотни счетчик может расти

    я думаю, что дело все таки в адресной строке, ведь для в php мы этот счетчик получаем именно оттуда

    и ещё — я почему-то не наблюдаю в видеоуроке, чтобы после index.php вообще что-то шло, откуда тогда у Вас берется переменная $_GET['page'] ?

    я уже несколько часов голову ломаю

  5. Виктор Гавриленко

    Здравствуйте, Денис!
    Сложно сказать не видя кода, что и почему не работает.
    А по поводу адресной строки, могу сказать следующее — мы данные передаем на сервер асинхронно, через метод GET, то есть без перезагрузки страницы — вот поэтому Вы их не видите в адресной строке. Для решения технических вопросов, у нас есть форум.

  6. jack

    Виктор
    Эту функцию можно использовать для автоматической загрузкой контента категорий или нужно изменить некоторые значения?

    • Виктор Гавриленко

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

  7. jack

    я использовал эту функцию для главной страницы и все работает отлично, но для категории не работает. Пример: мы используем здесь функцию function get_posts($page,$count_per_page) с 2 значения, для категории я использовал функцию с 3 Значение function get_cat_posts ($category,$page,$count_per_page) я должен изменить некоторые JavaScript?

    • Виктор Гавриленко

      Здравствуйте!
      Для вывода категории, В Вашем случае еще на сервер нужно передавать в идентификатор категории — параметр $category. Вот какие данные мы отправляем по уроку data:»page=»+page+»&move=1″, а Вам нужно добавить еще один параметр data:»page=»+page+»&move=1&category=»+$category. При этом по структуре Вашего сайта в коде JavaScript нужно получить доступ к переменной $category или если сказать более точно записать в нее идентификатор категории.

  8. Максим

    а не подскажите как на wordpress сея творение подключить?

  9. Дмитрий

    Перезалейте пожалуйста исходники, База данных не соответствует уроку, из-за это путаница!

  10. Вадим

    Спасибо за урок. Такой вопрос — у меня после подгрузки всех существующих страниц продолжает грузится какой то html код и параметру page присваивается несуществующее значение. Как вылечить? exit() не помогает

    • Виктор Гавриленко

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

  11. Михаил

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

  12. Михаил

    Разобрался почему так было.
    Если не прописано
    тогда $(window).height() равно $(document).height()

    Теперь новый вопрос. как сделать, если из БД вытащены все записи, прекращать работу java скрипта, чтобы он больше не отправлял запросы.

    • Виктор Гавриленко

      Здравствуйте!
      Убрать обработчик события можно с использованием метода $(‘window’).unbind(‘scroll’), то есть определяете, что в базе данных больше нет информации и передаете вместе с ответом какой то параметр, который будет указывать на то что необходимо убрать обработчик события. Как то так.

  13. Михаил

    можете привести пример что-то не получается передать параметр в скрипт. или дописать скрипт мы же в функции если нет записи делаем exit(false); может писать return false и в java скрипте реагировать на это.

  14. Михаил

    Незнаю правильно это или нет, но чтобы скрипт больше не работал после всей выборки из БД нужно дописать условие else
    if(html) { $(html).appendTo($(«#posts»)).hide().fadeIn(1000);
    $(«.pager»).text(page);
    }else{$(window).unbind(«scroll»);}

  15. Михаил

    Здравствуйте, подскажите как узнать данный параметр $(document).height(), но только для блока и пример данного скрипта не работает полностью срабатывает только одно условие, если кручу скролл вниз срабатывает условие if($(«.pl»).height() + $(«.pl»).scrollTop() >= («1100″) && !block) а другое условие не работает пока не перезагрузишь страницу

  16. Александр

    Здравствуйте, Виктор! Сделал по Вашему уроку Скрипт подгрузки контента у себя на сайте. Всё работает как-бы, но… Если страницу, где подгружается контент обновить, когда скролл находился в самом низу, то одни и те же данные подгружаются дважды. Как понял проблема в блокировке, а именно в переменной blok, либо в счётчике страниц, через переменную page (один и тот же оффсет отправляется в скрипт дважды, переменная не успевает обновиться почему-то). Пробовал функцию вызывать через setTimeout — не помогло. Всё тоже. В фаэрбаге наблюдаю, что в обработчик дважды отправляется запрос с одним и тем же оффсетом.
    И ещё заметил вот что: если, например, осталось подгрузить один элемент на страницу, то он подгружается дважды. Со счётчиками в этом случае всё нормально, — проверял.
    Как победить данную проблему? Уж очень большая головная боль?
    Пробовал использовать блокировку скрипта async: false — не помогло.
    Как быть?

    • Виктор Гавриленко

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

  17. shamil

    Здравстуйте!
    А какое условие нужно поставить если таблица пуста, используя для комментариев ? Страница полностью исчезает.

    • Виктор Гавриленко

      Здравствуйте!
      Можно на стороне сервера, выполнить проверку — если записей нет — вернуть к примеру в JavaScript, в качестве ответа FALSE.

      • shamil

        Здравствуйте.
        отформатировал код для класса,установил лимит,
        выборка паботает,но нет подгрузки,может в script.js нужно поменять настройки?
        indexOF например, подгрузка работает только при url:index.php и url:»",
        но перезагружается весь сайт, со следующеей страницей page.

  18. шамиль

    Виктор, я давно не могу сделать так, чтобы url обрабатывал отдельную функцию(метод).
    Например url:"$view=userdata"
    для private function getUserData(){......}
    Иначе у меня синхронизируется вся страница с другими функциями.Знаю ,что нужно добавлять отдельные классы для ajax, но как это сделать?

  19. Виктор Гавриленко

    Здравствуйте!
    Не совсем понял что именно Вы пытаетесь реализовать, уточните пожалуйста.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Я не робот.

Spam Protection by WP-SpamFree