Галерея с css3-переходами
11 июля 2013 | Опубликовано в css | 7 Комментариев »
В этом уроке мы будем создавать галерею, в которой с помощью CSS3- переходов реализуется эффект диагонального угасания фотографий. Мы будем сканировать на сервере папку с фотографиями и отображать их в полный размер экрана в виде сетки. Добавить новую фотографию можно просто скопировав два файла в папку галереи (предварительно отредактировав размеры изображения и превьюшки, для которой желательный размер 150 на 150 пикселей. В браузерах, которые не поддерживают соответствующие свойства css, анимация галереи будет происходить немного по-другому.
HTML
Как обычно, начинаем с HTML.
index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>Smooth Diagonal Fade Gallery with CSS3 Transitions</title> <!-- The Swipebox plugin --> <link href="assets/swipebox/swipebox.css" rel="stylesheet" /> <!-- The main CSS file --> <link href="assets/css/style.css" rel="stylesheet" /> <!--[if lt IE 9]> <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> </head> <body> <div id="loading"></div> <div id="gallery"></div> <!-- JavaScript Includes --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.0/jquery.min.js"></script> <script src="assets/swipebox/jquery.swipebox.min.js"></script> <script src="assets/js/jquery.loadImage.js"></script> <script src="assets/js/script.js"></script> </body> </html>
Галерея будет определяться с помощью библиотеки jQuery, которую объявляем перед закрытием тега body. Также я добавил небольшой лайтбокс -плагин Swipebox, но его можно заменить на любой другой лайтбокс. Два основных блока #loading и #gallery. При чем первый отвечает за загрузку GIF, второй собственно за саму галерею. Для блока #gallery устанавливаем , так как нам нужно, чтобы галерея занимала всю ширину и высоту страницы. Разметка для самих фотографий также не сложная:
<a href="assets/photos/large/34.jpg" class="swipebox static" style="width:148px;height:129px;background-image:url(assets/photos/thumbs/34.jpg)"> </a>
Фотографии в галереи имеют размер 150×150 px. При изменении размера окна браузера в стилях будет для фото определять новые атрибуты ширины и высоты. В разделе JS высможете увидеть.к ак мы будем их рассчитывать.
PHP-сканирование фото
Фотографии хранятся в двух папках - ssets/photos/thumbs/ для превьюшек и ssets/photos/large/ для полных размеров. С помощью PHP будем сканировать папки и выводит JSON с именами файлов.
load.php
// Scan all the photos in the folder $files = glob('assets/photos/large/*.jpg'); $data = array(); foreach($files as $f){ $data[] = array( 'thumb' => str_replace('large', 'thumbs', $f), 'large' => $f ); } // Duplicate the photos a few times, so that we have what to paginate in the demo. // You most certainly wouldn't want to do this with your real photos. // $data = array_merge($data, $data); // $data = array_merge($data, $data); // $data = array_merge($data, $data); header('Content-type: application/json'); echo json_encode(array( 'data' => $data, ));
Добавлять новые файлы очень просто. Для этого просто нужно скопировать нужные файлы в нужную папку( файлы с полным размером фото и превью должны иметь одинаковые имена). В примере вы видите, что фотографии дублированы по несколько раз. Скорее всего, вы захотите это изменить и обновить собственные фото.
Давайте перейдем к JavaScript!
JavaScript
Давайте рассмотрим, что мы будем делать:
- 1. Определяем AJAX GET-запрос на извлечение фотографий на диск с кода php.
- 2. Подсчитываем нужное количество фотографий, чтобы показать их на странице. Количество и размеры фото будут зависеть от размеров окна.
- 3. Подгружаем фотографии на страницу с помощью скрипта preloader script, который использует jQuery deferreds. В это время отображается блок #loading.
- 4. После того, как все файлы будут загружены, сгенерируем разметку для фото и добавим их к элементу #gallery. Потом мы будем генерировать эффект диагональной анимации и инициализировать Swipebox-галерею.
- 5. При нажатии пользователем на стрелку шаги 3 и 4 будут повторяться.
Код достаточно большой, чтобы показать его полностью. Поэтому давайте его рассмотри по частям. В данном случае смотрим на общую структуру кода.
assets/js/script.js
$(function(){ // Global variables that hold state var page = 0, per_page = 100, photo_default_size = 150, picture_width = photo_default_size, picture_height = photo_default_size, max_w_photos, max_h_photos data = []; // Global variables that cache selectors var win = $(window), loading = $('#loading'), gallery = $('#gallery'); gallery.on('data-ready window-resized page-turned', function(event, direction){ // Here we will have the JavaScript that preloads the images // and adds them to the gallery }); // Fetch all the available images with // a GET AJAX request on load $.get('load.php', function(response){ // response.data holds the photos data = response.data; // Trigger our custom data-ready event gallery.trigger('data-ready'); }); gallery.on('loading',function(){ // show the preloader loading.show(); }); gallery.on('loading-finished',function(){ // hide the preloader loading.hide(); }); gallery.on('click', '.next', function(){ page++; gallery.trigger('page-turned',['br']); }); gallery.on('click', '.prev', function(){ page--; gallery.trigger('page-turned',['tl']); }); win.on('resize', function(e){ // Here we will monitor the resizing of the window // and will recalculate how many pictures we can show // at once and what their sizes should be so they fit perfectly }).resize(); /* Animation functions */ function show_photos_static(){ // This function will show the images without any animations } function show_photos_with_animation_tl(){ // This one will animate the images from the top-left } function show_photos_with_animation_br(){ // This one will animate the images from the bottom-right } /* Helper functions */ function get_per_page(){ // Here we will calculate how many pictures // should be shown on current page } function get_page_start(p){ // This function will tell us which is the first // photo that we will have to show on the given page } function is_next_page(){ // Should we show the next arrow? } function is_prev_page(){ // Should we show the previous arrow? } });
Некоторые из определенных функций пустые. Но вы сможете увидеть их внизу страницы. Первая группа переменных определяет состоянии галереи - размер, массив картинок, текущую страницу. Это позволит более четко отделить логику и данные. Для четкой организации кода будем использовать пользовательские события.
После того, как вы прочли все комментарии в коде, приступаем к слушателям событий, которые будут выводить соответствующий кусок массива изображений в зависимости от текущей страницы.
gallery.on('data-ready window-resized page-turned', function(event, direction){ var cache = [], deferreds = []; gallery.trigger('loading'); // The photos that we should be showing on the new screen var set = data.slice(get_page_start(), get_page_start() + get_per_page()); $.each(set, function(){ // Create a deferred for each image, so // we know when they are all loaded deferreds.push($.loadImage(this.thumb)); // build the cache cache.push('<a href="' + this.large + '" class="swipebox"' + 'style="width:' + picture_width + 'px;height:' + picture_height + 'px;background-image:url(' + this.thumb + ')">'+ '</a>'); }); if(is_prev_page()){ cache.unshift('<a class="prev" style="width:' + picture_width + 'px;height:' + picture_height + 'px;"></a>'); } if(is_next_page()){ cache.push('<a class="next" style="width:' + picture_width + 'px;height:' + picture_height + 'px;"></a>'); } if(!cache.length){ // There aren't any images return false; } // Call the $.when() function using apply, so that // the deferreds array is passed as individual arguments. // $.when(arg1, arg2) is the same as $.when.apply($, [arg1, arg2]) $.when.apply($, deferreds).always(function(){ // All images have been loaded! if(event.type == 'window-resized'){ // No need to animate the photos // if this is a resize event gallery.html(cache.join('')); show_photos_static(); // Re-initialize the swipebox $('#gallery .swipebox').swipebox(); } else{ // Create a fade out effect gallery.fadeOut(function(){ // Add the photos to the gallery gallery.html(cache.join('')); if(event.type == 'page-turned' && direction == 'br'){ show_photos_with_animation_br(); } else{ show_photos_with_animation_tl(); } // Re-initialize the swipebox $('#gallery .swipebox').swipebox(); gallery.show(); }); } gallery.trigger('loading-finished'); }); });
Хотя мы и добавляем изображения в блок #gallery, их для прозрачности с помощью css определяется значение 0, что создает основу для функций анимации, первая из которых показывает фото без анимации, а две последние создают анимацию волны, начиная с верхней левой картинки, либо с нижней правой. Анимация на основе css будет происходить, когда мы в JQuery объявим имя класса изображения.
function show_photos_static(){ // Show the images without any animations gallery.find('a').addClass('static'); } function show_photos_with_animation_tl(){ // Animate the images from the top-left var photos = gallery.find('a'); for(var i=0; i<max_w_photos + max_h_photos; i++){ var j = i; // Loop through all the lines for(var l = 0; l < max_h_photos; l++){ // If the photo is not of the current line, stop. if(j < l*max_w_photos) break; // Schedule a timeout. It is wrapped in an anonymous // function to preserve the value of the j variable (function(j){ setTimeout(function(){ photos.eq(j).addClass('show'); }, i*50); })(j); // Increment the counter so it points to the photo // to the left on the line below j += max_w_photos - 1; } } } function show_photos_with_animation_br(){ // Animate the images from the bottom-right var photos = gallery.find('a'); for(var i=0; i<max_w_photos + max_h_photos; i++){ var j = per_page - i; // Loop through all the lines for(var l = max_h_photos-1; l >= 0; l--){ // If the photo is not of the current line, stop. if(j > (l+1)*max_w_photos-1) break; // Schedule a timeout. It is wrapped in an anonymous // function to preserve the value of the j variable (function(j){ setTimeout(function(){ photos.eq(j).addClass('show'); }, i*50); })(j); // Decrement the counter so it points to the photo // to the right on the line above j -= max_w_photos - 1; } } }
Следующая функция отлавливает событие изменения размеров экрана. Это может произойти при изменении размера окна браузера, либо при изменении ориентации устройства. В этой функции также подсчитываем необходимое количество фотографий, которые мы должны разместить на экране, а высчитываем точные размеры, которые нам лучше всего подойдут.
win.on('resize', function(e){ var width = win.width(), height = win.height(), gallery_width, gallery_height, difference; // How many photos can we fit on one line? max_w_photos = Math.ceil(width/photo_default_size); // Difference holds how much we should shrink each of the photos difference = (max_w_photos * photo_default_size - width) / max_w_photos; // Set the global width variable of the pictures. picture_width = Math.ceil(photo_default_size - difference); // Set the gallery width gallery_width = max_w_photos * picture_width; // Let's do the same with the height: max_h_photos = Math.ceil(height/photo_default_size); difference = (max_h_photos * photo_default_size - height) / max_h_photos; picture_height = Math.ceil(photo_default_size - difference); gallery_height = max_h_photos * picture_height; // How many photos to show per page? per_page = max_w_photos*max_h_photos; // Resize the gallery holder gallery.width(gallery_width).height(gallery_height); gallery.trigger('window-resized'); }).resize();
Последняя строка сообщает, что у нас уже есть нужная информация правильных значений для старта.
Следующие вспомогательные функции резумирую некоторые из наиболее часто используемых вычислений:
function get_per_page(){ // How many pictures should be shown on current page // The first page has only one arrow, // so we decrease the per_page argument with 1 if(page == 0){ return per_page - 1; } // Is this the last page? if(get_page_start() + per_page - 1 > data.length - 1){ // It also has 1 arrow. return per_page - 1; } // The other pages have two arrows. return per_page - 2; } function get_page_start(p){ // Which position holds the first photo // that is to be shown on the give page if(p === undefined){ p = page; } if(p == 0){ return 0; } // (per_page - 2) because the arrows take up two places for photos // + 1 at the end because the first page has only a next arrow. return (per_page - 2)*p + 1; } function is_next_page(){ // Should we show the next arrow? return data.length > get_page_start(page + 1); } function is_prev_page(){ // Should we show the previous arrow? return page > 0; }
Они хотя и имеют по несколько строк и используются только раз или два, но позволяют нашему коду быть более читабельным.
CSS
Наконец, приступим к css. Фотографии по дефолту имеют значение непрозрачности — 0, и transform: scale со значение 0.8. Также они имеют ряд настроек переходов. Класс .show повышает прозрачность и размер элементов и автоматически анимируется в браузерах.
assets/css/styles.css
#gallery{
position:fixed; top:0; left:0; width:100%; height:100%; } #gallery a{ opacity:0; float:left; background-size:cover; background-position: center center; -webkit-transform:scale(0.8); -moz-transform:scale(0.8); transform:scale(0.8); -webkit-transition:0.4s; -moz-transition:0.4s; transition:0.4s; } #gallery a.static:hover, #gallery a.show:hover{ opacity:0.9 !important; } #gallery a.static{ opacity:1; -webkit-transform:none; -moz-transform:none; transform:none; -webkit-transition:opacity 0.4s; -moz-transition:opacity 0.4s; transition:opacity 0.4s; } #gallery a.next, #gallery a.prev{ background-color:#333; cursor:pointer; } #gallery a.next{ background-image:url('../img/arrow_next.jpg'); } #gallery a.prev{ background-image:url('../img/arrow_prev.jpg'); } #gallery a.show{ opacity:1; -webkit-transform:scale(1); -moz-transform:scale(1); transform:scale(1); }
Класс .static, что определен в функции show_photos_static()
запрещает анимацию (за исключение непрозрачности) и сразу же показывает фотографию( иначе при изменении размера овна вы бы видели все тот же эффект диагонального угасания). Остальную часть кода вы сможете скачать и посмотреть в исходниках к этому уроку.
Автор - Martin Angelov
Перевод — Дежурка
9 октября 2013 в 17:24
28 ноября 2013 в 19:23
Круто парни! Помогли!!!!!!!!!!!!
22 октября 2015 в 13:35
Залил на хостинг и не работает.. TypeError: $.loadImage is not a function. На Денвере работает. В чем может быть причина??
ноября 7, 2015 at 7:46 пп
А не перепутал нигде большие и маленькие символы?
ноября 9, 2015 at 8:47 дп
Вот оно что «михалыч», спасибо.. действительно при загрузке на хостинг, у меня стояла голочка — перевести имена в нижний регистр... перезалил без нее и все норм... спс.. *YAHOO*
ноября 9, 2015 at 11:36 дп
В качестве дебугера использую Мозилу с модулем дебугера, очень удобно.