Привет! Все видели поиск изображений в Google?) Там, при нажатии на миниатюру, вы могли заметить интересный расширяющийся блок для предварительного просмотра увеличенного изображения . Это действительно хороший эффект, и это очень практично, что делает поиск намного легче. Как ни странно, галереи как у Google это редкость. Сегодня я хочу показать вам, как создать подобный эффект на сетке миниатюр. Идея заключается в том, чтобы при нажатии на миниатюру открыть блок предварительного просмотра и показать увеличенное изображение, а также некоторые другие материалы, такие как заголовок, описание,ссылку и т.д. Интересно и тяжело будет вычислять положение блока просмотра увеличенного изображения и прокручивать страницу в правильное положение. Будем расширять блок предварительного просмотра таким образом, чтобы в итоге смогли увидеть и соответствующий ряд миниатюр, и увеличенное изображение с описанием.
Итак, начнем!
HTML
Первоначально, мы должны создать сетку миниатюр, для этого будем использовать неупорядоченный список. Каждый элемент списка будет содержать якорь, который будет иметь несколько атрибутов:
<ul id="og-grid" class="og-grid"> <li> <a href="http://vk.com/d_a_r_k_m_a_s_t_e_r_s" data-largesrc="images/1.jpg" data-title="Lorem Ipsum" data-description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."> <img src="images/thumbs/1.jpg" alt="img01"/> </a> </li> <li> <a href="http://vk.com/d_a_r_k_m_a_s_t_e_r_s" data-largesrc="images/2.jpg" data-title="Lorem Ipsum" data-description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."> <img src="images/thumbs/2.jpg" alt="img02"/> </a> </li> <li><!-- ... --></li> <!-- ... --> </ul>
Значение href будет использоваться для построения ссылки в описании (это также очень удобно, когда JavaScript отключен). Атрибут data-largesrc содержит путь к большому изображению. data-title и data-description содержат название и описание, соответственно.
При нажатии на миниатюру, мы хотим чтобы блок детального просмотра показался под элементом списка. Для этого нам нужно будет вставить элемент в сетке. На самом деле, мы будем использовать сам элемент списка, а блок детального просмотра добавим сразу после якоря:
<li> <a href="http://vk.com/d_a_r_k_m_a_s_t_e_r_s" data-largesrc="images/2.jpg" data-title="Lorem Ipsum" data-description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."> <img src="images/thumbs/2.jpg" alt="img02"/> </a> <div class="og-expander"> <div class="og-expander-inner"> <span class="og-close"></span> <div class="og-fullimg"> <div class="og-loading"></div> <img src="images/2.jpg"> </div> <div class="og-details"> <h3>Lorem Ipsum</h3> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p> <a href="http://vk.com/d_a_r_k_m_a_s_t_e_r_s">Войти</a> </div> </div> </div> </li>
CSS
Итак, давайте начнем с сетки миниатюр. Она будет в полную ширину, также будем центрировать текст. В данном случае это означает, что мы центрируем блоки эскизов, так как у них будет установлено свойство display: inline-block:
.og-grid { list-style: none; padding: 20px 0; margin: 0 auto; text-align: center; width: 100%; } .og-grid li { display: inline-block; margin: 10px 5px 0 5px; vertical-align: top; height: 250px; }
Ссылки и изображения будут отображаться в виде блочных элементов и мы удалим некоторые стили по умолчанию:
.og-grid li > a, .og-grid li > a img { border: none; outline: none; display: block; position: relative; }
При нажатии на элемент, добавим класс к соответствующему пункту списка, который будет называться og-expanded. Также добавим небольшую стрелку к якорю в виде псевдо-элемента:
.og-grid li.og-expanded > a::after { top: auto; border: solid transparent; content: " "; height: 0; width: 0; position: absolute; pointer-events: none; border-bottom-color: #ddd; border-width: 15px; left: 50%; margin: -20px 0 0 -15px; }
Сам блок детального просмотра будет иметь класс og-expander, у этого элемента также будет абсолютное позиционирование. Начальная высота этого блока будет равна 0 и свойство overflow установим в hidden:
.og-expander { position: absolute; background: #ddd; top: auto; left: 0; width: 100%; margin-top: 10px; text-align: left; height: 0; overflow: hidden; } .og-expander-inner { padding: 50px 30px; height: 100%; }
По поводу блока детального просмотра, будем центрировать изображение внутри внешнего блока изображения путем установки выравнивания текста по центру, а самому изображению зададим display: inline-block. Изображение также будет иметь max-height и max-width 100%, дабы регулировать размеры изображения и растягивать его на весь окружающий блок:
.og-details { padding: 0 40px 0 20px; } .og-fullimg { text-align: center; } .og-fullimg img { display: inline-block; max-height: 100%; max-width: 100%; }
Делаем стили для текстовых элементов и ссылок:
.og-details h3 { font-weight: 300; font-size: 52px; padding: 40px 0 10px; margin-bottom: 10px; } .og-details p { font-weight: 400; font-size: 16px; line-height: 22px; color: #999; } .og-details a { font-weight: 700; font-size: 16px; color: #333; text-transform: uppercase; letter-spacing: 2px; padding: 10px 20px; border: 3px solid #333; display: inline-block; margin: 30px 0 0; outline: none; } .og-details a::before { content: '\2192'; display: inline-block; margin-right: 10px; } .og-details a:hover { border-color: #999; color: #999; }
Все остальные стили вы сможете увидеть в файлах и я уверен разобраться в них не составит для вас труда. Давайте лучше прейдем к Javascript :)
Javascript
Кэшируем некоторые элементы и инициализируем некоторые переменные)
//список элементов var $grid = $( '#og-grid' ), //сами элементы $items = $grid.children( 'li' ), //индекс текущего элемента current = -1, //позиция (сверху) пункта для которого будет открываться блок //используется для того чтобы знать в какой строке нужно открыть блок //детального просмотра previewPos = -1, //дополнительное количество пикселей для прокрутки окна scrollExtra = 0, //дополнительный запас отступов при расширении (между элементом //с открытым детальным просмотром и следующей строкой элементов) marginExpanded = 10, $window = $( window ), winsize, $body = $( 'html, body' ), //события transition transEndEventNames = { 'WebkitTransition' : 'webkitTransitionEnd', 'MozTransition' : 'transitionend', 'OTransition' : 'oTransitionEnd', 'msTransition' : 'MSTransitionEnd', 'transition' : 'transitionend' }, transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ], support = Modernizr.csstransitions, //настройки по умолчанию settings = { minHeight : 500, speed : 350, easing : 'ease' };
Далее приступим к предварительной загрузке всех изображений (миниатюр) в сетке. Затем сохраним смещение сверху и высоту каждого элемента в сетке, получим текущий размер окна, и инициализируем некоторые события:
function init( config ) { //настройки settings = $.extend( true, {}, settings, config ); //предварительная загрузка изображений $grid.imagesLoaded( function() { //сохраняем размер элементов и смещение saveItemInfo( true ); //получаем размер окна getWinSize(); //инициализация некоторых событий initEvents(); } ); } //сохраняем смещение элементов сверху и высоту function saveItemInfo( saveheight ) { $items.each( function() { var $item = $( this ); $item.data( 'offsetTop', $item.offset().top ); if( saveheight ) { $item.data( 'height', $item.height() ); } } ); } function getWinSize() { winsize = { width : $window.width(), height : $window.height() }; }
Обработаем события клика по миниатюре, а также по крестику при открытом блоке детального просмотра. При клике по миниатюре блок детального просмотра должен открываться или закрываться, в зависимости от текущего состояния, при клике на крестик блок должен просто закрываться. Также обработаем событие изменения размера окна, когда блок открывается и закрывается.
function initEvents() { $items.on( 'click', 'span.og-close', function() { hidePreview(); return false; } ).children( 'a' ).on( 'click', function(e) { var $item = $( this ).parent(); current === $item.index() ? hidePreview() : showPreview( $item ); return false; } ); $window.on( 'debouncedresize', function() { scrollExtra = 0; previewPos = -1; saveItemInfo(); getWinSize(); var preview = $.data( this, 'preview' ); if( typeof preview != 'undefined' ) { hidePreview(); } } ); }
При помощи функции showPreview мы будем инициализировать грубо говоря ряд иконок, которые в данный момент можно будет посмотреть в детальном блоке.
Если объяснять на пальцах, то при клике на первую картинку мы получаем развернутый блок детального просмотра и в случае, если мы будем кликать на соседние картинки в этом ряду, мы сможем просто подменять данные в развернутом блоке, дабы не открывать и закрывать его при каждом клике.
function showPreview( $item ) { var preview = $.data( this, 'preview' ), position = $item.data( 'offsetTop' ); scrollExtra = 0; if( typeof preview != 'undefined' ) { if( previewPos !== position ) { if( position > previewPos ) { scrollExtra = preview.height; } hidePreview(); } else { preview.update( $item ); return false; } } previewPos = position; preview = $.data( this, 'preview', new Preview( $item ) ); preview.open(); }
При инициализации детального просмотра миниатюры, мы создаем структуру с данными, которые привязаны к конкретному элементу:
create : function() { this.$title = $( '<h3></h3>' ); this.$description = $( '<p></p>' ); this.$href = $( '<a href="#">Visit website</a>' ); this.$details = $( '<div class="og-details"></div>' ).append( this.$title, this.$description, this.$href ); this.$loading = $( '<div class="og-loading"></div>' ); this.$fullimage = $( '<div class="og-fullimg"></div>' ).append( this.$loading ); this.$closePreview = $( '<span class="og-close"></span>' ); this.$previewInner = $( '<div class="og-expander-inner"></div>' ).append( this.$closePreview, this.$fullimage, this.$details ); this.$previewEl = $( '<div class="og-expander"></div>' ).append( this.$previewInner ); this.$item.append( this.getEl() ); if( support ) { this.setTransition(); } }
Используем функцию update для обновления данных в блоке детального просмотра:
update : function( $item ) { if( $item ) { this.$item = $item; } if( current !== -1 ) { var $currentItem = $items.eq( current ); $currentItem.removeClass( 'og-expanded' ); this.$item.addClass( 'og-expanded' ); this.positionPreview(); } current = this.$item.index(); var $itemEl = this.$item.children( 'a' ), eldata = { href : $itemEl.attr( 'href' ), largesrc : $itemEl.data( 'largesrc' ), title : $itemEl.data( 'title' ), description : $itemEl.data( 'description' ) }; this.$title.html( eldata.title ); this.$description.html( eldata.description ); this.$href.attr( 'href', eldata.href ); var self = this; if( typeof self.$largeImg != 'undefined' ) { self.$largeImg.remove(); } if( self.$fullimage.is( ':visible' ) ) { this.$loading.show(); $( '<img/>' ).load( function() { self.$loading.hide(); self.$largeImg = $( this ).fadeIn( 350 ); self.$fullimage.append( self.$largeImg ); } ).attr( 'src', eldata.largesrc ); } }
Для создания блока детального просмотра нам надо установить его высоту. Она будет равняться высоте окна минус высоту блока миниатюры. Дабы избежать случаев когда нам может не хватить высоты блока детального просмотра, уставим опцию minHeight, в которой мы укажем минимальную высоту, нужную для детального просмотра.
open : function() { setTimeout( $.proxy( function() { this.setHeights(); this.positionPreview(); }, this ), 25 ); } setHeights : function() { var self = this, onEndFn = function() { if( support ) { self.$item.off( transEndEventName ); } self.$item.addClass( 'og-expanded' ); }; this.calcHeight(); this.$previewEl.css( 'height', this.height ); this.$item.css( 'height', this.itemHeight ).on( transEndEventName, onEndFn ); if( !support ) { onEndFn.call(); } } calcHeight : function() { var heightPreview = winsize.height - this.$item.data( 'height' ) - marginExpanded, itemHeight = winsize.height; if( heightPreview < settings.minHeight ) { heightPreview = settings.minHeight; itemHeight = settings.minHeight + this.$item.data( 'height' ) + marginExpanded; } this.height = heightPreview; this.itemHeight = itemHeight; } positionPreview : function() { var position = this.$item.data( 'offsetTop' ), previewOffsetT = this.$previewEl.offset().top - scrollExtra, scrollVal = this.height + this.$item.data( 'height' ) + marginExpanded <= winsize.height ? position : this.height < winsize.height ? previewOffsetT - ( winsize.height - this.height ) : previewOffsetT; $body.animate( { scrollTop : scrollVal }, settings.speed ); }
При закрытии блока детального просмотра нам нужно сбросить высоту самого блока, а также элемента (картинки). Как только это будет сделано, сам блок и его структуру удаляем из DOM.
close : function() { var self = this, onEndFn = function() { if( support ) { $( this ).off( transEndEventName ); } self.$item.removeClass( 'og-expanded' ); self.$previewEl.remove(); }; setTimeout( $.proxy( function() { if( typeof this.$largeImg !== 'undefined' ) { this.$largeImg.fadeOut( 'fast' ); } this.$previewEl.css( 'height', 0 ); var $expandedItem = $items.eq( this.expandedIdx ); $expandedItem.css( 'height', $expandedItem.data( 'height' ) ).on( transEndEventName, onEndFn ); if( !support ) { onEndFn.call(); } }, this ), 25 ); return false; }
Хух) Вот и все! Насыщенный урок) но если все перечитать и подумать, то уверен что ничто не помешает вам разобраться в этом уроке. Уверен также, что вы найдете интересное применение данному скрипту!