Сетка миниатюр с возможностью расширенного представления

В последнее время поиск изображений через Google несколько преобразился. Сегодня мы рассмотрим, каким образом можно организовать подобный функционал. Идея заключается в отображении более крупного изображения при клике на его миниатюру, а также вывод названия, описания и ссылки.

Ключевым моментом будет вычисление нужного размера полного изображения и перемещение страницы на нужную позицию. При показе крупного изображения, мы также оставим видимым его миниатюру, а весь оставшийся контент накроем блоком серого цвета. Также мы не будем использовать крупные изображения, так что на крупных мониторах могут образоваться свободные области.

Итак, давайте начнём!

HTML

Для начала, нам необходимо отобразить сетку изображений. Для этого мы воспользуемся ненумерованным списком. Каждый элемент списка, будет содержать элемент ссылки с несколькими очень важными атрибутами:

<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). В атрибутах data-largesrc содержится адрес полного изображения, data-title - название, data-description - описание.

При клике на миниатюру более крупное представление будет раскрываться чуть ниже, поэтому его html код мы поместим в тот же элемент списка, прямо за элементом ссылки:

<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 префиксов для разных браузеров. Полный код можно найти в исходниках, которые доступны для скачивания.

Итак, давайте начнём с самой сетки. Она будет размером во весь экран, текст будет располагаться по центру. Также будут отцентрированы и все миниатюры, благодаря данной записи “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%;
}

Знак закрытия панели будет формироваться из псевдо-элементов (двух повёрнутых линий):

.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;
}

Контейнеры для изображения и описания будут 50% в ширину, а также прикреплены друг к другу:

.og-fullimg,
.og-details {
    width: 50%;
    float: left;
    height: 100%;
    overflow: hidden;
    position: relative;
}

Опции изображения 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;
}

Отображение загрузки изображения будет располагаться в том же контейнере, где и картинка. Данный элемент мы реализуем средствами 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; }
}

Ну и напоследок, добавим несколько медиа запросов для того, чтобы скорректировать размер такта, а также уберём панель полного изображения, если размер экрана меньше 650px. В этом случае, размер миниатюры и так велик:

@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.

JavaScript

Для начала, давайте закэшируем некоторые данные:

    // списки элементов
    var $grid = $( '#og-grid' ),
    // элементы
    $items = $grid.children( 'li' ),
    // индекс текущего раскрытого элемента
    current = -1,
    // позиция (top) раскрывшегося элемента
    previewPos = -1,
    // количество пикселей, на которое нужно прокрутить страницу
    scrollExtra = 0,
    // значение margin раскрытого элемента (между изображением и следующим элементов в строке)
    marginExpanded = 10,
    $window = $( window ), winsize,
    $body = $( 'html, body' ),
    // события трансформации (перемещения)
    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() };
}

Функция showPreview предназначена для инициализации объекта Preview, который в последствии будет раскрываться, показывая название, описание и крупное изображение. Если данный объект уже создан, то мы просто обновим его внутренние данные (в зависимости от того, какое действие совершил пользователь: закрыл представление, кликнул на другую миниатюру и так далее).

Для проверки клика по миниатюре, которая находится в том же ряду с уже кликнутой (раскрытой), воспользуемся значением смещения top данных элементов:

function showPreview( $item ) {

    var preview = $.data( this, 'preview' ),
        // смещение сверху
        position = $item.data( 'offsetTop' );

    scrollExtra = 0;

    // если представление полного изображения существует и previewPos отличается (разные строки в сетке), закроем панель
    if( typeof preview != 'undefined' ) {

        // не в одной и той же строке
        if( previewPos !== position ) {
            // если position > previewPos, то берём в расчёт текущую высоту панели представления для скроллинга (смещения) страницы
            if( position > previewPos ) {
                scrollExtra = preview.height;
            }
            hidePreview();
        }
        // если в той же строке
        else {
            preview.update( $item );
            return false;
        }

    }

    // обновляем previewPos
    previewPos = position;
    // инициализируем новую панель для представления элемента, по которому был осуществлён клик
    preview = $.data( this, 'preview', new Preview( $item ) );
    // раскрыть представление
    preview.open();

}

Объект Preview будет ссылаться на элемент, который отображается на текущем этапе (Preview.$item), а также будет иметь доступ к индексу раскрытого элемента (Preview.expandedIdx). Благодаря этим значениям, мы можем определить, закрывать панель представления (при клике по другой миниатюре из другой строки сетки) или оставить панель и только обновить данные (при клике по миниатюре в той же строке сетки).

// объект preview
function Preview( $item ) {
    this.$item = $item;
    this.expandedIdx = this.$item.index();
    this.create();
    this.update();
}

Теперь, когда объект Preview готов, можем взяться за код, который будет выводить детализированную информацию об изображении:

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 );
    // прикрепляем html код к элементу
    this.$item.append( this.getEl() );
    // задаём элементу настройки перемещения
    if( support ) {
        this.setTransition();
    }
}

Функция update будет задействована для обновления информации в панели представления:

update : function( $item ) {

    // обновить настройки элемента
    if( $item ) {
        this.$item = $item;
    }

    // если раскрыта, убираем класс "og-expanded" с текущего элемента
    if( current !== -1 ) {
        var $currentItem = $items.eq( current );
        $currentItem.removeClass( 'og-expanded' );
        this.$item.addClass( 'og-expanded' );
        // позиция Preview объекта
        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() {

    // сместить страницу
    // вариант 1 : высота превью + высота элемента (помещается в размер окна)
    // вариант 2 : высота превью + высота элемента (не помещается в размер окна). Получившийся размер меньше размера окна.
    // вариант 3 : высота превью + высота элемента (не помещается в размер окна). Получившийся размер больше размера окна.
    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 );

}

При закрытии панели необходимо поставить всё на свои места и удалить скрытый html из 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 );
// текущий раскрытый элемент (может отличаться от 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;

}

Надеюсь, данный урок и его результат будут вам полезны!

Источник: http://feedproxy.google.com/~r/ruseller/CdHX/~3/2q35KhfBxWE/lessons.php

Читать комменты и комментировать

Добавить комментарий / отзыв



Защитный код
Обновить

Сетка миниатюр с возможностью расширенного представления | | 2013-04-05 23:21:38 | | Статьи Web-мастеру | | В последнее время поиск изображений через Google несколько преобразился. Сегодня мы рассмотрим, каким образом можно организовать подобный функционал. Идея заключается в отображении более крупного | РэдЛайн, создание сайта, заказать сайт, разработка сайтов, реклама в Интернете, продвижение, маркетинговые исследования, дизайн студия, веб дизайн, раскрутка сайта, создать сайт компании, сделать сайт, создание сайтов, изготовление сайта, обслуживание сайтов, изготовление сайтов, заказать интернет сайт, создать сайт, изготовить сайт, разработка сайта, web студия, создание веб сайта, поддержка сайта, сайт на заказ, сопровождение сайта, дизайн сайта, сайт под ключ, заказ сайта, реклама сайта, хостинг, регистрация доменов, хабаровск, краснодар, москва, комсомольск |
 
Поделиться с друзьями: