Яндекс.Метрика

Дизайн-журнал №1. Актуальная информация для дизайнеров, веб дизайнеров, программистов и разработчиков сайтов.

Адаптивная сетка изображений с предварительным просмотром

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;
}
Загрузка элемента будет находиться в том же контейнере, что и само изображение. Мы не будем использовать никаких картинок — только средства CSS. Мы создадим маленький кружок и установим три тени. Затем мы создаем анимацию, которая  последовательно менять цвет фона и цвет тени.
.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

Перевод: Дежурка




Комментарии

  1. Setevaja_Vilka
    Thumb up Thumb down 0

    Информация конечно уникальная и весьма полезная, но проработайте текст еще, так как много опечаток.

  2. Moor
    Thumb up Thumb down 0

    Спасибо! Не подскажете что то подобное но на все окно и чтобы размеры самих картинок разные были? Ищу.

  3. mackay
    Thumb up Thumb down +1

    спасибо

  4. Franco
    Thumb up Thumb down 0

    Очень хорошо! Один вопрос, как изменить текст «Перейти на сайт»?. Где хотят менять, что, с другой текст?.

    Конечно, большое спасибо!

    Dmitry Ответ:

    Thumb up Thumb down 0

    \js\grid.js

    Line 347: this.$href = $('Visit website');

  5. Василиса
    Thumb up Thumb down 0

    Подскажите, как быть. Хочу контент в раскрывающемся окне сделать побольше. Как мне отредактировать высоту блоков(которые ниже располагаются) и самого окна с контентом?

    При уменьшении окна, нижние блоки то напрыгивают на контент, то слишком далеко*(

  6. Максим
    Thumb up Thumb down 0

    Мне вот интересно, а можно ли подобное реализовать силами CSS3 без использования js?

  7. Игорь
    Thumb up Thumb down 0

    Добрый день, скажите пожалуйста, как сделать так, чтобы при нажатии на квадрат сетки, он разьезжался на большую высоту, для того чтобы там можно было вместить больше текста или вставить еще картинок?