Сегодня мы расскажем вам как применить CSS-свойство clip для создания гладкого перехода при нажатии по элементу. Идея заключается в том, чтобы показать нечто вроде наложения так, будто оно находится под соответствующим элементом. Клик по элементу приведет к созданию эффекта вырезанного элемента, который также раскроет еще один слой с контентом.
Мы реализуем это следующим образом: сначала создаем список элементов, которые располагаются в стиле metro:
Каждый блок будет содержать элемент (наложение), который будет позиционирован абсолютно. Этот элемент будет распространяться по всей странице, но мы его не увидим, так как уровень его непрозрачности будет выставлен на 0. При нажатии на блок мы применим правило clip: rect()
и вырежем соответствующую часть внутреннего зафиксированного элемента. После этого мы анимируем clip для того, чтобы раскрыть всю ширину и высоту наложения, которое занимает все окно просмотра:
Нажатие по кнопке закрытия скроет эффект, а наложение уменьшится до размеров пункта меню, после чего совсем исчезнет.
Для начала напишем HTML-код. Для блоков воспользуемся неупорядоченным списком. Каждый пункт меню будет иметь класс icon и специальный класс span, отвечающий за ширину блока. Внутрь мы добавим некоторый текст и раздел overlay. Наложение будет содержать структуру, которая представляет собой блок из колонок. Каждая колонка будет иметь несколько блоков span, иконку погоды и температуры:
<ul id="rb-grid" class="rb-grid clearfix">
<li class="icon-clima-1 rb-span-2">
<h3>Лиссабон</h3>
<span class="rb-temp">21°C</span>
<div class="rb-overlay">
<span class="rb-close">close</span>
<div class="rb-week">
<div><span class="rb-city">Лиссабон</span><span class="icon-clima-1"></span><span>21°C</span></div>
<div><span>Пон.</span><span class="icon-clima-1"></span><span>19°C</span></div>
<div><span>Вт.</span><span class="icon-clima-2"></span><span>19°C</span></div>
<div><span>Ср.</span><span class="icon-clima-2"></span><span>18°C</span></div>
<div><span>Чт.</span><span class="icon-clima-2"></span><span>17°C</span></div>
<div><span>Пт.</span><span class="icon-clima-1"></span><span>19°C</span></div>
<div><span>Сб.</span><span class="icon-clima-1"></span><span>22°C</span></div>
<div><span>Вс.</span><span class="icon-clima-1"></span><span>18°C</span></div>
</div>
</div>
</li>
<li class="icon-clima-2">
<h3>Париж</h3><span class="rb-temp">11°C</span>
<div class="rb-overlay">
<!-- ... -->
</div>
</li>
<li><!-- ... --></li>
<!-- ... -->
</ul>
С HTML-кодом все. Пришел час CSS!
CSS стили погодного информера
Уберем стилизацию списка, а неупорядоченный список отцентрируем в родительском элементе:
.rb-grid {
list-style: none;
text-align: center;
margin: 0 auto;
}
Элементы списка li будут иметь динамическую ширину, высоту в 15em и будут выравнены по левой стороне:
.rb-grid li {
width: 24%;
height: 15em;
margin: 0.5%;
background: #8CC7DF;
color: #fff;
display: block;
float: left;
padding: 1.6em;
cursor: pointer;
position: relative;
}
Здесь есть три разные ширины для наших сеточных элементов: дефолтная с шириной 24%, а другие:
.rb-grid li.rb-span-2 {
width: 49%;
}
.rb-grid li.rb-span-4 {
width: 99%;
}
Для названия города используем следующие стили:
.rb-grid li h3 {
font-size: 2.6em;
font-weight: 100;
}
Мы подключим шрифт с иконками. Изучите файл climacons.css, чтобы увидеть включенные в него иконки. мы воспользуемся классом icon, чтобы добавить иконку c помощью псевдо-элемента. В нашей сетке нам нужно абсолютно спозиционировать их в правом нижнем углу, чтобы они появлялись немного обрезаными:
.rb-grid li[class^="icon-"]:before,
.rb-grid li[class*=" icon-"]:before {
font-size: 10em;
position: absolute;
display: block;
width: 100%;
height: 100%;
top: 0;
left: 0;
line-height: 3;
opacity: 0.4;
text-align: right;
pointer-events: none;
}
Температура будет полупрозрачной, ктоме того, мы добавим переход (transition) для плавного изменения ее прозрачности:
.rb-temp {
display: block;
font-size: 2em;
opacity: 0.5;
-moz-transition: all 0.3s ease-in-out;
-o-transition: all 0.3s ease-in-out;
-webkit-transition: all 0.3s ease-in-out;
transition: all 0.3s ease-in-out;
}
При наведении курсора мыши на элемент списка, мы увеличиваем его прозрачность:
.rb-grid li:hover .rb-temp {
opacity: 1;
}
Нашим окончательным результатом должно быть полноэкранное наложение, поэтому мы установим его ширину и высоту на 100% и дадим ему фиксированную позицию. Мы сделали так, что оно появляется поверх всего, но это приводит к наложению элементов друг на друга, поэтому изначально нам нужно выставить параметр z-index на -1. Это позволит поставить их за содержанием страницы. Установка непрозрачности до 0 сделает его невидимыми:
.rb-overlay {
opacity: 0;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
-moz-transition: all 0.4s ease;
-webkit-transition: all 0.4s ease;
-o-transition: all 0.4s ease;
transition: all 0.4s ease;
z-index: -1;
pointer-events: none;
cursor: default;
}
Это и будет исходное положение нашего наложения. Как только мы нажимаем на элемент списка, мы установим правильное значение rect()
для свойства clip свойства и расширим наложение с помощью анимирования значения rect()
. У каждого наложения будет кнопка закрытия, которая будет расположена в правом верхнем углу:
.rb-close {
position: absolute;
top: 0.4em;
right: 0.4em;
width: 2em;
height: 2em;
text-indent: -9000px;
cursor: pointer;
z-index: 1000;
}
.rb-close:before {
content: 'x';
font-weight: 100;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
font-size: 3em;
line-height: 0.6;
text-align: center;
text-indent: 0px;
}
Обертка для колонок будет иметь класс rb-week (в нем также будет колонка текущей погоды). Нам нужно выставить ее ширину и высоту на 100%, чтобы у нас была возможность правильно выставить высоту и ширину ее дочерних элементов:
.rb-week {
width: 100%;
height: 100%;
}
Колонки будут иметь ширину 10% (за исключением первой, у которой ширина составит 30%), и все они будут выравниваться по левому крау:
.rb-week > div {
width: 10%;
height: 100%;
float: left;
position: relative;
padding: 3% 0;
}
.rb-week > div:first-child {
width: 30%;
}
Итак, у нас 8 колонок, из них 7 имеют ширину 10%. В сумме это будет 70%, поэтому левая колонка (самая первая) у нас будет занимать оставшиеся 30%. Каждый из span-элементов будет иметь высоту 30% и небольшие отступы:
.rb-week span {
padding: 5% 0;
font-size: 2em;
font-weight: 100;
display: block;
margin: auto 0;
height: 30%;
width: 100%;
line-height: 0.8;
}
В span-элементе с названием города будет использоваться более тонкий шрифт:
.rb-week span.rb-city {
font-weight: 700;
padding: 1% 10%;
font-size: 1em;
line-height: 1.2;
}
У иконок шрифт будет крупнее, поэтому нам понадобится сбросить параметр font-weight, поскольку мы его изменили в другом правиле:
.rb-week [class^="icon-"]:before {
font-size: 2.5em;
font-weight: normal;
}
Иконка в колонке текущей погоды будет почти прозрачной:
.rb-week > div:first-child [class^="icon-"] {
opacity: 0.1;
}
Теперь займемся настройкой фона для каждого элемента списка и каждой колонки в наложениях. Напомню, у нас есть 11 элементов списка:
/* Цвета */
/* Сетка */
.rb-grid li:nth-child(1) { background: #3399CC; }
.rb-grid li:nth-child(2) { background: #33CCCC; }
.rb-grid li:nth-child(3) { background: #996699; }
.rb-grid li:nth-child(4) { background: #C24747; }
.rb-grid li:nth-child(5) { background: #e2674a; }
.rb-grid li:nth-child(6) { background: #FFCC66; }
.rb-grid li:nth-child(7) { background: #99CC99; }
.rb-grid li:nth-child(8) { background: #669999; }
.rb-grid li:nth-child(9) { background: #CC6699; }
.rb-grid li:nth-child(10) { background: #339966; }
.rb-grid li:nth-child(11) { background: #666699; }
В каждом наложении у нас по 8 колонок:
/* Колонки наложения */
.rb-grid li:nth-child(1) .rb-week > div:nth-child(1) { background: #3399CC; }
.rb-grid li:nth-child(1) .rb-week > div:nth-child(2) { background: #2D87B4; }
.rb-grid li:nth-child(1) .rb-week > div:nth-child(3) { background: #297AA3; }
.rb-grid li:nth-child(1) .rb-week > div:nth-child(4) { background: #256E93; }
.rb-grid li:nth-child(1) .rb-week > div:nth-child(5) { background: #216283; }
.rb-grid li:nth-child(1) .rb-week > div:nth-child(6) { background: #1D5672; }
.rb-grid li:nth-child(1) .rb-week > div:nth-child(7) { background: #184962; }
.rb-grid li:nth-child(1) .rb-week > div:nth-child(8) { background: #143D52; }
.rb-grid li:nth-child(2) .rb-week > div:nth-child(1) { background: #33CCCC; }
.rb-grid li:nth-child(2) .rb-week > div:nth-child(2) { background: #2DB4B4; }
.rb-grid li:nth-child(2) .rb-week > div:nth-child(3) { background: #29A3A3; }
.rb-grid li:nth-child(2) .rb-week > div:nth-child(4) { background: #259393; }
.rb-grid li:nth-child(2) .rb-week > div:nth-child(5) { background: #218383; }
.rb-grid li:nth-child(2) .rb-week > div:nth-child(6) { background: #1D7272; }
.rb-grid li:nth-child(2) .rb-week > div:nth-child(7) { background: #186262; }
.rb-grid li:nth-child(2) .rb-week > div:nth-child(8) { background: #145252; }
/* ... */
...и так для всех 11 блоков. Кроме того, что нам необходимо позаботиться о просмотре на мобильных устройствах и планшетах. В этом нам поможет CSS свойство media. Сделаем чтобы при небольших размерах экрана блоки не отображались в сетке:
@media screen and (max-width: 63.125em) {
.rb-grid li,
.rb-grid li.rb-span-2,
.rb-grid li.rb-span-4 {
width: 100%;
height: 10em;
text-align: left;
}
.rb-grid li[class^="icon-"]:before,
.rb-grid li[class*=" icon-"]:before {
font-size: 6em;
left: auto;
right: 0;
line-height: 2.5;
}
.rb-grid li > div {
text-align: center;
}
}
Колонки наложения и текст внутри будут обработаны специальным плагином FitText, поэтому нам не придется сильно изменять верстку. Теперь настало время написать код на jQuery (увы, без него никак).
javascript-код
Давайте начнем с кэширования некоторых элементов и инициализации нескольких переменных:
var $items = $('#rb-grid > li'),
transEndEventNames = {
'WebkitTransition' : 'webkitTransitionEnd',
'MozTransition' : 'transitionend',
'OTransition' : 'oTransitionEnd',
'msTransition' : 'MSTransitionEnd',
'transition' : 'transitionend'
},
// название перехода
transEndEventName = transEndEventNames[Modernizr.prefixed('transition')],
// элементы window и body
$window = $(window),
$body = $('BODY'),
// поддержка переходов
supportTransitions = Modernizr.csstransitions,
// индекс текущего элемента
current = -1,
// ширина и высота окна
winsize = getWindowSize();
Поясним что мы тут написали: сначала мы применяем jQuery-плагин FitText к элементам текста в колонках в наложениях для его адаптации к маленьким экранам. Затем мы привязываем каждое событие щелчка левой кнопкой мыши к пунктам и кнопкам закрытия (для каждого элемента списка). Также, нам нужно получить текущие значения ширины и высота окна, поэтому мы привязываем событие масштабирования к элементу окна.
function init(options) {
// применяем плагин fittext
$items.find('div.rb-week > div span')
.fitText(0.3)
.end()
.find('span.rb-city')
.fitText(0.5);
initEvents();
}
Когда мы кликаем по какому-нибудь элементу списка, происходит два перехода. Первый отвечает за применение параметра clip, который обрежет необходимую область для текущего элемента списка, а, изменяя параметр непрозрачности, мы покажем само наложение. Второй переход необходим чтобы наложение «расширилось» и заняло всю область окна. Для первого перехода нам нужны значения, соответствующие текущим позиции и размерам элементам списка. Их мы получаем с помощью функции getItemLayoutProp
. Для второго перехода нам требуются значения ширины и высоты окна.
Обратите внимание еще вот на что: мы «отображаем и скрываем» скроллинг страницы между положениями, так как нам не требуется прокрутка когда наложение раскрыто на весь экран. Также, мы задаем параметру z-index у наложения самое высокое значение, чтобы наше наложение всегда было поверх остальных элементов. Свойству pointer-events мы присваиваем значение auto чтобы и по наложению, и его содержимому можно было кликать. Если переходы не поддерживаются (предусмотрим и такую возможность), то мы просто пропускаем первое положение, и тогда при клике наложение мгновенно раскроется и покажется пользователю.
function initEvents() {
$items.each( function() {
var $item = $( this ),
$close = $item.find('span.rb-close'),
$overlay = $item.children('div.rb-overlay');
$item.on( 'click', function() {
if( $item.data('isExpanded') ) {
return false;
}
$item.data('isExpanded', true);
// Сохраняем текущий индекс у элемента в списке
current = $item.index();
var layoutProp = getItemLayoutProp($item),
clipPropFirst = 'rect('+ layoutProp.top + 'px ' + (layoutProp.left+layoutProp.width) + 'px ' + (layoutProp.top + layoutProp.height) + 'px ' + layoutProp.left + 'px)',
clipPropLast = 'rect(0px ' + winsize.width + 'px ' + winsize.height + 'px 0px)';
$overlay.css( {
clip : supportTransitions ? clipPropFirst : clipPropLast,
opacity : 1,
zIndex: 9999,
pointerEvents : 'auto'
});
if( supportTransitions ) {
$overlay.on(transEndEventName, function() {
$overlay.off(transEndEventName);
setTimeout( function() {
$overlay.css('clip', clipPropLast).on(transEndEventName, function() {
$overlay.off(transEndEventName);
$body.css('overflow-y', 'hidden');
});
}, 25);
} );
}
else {
$body.css( 'overflow-y', 'hidden' );
}
});
...
});
...
}
function getItemLayoutProp($item) {
var scrollT = $window.scrollTop(),
scrollL = $window.scrollLeft(),
itemOffset = $item.offset();
return {
left : itemOffset.left - scrollL,
top : itemOffset.top - scrollT,
width : $item.outerWidth(),
height : $item.outerHeight()
};
}
Что касается события click элемента для закрытия наложения, то здесь мы просто возвращаемся к тому, что уже было сделано ранее:
function initEvents() {
$items.each(function() {
...
$close.on('click', function() {
$body.css('overflow-y','auto');
var layoutProp = getItemLayoutProp( $item ),
clipPropFirst = 'rect(' + layoutProp.top + 'px ' + (layoutProp.left + layoutProp.width) + 'px ' + (layoutProp.top + layoutProp.height) + 'px ' + layoutProp.left + 'px)',
clipPropLast = 'auto';
// reset current
current = -1;
$overlay.css({
clip : supportTransitions ? clipPropFirst : clipPropLast,
opacity : supportTransitions ? 1 : 0,
pointerEvents : 'none'
} );
if(supportTransitions) {
$overlay.on( transEndEventName, function() {
$overlay.off( transEndEventName );
setTimeout( function() {
$overlay.css('opacity', 0).on( transEndEventName, function() {
$overlay.off( transEndEventName ).css({clip : clipPropLast, zIndex: -1});
$item.data('isExpanded', false);
});
}, 25);
} );
}
else {
$overlay.css( 'z-index', -1 );
$item.data( 'isExpanded', false );
}
return false;
} );
} );
...
}
Вот и все! Надеюсь вы не зря потратили время на изучение этого урока и совсем скоро я увижу нечто подобное в одном из ваших проектов.