Skip to content

Анимированное меню по краям страницы

На днях я видел хорошую концепцию меню на сайте UI8. CreativeDash реализовали эту концепцию, и у меня сразу появилось несколько идей для эффектов, связанных с анимацией границ. Сегодня я хочу рассказать вам, как создать что-то подобное, и показать некоторые более вдохновляющие примеры.

В этом уроке мы рассмотрим вторую демонстрацию, где иконка меню находится в верхнем левом углу и меню расположено на левой стороне.

Итоговый результат
Итоговый результат

Обратите внимание, что мы будем использовать переходы и анимации для псевдо-элементов, которые не будут работать в некоторых браузерах (например, Safari и Mobile Safari). Итак, давайте начнем!

Разметка

HTML структура нашего меню состоит из элемента nav, в котором расположены триггер и неупорядоченный список с пунктами-иконками меню:

<nav id="bt-menu" class="bt-menu">
    <a href="#" class="bt-menu-trigger"><span>Menu</span></a>
    <ul>
        <li><a href="#" class="bt-icon icon-zoom">Zoom</a></li>
        <li><a href="#" class="bt-icon icon-refresh">Refresh</a></li>
        <li><a href="#" class="bt-icon icon-lock">Lock</a></li>
        <li><a href="#" class="bt-icon icon-speaker">Sound</a></li>
        <li><a href="#" class="bt-icon icon-star">Favorite</a></li>
    </ul>
</nav>

Вперед, придадим стиля нашему меню!

CSS

Примечание: в уроке все вендорные префиксы опущены, но вы найдете их в демонстрации

Используем box-sizing: border-box:

*,
*:after,
*::before {
    box-sizing: border-box;
}

Добавим стили для body и главного контейнера:

body  {
    background: #04a466;
}

.container {
    padding: 80px;
}

Внутренний отступ предоставляет пространство вокруг контента. Это гарантирует нам, что когда появится рамка, для нее будет достаточно места.

Меню будет фиксировано, чтобы всегда оставалось у нас на виду. Мы устанавливаем начальный стиль границ, который будет увеличиваться. Установка начальной высоты в 0 поможет убедиться, что меню изначально скрыто. Переход будет осуществляться за 0,3 секунды:

.bt-menu {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 0;
    border-width: 0px;
    border-style: solid;
    border-color: #333;
    background-color: rgba(0,0,0,0);
    transition: border-width 0.3s, background-color 0.3s, height 0s 0.3s;
}

Когда мы откроем меню, высота измениться до 100%, и ширина границ измениться до 90 пикселей слева и до 30 — с других сторон, фоновый цвет будет полупрозрачным, используя RGBA:

.bt-menu.bt-menu-open {
    height: 100%;
    border-width: 30px 30px 30px 90px;
    background-color: rgba(0,0,0,0.3);
    transition: border-width 0.3s, background-color 0.3s;
}

Теперь мы должны использовать маленькую хитрость. Мы добавим еще один элемент, используя JavaScript, который будет служить в качестве фиктивного контейнера. Этот элемент будет перекрывать всю страницу, за исключением границ меню. Это позволит нам определить клик по свободному пространству, чтобы закрыть меню:

.bt-overlay {
    position: absolute;
    width: 100%;
}

Когда мы открываем меню, данный элемент должен иметь полную высоту:

.bt-menu-open .bt-overlay {
    height: 100%;
}

Давайте зададим стили триггеру. Мы дадим ему фиксированное позицию и расположим в левом верхнем углу страницы:

.bt-menu-trigger {
    position: fixed;
    top: 15px;
    left: 20px;
    display: block;
    width: 50px;
    height: 50px;
    cursor: pointer;
}

Триггер содержит в себе иконку (три горизонтальные линии). Ее мы расположим в середине триггера:

.bt-menu-trigger span {
    position: absolute;
    top: 50%;
    left: 0;
    display: block;
    width: 100%;
    height: 4px;
    margin-top: -2px;
    background-color: #fff;
    font-size: 0px;
    user-select: none;
    transition: background-color 0.3s;
}

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

.bt-menu-open .bt-menu-trigger span {
    background-color: transparent;
}

Теперь, давайте создадим две другие линии. Псевдо-элементы будут расположены абсолютно и их высота будет та же, как у родителя, установив ее на 100%:

.bt-menu-trigger span:before,
.bt-menu-trigger span:after {
    position: absolute;
    left: 0;
    width: 100%;
    height: 100%;
    background: #fff;
    content: '';
    transition: transform 0.3s;
}

Чтобы позиционировать их корректней, используем translateY:

.bt-menu-trigger span:before {
    transform: translateY(-250%);
}

.bt-menu-trigger span:after {
    transform: translateY(250%);
}

Крест будет сформирован при открытии меню, установив TranslateY в 0 и повернув псевдо-элементы соответственно:

.bt-menu-open .bt-menu-trigger span:before {
    transform: translateY(0) rotate(45deg);
}

.bt-menu-open .bt-menu-trigger span:after {
    transform: translateY(0) rotate(-45deg);
}

Неупорядоченный список с нашими иконками также будет обладать фиксированной позицией и мы установим его к левой стороне окна:

.bt-menu ul {
    position: fixed;
    top: 75px;
    left: 0;
    margin: 0;
    padding: 0;
    width: 90px;
    list-style: none;
    backface-visibility: hidden;
}

Давайте установим элементам списка полную ширину:

.bt-menu ul li,
.bt-menu ul li a {
    display: block;
    width: 100%;
    text-align: center;
}

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

.bt-menu ul li {
    padding: 16px 0;
    opacity: 0;
    visibility: hidden;
    transition: transform 0.3s, opacity 0.2s, visibility 0s 0.3s;
}

Теперь трансформируем каждый из элементов списка по-отдельности так, что они все помещены по середине и влево, пока они не скрыты (-100% по оси Y):

.bt-menu ul li:first-child { 
    transform: translate3d(-100%,200%,0);
}

.bt-menu ul li:nth-child(2) { 
    transform: translate3d(-100%,100%,0);
}

.bt-menu ul li:nth-child(3) { 
    transform: translate3d(-100%,0,0);
}

.bt-menu ul li:nth-child(4) { 
    transform: translate3d(-100%,-100%,0);
}

.bt-menu ul li:nth-child(5) { 
    transform: translate3d(-100%,-200%,0);
}

При открытии меню, элементы списка станут видимыми (мгновенно, потому что мы не устанавливаем transition на них). Они также перемещаются в их первоначальное положение, установив translate3d в 0 по всем осям:

.bt-menu.bt-menu-open ul li {
    visibility: visible;
    opacity: 1;
    transition: transform 0.3s, opacity 0.3s;
    transform: translate3d(0,0,0);
}

Теперь займёмся ссылками. Мы будем использовать шрифт из исконок, который вы можете создать на сервисах IcoMoon или Fontastic.

Скроем их по-умолчанию, поставив размер шрифта 0:

.bt-menu ul li a {
    display: block;
    outline: none;
    color: transparent;
    text-decoration: none;
    font-size: 0px;
}

Сбросим размер текста для псевдо-элементов, которые содержат иконки. Нам придется использовать пиксели, потому что значения em тут работать не будут, т.к. у основного элемента размер шрифта равен 0:

.bt-menu ul li a:before {
    color: #04a466;
    font-size: 48px;
    transition: color 0.2s;
}

При наведении мыши их цвет меняется на белый:

.bt-menu ul li a:hover:before,
.bt-menu ul li a:focus:before  {
    color: #fff;
}

Сделаем иконки чуть меньше, если пользователь просматривает сайт через мобильное устройство:

@media screen and (max-height: 31.125em) {
    .bt-menu ul li a:before {
        font-size: 32px;
    }
}

Со стилями всё. Приступаем к JavaScript.

JavaScript

Наш скрипт довольно прост: при клике по ссылке, мы сменяем классы bt-menu-open и bt-menu-close на элементе nav. Добавление закрывающего класса необходимо тогда, когда используется анимация для иконки триггера (демо 1). Это позволяет нам проигрывать только обратную анимацию при закрытии меню.

При клике на слой-наложение (помните мы использовали трюк и добавили слой .bt-overlay), меню будет закрыто. Так же нам нужно обеспечить поддержку событий прикосновений:

(function() {

    // http://stackoverflow.com/a/11381730/989439
    function mobilecheck() {
        var check = false;
        (function(a){if(/(android|ipad|playbook|silk|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera);
        return check;
    }

    function init() {

        var menu = document.getElementById( 'bt-menu' ),
            trigger = menu.querySelector( 'a.bt-menu-trigger' ),
            // event type (if mobile, use touch events)
            eventtype = mobilecheck() ? 'touchstart' : 'click',
            resetMenu = function() {
                classie.remove( menu, 'bt-menu-open' );
                classie.add( menu, 'bt-menu-close' );
            },
            closeClickFn = function( ev ) {
                resetMenu();
                overlay.removeEventListener( eventtype, closeClickFn );
            };

        var overlay = document.createElement('div');
        overlay.className = 'bt-overlay';
        menu.appendChild( overlay );

        trigger.addEventListener( eventtype, function( ev ) {
            ev.stopPropagation();
            ev.preventDefault();

            if( classie.has( menu, 'bt-menu-open' ) ) {
                resetMenu();
            }
            else {
                classie.remove( menu, 'bt-menu-close' );
                classie.add( menu, 'bt-menu-open' );
                overlay.addEventListener( eventtype, closeClickFn );
            }
        });

    }

    init();

})();

Вот и всё! Я надеюсь, данный урок будет вам полезен! Проверьте оставшиеся демонстрации. Последний концепт предлагает вам оформление полноэкранного видео-плеера.

Источник: Codrops