Веб-программирование

Горизонтальное расположение контента

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

Горизонтальное расположение контента
Поделиться в соцсетях:

Задача состоит в том, чтобы это работало с различными размерами окна, поэтому мы будем изменять панели с контентом, если экран не достаточно широк.

Мы будем использовать несколько полезных плагинов jQuery:

  • History.js by Benjamin Lupton
  • jScrollPane by Kelvin Luck
  • jquery-sparkle by Benjamin Lupton
  • jquery.easing
  • jquery.smartresize by Louis Remi

В демо мы используем книгу Чарльза Дарвина "Происхождение видов путем естественного отбора", 6-е издание которого вы можете найти в Project Gutenberg.

HTML-разметка

HTML-код будет состоять из меню, которое будет зафиксированно в левой части экрана и контейнера, который будет содержать панели с контентом. Все это будет обернуто в основной контейнер:

<div id="hs-container" class="hs-container">
    <!-- ... -->
</div>

Меню будет иметь следующую структуру:

<aside class="hs-menu" id="hs-menu">

    <div class="hs-headline">
        <h1>
            <small>The</small> Origin <small>of</small> Species
        </h1>
        <small>6<sup>th</sup> Edition</small>
        <span class="author">by Charles Darwin</span>
    </div>

    <nav>
        <a href="#introduction">
            <span>Introduction</span>
        </a>
        <a href="#chapter1">
            <span>Chapter I.</span>
            <span>Variation under Domestication</span>
        </a>
        <a href="#chapter2">
            <span>Chapter II.</span>
            <span>Variation Under Nature </span>
        </a>
        <!-- ... -->
    </nav>

</aside>

<a href="#hs-menu" class="hs-totop-link">Go to the top</a>

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

Содержание будет иметь следующую структуру:

<div class="hs-content-scroller">

    <div class="hs-content-wrapper">

        <article class="hs-content" id="introduction">
            <div class="hs-inner">
                <h2>Introduction</h2>
                <p>...</p>
            </div>
        </article>

        <article class="hs-content" id="chapter1">
            <div class="hs-inner">
                <h2>Chapter I.</h2>
                <h3>Variation Under Domestication</h3>
                <h4>Causes of Variability</h4>
                <p>...</p>
            </div>
        </article>

    <!-- ... -->

    </div>

</div>

Первая оболочка с классом hs-content-scroller будет действовать как маска, которая имеет ширину и высоту и которая видна только в окне просмотра. Это будет область, которую мы будем видеть при горизонтальной прокрутке, вторая же оболочка с классом hs-content-wrapper будет так велика, как сумма ширины всех статей.

Как видите, каждая статья получит свой ID, на который мы ссылаемся в нашей навигации.

Теперь перейдем к написанию стилей.

CSS

Итак, наша главная цель заключается в фиксации боковой панели в левой части экрана и поместить содержимое в виде горизонтального стека. Давайте сначала зададим стили для body и некоторых заголовков. Мы установим, overflow-x и overflow-y как hidden. Сначала подключим normalize.css, это разумная альтернатива общего сброса отступов для различных элементов страницы:

@import url('normalize.css');

body{
    font-family: Baskerville, "Hoefler Text", Garamond, "Times New Roman", serif;
    background: #e9f0f5 url(../images/pattern.png) repeat top left;
    font-weight: 400;
    font-size: 12px;
    color: #333;
    overflow-y: hidden;
    overflow-x: hidden;
}

Далее давайте определим некоторые общие стили для текста:

h1, h3, h4 {
    font-weight: 400;
}
h1 {
    font-style: italic;
    border-bottom: 1px solid rgba(126, 126, 126, 0.3);
    padding: 35px 15px 15px 15px;
    margin: 0px 20px 20px 20px;
    position: relative;
    font-size: 38px;
}
h2 {
    font-size: 40px;
    padding-bottom: 15px;
    border-bottom: 5px solid rgba(190, 211, 226, 0.2);
    color: #a9becd;
    text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.4);
    box-shadow: 0px 1px 0px 0px rgba(255, 255, 255, 0.4);
    font-weight: 700;
}
h3 {
    font-style: italic;
    font-size: 26px;
    color: #585959;
    text-shadow: 1px 0px 1px rgba(255, 255, 255, 0.4);
}
h4 {
    text-transform: uppercase;
    letter-spacing: 5px;
    line-height: 20px;
    padding: 10px 0px;
    color: #626a6f;
    border-bottom: 1px solid rgba(126, 126, 126, 0.1);
    box-shadow: 0px 1px 0px 0px rgba(255, 255, 255, 0.4);
}
a {
    color: #308fd9;
    text-decoration: none;
    text-transform: uppercase;
    letter-spacing: 2px;
}
a:hover {
    color: #87b6da;
}
.hs-headline {
    text-align: center;
}
.hs-author {
    text-transform: uppercase;
    display: block;
    letter-spacing: 2px;
    font-weight: 700;
    padding: 20px 10px;
}

Теперь зададим положение для меню:

.hs-menu {
    position: fixed;
    z-index: 100;
    color: #f8f8f8;
    background: #131313;
    width: 200px;
    left: 0px;
    top: 0px;
    height: 100%;
}

Установим position: fixed, чтобы меню оставалось в боковой панели в левой части экрана.

Навигация будет иметь следующие стили:

.hs-menu nav {
    position: absolute;
    top: 250px;
    left: 0px;
    right: 0px;
    bottom: 50px;
}

Навигация будет иметь position: absolute с установленными верхним и нижним значениями, что позволит сделать её растягивающейся по высоте.

Для ссылок зададим следующие стили:

.hs-menu nav a {
    display: block;
    padding: 10px 20px;
    text-align: center;
    outline: none;
    border-bottom: 1px dashed rgba(126, 126, 126, 0.2);
}
.hs-menu nav a:active {
    box-shadow: 7px 0px 17px #000 inset;
}
.hs-menu nav a:first-child {
    border-top: 1px dashed rgba(126, 126, 126, 0.2);
}

Для второго тега span в навигации будут немного другие стили:

.hs-menu nav a span:nth-child(2) {
    display: block;
    color: #fff;
    font-style: italic;
    font-weight: 400;
    text-transform: none;
    padding-top: 3px;
}

Теперь зададим стили для контента. Как упоминалось ранее, область с классом .hs-content-scroller будет действовать как маска, где любое переполнение области не будет видно. Это по сути тот же принцип, что и в слайдерах. Левый отступ будет установлен равным 200 пикселей, так как там у нас боковая панель:

.hs-content-scroller {
    position: absolute;
    left: 200px;
    right: 0px;
    overflow: hidden;
    height: 100%;
}

Следующая оболочка будет иметь ширину, которая позволяет поместить внутри ее все внутренние панели с содержанием, если укладывають их горизонтально. А так как мы установили overflow: hidden, поэтому панели с содержанием будет иметь кастомные полосы прокрутки.

.hs-content-wrapper {
    width: 7950px;
    position: absolute;
    height: 100%;
    overflow: hidden;
}

Каждая панель с контентом будет иметь ширину 500 пикселей и float: left. Мы добавим transition для фона, когда мы наводим курсор мыши и когда мы добавляем класс active:

.hs-content {
    width: 500px;
    overflow-y: scroll;
    height: 100%;
    float: left;
    border-right: 1px dashed rgba(126, 126, 126, 0.2);
    border-left: 1px dashed rgba(255, 255, 255, 0.5);
    background: transparent;
    transition: background 0.3s linear;
}
.hs-content:hover, .hs-content-active {
    background: #f1f5f8;
}

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

.hs-content:hover .jspVerticalBar,
.hs-menu nav:hover .jspVerticalBar {
    opacity: 1;
}


Первая панель с контентом будет немного уже:

.hs-content:first-child {
    width: 400px;
}

Давайте добавим некоторые стили для текстовых элементов:

.hs-inner {
    padding: 30px 20px 10px 30px;
}
.hs-inner p {
    font-size: 18px;
    line-height: 24px;
    text-align: justify;
    position: relative;
    padding: 10px 0px;
    text-shadow: 1px 0px 1px rgba(255, 255, 255, 0.8);
}
.hs-inner p:first-letter {
    font-size: 28px;
}
#introduction .hs-inner p:first-of-type {
    font-size: 24px;
    text-align: left;
    line-height: 36px;
    font-style: italic;
    color: #75838D;
    letter-spacing: 0px;
}

Помните маленькую ссылку сразу после боковой панели? При нажатии на её страница прокручивается вверх. Но нам нужна эта ссылка только когда наш экран недостаточно большой, когда контент расположен по вертикали, так что мы укажем изначально для ссылки display: none. В media query мы затем покажем её.

a.hs-totop-link {
    display: none;
    position: fixed;
    z-index: 10000;
    bottom: 0px;
    width: 100%;
    height: 40px;
    line-height: 40px;
    font-size: 14px;
    color: #aaa;
    text-shadow: 1px 1px 1px #fff;
    font-weight: 700;
    cursor: pointer;
    text-transform: uppercase;
    text-align: center;
    background: linear-gradient(top, #ffffff 0%,#f3f3f3 50%,#ededed 51%,#ffffff 100%);
    border-top: 1px solid #cacaca;
}

Когда экран уменьшается до размера iPad, мы хотим показать горизонтальную полосу прокрутки.

/* Media Queries */
@media screen and (max-width: 1024px) {
	.jspVerticalBar{
		opacity: 1;
	}
	.hs-content-scroller {
		overflow-x: scroll;
	}
	.hs-content:hover {
		background: transparent;
	}
	.hs-content-active:hover {
		background: #f1f5f8;
	}
}

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

@media screen and (max-width: 715px) {
    body {
        overflow-x: auto;
        overflow-y: auto;
    }
    a.hs-totop-link {
        display: block;
    }
    .hs-menu {
        position: relative;
        width: 100%;
        height: 460px;
    }
    .hs-menu nav {
        top: 230px;
        bottom: 20px;
    }
    .hs-content-scroller {
        position: relative;
        height: auto;
        left: 0;
    }
    .hs-content-wrapper {
        height: auto;
        width: auto;
        margin-left: 0px;
    }
    .hs-content {
        border: none;
    }
    .hs-content, .hs-content:first-child {
        width: auto;
        float: none;
        overflow-y: auto;
    }
}

И это все, что касается стилей! Теперь давайте взглянем на JavaScript!

JavaScript

Во-первых, мы определим некоторые переменные:

var $container = $( '#hs-container' ),
// the scroll container that wraps the articles
$scroller= $container.find( 'div.hs-content-scroller' ),
$menu= $container.find( 'aside' ),
// menu links
$links= $menu.find( 'nav > a' ),
$articles= $container.find( 'div.hs-content-wrapper > article' ),
// button to scroll to the top of the page
// only shown when screen size < 715
$toTop = $container.find( 'a.hs-totop-link' ),
// the browser nhistory object
History = window.History,
// animation options
animation = { speed : 800, easing : 'easeInOutExpo' },
// jScrollPane options
scrollOptions = { verticalGutter : 0, hideFocus : true },

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

init = function() {
    // initialize the jScrollPane on both the menu and articles
    _initCustomScroll();
    // initialize some events
    _initEvents();
    // sets some css properties
    _layout();
    // jumps to the respective chapter
    // according to the url
    _goto();
}

Первый шаг заключается в создании/инициализации JScrollPane (кастомные полосы прокрутки) для меню и статей. Однако, для статьи мы не будем делать это, в случае если размер экрана меньше, чем 715px:

_initCustomScroll   = function() {
    // Only add custom scrolling to articles if screen size > 715.
    // If not, the articles will be expanded (vertical layout)
    if( $(window).width() > 715 ) {
        $articles.jScrollPane( scrollOptions );
    }
    // add custom scrolling to menu
    $menu.children( 'nav' ).jScrollPane( scrollOptions );
}

Когда размер окна будет изменяться, мы должны заново инициализировать jScrollPane, или уничтожить его в случае, если размер экрана становится меньше, чем 715px.

Мы используем History.js от Benjamin Lupton, чтобы контролировать историю состояний, когда пользователь переходит по странице:

$(window).on({
    // when resizing the window we need to reinitialize or destroy the jScrollPanes
    // depending on the screen size
    'smartresize' : function( event ) {
        _layout();
        $('article.hs-content').each( function() {
            var $article = $(this),
            aJSP = $article.data( 'jsp' );
            if ( $(window).width() > 715 ) {
                ( aJSP === undefined ) ? $article.jScrollPane( scrollOptions ) : aJSP.reinitialise();
                _initArticleEvents();
            }
            else {
                // destroy article's custom scroll if screen size <= 715px
                if ( aJSP !== undefined )
                aJSP.destroy();
                $container.off( 'click', 'article.hs-content' );
            }
        });
        var nJSP = $menu.children( 'nav' ).data( 'jsp' );
        nJSP.reinitialise();
        // jumps to the current chapter
        _goto();
    },
    // triggered when the history state changes - jumps to the respective chapter
    'statechange' : function( event ) {
        _goto();
    }
});

Когда мы нажимаем на ссылку статьи или меню, мы проверяем о какой главе идет речь, и мы будем изменять состояние объекта browser history. Это вызовет событие StateChange, которое, в свою очередь, сделает переход к соответствующей области.

$links.on( 'click', function( event ) {
    var href  = $(this).attr('href'),
    chapter= ( href.search(/chapter/) !== -1 ) ? href.substring(8) : 0;
    _saveState( chapter );
    return false;
});

$container.on( 'click', 'article.hs-content', function( event ) {
    var id = $(this).attr('id'),
    chapter = ( id.search(/chapter/) !== -1 ) ? id.substring(7) : 0;
    _saveState( chapter );
    return false;
});

_saveState = function( chapter ) {
    // adds a new state to the history object
    // this will trigger the statechange on the window
    if ( History.getState().url.queryStringToJSON().chapter !== chapter ) {
        History.pushState( null, null, '?chapter=' + chapter );
    }
}

Мы также будем изменять переменную "scroller" (область с классом "hs-content-scroller") в соответствии с размером экрана.

_layout= function() {
    var windowWidth = $(window).width();
    switch( true ) {
        case ( windowWidth <= 715 ) : $scroller.scrollLeft( 0 ).css( 'overflow', 'visible' ); break;
        case ( windowWidth <= 1024 ): $scroller.css( 'overflow-x', 'scroll' ); break;
        case ( windowWidth > 1024 ) : $scroller.css( 'overflow', 'hidden' ); break;
    };
}

Функция _goto запускает анимацию для изменения области/главы на странице. Мы получаем текущую главу из истории URL и  мы либо перемещаемся влево, либо вверх, в зависимости от размера экрана.

_goto= function( chapter ) {
    // get the url from history state (e.g. chapter=3) and extract the chapter number
    var chapter = chapter || History.getState().url.queryStringToJSON().chapter,
    isHome  = ( chapter === undefined ),
    // we will jump to the introduction chapter if theres no chapter
    $article = $( chapter ? '#' + 'chapter' + chapter : '#' + 'introduction' );

    if ( $article.length ) {
        // left / top of the element
        var left = $article.position().left,
        top = $article.position().top,
        // check if we are scrolling down or left
        // is_v will be true when the screen size < 715
        is_v  = ( $(document).height() - $(window).height() > 0 ),
        // animation parameters:
        // if vertically scrolling then the body will animate the scrollTop,
        // otherwise the scroller (div.hs-content-scroller) will animate the scrollLeft
        param  = ( is_v ) ? { scrollTop : (isHome) ? top : top + $menu.outerHeight( true ) } : { scrollLeft : left },
        $elScroller = ( is_v ) ? $( 'html, body' ) : $scroller;

        $elScroller.stop().animate( param, animation.speed, animation.easing, function() {
            // active class for selected chapter
            $articles.removeClass( 'hs-content-active' );
            $article.addClass( 'hs-content-active' );
        } );
    }
}

И вот и всё! Я надеюсь, вам понравился этот урок и он будет вам полезен!

Перевод статьи с tympanus.net/codrops

Новый комментарий

Имя:
Для редактирования комментария осталось 10 минут
Комментарии отсутствуют