Округленная галерея изображений
В данном уроке мы создадим округленную галерею изображений, которая выводит ваши картинки уникальным способом. Идея заключается в том, чтобы показывать миниатюры альбома в круглом оформлении и обеспечить прокручивание списка миниатюр с помощью курсора мыши. При нажатии на миниатюру, она трансформируется в большой круг, и выводится полноразмерное изображение, которое автоматически подгоняется под размер экрана. Навигация по изображениям осуществляется с помощью проскальзывания текущего изображения за пределы экрана, а новое изображение появляется с помощью эффекта масштабирования из центра.
В проекте используется плагин для прокрутки миниатюр: автор Manos Malihu.
Фотографии сделаны автором geishaboy500.
Разметка HTML
Начнем с разметки HTML для нашего проекта. Будет использоваться всего лишь несколько элементов. Начнем с верхнего меню, которое появляется в режиме просмотра полноразмерных изображений:
Нам нужна возможность вернуться к режиму просмотра миниатюр, область описания изображения и информационная область, в которой выводится номера текущего выводимого альбома и изображения. Пустой элемент span используется для добавления стрелки "назад".
Также нужен элемент индикатора загрузки:
Теперь рассмотрим элемент h1 заголовка галереи:
Округленнаягалерея изображений
Он будет выскальзывать сверху при загрузке изображений, поэтому начальное значение свойства top равно -90 px. В разделе JavaScript мы будем сдвигать его на позицию 30 px. Вы можете добавить стиль заголовка в таблицы CSS, вместо использования встроенного стиля.
Следующий элемент - контейнер, который содержит все альбомы миниатюр. Так как используется дополнительный плагин для прокрутки миниатюр, то разметка основана на его структуре. Можно одновременно выводить несколько альбомов миниатюр, в коде примера закомментирован код для еще двух альбомов:
...
Пустой элемент span внутри элемента div, который содержит изображение, используется для создания маскирующего слоя с изображением круга для миниатюр, придавая им оригинальный вид.
Теперь добавляем желтый круг, который будет использоваться для создания эффекта туннеля при выводе полноразмерного изображения:
Контейнер для полноразмерного изображения с навигацией будет иметь следующую структуру:
Нижний колонтитул страницы может использоваться для вывода дополнительной информации:
CSS
Сначала сбрасываются установки общих правил для ссылок, списков и тега body:
*{ margin:0; padding:0; } a{ text-decoration:none; outline:none; } ul{ list-style:none; } body{ background:#222 url(../bg.jpg) repeat top left; font-family:"Trebuchet MS", "Myriad Pro", Helvetica, sans-serif; font-size:13px; color: #fff; text-transform:uppercase; text-shadow:0px 0px 1px #fff; letter-spacing:1px; overflow:hidden; }
Верхнее меню для режима просмотра полноразмерного изображения имеет следующие стили:
.top_menu{ height:30px; line-height:30px; position:absolute; top:-30px; left:0; width:100%; overflow:hidden; background:#010101; border-bottom:1px solid #000; z-index:100; -moz-box-shadow:0px 0px 4px #010101; -webkit-box-shadow:0px 0px 4px #010101; box-shadow:0px 0px 4px #010101; }
(Если вы знаете, что ваш текст будет занимать только одну строку в определенном элементе, то вы можете установить высоту строки равной высоте элемента для вертикального центрирования текста.) Свойство top получает значение -30px для того, чтобы скрыть элемент.
Ссылка "Назад" имеет следующие стили:
.top_menu .back{ position:absolute; top:0px; left:10px; height:30px; line-height:30px; cursor:pointer; color:#aaa; }
Иконка с маленькой стрелкой добавляется для ссылки "Назад", которая имеет одинаковый с навигационным элементом фон. Используется двойная ширина, чтобы вывести дублированное изображение:
.top_menu .back span{ width:14px; height:30px; display:block; float:left; background:#000 url(../images/prev.png) repeat-x center left; margin-right:5px; opacity:0.5; } .top_menu .back:hover{ color:#fff; } .top_menu .back:hover span{ opacity:0.9; }
Описание будет выводиться по центру:
.top_menu span.description{ font-style:italic; position:absolute; width:100%; text-align:center; top:0px; left:0px; height:30px; line-height:30px; }
А блок информации будет смещаться вправо:
.top_menu .info{ float:right; margin-right:10px; }
Основной заголовок позиционируется абсолютно, так как мы будем его анимировано выдвигать сверху. Также он получает желтый фон и прелестную тень:
.header{ height:80px; position:absolute; top:30px; left:0px; width:100%; overflow:hidden; z-index:90; background-color:#ffd800; -moz-box-shadow:0px 0px 10px #010101; -webkit-box-shadow:0px 0px 10px #010101; box-shadow:0px 0px 10px #010101; } h1{ font-size:60px; padding-left:20px; color:#010101; line-height:80px; text-shadow:1px 1px 1px #000; } h1 span{ font-size:16px; float:right; margin-right:20px; }
Прежде чем задать стили для контейнера миниатюр разберем стили других элементов.
Полноразмерное изображение позиционируется абсолютно, но его окончательные координаты определяются в JavaScript:
.preview img{ position:absolute; left:0; top:0; -moz-box-shadow:1px 1px 5px #111; -webkit-box-shadow:1px 1px 5px #111; box-shadow:21px 1px 5px #111; -webkit-box-reflect: below 5px -webkit-gradient( linear, left top, left bottom, from(transparent), color-stop(0.8, transparent), to(rgb(255, 216, 0)) ); }
Мы добавим несколько чудесных отражений и теней для вывода в браузерах webkit.
Нижний колонтитул имеет почти такой же стиль, как и верхнее меню, за исключением позиционирования и некоторых других деталей, таких как окантовка:
.footer{ z-index:100; height:30px; line-height:30px; text-align:center; font-size:10px; background:#010101; border-top:1px solid #000; position:absolute; bottom:0px; left:0px; width:100%; -moz-box-shadow:0px 0px 4px #010101; -webkit-box-shadow:0px 0px 4px #010101; box-shadow:0px 0px 4px #010101; } .footer a{ color:#999; text-decoration:none; margin:40px; }
Индикатор загрузки будет помещен в центр страницы и окружен рамкой со скругленными углами с радиусом равным половине его ширины/высоты:
.loader{ display:none; width:200px; height:200px; background: #000 url(../images/ajax-loader.gif) no-repeat center center; position:fixed; top:50%; left:50%; margin:-100px 0px 0px -100px; opacity: 0.7; z-index:9999; -moz-border-radius:100px; -webkit-border-radius:100px; border-radius:100px; }
Элементы навигации имеют следующие общие стили
a.next_image, a.prev_image{ width:50px; height:50px; position:fixed; top:50%; margin-top:-25px; cursor:pointer; opacity:0.7; z-index:999999; -moz-box-shadow:0px 0px 3px #000; -webkit-box-shadow:0px 0px 3px #000; box-shadow:0px 0px 3px #000; -moz-border-radius:25px; -webkit-border-radius:25px; border-radius:25px; } a.next_image:hover, a.prev_image:hover { opacity:0.9; }
А различаются такими стилями:
a.next_image{ background:#000 url(../images/next.png) no-repeat center center; right:-50px; } a.prev_image{ background:#000 url(../images/prev.png) no-repeat center center; left:-50px; }
Изначально они оба скрыты, поэтому их позиционирование слева/справа имеет отрицательные значения.
Теперь рассмотрим стиль для контейнера миниатюр:
.thumbnails_wrapper{ position:absolute; top:-255px; left:0px; width:100%; -moz-box-shadow:0px 3px 5px #000; -webkit-box-shadow:0px 3px 5px #000; box-shadow:0px 3px 5px #000; }
Изображение круга будет позиционироваться в центре страницы и имеет ширину и высоту равную 0. Оно будет анимировано с помощью JavaScript:
.tshf_container{ height:85px; position:relative; width:100%; background:#ffd800; } .tshf_container .thumbScroller{ position:relative; width:100%; overflow:hidden; } .tshf_container .thumbScroller, .tshf_container .thumbScroller .container, .tshf_container .thumbScroller .content{ height:85px; } .tshf_container .thumbScroller .container{ position:relative; left:0; } .tshf_container .thumbScroller .content{ float:left; } .tshf_container .thumbScroller .content div{ height:100%; position:relative; } .tshf_container .thumbScroller img{ border:none; } .tshf_container .thumbScroller .content div a{ display:block; height:85px; width:85px; } .tshf_container .thumbScroller .content div a img{ width:85px; } .tshf_container .thumbScroller .content div a:hover{ border-color:#fff; }
Теперь адаптируем плагин для прокрутки миниатюр:
.tshf_container{ height:85px; position:relative; width:100%; background:#ffd800; } .tshf_container .thumbScroller{ position:relative; width:100%; overflow:hidden; } .tshf_container .thumbScroller, .tshf_container .thumbScroller .container, .tshf_container .thumbScroller .content{ height:85px; } .tshf_container .thumbScroller .container{ position:relative; left:0; } .tshf_container .thumbScroller .content{ float:left; } .tshf_container .thumbScroller .content div{ height:100%; position:relative; } .tshf_container .thumbScroller img{ border:none; } .tshf_container .thumbScroller .content div a{ display:block; height:85px; width:85px; } .tshf_container .thumbScroller .content div a img{ width:85px; } .tshf_container .thumbScroller .content div a:hover{ border-color:#fff; }
Мы используем дополнительный элемент span для добавления нашего слоя с кругом:
.tshf_container .thumbScroller .content span{ cursor:pointer; position:absolute; width:85px; height:85px; top:0px; left:0px; background:transparent url(../images/thumb_overlay.png) no-repeat top left; } .tshf_container .thumbScroller .content div:hover span{ background:transparent url(../images/thumb_overlay_hover.png) no-repeat top left; }
JavaScript
Сначала подключим следующие скрипты:
Затем кэшируем некоторые важные элементы и определяем индексы:
//кэшируем некоторые элементы var $thumbnails_wrapper = $('#thumbnails_wrapper'), $thumbs = $thumbnails_wrapper.find('.tshf_container').find('.content'), $top_menu = $('#top_menu'), $header = $('#header'), $bubble = $('#bubble'), $loader = $('#loader'), $preview = $('#preview'), $thumb_images = $thumbnails_wrapper.find('img'), total_thumbs = $thumb_images.length, $next_img = $('#next_image'), $prev_img = $('#prev_image'), $back = $('#back'), $description = $('#description'), //Текущий альбом и фотография //(индексы tshf_container и элементов содержания) currentAlbum = -1, currentPhoto = -1;
Затем выводим индикатор загрузки (пока миниатюры не будут загружены) и вызываем функцию для открытия альбомов:
//Показываем индикатор загрузки в процессе //загрузки всех изображений $loader.show(); //Показываем основное меню и меню миниатюр openPhotoAlbums();
А вот как она выглядит:
function openPhotoAlbums(){ //Предварительная загрузка всех миниатюр var cnt_loaded = 0; $thumb_images.each(function(){ var $thumb = $(this); var image_src = $thumb.attr('src'); $('').load(function(){ ++cnt_loaded; if(cnt_loaded == total_thumbs){ $loader.hide(); createThumbnailScroller(); //Показываем основное меню и меню миниатюр $header.stop() .animate({'top':'30px'},700,'easeOutBack'); $thumbnails_wrapper.stop() .animate({'top':'110px'},700,'easeOutBack'); } }).attr('src',image_src); }); }
Функция “createThumbnailScroller()” создает элемент прокрутки миниатюр. Она будет рассмотрена позже.
Затем мы определяем, что происходит при нажатии кнопки мыши на миниатюре:
//Событие click для миниатюры $thumbs.bind('click',function(e){ //Выводим индикатор загрузки $loader.show(); var $thumb = $(this), //Соответствующее полноразмерное изображение img_src = $thumb.find('img.thumb') .attr('src') .replace('/thumbs',''); //Отслеживаем текущий альбом / фотографию currentPhoto= $thumb.index(), currentAlbum= $thumb.closest('.tshf_container') .index(); //Выводим текущий альбом и фоторафию updateInfo(currentAlbum,currentPhoto); //Предварительная загрузка полноразмерного изображения $('').load(function(){ var $this = $(this); //Записываем размер, который полноразмерное изображение //должно иметь при выводе saveFinalPositions($this); //margin_circle - диаметр для изображения круга var w_w = $(window).width(), w_h = $(window).height(), margin_circle = w_w + w_w/3; if(w_h>w_w) margin_circle = w_h + w_h/3; //Изображение будет позиционироваться в центре //с шириной и высотой 0px $this.css({ 'width' : '0px', 'height' : '0px', 'marginTop' : w_h/2 +'px', 'marginLeft': w_w/2 +'px' }); $preview.append($this); //Скрываем заголовок $header.stop().animate({'top':'-90px'},400, function(){ $loader.hide(); //Показываем верхнее меню с кнопкой "Назад", //а также информацию о текущем альбоме/фотографии $top_menu.stop() .animate({'top':'0px'},400,'easeOutBack'); //Анимируем изображение круга $bubble.stop().animate({ 'width' : margin_circle + 'px', 'height' : margin_circle + 'px', 'marginTop' : -margin_circle/2+'px', 'marginLeft': -margin_circle/2+'px' },700,function(){ //Решаем проблему изменения размера $('BODY').css('background','#FFD800'); }); //По истечении 200ms анимируем полноразмерное изображение //и выводим кнопки навигации setTimeout(function(){ var final_w = $this.data('width'), final_h = $this.data('height'); $this.stop().animate({ 'width' : final_w + 'px', 'height' : final_h + 'px', 'marginTop' : w_h/2 - final_h/2 + 'px', 'marginLeft': w_w/2 - final_w/2 + 'px' },700,showNav); //Выводим описание $description.html($thumb.find('img.thumb').attr('alt')); },200); }); //скрываем миниатюры $thumbnails_wrapper.stop() .animate({ 'top' : w_h+'px' },400,function(){ //Решаем проблему изменения размера $(this).hide(); }); }).attr('src',img_src); });
Когда пользователь нажимает на кнопку "следующий" выполняется функция:
//Событие click для кнопки "следующий" $next_img.bind('click',function(){ //Увеличиваем currentPhoto ++currentPhoto; //Текущий альбом: var $album = $thumbnails_wrapper.find('.tshf_container') .eq(currentAlbum), //следующий элемент, миниатюра для вывода $next = $album.find('.content').eq(currentPhoto), $current = $preview.find('img'); if($next.length == 0 || $current.is(':animated')){ --currentPhoto; return false; } else{ $loader.show(); updateInfo(currentAlbum,currentPhoto); //Предварительная загрузка изображения var img_src = $next.find('img.thumb') .attr('src') .replace('/thumbs',''), w_w = $(window).width(), w_h = $(window).height(); $('').load(function(){ var $this = $(this); //Записываем размер, который полноразмерное изображение должно иметь //при выводе saveFinalPositions($this); $loader.hide(); $current.stop() .animate({'marginLeft':'-'+($current.width()+20)+'px'},500,function(){ //Текущее изображение удаляем $(this).remove(); }); //Новое изображение будет позиционироваться в центре //с шириной и высотой 0px $this.css({ 'width' : '0px', 'height' : '0px', 'marginTop' : w_h/2 +'px', 'marginLeft': w_w/2 +'px' }); $preview.prepend($this); var final_w = $this.data('width'), final_h = $this.data('height'); $this.stop().animate({ 'width' : final_w + 'px', 'height' : final_h + 'px', 'marginTop' : w_h/2 - final_h/2 + 'px', 'marginLeft': w_w/2 - final_w/2 + 'px' },700); //Выводим описание $description.html($next.find('img.thumb').attr('alt')); }).attr('src',img_src); } });
А при нажатии на кнопку "предыдущее" выполняется функция:
//Событие click для кнопки "Предыдущее" $prev_img.bind('click',function(){ --currentPhoto; //Текущий альбом var $album = $thumbnails_wrapper.find('.tshf_container') .eq(currentAlbum), $prev = $album.find('.content').eq(currentPhoto), $current = $preview.find('img'); if($prev.length == 0 || $current.is(':animated') || currentPhoto < 0){ ++currentPhoto; return false; } else{ $loader.show(); updateInfo(currentAlbum,currentPhoto); //Предварительная загрузка изображения var img_src = $prev.find('img.thumb') .attr('src') .replace('/thumbs',''), w_w = $(window).width(), w_h = $(window).height(); $('').load(function(){ var $this = $(this); //Записываем размер, который должно иметь большое изображение при выводе saveFinalPositions($this); $loader.hide(); $current.stop() .animate({'marginLeft':(w_w+20)+'px'},500,function(){ //Текущее изображение удаляем $(this).remove(); }); //Новое изображение будет выводиться в центре //с шириной и высотой 0px $this.css({ 'width' : '0px', 'height' : '0px', 'marginTop' : w_h/2 +'px', 'marginLeft': w_w/2 +'px' }); $preview.append($this); var final_w = $this.data('width'), final_h = $this.data('height'); $this.stop().animate({ 'width' : final_w + 'px', 'height' : final_h + 'px', 'marginTop' : w_h/2 - final_h/2 + 'px', 'marginLeft': w_w/2 - final_w/2 + 'px' },700); //Выводим описание $description.html($prev.find('img.thumb').attr('alt')); }).attr('src',img_src); } });
При изменении размеров окна нам нужно пересчитать положение и размеры изображения:
//При изменении размеров окна вычисляем размеры текущего изображения $(window).resize(function(){ var $current = $preview.find('img'), w_w = $(window).width(), w_h = $(window).height(); if($current.length > 0){ saveFinalPositions($current); var final_w = $current.data('width'), final_h = $current.data('height'); $current.css({ 'width' : final_w + 'px', 'height' : final_h + 'px', 'marginTop' : w_h/2 - final_h/2 + 'px', 'marginLeft': w_w/2 - final_w/2 + 'px' }); } });
При нажатии на ссылку "Назад" нужно закрыть текущее изображение:
//Событие click для кнопки "Назад" $back.bind('click',closePreview)
Функции для вывода элементов навигации:
//Выводим кнопки навигации function showNav(){ $next_img.stop().animate({ 'right' : '10px' },300); $prev_img.stop().animate({ 'left' : '10px' },300); } //Скрываем кнопки навигации function hideNav(){ $next_img.stop().animate({ 'right' : '-50px' },300); $prev_img.stop().animate({ 'left' : '-50px' },300); }
Следующая функция обновляет блок информации об изображении:
//Обновляем текущий альбом и информацию о текущей фотографии function updateInfo(album,photo){ $top_menu.find('.album_info') .html('Альбом ' + (album+1)) .end() .find('.image_info') .html(' / Фото ' + (photo+1)) }
Следующая функция выполняет окончательный расчет размеров и положения изображения в соответствии с размерами окна:
//Вычисляем окончательные значения высоты и ширины //для изображения на основе размеров окна function saveFinalPositions($image){ var theImage = new Image(); theImage.src = $image.attr("src"); var imgwidth = theImage.width; var imgheight = theImage.height; //140 = 2*60 кнопок следующий/предыдущий + 20 поля var containerwidth = $(window).width() - 140; //150 = 30 заголовка + 30 нижнего колонтитула + дополнительные 90 var containerheight = $(window).height() - 150; if(imgwidth > containerwidth){ var newwidth = containerwidth; var ratio = imgwidth / containerwidth; var newheight = imgheight / ratio; if(newheight > containerheight){ var newnewheight = containerheight; var newratio = newheight/containerheight; var newnewwidth =newwidth/newratio; theImage.width = newnewwidth; theImage.height= newnewheight; } else{ theImage.width = newwidth; theImage.height= newheight; } } else if(imgheight > containerheight){ var newheight = containerheight; var ratio = imgheight / containerheight; var newwidth = imgwidth / ratio; if(newwidth > containerwidth){ var newnewwidth = containerwidth; var newratio = newwidth/containerwidth; var newnewheight =newheight/newratio; theImage.height = newnewheight; theImage.width= newnewwidth; } else{ theImage.width = newwidth; theImage.height= newheight; } } $image.data({'width':theImage.width,'height':theImage.height}); }
Когда нажимается ссылка "Назад" нужно свернуть текущее изображение, желтый круг и вывести миниатюры для просмотра:
//Запускается, когда пользователь нажмет на кнопке "Назад". //Скрывает текущее изображение и круг, //затем выводит основное меню и миниатюры function closePreview(){ var $current = $preview.find('img'), w_w = $(window).width(), w_h = $(window).height(), margin_circle = w_w + w_w/3; if(w_h>w_w) margin_circle = w_h + w_h/3; if($current.is(':animated')) return false; //Скрываем навигацию hideNav(); //Скрываем верхнее меню $top_menu.stop() .animate({'top':'-30px'},400,'easeOutBack'); //Скрываем изображения $current.stop().animate({ 'width' : '0px', 'height' : '0px', 'marginTop' : w_h/2 +'px', 'marginLeft': w_w/2 +'px' },700,function(){ $(this).remove(); }); //Анимируем изображение круга. //Первый набор позиционирован правильно - //его нужно изменить при изменении размеров окна setTimeout(function(){ $bubble.css({ 'width' : margin_circle + 'px', 'height' : margin_circle + 'px', 'margin-top' : -margin_circle/2+'px', 'margin-left': -margin_circle/2+'px' }); $('BODY').css('background','url("bg.jpg") repeat scroll left top #222222'); $bubble.animate({ 'width' : '0px', 'height' : '0px', 'marginTop' : '0px', 'marginLeft': '0px' },500); },200); setTimeout(function(){ $header.stop() .animate({'top':'30px'},700,'easeOutBack'); $thumbnails_wrapper.stop() .show() .animate({'top':'110px'},700,'easeOutBack'); },600); }
И в завершении мы определяем функцию, которая делает наши миниатюры прокручиваемыми с помощью плагина:
//Строим прокрутчик миниатюр //(http://manos.malihu.gr/jquery-thumbnail-scroller) function createThumbnailScroller(){ /* Параметры функции ThumbnailScroller: 1) id контейнера (div id) 2) Тип прокручивания миниатюр. Значения: "horizontal", "vertical" 3) Поля для первой и последней миниатюры (для лучшей работы курсора) 4) Величина сглаживания прокрутки (0 - сглаживания нет) 5) Тип сглаживания прокрутки 6) Прозрачность миниатюр по умолчанию 7) Скорость затухания миниатюр под курсором мыши (в миллисекундах) */ ThumbnailScroller("tshf_container1","horizontal",10,800,"easeOutCirc",0.5,300); //ThumbnailScroller("tshf_container2","horizontal",10,800,"easeOutCirc",0.5,300); //ThumbnailScroller("tshf_container3","horizontal",10,800,"easeOutCirc",0.5,300); }
Готово!
Источник урока: tympanus.net/codrops/2010/12/10/bubbleriffic-image-gallery/