Реальное применение свойства clip для формирования визуального эффекта

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

Сначала создаем список пунктов, оформленных в стиле прямоугольников Metro:

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



Нажатие на кнопке закрытия вызовет реверс эффекта, и покрывающий слой свернется до размеров пункта списка и исчезнет.

Разметка HTML

Для прямоугольников будем использовать неупорядоченный список. Каждый пункт списка имеет класс иконки и опциональный класс "span”, который управляет шириной прямоугольника. Внутрь мы добавляем текст и элемент div покрывного слоя. Покрывной слой имеет структуру табличного шаблона. Для примера мы выбрали тему погоды, поэтому на покрывном слое представлена имитация прогноза на 7 дней. Каждый столбец дня имеет несколько элементов 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>


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


Существует три различных ширины для пунктов нашей сетки. По умолчанию один вариант 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;
}[code]

Включаем файл CSS для иконок, которые будут использоваться на странице. Это шрифт Climacons (автор Adam Whitcroft). Вы можете просмотреть файл climacons.css, чтобы понять,какие иконки мы включили. Для добавления иконок с помощью псевдо элемента мы используем класс иконок.. В нашей сетке они будут позиционироваться абсолютно в правом нижнем углу и выводиться с небольшим усечением:

[code].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;
}


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

Код
.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() для свойства clip и расширяем покрывной слой с помощью анимации.

Рассмотрим остальные стили.

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

Код
.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%;
}


У нас есть восемь колонок: 7 из них занимают 70% (7 раз по 10%), а восьмая - 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;
}


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

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


…и так lzk всех 11 прямоугольников.

И в завершении воспользуемся медиа запросами для маленьких экранов.

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

Код
@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

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

Код
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 к элементам текста колонок в покровным слое, чтобы масштабировать текст в соответствии с размером экрана.

Затем привязываем событие click к пунктам и кнопки закрытия.

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

Код
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, чтобы покровной слой принимал события click. Если переходы не поддерживаются, первое состояние пропускается и покровной слой сразу расширяется до полного размера.

Код
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()
  };
   
}


При нажатии кнопки "закрыть" все происходит в обратном порядке:

Код
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';
   
  // Сбрасывает текущее значение
  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;
   
  } );
   
  } );
   
  ...
   
}

Готово!

  • FalleN

  • 2393

  • 1

  • 208
Теги: Transition, css3, clip

Ссылки на статью:

Похожие статьи: