Введение в CSS Clip: Практика

В предыдущей статье, Введение в CSS Clip, мы сделали большой обзор CSS свойства Clip и функции rect(). В данном уроке мы перейдем к практике. Создадим аккуратный и простой эффект, который раскрывает дополнительный контент и наложение во весь экран.

Сложность

Мы расскажем, как усилить CSS свойство Clip, чтобы сделать гладкий переход при нажатии по блочному элементу. Идея состоит в том, чтобы отобразить некоторое наложение так, будто оно расположено фактически над самим элементом. При нажатии по элементу создадим эффект обрезки, отображаем новый слой, который будет расширятся.

Для начала мы создадим список элементов, в стиле Metro плитки:

Каждая из плиток будет иметь специальный элемент (наложение), с фиксированной позицией. Этот элемент фактически находится на всей странице, скроем его, изменив непрозрачность к 0. Когда мы щелкнем по плитке, будем использовать свойство clip: rect(), чтобы обрезать соответствующую часть внутреннего фиксированного элемента. Затем анимируем свойство clip:

HTML разметка

Для плитки, мы будем использовать неупорядоченный список. У каждого элемента списка будет иконка, а также span, который будет управлять шириной поля. Внутри мы добавим текст и контейнер с наложением. Наложение будет иметь структуру в виде колонок. Так как мы выбрали приложение погоды, создадим прогноз погоды на семь дней. У каждого из столбцов будут некоторые блоки, которые мы будем использовать для отображения дней, иконки погоды и температуры:

<ul id="rb-grid" class="rb-grid clearfix">
    <li class="icon-clima-1 rb-span-2">
        <h3>Lisbon</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">Lisbon</span><span class="icon-clima-1"></span><span>21°C</span></div> 

                  <div><span>Mon</span><span class="icon-clima-1"></span><span>19°C</span></div> 

                  <div><span>Tue</span><span class="icon-clima-2"></span><span>19°C</span></div> 

                  <div><span>Wed</span><span class="icon-clima-2"></span><span>18°C</span></div> 

                  <div><span>Thu</span><span class="icon-clima-2"></span><span>17°C</span></div> 

                  <div><span>Fri</span><span class="icon-clima-1"></span><span>19°C</span></div> 

                  <div><span>Sat</span><span class="icon-clima-1"></span><span>22°C</span></div> 

                  <div><span>Sun</span><span class="icon-clima-1"></span><span>18°C</span></div> 

              </div> 

          </div> 

           

      </li>
    <li class="icon-clima-2"> 

          <h3>Paris</h3><span class="rb-temp">11°C</span> 

          <div class="rb-overlay"> 

              <!-- ... --> 

          </div> 

      </li>
    <li><!-- ... --></li>
    <!-- ... -->
</ul>

CSS

Расположим неупорядоченный список по центру родительского элемента:

.rb-grid {
    list-style: none;
    text-align: center;
    margin: 0 auto;
}

Для элементов списка добавим обтекание слева и зададим высоту в 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;
}

Есть три различных значения ширины для наших элементов сетки:

.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 Font от Adam Whitcroft. В основном мы используем класс "icon-", чтобы добавить иконку с псевдо-элементом. Расположим её на сетке в правом нижнем углу:

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

Температуру сделаем полупрозрачной, а также добавим для неё переход в прозрачности:

.rb-temp {
    display: block;
    font-size: 2em;
    opacity: 0.5;
    transition: all 0.3s ease-in-out;
}

При наведении над элементом списка мы просто увеличим его:

.rb-grid li:hover .rb-temp {
    opacity: 1;
}

Теперь, давайте рассмотрим контейнер с наложением. Окончательный вид, который мы хотели бы видеть - полноэкранное наложение, для этого мы должны установить ширину и высоту к 100%. Необходимо что бы наложение находилось поверх всего элемента, поэтому зададим фиксированное расположение, а также установим z-index в -1. Это поместит все плитки позади контента страницы. Установка непрозрачности сделает их невидимыми:

.rb-overlay {
    opacity: 0;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    transition: all 0.4s ease;
    z-index: -1;
    pointer-events: none;
    cursor: default;
}

Как только мы нажимаем по элементу списка, установим корректное значение функции rect() для свойства CSS 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%;
}

Для каждого 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;
}

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

.rb-week span.rb-city {
    font-weight: 700;
    padding: 1% 10%;
    font-size: 1em;
    line-height: 1.2;
}

У иконок будет увеличенный размер шрифта:

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

И для каждого наложения у нас есть по восемь столбцов:

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

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

@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, поэтому нам не придется менять расположение вручную.

JavaScript

Давайте начнем c кэширования некоторых элементов и инициализации переменных:

var $items = $( '#rb-grid > li' ),
    transEndEventNames = {
        'WebkitTransition' : 'webkitTransitionEnd',
        'MozTransition' : 'transitionend',
        'OTransition' : 'oTransitionEnd',
        'msTransition' : 'MSTransitionEnd',
        'transition' : 'transitionend'
    },
 
    // transition end event name
    transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ],
 
    // элементы window и body
    $window = $( window ),
    $body = $( 'BODY' ),
 
    // transitions support
    supportTransitions = Modernizr.csstransitions,
 
    // current item's index
    current = -1,
 
    // ширина и высота window
    winsize = getWindowSize();

Во-первых, устанавливаем jQuery плагин FitText к текстовым элементам в столбцах с наложением, чтобы уменьшить масштаб текста для небольших экранов.

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

function init( options ) {      
    // apply fittext plugin
    $items.find( 'div.rb-week > div span' ).fitText( 0.3 ).end().find( 'span.rb-city' ).fitText( 0.5 );
    initEvents();
}

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

Мы отображаем/скрываем прокрутку страницы между состояниями, как только конечное состояние (наложение) достигнуто. Устанавливаем z-index наложения в высокое значение, таким образом, он остается поверх полей и добавим событие pointer со значением auto, чтобы контент можно было активировать при нажатии. Если CSS переходы не поддерживаются браузером, мы пропускаем первое состояние и при нажатии, наложение будет сразу расширено во весь экран.

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;
 
        } );
 
    } );
 
    ...
 
}
ДЕМО СКАЧАТЬ

 

Перевод статьи Putting CSS Clip to Work: Expanding Overlay Effect

Вход

Уважаемый пользователь! Мы обнаружили, что вы используете AdBlock и вынуждены скрыть часть материалов на нашем сайте. Siteacademy существует и развивается за счет доходов от рекламы. Просим внести наш сайт в список исключений или отключить Блокировщик рекламы на нашем сайте.