Отображение морфинга, которое оживляет в соответствии с размером его содержимого.
Несколько месяцев назад Stripe.com запустила новый дизайн сайта. Это выглядит потрясающе. Одна вещь, которую мы нарыли, - это раскрывающийся список навигации по морфингам: вместо того, чтобы скрывать и показывать новый раскрывающийся «контейнер», когда пользователь переключается с одного элемента навигации на другой, они оживляют раскрывающийся фон, чтобы освободить место для разных размеров контента.
Создание структуры
Структура HTML состоит из двух основных элементов: nav.main-nav
для верхних элементов навигации и div.morph-dropdown-wrapper
, обертывающих выпадающие элементы.
Для каждого элемента списка в элементе nav.main-nav
в оболочке .morph-dropdown-wrapper
создается li.dropdown
.
<header class="cd-morph-dropdown">
<a href="#0" class="nav-trigger">Open Nav<span aria-hidden="true"></span></a>
<nav class="main-nav">
<ul>
<li class="has-dropdown gallery" data-content="about">
<a href="#0">About</a>
</li>
<li class="has-dropdown links" data-content="pricing">
<a href="#0">Pricing</a>
</li>
<li class="has-dropdown button" data-content="contact">
<a href="#0">Contact</a>
</li>
</ul>
</nav>
<div class="morph-dropdown-wrapper">
<div class="dropdown-list">
<ul>
<li id="about" class="dropdown gallery">
<!-- dropdown content here -->
</li>
<li id="pricing" class="dropdown links">
<!-- dropdown content here -->
</li>
<li id="contact" class="dropdown button">
<!-- dropdown content here -->
</li>
</ul>
<div class="bg-layer" aria-hidden="true"></div>
</div> <!-- dropdown-list -->
</div> <!-- morph-dropdown-wrapper -->
</header>
Внутри div.morph-dropdown-wrapper
был создан дополнительный div.bg-layer
и используется для создания фонового рисунка.
Добавление стиля
На небольших устройствах по умолчанию скрывается оболочка div.morph-dropdown-wrapper
; когда пользователь нажимает значок меню, в раскрывающийся список .cd-morph-dropdown
добавляется класс .nav-open
, чтобы показать навигацию.
.cd-morph-dropdown {
position: relative;
}
.cd-morph-dropdown .morph-dropdown-wrapper {
display: none;
position: absolute;
top: 60px;
left: 0;
width: 100%;
}
.cd-morph-dropdown.nav-open .morph-dropdown-wrapper {
display: block;
}
На больших устройствах (ширина окна просмотра более 1000 пикселей), .dropdown-list
и элементы li.dropdown
скрыты по умолчанию.
@media only screen and (min-width: 1000px) {
.cd-morph-dropdown .dropdown-list {
position: absolute;
top: 0;
left: 0;
visibility: hidden;
}
.cd-morph-dropdown .dropdown {
position: absolute;
left: 0;
top: 0;
opacity: 0;
visibility: hidden;
width: 100%;
transition: opacity .3s, visibility .3s;
}
}
Когда пользователь наводится над одним из элементов внутри nav.main-nav
, класс .is-dropdown-visible
добавляется в раскрывающийся список .cd-morph-dropdown
, а видимость списка .dropdown-list
изменяется на видимый. На данный момент выпадающая оболочка видна, но ее содержимое по-прежнему скрыто. Чтобы показать контент, выбранный пользователем, класс .active
добавляется в выбранный элемент li.dropdown
(тот, у которого идентификатор равен содержимому содержимого элемента навигации, на котором нависает пользователь).
@media only screen and (min-width: 1000px) {
.cd-morph-dropdown .dropdown.active {
opacity: 1;
visibility: visible;
}
}
Поскольку элементы li.dropdown
имеют абсолютную позицию, они фактически не занимают пространство внутри своего родителя (div.dropdown-list
), это означает, что ширина и высота div.dropdown-list
не изменяются в зависимости от видимого содержимого. Поскольку его свойство переполнения установлено в скрытое (чтобы содержимое не было видимым вне раскрывающей оболочки), мы используем JavaScript для изменения его высоты и ширины, чтобы убедиться, что выбранный раскрывающийся контент всегда отображается.
Чтобы создать раскрывающийся фон, мы используем div.bg-layer
. Он имеет абсолютное положение, ширину и высоту, равную 1px и непрозрачность нуля. Когда класс .is-dropdown-visible
добавлен в раскрывающийся список .cd-morph-dropdown
, его непрозрачность изменяется на единицу и масштабируется (с использованием JavaScrip), чтобы соответствовать видимой области содержимого.
@media only screen and (min-width: 1000px) {
.cd-morph-dropdown .bg-layer {
/* morph dropdown background */
position: absolute;
top: 0;
left: 0;
height: 1px;
width: 1px;
background: #FFFFFF;
opacity: 0;
transition: opacity .3s;
transform-origin: top left;
}
.cd-morph-dropdown.is-dropdown-visible .bg-layer {
opacity: 1;
transition: transform .3s, opacity .3s;
}
}
Когда пользователь переходит от элемента навигации к другому, значения scaleX и scaleY уровня div.bg-layer
изменяются (с использованием JavaScript) для создания эффекта морфинга.
Обработка событий
Чтобы реализовать эту навигацию, мы создали объект morphDropdown
и использовали метод bindEvents
для присоединения обработчиков событий к соответствующим элементам.
function morphDropdown( element ) {
this.element = element;
this.mainNavigation = this.element.find('.main-nav');
this.mainNavigationItems = this.mainNavigation.find('.has-dropdown');
this.dropdownList = this.element.find('.dropdown-list');
//...
this.bindEvents();
}
Метод bindEvents
используется для обнаружения событий mouseenter
/ mouseleave
в элементах .has-dropdown
и .dropdown
.
morphDropdown.prototype.bindEvents = function() {
var self = this;
this.mainNavigationItems.mouseenter(function(event){
//hover over one of the nav items -> show dropdown
self.showDropdown($(this));
}).mouseleave(function(){
//if not hovering over a nav item or a dropdown -> hide dropdown
if( self.mainNavigation.find('.has-dropdown:hover').length == 0 && self.element.find('.dropdown-list:hover').length == 0 ) self.hideDropdown();
});
//...
};
Метод showDropdown
заботится об изменении значений высоты, ширины и translateX .dropdown-list
и масштабирования вверх / вниз элемента .bg-layer
.
morphDropdown.prototype.showDropdown = function(item) {
var selectedDropdown = this.dropdownList.find('#'+item.data('content')),
selectedDropdownHeight = selectedDropdown.innerHeight(),
selectedDropdownWidth = selectedDropdown.children('.content').innerWidth(),
selectedDropdownLeft = item.offset().left + item.innerWidth()/2 - selectedDropdownWidth/2;
//update dropdown and dropdown background position and size
this.updateDropdown(selectedDropdown, parseInt(selectedDropdownHeight), selectedDropdownWidth, parseInt(selectedDropdownLeft));
//add the .active class to the selected .dropdown and .is-dropdown-visible to the .cd-morph-dropdown
//...
};
morphDropdown.prototype.updateDropdown = function(dropdownItem, height, width, left) {
this.dropdownList.css({
'-moz-transform': 'translateX(' + left + 'px)',
'-webkit-transform': 'translateX(' + left + 'px)',
'-ms-transform': 'translateX(' + left + 'px)',
'-o-transform': 'translateX(' + left + 'px)',
'transform': 'translateX(' + left + 'px)',
'width': width+'px',
'height': height+'px'
});
this.dropdownBg.css({
'-moz-transform': 'scaleX(' + width + ') scaleY(' + height + ')',
'-webkit-transform': 'scaleX(' + width + ') scaleY(' + height + ')',
'-ms-transform': 'scaleX(' + width + ') scaleY(' + height + ')',
'-o-transform': 'scaleX(' + width + ') scaleY(' + height + ')',
'transform': 'scaleX(' + width + ') scaleY(' + height + ')'
});
};