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

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

Сначала создадим верстку, сделаем скриптовую часть, и лишь потом объединим все это в плагин.

Верстка

Так будет выглядеть html структура нашей галереи

<div class="gallery">
	<div class="prev"></div><!--кнопка прокрутки назад-->
	<div class="next"></div><!--кнопка прокрутки вперед-->
	<div class="carusel">
		<!--здесь будет фото-->
		<div style="clear:both"></div>
	</div>
</div>

gallery - это наша галерея, учитывая, что на странице их может быть несколько, то работаем с классом, а не id

prev и next - это кнопки прокрутки

carusel - как положено по названию будет перемещаться, забегая вперед скажу, что делать мы это будем с помощью изменения margin-left

css для галереи

.gallery{overflow:hidden;height:300px;position:relative;border:1px solid #eee;}
.carusel{position:relative;width:5000px;}
.carusel a{float:left;display:block;margin-right:5px;}
.prev,.next{
	position:absolute;
	left:0px;
	z-index:99;
	width:8%; 
	background:#eee url(img/prev.png) no-repeat;
	background-position: 50% 50%;
	height:300px;
	opacity:0.5;
	cursor:pointer;
}
.next{
	left:92%;
	background-image:url(img/next.png)
}

тут все просто, у главного блока gallery, ставим overflow:hidden, дабы скрыть лишние фото.  Немаловажный момент position:relative; Дело в том, что блоки prev и next нужно размещать поверх carusel. Они будут видны всегда. Сделать это по другому нежели через position:absolute; сложно. Если просто сделать position:absolute; для prev и next, то они позиционируются относительно всего документа. Если галерея на весь экран, то все хорошо. Однако мы делаем плагин, который должен работать везде.

Тут нам поможет одно важное свойство  абсолютного позиционирования - если родительский блок имеет position:relative;, то absolute позиционирует относительно родительского элемента, а не документа. Именно поэтому мы поставили для нашей gallery, position:relative;, а для crausel position:absolute;

Кнопку next, смещаем по оси x на 92%, так как сам элемент в ширину равен 8%. Для кнопок я подыскал подходящие иконки. Сделать это можно к примеру тут

Макет готов. Вот как он выглядит у меня. Все фото, кроме моего =), любезно  предоставил сайт про знаменитостей celebitchy.ru

Красиво?! Теперь этот макет надо оживить.

Карусель 

Чтобы показать фото, которые находятся правее, но не видны из-за overflow:hidden; нужно менять css свойство margin-left у carusel. Определим для этого отдельную функцию step(shag), но перед этим нам нужно определить реальный размер carusel. Для этого просто проссуммируем ширину всех блоков внутри carusel. Кроме этого, нам понадобятся ширина самой gallery, величина приращения на каждом шаге прокрутки и скорость. 

var galleryWidth = 0; // ширина самой галлереи
var shag = 200; // при каждом шаге будем двигать карусель на 100 пиксел
var speed = 200; // время в миллисекундах, за которое галерея пройдет 1 шаг, т.е. сдвинется на shag или 200px
var $gallery  = 0; // вспомогательные переменные, они пригодятся, когда мы будем делать плагин
var $carusel = 0; // хороший тон находить элемент единожды, а потом использовать ссылку на него
// вычислять реальную длину карусели придется, всегда, иначе глючит в chrome
var caruselWidth = function(){
	var w = 0;
	$carusel.find('a').each(function(){
		w+=$(this).find('img').width()+5;
	})
	return w;
}
$(function(){
	// запускать функцию надо только после того, как DOM полностью загружен
	$gallery = $('.gallery'); // находим нашу галерею
	$carusel = $gallery.find('.carusel');// находим в ней карусель
	galleryWidth = $gallery.width()
})

Величина galleryWidth и функция  caruselWidth() нужны для того, чтобы верно определить максимальную margin-left прокрутки carusel влево.

Функция step, в простейшем случае выглядит так

function step(direct){
	direct= direct>0?'-':'+';
	$carusel.stop().animate({'marginLeft':+direact+'='+shag+'px'},speed)
}

Прокручиваем carusel с при помощи метода animate, библиотеки jquery. Метод позволяет использовать оператор декремента и инкремента, как в обычном js("-=" и "+=" соответственно) 

Далее надо "повесить" данную функцию на событие click у next и prev.

$gallery.find('div.next,div.prev').click(function(){
	step(this.className=='next'?1:-1);
})

Вот что получилось у меня, покликайте вправо или влево

Как Вы могли заметить, у этой прокрутки есть один недостаток. Прокрутка ничем не ограничена, и карусель можно прокручивать бесконечно. Это неправильно. Изменим функцию step, тут нам пригодятся caruselWidth и galleryWidth

function step(direct){
	var marginLeft = parseInt($carusel.css('marginLeft'));// текущее положение карусели
	var wdth = caruselWidth() - galleryWidth; // максимальная прокрутка влево
	if( direct > 0 ){
		// прокрутка влево
		// если текущая прокрутка + shag не превышает максимально допустимую
		if( wdth >= Math.abs(marginLeft)+shag ){
			// то просто прокручиваем на shag
			$carusel.stop().animate({'marginLeft':'-='+shag+'px'},speed)
		// иначе докручиваем до края, и все
		}else $carusel.stop().animate({'marginLeft':'-'+wdth+'px'},speed)
	}else{
		// аналогично для прокрутки вправо, но тут крутим до нуля
		if( 0 <= Math.abs(marginLeft)-shag ){
			$carusel.stop().animate({'marginLeft':'+='+shag+'px'},speed)
		}else $carusel.stop().animate({'marginLeft':'0px'},speed)
	}
}

теперь галерея ведет себя более верно, для красоты я еще добавил эффект затухания при наведении на next и prev

$gallery.find('div.next,div.prev').hover(function(){
	jQuery(this).stop().animate({'opacity':'0.8'},400);
},function(){
	jQuery(this).stop().animate({'opacity':'0.5'},400);
}).click(function(){
	step(this.className=='next'?1:-1);
})

посмотрим что получилось

при наведении next и prev становятся чуть менее прозрачными.

Mousewheel

Отличная галерея, но машинально хочется прокрутить ее колесиком мышки. В этом нам поможет отличный jquery плагин mousewheel подключаем к нашему проекту

<script type="text/javascript" src="js/jquery.mousewheel.min.js" charset="utf-8"></script>

Он очень прост в применении. 

$gallery.mousewheel(function(event, delta) {
    step(delta)// тут возможно нужно использовать -delta, т.е. инвертируем направление. кому как удобно
});

чтобы страница не прокручивалась, надо добавить event.stopPropagation(); и event.preventDefault(); 

$gallery.mousewheel(function(event,delta){
	step(-delta);
	event.stopPropagation&&event.stopPropagation(); // если поддерживается, выполняем
	event.preventDefault&&event.preventDefault();
})

Галерея практически готова, но остался еще один забавный эффект

Drag&Drop перетаскивание

Все наверно видели, как в iphone пролистывается галерея. Или в Google Picasa Фото прокручивается простым перетаскиванием до нужного места, при этом если перетаскивать быстро и отпустить, то carusel должна еще немного двигаться по инерции.

Чтобы работать с  Drag&Drop, нам нужны 3 метода: mouseup,mousedown и mousemove. На событие  mousedown мы поднимаем флаг, что в данный момент карусель должна следовать за мышкой.

Событии mousemove происходит всегда, когда курсор мыши перемещается над объектом. В нем мы проверяем поднят ли флаг переноса, если да то перемещаем карусель.

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

Для измерения скорости напишем функцию, и объявим несколько глобальных переменных

var oldmovex = 1;
var oldmovetime = 1;
var carWidth = 0;
var curSpeedObj = {'x':0,'t':0};
function measureSpeed(clientX){
	var curtime  = new Date().getTime();
	curSpeedObj.x = Math.abs(clientX-oldmovex); // запомним приращения движения
	curSpeedObj.t = curtime-oldmovetime;	// и прошедшее время
	oldmovex = clientX;
	oldmovetime = curtime;
}

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

Для начала опишем mousedown

$carusel.mousedown(function(event){
	var evnt = event || window.event; // кроссбраузерный код
	oldx = evnt.clientX; // запоминаем текущее положение курсора мыши по оси x
	carWidth = caruselWidth();	
	oldMargin =  parseInt($carusel.css('marginLeft')); // также запоминаем смещение карусели
	startDrag = true; // поднимаем флаг
})

Событие mousemove, тут и происходит перетаскивание

$carusel.mousemove(function(event){
  if( startDrag ){
	var evnt = event || window.event;
	measureSpeed(evnt.clientX);// замеряем скорость
	var shg = evnt.clientX-oldx; // находим приращение по x на которое нужн сдвинуть 
	if(  Math.abs(oldMargin) - shg <= carWidth - galleryWidth &&  Math.abs(oldMargin) - shg >= 0)
		$carusel.css('marginLeft',oldMargin+shg)// двигаем карусель если курсор не находится за пределами галереи
	else{
		if(Math.abs(oldMargin) - shg>=0)
			$carusel.css('marginLeft','0px'); // иначи пристыковываем к краям
		else 
			$carusel.css('marginLeft','-'+carWidth-galleryWidth+'px');
	}
  }
})

Событие mouseup, лучше сделать глобальным. Потому что пользователь может отпустить мышь, не над объектом. 

$(window).mouseup( function(event){
	// работаем только если флаг поднят
	if(startDrag){
		var evnt = event || window.event;
		startDrag = false; // опускаем флаг
		// если скорость перемещения была больше чем некий допустимый коефициент 
		if(curSpeedObj.x/curSpeedObj.t>0.5){
			// создаем эффект движения по инерции
			step(-(evnt.clientX-oldx),curSpeedObj.x*20,curSpeedObj.t*20,'linear');
			curSpeedObj.x = curSpeedObj.t = 0;
		}
	}
})

Как вы могли заметить, я слегка изменил вызов функции step. Теперь speed и shag для нее, это не глобальные переменные, а параметры. Еще я добавил значение easing. Дело в том, что метод animate, своим 3-им параметром может получать характер своего движения. В jquery по умолчанию доступны только два значения easing: linear и swing. При первом анимация от начала и до конца движения будет иметь одну и туже скорость, при втором она будет напоминать качели - сначала медленно, затем по середине быстро, и в конце снова медленно. 

Если Вам будет недостаточо этих двух параметров (linear или swing), к примеру Вам захочется сделать эффект торможения, то рекомендую плагин jQuery Easing В нем больше 20 различных вариаций easing. Поиграйтесь на досуге, можно делать очень занимательные эффекты.

Но, в моем плагине я остановился на linear, его вполне достаточно. А добавил в step я его затем, что бы Вы сами поигрались и выбрали интересный Вам эффект. Кроме того, когда мы будем делать плагин jQuery, то вынесем это значение в настройки.

Теперь  step будет таким

function step(direct,shag,speed,easing){
	var marginLeft = parseInt($carusel.css('marginLeft'));// текущее положение карусели
	var wdth = caruselWidth() - galleryWidth; // максимальная прокрутка влево
	if( direct > 0 ){
		// прокрутка влево
		// если текущая прокрутка + shag не превышает максимально допустимую
		if( wdth >= Math.abs(marginLeft)+shag ){
			// то просто прокручиваем на shag
			$carusel.stop().animate({'marginLeft':'-='+shag+'px'},speed,easing)
		// иначе докручиваем до края, и все
		}else $carusel.stop().animate({'marginLeft':'-'+wdth+'px'},speed,easing)
	}else{
		// аналогично для прокрутки вправо, но тут крутим до нуля
		if( 0 <= Math.abs(marginLeft)-shag ){
			$carusel.stop().animate({'marginLeft':'+='+shag+'px'},speed,easing)
		}else $carusel.stop().animate({'marginLeft':'0px'},speed,easing)
	}
}

Это значит, что теперь надо везде, где мы использовали  step, надо добавить в вызов соответствующие параметры. 

step(delta,shag,speed,'linear') // в mousewheel, здесь можно поиграть и поставить swing
step(this.className=='next'?1:-1,shag,speed,'linear') // в next,prev click

Еще в firefox, chrome и ie, при перетаскивании за картинку, происходит перетаскивание самой картинки а не карусели. Это поведение надо заглушить

$carusel.find('a').mousedown(function(event){
	var evnt = event || window.event;
	if (evnt.preventDefault) evnt.preventDefault() // глушим стандартное поведение
}).click(function(){
	return false; // также глушим, переход по ссылке
})

Все практически готово. Посмотрим на результат

Так как сделано это все ради урока, все коэффициенты нужно править.  

Plugin jQuery

Галерея можно сказать готова, осталось объединить все это в плагин на jQuery.

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

(function($){
// сюда весь код
})(jQuery)

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

Подробно о том, как писать плагины Вы можете почитать в статье Как написать плагин на jQuery

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

(function($){
$.fn.xdGallery = function(galleryOptions){
	//настройки по умолчанию
	var defaultOptions = {
		'shag':200,	 // px на один шаг прокрутки
		'speed':200, // время за которое карусель прокрутится на один шаг(shag)
		'itemClass':'itemImage',
		'easingWheel':'swing', // характе прокрутки при вращении колесиком
		'easingNext':'linear', // характер прокрутки при прокрутке через next,prev
		'easingDrag':'linear', // характер прокрутки при перетаскивании, эффект инерции
		'invertWheel':1,// инвертировать прокрутку при вращении колесика мышки
		'invertNext':-1, // инвертировать прокрутку при нажатии кнопок next и prev
		'orient':'horizontal', // ориентация галереи, horizontal(по умолчанию) или vertical
		'onHoverNext':false, // прокручивать карусель при наведении на кнопку next или prev
		'navigation':false, // навигация, если false, то выключена. Если >0 то равно на сколько табов будет делиться карусель, если указать 0, то будет автоматически подбирать под количество items

	};
	// объединяем дефолтные и пользовательские настройки, приоритет имеют настройки пользователя
	var options = $.extend({}, defaultOptions, galleryOptions);
	if (this.length > 1){
		this.each(function(){ 
			$gallery = $(this);
			// здесь инициализируем галерею
			// затем, вешаем на нее все события
		});
	}
	return this;
}
})(jQuery);

Как и всегда выкладываю все исходники

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

Желаю удачи!

Оставлять комментарии могут только зарегистрированные пользователи

Комментарии  

Сивый
# Сивый 07.03.2012 15:15
вопросов несколько..

а именно:

1. зачем в исходниках оставлять кучу ненужного мусора? согласись, гораздо красивее будет, если там все будет по полочкам лежать..

2. извини, но верстка шлак.. смысл позиционировать кнопку "next" на глаз? не проще ли задать ей right: 0 ?

Вобщем, если вопрос актуален и ты готов принять помощь - пиши, буду рад посотрудничать :)



З.Ы. за плагин большое спасибо.. вещь стоящая.. (именно поэтому и намекаю на то, что можно лучше)
sovyonok
# sovyonok 27.06.2012 21:38
Классно, конечно. А как заставить её работать с colorbox? т.е. чтобы каждую картинку можно было открыть в лайтбоксе? Спасибо.
Максим
# Максим 26.07.2012 11:17
ребята, у меня у одного выскакивает ошибка: Uncaught ReferenceError: direact is not defined, ещё при первоначальном вешании события на клик по прев, некст?



устранить конечно не сложно, удалив лишнюю букву, но тогда ноль реакции на клик. Можно конечно и дальше править, но это уже совсем другая история. Мне кажется, что код должен быть рабочим и при копипасте. Спасибо
Leroy
# Leroy 26.07.2012 16:00
что за буква? где конкретно выскакивает?
Мария
# Мария 16.09.2012 02:29
Вот интересная jQuery галерея - http://lecaw.ru/index.php/jquery/item/296-neobichnaya-galereya-izobrazheniy
Leroy
# Leroy 16.09.2012 03:40
я как-то рассказывал про такую в статье, вроде бы даже ее
kvejo
# kvejo 06.01.2014 19:23
здравствуйте

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

1 --- как сделать чтобы он отображался не на верху а внизу стронции?

2 --- как сделать чтобы можно было нажать на фото и страничка менялось?

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

заранее благодарю