Фотогалерея товаров для интернет-магазина.
Вступление.
В различных интернет-магазинах, в карточке товара, очень часто используется фотогалерея, с помощью которой этот товар можно рассмотреть подробнее, в разных проекциях. В этой статье мы подробно рассмотрим создание такой галереи для интернет-магазина. Так же мы создадим удобную, универсальную систему управления этой галереей.
Для начала, давайте определимся со структурой фотогалереи и способами управления ею.
Итак, структура галереи для интернет-магазина будет состоять из трёх основных блоков:
1. блок с тумбами картинок;
2. блок, куда будет выводится полноразмерная картинка;
3. блок с кнопками навигации по галереи.
Для управления сменой картинок, давайте используем максимально возможные варианты:
— кнопками навигации «предыдущая», «следующая», «первая», «последняя»;
— кликом по тумбе картинки;
— кликом по большой картинке, при этом будет выводится следующая картинка;
— клавишами со стрелочками «влево» и «вправо»;
— колёсиком мышки.
Вроде со всем определились, ничего не упустили. Давайте приступим непосредственно к созданию фотогалереи.
HTML-вёрстка галереи для интернет-магазина.
HTML-вёрстка галереи очень простая и, как писалось ранее, состоит всего из трёх блоков:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <div class="wrap"> 	<h1>Просмотр фотографий</h1> 	<div class="flex" data-gallary> 		<div class="container"> 			<div class="viewport"> 				<div class="thumbs content-box"> 					<img src="images/thumbnails/IMG_2212.jpg"> 					<img src="images/thumbnails/IMG_2222.jpg"> 					<img src="images/thumbnails/IMG_2227.jpg"> 					<img src="images/thumbnails/IMG_2235.jpg"> 					<img src="images/thumbnails/IMG_2259.jpg"> 					<img src="images/thumbnails/IMG_2269.jpg"> 					<img src="images/thumbnails/IMG_2273.jpg"> 					<img src="images/thumbnails/IMG_2287.jpg"> 				</div> 			</div> 		</div> 		<div> 			<div class="photo-box"> 				<img src="images/photos/IMG_2212.jpg"> 			</div> 			<div class="flex control-row"> 				<button type="button" class="btn" data-control="first">First</button> 				<button type="button" class="btn" data-control="prev">Previous</button> 				<button type="button" class="btn" data-control="next">Next</button> 				<button type="button" class="btn" data-control="last">Last</button> 			</div> 		</div> 	</div> </div> | 
В данном варианте, блок с тумбами фотографий расположен слева от основного фото. Вы его можете расположить и справа, и вверху, и под основной фотографией — это ни как не повлияет на функционирование галереи.
Тумбы и большие фото разнесены по разным папкам — thumbnail и photos соответственно. Их можно было бы положить и в одну папку, добавив именам картинок префиксы, чтобы различать, где тумба, а где полноразмерное фото. При этом сам js-скрипт не изменится.
Как видите из вёрстки, я не стал оборачивать ни тумбы, ни кнопки навигации в дополнительные <div> или <li> — в этом нет совершенно никакой необходимости и незачем усложнять вёрстку и замусоривать её лишними элементами.
Ещё один немаловажный момент. В блоке <div class="photo-box"></div> по умолчанию находится фотография. Это необходимо для того, чтобы пока не загрузился скрипт, в галерее не было пустого места.
Не нужно оборачивать тумбу ссылкой, используя её
href, как указатель на полноразмерную фотографию. Как с точки зрения семантики, так и с точки зрения СЕО — тэг <a> должен использоваться только для формирования ссылок, ведущих на другие страницы сайта или другой интернет-ресурс.JS-скрипту, для вывода полноразмерного фото, достаточно
src тумбы.
Таблица стилей галереи для интернет-магазина.
Представлены только стили, относящиеся непосредственно к самой галерее.
Для уменьшения размера таблицы стилей, я не буду приводить свойства с вендорными префиксами, обеспечивающими кроссбраузерность. Не забывайте прописывать их при реализации своих проектов.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | button { 	width: 100px; 	height: 30px; 	display: block; 	font-weight: 500; 	font-size: 12px; 	line-height: 24px; 	color: #eee; 	font-family: 'Roboto', sans-serif; 	text-decoration: none; 	text-transform: uppercase; 	text-align: center; 	float: left; 	margin-right: 10px; 	user-select: none; 	border: solid 1px #357ebd; 	border-radius: 3px; 	outline: none; 	background: #428bca; 	cursor: pointer; 	transition: all 0.2s; } button:hover { 	border-color: #285e8e; 	background: #3276b1; } .photo-box {  	width: 640px; 	height: 426px; 	outline: solid 1px #666; 	cursor: pointer; } .container { 	width: 176px; 	height: 426px; 	position: relative; 	margin-right:12px; } .viewport { 	overflow: hidden; 	position: absolute; 	left: 2px; 	right: 3px; 	top: 0; 	bottom: 0; } .content-box { 	height: 100%; 	position: absolute; 	left: 0; 	right: -17px; 	overflow-y: scroll; } .thumbs { overflow: auto; } 	.thumbs img { cursor: pointer; } 	.thumbs img + img { margin-top: 2px; } .control-row { padding: 20px 0 30px; } | 
Пишем JS-скрипт фотогалереи для интернет-магазина.
И вот мы подошли к написанию JS-скрипта вывода и управления галереей интернет-магазина. Первое, с чего мы начнём, это создадим анонимную самозапускающуюся функцию, внутри которой и будет расположен наш код. Вообще, надо взять за правило ограничивать область видимости скрипта, чтобы не было конфликтов с другими JS-скриптами подключенными к странице. Мало ли, совпадут названия переменных или функций…
| 1 2 3 4 5 6 | ;(function() { 	'use strict'; })(); | 
При написании JS-скрипта мы будем использовать конструкцию Class. Это позволит создать несколько экземпляров галереи товаров для интернет-магазина на одной странице.
Создание экземпляров фотогалерей товаров для интернет-магазина.
В-первую очередь, создадим коллекцию фотогалерей, которые расположены на странице. Далее, с помощью метода for...of переберём полученную коллекцию, при этом мы выполним следующие действия:
— создадим экземпляр текущей галереи, используя конструктор класса Gallery;
— зарегистрируем обработчики событий.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | ;(function() { 	'use strict'; 	class Gallery { 		constructor(gallery) { 		} 	} 	// выбираем все фотогалереи на странице 	const galleries = document.querySelectorAll('[data-gallary]'); 	// перебираем полученную коллекцию элементов 	for (let gallery of galleries) { 		// создаём экземпляр фотогалереи товаров для интернет-магазина 		const goodsgallery = new Gallery(gallery); 	} })(); | 
Весь дальнейший JS-код мы будем писать внутри конструкции class Gallery { ... }.
Прежде всего рассмотрим конструктор класса Gallery. Конструктор инициализирует ряд объектов и переменных, содержащих информацию об экземпляре галереи. В качестве аргумента конструктор принимает объект галереи товара, экземпляр которого создаётся в данный момент.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class Gallery { 	constructor(gallery) { 		// контейнер для маленьких картинок (тумб) 		this.thumbsBox = gallery.querySelector('.thumbs'); 		// коллекция маленьких картинок (тумб) 		this.thumbs = this.thumbsBox.querySelectorAll('img'); 		// объект, в который будем выводить большую картинку 		this.image = gallery.querySelector('.photo-box img'); 		// объект (родительский элемент), содержащий кнопки навигации 		this.control = gallery.querySelector('.control-row'); 		// кол-во фотографий в галереи 		this.count = this.thumbs.length; 		// индекс отображаемой фотографии, при инициализации скрипта 		// он по умолчанию равен 0 		this.current = 0; 		// регистрируем обработчики событий на странице с фотогалерей  		this.registerEventsHandler(); 	} } | 
Конструктор, кроме инициализации объектов и переменных, вызывает функцию registerEventsHandler(), которая регистрирует обработчики событий на странице:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | registerEventsHandler(e) { 	// клик по контейнеру '.control-row', в котором находятся кнопки  	// управления 	this.control.addEventListener('click', this.buttonControl.bind(this)); 	// клик по большой картинке 	this.image.addEventListener('click', this.imageControl.bind(this)); 	// вращение колёсика мыши 	this.image.addEventListener('wheel', this.wheelControl.bind(this)); 	// управление стрелками 'влево' и 'вправо' 	document.addEventListener('keydown', this.keyControl.bind(this)); 	// клик по тумбе 	this.thumbsBox.addEventListener('click', this.thumbControl.bind(this)); } | 
Вывод полноразмерной фотографии в галерее для интернет-магазина.
Теперь рассмотрим функцию вывода полноразмерной фотографии — showPhoto(). Аргументом этой функции является индекс или текущей тумбы, или рассчитанный функциями управления выводом полноразмерного фото.
Итак, на вход функции showPhoto поступил индекс. Считываем у тумбы с таким индексом значение атрибута src и заменяем в нём название папки, в которой хранятся фотографии с thumbnails на photos. В результате такой нехитрой операции получаем путь (src) к большой фотографии. Полученное значение присваиваем атрибуту src объекта image, в результате чего, на экран выводится фото в полный размер.
Код функции showPhoto() и комментарии к нему:
| 1 2 3 4 5 6 7 8 9 10 11 12 | showPhoto(i) { 	// используя полученный в качестве аргумента индекс 	// получаем 'src' тумбы в коллекции 	const src = this.thumbs[i].getAttribute('src'); 	// полученный 'src' прописываем у большой картинки, предварительно 	// изменив путь (название папки) 	this.image.setAttribute('src', src.replace('thumbnails', 'photos')); 	// устанавливаем текущий индекс равным индексу тумбы в коллекции 	this.current = i; } | 
Ранее говорилось, что можно и тумбы, и полноразмерные фото можно положить в одну папку, присвоив им разные префиксы. Давайте посмотрим, что нужно изменить в функции showPhoto для этого. Обратите ещё раз внимание на строку:
| 1 2 3 | this.image.setAttribute('src', src.replace('thumbnails', 'photos')); | 
Замените в ней thumbnails и photos на префикс_тумбы и префикс_фото соответственно. Больше ничего исправлять не нужно.
На данном этапе у нас уже есть вполне работоспособная фотогалерея товаров для интернет-магазина. Полноразмерные фото переключаются при клике по тумбе, но мы хотели сделать расширенное управление галереей. Давайте расширим функционал нашего js-скрипта.
Расширенное управление фотогалереей для интернет-магазина.
Прежде чем начинать разбираться с функциями управления галереей, нужно вспомнить основы JavaScript, а именно, взятие остатка %. Результат a % b – это остаток от деления a на b. На этом основано получение индекса очередной фотографии, который является аргументом функции showPhoto.
Листаем галерею с помощью кнопок навигации.
Для управления фотогалереей кнопками навигации будем использовать делегирование события. Обработчик события будем вешать не на каждую кнопку навигации, а на их родительский элемент control, сократив таким образом, значительное количество JS-кода. При срабатывании обработчика вызывается функция buttonControl(), в которой реализован весь алгоритм управления.
Код функции buttonControl() и комментарии к нему:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | buttonControl(e) { 	// если click был сделан вне кнопок, прекращаем работу функции 	if (e.target.tagName != 'BUTTON') return; 	const ctrl = e.target.dataset.control; 	// каждому свойству объекта соответствует формула расчёта 	// индекса, учитывающая кол-во фотографий и текущий индекс 	let argControl = { 		first: 0, 		last: this.count - 1, 		prev: (this.count + this.current - 1) % this.count, 		next: (this.current + 1) % this.count 	}; 	// индекс следующей фотографии, в зависимости от функционала 	// кнопки навигации 	const i = argControl[ctrl]; 	this.showPhoto(i); } | 
Листание галереи кликом по полноразмерной фотографии.
Вешаем на объект image обработчик события. Передаём функции showPhoto индекс очередного фото, равный остатку от деления увеличенного на единицу текущего индекса на количество фотографий в галерее.
При срабатывании обработчика события вызывается функция imageControl().
| 1 2 3 4 5 6 | imageControl(e) { 	// показываем следующее фото 	this.showPhoto((this.current + 1) % this.count); } | 
Теперь можно кликать по полноразмерному фото, листая галерею по кругу.
Листаем галерею стрелками «влево» и «вправо» на клавиатуре.
С помощью функции addEventListener вешаем на страницу с фотогалереей обработчик события keydown — нажатие на любую клавишу клавиатуры. При срабатывании обработчика события keydown, вызывается функция keyControl().
Поучаем код нажатой клавиши. Если этот код равен 37 (влево) или 39 (вправо), вызываем функцию showPhoto с соответствующим аргументом. В противном случае, прекращаем работу функции.
Код функции keyControl() и комментарии к нему:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | keyControl(e) { 	// отключаем действия по умолчанию 	e.preventDefault(); 	// код нажатой клавиши 	const code = e.which; 	// если код клавиши не соответствует коду клавиш вправо или влево, 	// то прекращаем работу функции 	if (code != 37 && code != 39) return; 	// каждому свойству объекта соответствует формула расчёта 	// индекса, учитывающая кол-во фотографий и текущий индекс 	let argControl = { 		37: (this.count + this.current - 1) % this.count, 		39: (this.current + 1) % this.count 	} 	this.showPhoto(argControl[e.which]); } | 
Листаем галерею вращая колёсико мышки.
Управление выводом фото колёсиком мышки будет срабатывать только в том случае, если было наведение курсора на полноразмерную фотографию. Для этого обработчик события повесим на объект image.
Если наступило событие wheel — прокручивание колёсика мышки, вызывается функция wheelControl().
По значению свойства deltaY события wheel определяем направление вращения колёсика. Далее, вызываем функцию showPhoto с аргументом, зависящим от направления вращения.
Код функции wheelControl() и комментарии к нему:
| 1 2 3 4 5 6 7 8 9 | wheelControl(e) { 	// отключаем поведение по умолчанию - скролл страницы 	e.preventDefault(); 	let i = (e.deltaY > 0) ? (this.current + 1) % this.count : (this.count + this.current - 1) % this.count; 	this.showPhoto(i) } | 
Листаем галерею кликом по «тумбе» — маленькой фотографии.
Учитывая, что малоразмерных фотографий может быть очень много, снова используем делегирование событий. Повесим обработчик события на родительский элемент — объект thumbsBox и при срабатывании обработчика вызовем функцию thumbControl().
В функции thumbControl(), в-первую очередь, определяем картинку, по которой был сделан клик. Далее, нам необходимо определить индекс картинки в коллекции thumbs, чтобы передать этот индекс в функцию showPhoto() для вывода полноразмерного фото. И вот здесь у нас возникает проблема.
Индекс можно определить с помощью встроенного метода indexOf, но этот метод есть только у массива, у коллекции он отсутствует. Есть два варианта решения этой проблемы:
1. Преобразовать коллекцию thumbs в массив и далее спокойно использовать метод indexOf.
2. Заимствование (одалживание) метода. Мы берём (заимствуем) метод indexOf из обычного массива [].indexOf. И используем [].indexOf.call, чтобы выполнить его в контексте thumbs.
Посмотрим JS-код первого и второго вариантов:
| 1 2 3 4 5 6 7 8 | // вариант 1 // Array.from(this.thumbs) преобразует коллекцию в массив const i = Array.from(this.thumbs).indexOf(target); // вариант 2 //[].indexOf.call - одалживание метода indexOf const i = [].indexOf.call(this.thumbs, target); | 
Я не могу сказать, какой вариант более предпочтительный. Хотя мне кажется, что преобразование коллекции в массив при каждом клике по тумбе, более затратный по сравнению с одалживанием метода. Поэтому я выбрал вариант 2.
Хотелось бы в комментариях к статье прочитать ваше мнение. Возможно я ошибаюсь, а возможно, при таком количестве фото, без разницы, какой вариант использовать.
Полный код функции thumbControl() и комментарии к нему:
| 1 2 3 4 5 6 7 8 9 10 11 | thumbControl(e) { 	// получаем элемент по которому был сделан клик 	const target = e.target; 	if (target.tagName != 'IMG') return; 	// получаем индекс фотографии в коллекции 	// [].indexOf.call - одалживание метода indexOf 	const i = [].indexOf.call(this.thumbs, target); 	this.showPhoto(i); } | 
Заключение.
Итак, мы создали фотогалерею с универсальным управлением, которую можно использовать в интернет-магазине для вывода фотографий товаров. Конечно, это упрощённый по функционалу вариант, для того, чтобы вам было легче разобраться с работой галереи. Её функционал можно расширить, например, выделить тумбу текущей (просматриваемой) фотографии или заставить прокручиваться тумбы, при смене полноразмерного фото, можно использовать различную анимацию при появлении новой фотографии и т д. Возможно, в одной из следующих статей мы это всё и рассмотрим.
Комментарии
- 
			Комментарии должны содержать вопросы и дополнения по статье, ответы на вопросы других пользователей.
 Комментарии содержащие обсуждение политики, будут безжалостно удаляться.
- 
			Для удобства чтения Вашего кода, не забываейте его форматировать. Вы его можете подсветить код с помощью тега <pre>:
 —<pre class="lang:xhtml">- HTML;
 —<pre class="lang:css">- CSS;
 —<pre class="lang:javascript">- JavaScript.
- Если что-то не понятно в статье, постарайтесь указать более конкретно, что именно не понятно.
- 
		- 
		Данный скрипт и не предусматривает это, т. к. написан в функциональном стиле. Чтобы на одной странице работало несколько галерей, скрипт необходимо переписать в прототипном стиле: создать конструктор, который будет создавать экземпляры галерей, от которых будут наследоваться методы, отвечающие за работу галереи. 
 При таком подходе, количество галерей ограничено только пределами разумного.- 
		Это трудно сделать? Можете мне помочь, для меня это очень важно. - 
		Отправил письмо на ваш e-mail. 
 
- 
		
 
- 
		
 
- 
		
- 
		Можете мне тоже помочь? не работает с двумя галереями.. - 
		Проверьте уникальность id у каждой галереи и их соответствие при вызове конструктора. 
 
- 
		
- 
		Не как не получается сделать две разные галереи на одной странице. - 
		Обновил архив. Оказывается в нём не доставало файлов. Смотрите HTML и JS с расширением prototype. 
 Приношу извинения, за неполный архив.
 
- 
		
- 
		загружаю страницу с компа, ни работает увеличение превью фото при клике на них, в чём может быть дело, все пути прописал верно, все фото отображаются. - 
		Так трудно, что-либо посоветовать не видя вашей сборки. Посмотрите в консоли, есть ли какие-то ошибки скрипта js. 
 
- 
		
- 
		Здравствуйте. Спасибо за галерею, нужная вещь. Подскажите как добавить такой стиль, присвоение класса выбранной тумбе для выделения ее своим стилем? - 
		В ф-ии showPhoto() вычисляется текущая тумба this.thumbs[i]. Нужно перебрать с помощью for…of коллекцию тумб this.thumbs и сравнить каждый элемент коллекции с this.thumbs[i]. Если они совпадают, то добавить элементу коллекции класс ‘active’, если нет — убрать. Совет. Не делайте рамку для выделения непосредственно самой тумбе для избежания дёргания изображений. Используйте для этого псевдоэлемент ‘::before’ или ‘::after’. - 
		К сожалению не знаком с JS настолько, чтобы взять и сделать. Поиск примера в инете, тоже ничего не дал. Поэтому просьба к вам, написать чуть подробнее код присвоения класса. И наверное было бы не плохо включить данный функционал в код галереи по умолчанию. В любом случае спасибо. 
- 
		Обнаружил еще один баг. На странице, где размещена галерея в полях формы (input) не вводится текст с клавиатуры. Как только отключаю файл со скриптом — все нормализуется не могли бы помочь мне разобраться с этой проблемой. очень жаль отказываться от данной галереи, очень долго искал рабочий вариант. При необходимости дам ссылку на страницу где наблюдается баг. 
 
- 
		
 
- 
		
Если установить 2 такие галереи на одну страницу то работает только первая, как это исправить. Чтобы работали все. На второй галереи фото не переключаются.