Адаптивная сетка изображений с предварительным просмотром
28 марта 2013 | Опубликовано в css | 8 Комментариев »
Если вы в последнее время искали изображение в Google, возможно, заметили интересную технику предварительного просмотра изображений при нажатии на превьюшку. Это очень хороший эффект и он значительно упрощает поиск. Сегодня хотим показать вам, как сделать такой эффект. Идея будет заключаться в том, чтобы при нажатии на маленькое изображение, открывалось большое с некоторыми другими элементами, такими как заголовок, описание и ссылки. Обратите внимание, что мы не будем использовать очень большие изображения для предварительного просмотра в демо, так что на больших мониторах вы можете увидеть много пустого пространства.
Разметка
Первоначально мы должны разместить маленькие изображения. Для этого будем использовать неупорядоченный список. Каждый элемент списка будет содержать якорь с несколькими атрибутами:
<ul id="og-grid" class="og-grid"> <li> <a href="http://cargocollective.com/jaimemartinez/" data-largesrc="images/1.jpg" data-title="Azuki bean" data-description="Swiss chard pumpkin bunya nuts maize plantain aubergine napa cabbage soko coriander sweet pepper water spinach winter purslane shallot tigernut lentil beetroot."> <img src="images/thumbs/1.jpg" alt="img01"/> </a> </li> <li> <a href="http://cargocollective.com/jaimemartinez/" data-largesrc="images/2.jpg" data-title="Veggies sunt bona vobis" data-description="Komatsuna prairie turnip wattle seed artichoke mustard horseradish taro rutabaga ricebean carrot black-eyed pea turnip greens beetroot yarrow watercress kombu."> <img src="images/thumbs/2.jpg" alt="img02"/> </a> </li> <li><!-- ... --></li> <!-- ... --> </ul>
HREF
будет использоваться в окне предварительного просмотра для описания (это также очень удобно, когда JavaScript отключен). Атрибут largesrc
содержит путь к увеличенному изображению. data-title и
data-description
содержат название и описание.
Когда мы нажимаем на миниатюру, мы хотим, чтобы увеличенное изображение появилось под элементом списка. Для этого нам нужно будет вставить элемент в сетку. Фактически мы будем использовать элемент списка и добавлять превью после якоря.
<li> <a href="http://cargocollective.com/jaimemartinez/" data-largesrc="images/2.jpg" data-title="Veggies sunt bona vobis" data-description="Komatsuna prairie turnip wattle seed artichoke mustard horseradish taro rutabaga ricebean carrot black-eyed pea turnip greens beetroot yarrow watercress kombu."> <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>Veggies sunt bona vobis</h3> <p>Komatsuna prairie turnip wattle seed artichoke mustard horseradish taro rutabaga ricebean carrot black-eyed pea turnip greens beetroot yarrow watercress kombu.</p> <a href="http://cargocollective.com/jaimemartinez/">Visit website</a> </div> </div> </div> </li>
CSS
Обратите внимание, что CSS не будет содержать префиксы, но вы сможете их найти в файлах.
Итак, давайте начнем с сетки изображений.
.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, и этот элемент будет позиционирован абсолютно. 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%; }
Закрытие превьюшки будет происходить с помощью псевдо-элементов.
.og-close { position: absolute; width: 40px; height: 40px; top: 20px; right: 20px; cursor: pointer; } .og-close::before, .og-close::after { content: ''; position: absolute; width: 100%; top: 50%; height: 1px; background: #888; transform: rotate(45deg); } .og-close::after { transform: rotate(-45deg); } .og-close:hover::before, .og-close:hover::after { background: #333; }
.og-fullimg,
.og-details { width: 50%; float: left; height: 100%; overflow: hidden; position: relative; }
Изображения имеют максимальную высоту и максимальную ширина 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; }
.og-loading { width: 20px; height: 20px; border-radius: 50%; background: #ddd; box-shadow: 0 0 1px #ccc, 15px 30px 1px #ccc, -15px 30px 1px #ccc; position: absolute; top: 50%; left: 50%; margin: -25px 0 0 -25px; animation: loader 0.5s infinite ease-in-out both; } @keyframes loader { 0% { background: #ddd; } 33% { background: #ccc; box-shadow: 0 0 1px #ccc, 15px 30px 1px #ccc, -15px 30px 1px #ddd; } 66% { background: #ccc; box-shadow: 0 0 1px #ccc, 15px 30px 1px #ddd, -15px 30px 1px #ccc; } }
Последнее, но не менее важное, мы добавим два медиа-запроса.
@media screen and (max-width: 830px) {
.og-expander h3 { font-size: 32px; } .og-expander p { font-size: 13px; } .og-expander a { font-size: 12px; } } @media screen and (max-width: 650px) { .og-fullimg { display: none; } .og-details { float: none; width: 100%; } }
JAVASCRIPT
Давайте объявим некоторые переменные:
// list of items var $grid = $( '#og-grid' ), // the items $items = $grid.children( 'li' ), // current expanded item´s index current = -1, // position (top) of the expanded item // used to know if the preview will expand in a different row previewPos = -1, // extra amount of pixels to scroll the window scrollExtra = 0, // extra margin when expanded (between the preview element and the next item row) marginExpanded = 10, $window = $( window ), winsize, $body = $( 'html, body' ), // transitionend events transEndEventNames = { 'WebkitTransition' : 'webkitTransitionEnd', 'MozTransition' : 'transitionend', 'OTransition' : 'oTransitionEnd', 'msTransition' : 'MSTransitionEnd', 'transition' : 'transitionend' }, transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ], // support for csstransitions support = Modernizr.csstransitions, // default settings settings = { minHeight : 500, speed : 350, easing : 'ease' };
Начнем с предварительной загрузки изображений (эскизов) в сетку. Затем мы сохраняем высоту для каждого элемента и получаем высоту текущего окна, инициализируем некоторые события:
function init( config ) { // the settings.. settings = $.extend( true, {}, settings, config ); // preload all images $grid.imagesLoaded( function() { // save item´s size and offset saveItemInfo( true ); // get window´s size getWinSize(); // initialize some events initEvents(); } ); } // saves the item´s offset top and height (if saveheight is true) 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() { // when clicking an item, show the preview with the item´s info and large image; // close the item if already expanded. // also close if clicking on the item´s cross $items.on( 'click', 'span.og-close', function() { hidePreview(); return false; } ).children( 'a' ).on( 'click', function(e) { var $item = $( this ).parent(); // check if item already opened current === $item.index() ? hidePreview() : showPreview( $item ); return false; } ); // on window resize get the window´s size again // reset some values.. $window.on( 'debouncedresize', function() { scrollExtra = 0; previewPos = -1; // save item´s offset saveItemInfo(); getWinSize(); var preview = $.data( this, 'preview' ); if( typeof preview != 'undefined' ) { hidePreview(); } } ); }
С помощью функции showPreview мы инициализируем объект Preview, который, в свою очередь, будет открывать или скрывать описание и большую версию картинки. В случае, если Preview инициализирована, мы будем обновлять превьюшки с новым описанием или скрывать ее и инициализировать/открывать новую.
function showPreview( $item ){
var preview = $.data( this, 'preview' ), // item´s offset top position = $item.data( 'offsetTop' ); scrollExtra = 0; // if a preview exists and previewPos is different (different row) from item´s top, then close it if( typeof preview != 'undefined' ) { // not in the same row if( previewPos !== position ) { // if position > previewPos then we need to take the current preview´s height in consideration when scrolling the window if( position > previewPos ) { scrollExtra = preview.height; } hidePreview(); } // same row else { preview.update( $item ); return false; } } // update previewPos previewPos = position; // initialize new preview for the clicked item preview = $.data( this, 'preview', new Preview( $item ) ); // expand preview overlay preview.open(); }
Объект Preview будет иметь ссылку на отображаемую в этот момент иконку (Preview.$item) и индекс расширенной картинки (Preview.expandedIdx).
// the preview obj / overlay function Preview( $item ) { this.$item = $item; this.expandedIdx = this.$item.index(); this.create(); this.update(); }
Как только объект Preview будет инициализирован, создаем необходимую структуру, в которой к объекту будут добавлены некоторые детали.
create : function(){
// create Preview structure: 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 ); // append preview element to the item this.$item.append( this.getEl() ); // set the transitions for the preview and the item if( support ) { this.setTransition(); } }
Затем мы заполняем эту структуру необходимым описанием (находятся в атрибутах данных и href).
Функция updat также будет использоваться, но только для обновления контента превью.
update : function( $item ){
// update with new item´s details if( $item ) { this.$item = $item; } // if already expanded, remove class "og-expanded" from current item and add it to new item if( current !== -1 ) { var $currentItem = $items.eq( current ); $currentItem.removeClass( 'og-expanded' ); this.$item.addClass( 'og-expanded' ); // position the preview correctly this.positionPreview(); } // update current value current = this.$item.index(); // update preview´s content 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; // remove the current image in the preview if( typeof self.$largeImg != 'undefined' ) { self.$largeImg.remove(); } // preload large image and add it to the preview // for smaller screens we don´t display the large image (the last media query will hide the wrapper of the image) 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() { // set the height for the preview and the item this.setHeights(); // scroll to position the preview in the right place 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() { // scroll page // case 1 : preview height + item height fits in window´s height // case 2 : preview height + item height does not fit in window´s height and preview height is smaller than window´s height // case 3 : preview height + item height does not fit in window´s height and preview height is bigger than window´s height 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 ); // the current expanded item (might be different from this.$item) var $expandedItem = $items.eq( this.expandedIdx ); $expandedItem.css( 'height', $expandedItem.data( 'height' ) ).on( transEndEventName, onEndFn ); if( !support ) { onEndFn.call(); } }, this ), 25 ); return false; }
Автор урока: MARY LOU
Перевод: Дежурка
28 марта 2013 в 14:34
Информация конечно уникальная и весьма полезная, но проработайте текст еще, так как много опечаток.
28 марта 2013 в 23:19
Спасибо! Не подскажете что то подобное но на все окно и чтобы размеры самих картинок разные были? Ищу.
13 апреля 2013 в 8:07
спасибо
10 мая 2013 в 23:51
Очень хорошо! Один вопрос, как изменить текст «Перейти на сайт»?. Где хотят менять, что, с другой текст?.
Конечно, большое спасибо!
мая 15, 2015 at 3:08 дп
\js\grid.js
Line 347: this.$href = $('Visit website');
25 августа 2015 в 16:16
Подскажите, как быть. Хочу контент в раскрывающемся окне сделать побольше. Как мне отредактировать высоту блоков(которые ниже располагаются) и самого окна с контентом?
При уменьшении окна, нижние блоки то напрыгивают на контент, то слишком далеко*(
5 мая 2016 в 13:56
Мне вот интересно, а можно ли подобное реализовать силами CSS3 без использования js?
31 июля 2016 в 14:29
Добрый день, скажите пожалуйста, как сделать так, чтобы при нажатии на квадрат сетки, он разьезжался на большую высоту, для того чтобы там можно было вместить больше текста или вставить еще картинок?