В этом уроке мы будем создавать слайдер изображений с тремя 3D панелями на JQuery. Идея состоит в том, чтобы создать одну главную панель и две боковых, которые немного повернуты создавая 3D эффект. При навигации мы будем скользить к соответствующему изображению на каждой панели. Мы будем использовать CSS 3D Transforms с perspective и CSS Transitions.
Изображения предоставил geishaboy500 и находятся под лицензией Creative Commons Attribution 2.0 Generic (CC BY 2.0).
HTML-разметка
Первоначальная структура будет состоять из блоков с фигурами. Каждая фигура будет содержать изображение и информацию с названием и описанием для изображения:
<div class="fs-slider" id="fs-slider">
<figure>
<img src="/images/1.jpg" alt="image01" />
<figcaption>
<h3>Eloquence</h3>
<p>American apparel flexitarian put a bird on it, mixtape typewriter irony aesthetic. </p>
</figcaption>
</figure>
<figure>
<img src="/images/2.jpg" alt="image02" />
<figcaption>
<h3>Quintessential</h3>
<p>Cardigan craft beer mixtape, skateboard forage fixie truffaut messenger bag. </p>
</figcaption>
</figure>
<!-- ... -->
</div>
Нам нужно что бы jQuery плагин преобразовывал эту структуру в следующую:
<section class="fs-container">
<div class="fs-wrapper">
<div class="fs-slider" id="fs-slider">
<div class="fs-block">
<figure style="display: block; ">
<img src="/images/1.jpg" alt="image01" />
<figcaption>
<h3>Eloquence</h3>
<p>American apparel flexitarian put a bird on it, mixtape typewriter irony aesthetic. </p>
</figcaption>
</figure>
</div><!-- /fs-block -->
<div class="fs-block">
<!-- ... -->
</div>
<!-- ... -->
</div><!-- /fs-slider -->
<nav class="fs-navigation">
<span>Previous</span>
<span>Next</span>
</nav>
</div><!-- /fs-wrapper -->
</section><!-- /fs-container -->
Каждая фигура будет с оболочкой в div с классом fs-block и также добавим навигацию.
CSS
Мы установим ширину слайдера в процентах для увеличения быстродействия. Но мы также определим минимальную и максимальную ширину, так что бы она не слишком сжимала или увеличивала пропорции слайдера. Мы добавим небольшие отступы по сторонам, потому что наши блоки будут расположены с помощью CSS и это не повлияет на ширину элемента:
.fs-container {
margin: 20px auto 50px auto;
position: relative;
width: 40%;
padding: 0 15%;
max-width: 700px;
min-width: 220px;
height: 500px;
box-sizing: content-box;
}
Давайте добавим тень под слайдером, используя псевдо-элемент. Добавим фоновое изображение размером в 100% это будет гарантировать что тень будет такого же размера как и сам слайдер:
.fs-container:before {
content: '';
position: absolute;
bottom: -40px;
background: transparent url(../images/shadow.png) no-repeat center center;
height: 90px;
width: 90%;
left: 5%;
opacity: 0.8;
background-size: 100% 100%;
}
Добавим дополнительную оболочку вокруг слайдера для перспективы:
.fs-wrapper {
width: 100%;
height: 100%;
position: relative;
perspective: 1000px;
}
Сам слайдер должен иметь 3d transform-style:
.fs-slider{
width: 100%;
height: 100%;
position: absolute;
transform-style: preserve-3d;
pointer-events: none;
}
Каждый блок будет размещен по центру, установив ширину до 70% и левое значение до 15%. Мы также добавим переход к боковым блокам, так как мы хотим создать красивый эффект при наведении курсора:
.fs-block {
margin: 0;
position: absolute;
width: 70%;
height: 100%;
left: 15%;
pointer-events: auto;
transition: all 1s ease;
}
Теперь мы должны расположить наши блоки. Первый будет установлен слева при помощи translateX со значением -100%. Поворачивая его на -35 градусов по оси Y, мы достигнем эффекта отдаленности от центрального блока:
.fs-block:nth-child(1) {
transform-origin: top right;
transform: translateX(-100%) rotateY(-35deg);
}
При наведении сделаем, чтобы крайний блок отодвигался немного вперед. Мы используем Modernizr, поэтому эффект с наведением курсора не будет работать на сенсорных устройствах (будет класс «no-touch»):
.no-touch .fs-block:nth-child(1):hover {
transform: translateX(-100%) rotateY(-30deg);
}
У центральной панели будет z-index со значением 100, потому что нам нужно удостовериться, что он находится поверх остальных окон:
.fs-block:nth-child(2) {
z-index: 100;
}
Последний блок установим справа и повернем его к фону:
.fs-block:nth-child(3) {
transform-origin: top left;
transform: translateX(100%) rotateY(35deg);
}
И также как и с первым блоком, он будет поворачиваться при наведении курсора:
.no-touch .fs-block:nth-child(3):hover {
transform: translateX(100%) rotateY(30deg);
}
Давайте добавим к панелям некоторые полупрозрачные оверлейные элементы, чтобы они выглядели более реалистичными. Мы будем использовать псевдо-класс :after и добавим рамку шириной 1px (чтобы устранить разрыв между панелями):
.fs-block:after{
content: '';
position: absolute;
width: 100%;
height: 100%;
z-index: 1000;
pointer-events: none;
box-sizing: content-box;
border-left: 1px solid rgba(119,119,119,1);
border-right: 1px solid rgba(119,119,119,1);
left: -1px;
}
У каждого блока будет различный тип градиента:
.fs-block:nth-child(1):after {
background:
linear-gradient(
to right,
rgba(0,0,0,0.65) 0%,
rgba(0,0,0,0.2) 100%
);
}
Следующий градиент для центральной панели, он придаст ей согнутый эффект:
.fs-block:nth-child(2):after {
opacity: 0.8;
background:
linear-gradient(
to right,
rgba(0,0,0,0.5) 0%,
rgba(0,0,0,0.12) 21%,
rgba(0,0,0,0.03) 31%,
rgba(0,0,0,0) 50%,
rgba(0,0,0,0.03) 70%,
rgba(0,0,0,0.12) 81%,
rgba(0,0,0,0.5) 100%
);
}
У последнего блока будет градиент как и у первого но с инверсией:
.fs-block:nth-child(3):after {
background:
linear-gradient(
to right,
rgba(0,0,0,0.2) 0%,
rgba(0,0,0,0.65) 100%
);
}
Теперь, давайте разработаем элементы фигур. Зададим позицию и заполним ими весь блок:
.fs-block figure {
width: 100%;
height: 100%;
margin: 0;
position: absolute;
top: 0;
left: 0;
overflow: hidden;
z-index: 1;
}
Идея состоит в том, чтобы применить другую фигуру к блоку при его перемещении. Таким образом, мы должны установить z-index первой фигуры выше, так чтобы он больше нигде не встречался. Затем мы изменим ширину первой фигуры к 0%, который затем покажет вторую:
.fs-block figure:first-child{
z-index: 10;
}
Изображению также зададим абсолютную позицию:
.fs-block figure img {
position: absolute;
top: 0;
left: 0;
display: block;
}
Элемент figcation будет иметь полупрозрачный фон, и мы добавим к нему transition при помощи класса fs-transition (только при перемещении):
.fs-block figcaption {
padding: 0 20px;
margin: 0;
position: absolute;
width: 100%;
top: 25%;
background: rgba(0,0,0,0.4);
overflow: hidden;
height: 0%;
opacity: 0;
text-align: center;
transition: all 700ms cubic-bezier(0, 0, .15, 1);
}
.fs-block figcaption.fs-transition {
height: 35%;
opacity: 1;
}
Стиль текстовых элементов:
.fs-block figcaption h3 {
font-size: 40px;
line-height: 40px;
margin: 0;
padding: 20px 0;
color: #fff;
text-shadow: 1px 1px 1px rgba(0,0,0,0.3);
font-family: 'Prata', serif;
font-weight: normal;
}
.fs-block figcaption p {
color: #fff;
padding: 20px 0;
margin: 0;
text-shadow: 1px 1px 1px rgba(0,0,0,0.2);
border-top: 1px solid rgba(255,255,255,0.2);
box-shadow: 0 -1px 0 rgba(0,0,0,0.3);
}
Навигацию поместим в нижний угол:
.fs-navigation {
position: absolute;
z-index: 2000;
bottom: 10px;
right: 15%;
margin-right: 15px;
user-select: none;
}
Стрелки навигации поместим в контейнеры, сделаем их плавающими, а сами стрелки установим как фоновое изображение:
.fs-navigation span {
float: left;
width: 26px;
height: 26px;
border-radius: 4px;
text-indent: -90000px;
cursor: pointer;
opacity: 0.6;
margin-right: 3px;
background: rgba(0,0,0,0.4) url(../images/arrow.png) no-repeat 50% 50%;
pointer-events: auto;
}
Второй контейнер будет поворачиваться так, чтобы стрелка показывала направо:
.fs-navigation span:nth-child(2) {
transform: rotate(180deg);
}
При наведении курсора мыши увеличим непрозрачность:
.fs-navigation span:hover{
opacity: 1;
}
Теперь, давайте добавим переходы для более гладкого эффекта. У каждой панели будет своя задержка. Так как нам нужно оживить наши панели с правой стороны, поэтому зададим для первой панели самую высокую задержку.
.fs-block:nth-child(1) figure {
transition: width 900ms cubic-bezier(0, 0, .15, 1) 600ms;
}
.fs-block:nth-child(2) figure {
transition: width 900ms cubic-bezier(0, 0, .15, 1) 300ms;
}
.fs-block:nth-child(3) figure {
transition: width 900ms cubic-bezier(0, 0, .15, 1);
}
Если хотите попробовать различные функции синхронизации cubic-bezier, можете воспользоваться этим онлайновым инструментом.
Добавим медиа-запросы для того что бы скорректировать текстовые элементы:
/* Media Queries */
@media screen and (max-width: 1024px) {
.fs-block figcaption h3 {
font-size: 26px;
}
}
@media screen and (max-width: 768px) {
.fs-block figcaption {
padding: 0 10px;
}
.fs-block figcaption h3 {
font-size: 16px;
padding: 10px 0;
}
.fs-block figcaption p {
font-size: 13px;
}
}
JavaScript
В опциях плагина зададим только настройки автоматического воспроизведения. Как и прежде, мы настроим конфигурацию переходов для CSS.
$.ImgSlider.defaults = {
autoplay : false,
interval : 4000
};
Мы начнем с предварительной загрузки всех изображений, после этого выполним функции _init:
_init : function( options ) {
// опции
this.options = $.extend( true, {}, $.ImgSlider.defaults, options );
this.current = 0;
// https://github.com/twitter/bootstrap/issues/2870
var transEndEventNames = {
'WebkitTransition' : 'webkitTransitionEnd',
'MozTransition' : 'transitionend',
'OTransition' : 'oTransitionEnd',
'msTransition' : 'MSTransitionEnd',
'transition' : 'transitionend'
};
this.transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ];
// инициализация элементов
this.$initElems = this.$el.children( 'figure' );
// tобщее количество элементов
this.initElemsCount = this.$initElems.length;
if( this.initElemsCount < 3 ) {
return false;
}
// расположение
this._layout();
// события init
this._initEvents();
// автовоспроизведение
if( this.options.autoplay ) {
this._startSlideshow();
}
}
Здесь мы в основном кэшируем некоторые элементы и инициализируем переменные, которые будут использоваться позже. Если у нас будет больше чем три элемента или изображения, то мы создадим структуру упомянутую выше. Также установим событие изменения размеров окна.
Наконец, если опция автоматического воспроизведения включена, мы инициализируем автоматическое слайд-шоу.
_layout : function() {
this.$initElems.wrapAll( '<div class="fs-temp"></div>' ).hide();
this.$initElems
.filter( ':lt(3)' )
.clone()
.show()
.prependTo( this.$el )
.wrap( '<div class="fs-block"></div>' );
this.$el
.wrap( '<section class="fs-container"></section>' )
.wrap( '<div class="fs-wrapper"></div>' );
this.$blocks = this.$el.children( 'div.fs-block' );
// кэшируем 3 основных блока
this.$blockL = this.$blocks.eq( 0 );
this.$blockC = this.$blocks.eq( 1 );
this.$blockR = this.$blocks.eq( 2 );
this.$blockC.find( 'figcaption' ).addClass( 'fs-transition' );
// все элементы
this.$temp = this.$el.find( 'div.fs-temp' );
// изменение размеров изображения
this._resizeBlocks();
// добавление навигации
if( this.initElemsCount > 3 ) {
var $nav = $( '<nav class="fs-navigation"><span>Previous</span><span>Next</span></nav>' ).appendTo( this.$el.parent() );
// следующий и предыдущий
this.$navPrev = $nav.find( 'span:first' );
this.$navNext = $nav.find( 'span:last' );
this._initNavigationEvents();
}
}
Все начальные элементы будут скрыты в оболочке с классом fs-temp.
Мы также должны изменить размер каждого изображения в соответствии с размером её оболочки. Описание элемента отображается только у центральной панели, в то время как все остальные — скрыты. Создадим кнопки навигации и инициализируем их события, если у нас более трех элементов.
_initNavigationEvents : function() {
var _self = this;
this.$navPrev.on( 'click.imgslider', function() {
if( _self.options.autoplay ) {
clearTimeout( _self.slideshow );
_self.options.autoplay = false;
}
_self._navigate( 'left' );
} );
this.$navNext.on( 'click.imgslider', function() {
if( _self.options.autoplay ) {
clearTimeout( _self.slideshow );
_self.options.autoplay = false;
}
_self._navigate( 'right' );
} );
}
_navigate : function( dir ) {
if( this.isAnimating === true ) {
return false;
}
this.isAnimating = true;
var _self = this,
$items = this.$temp.children(),
LIndex, CIndex, RIndex;
this.$blocks.find( 'figcaption' ).hide().css( 'transition', 'none' ).removeClass( 'fs-transition' );
if( dir === 'right' ) {
LIndex = this.current + 1;
CIndex = this.current + 2;
RIndex = this.current + 3;
if( LIndex >= this.initElemsCount ) {
LIndex -= this.initElemsCount
}
if( CIndex >= this.initElemsCount ) {
CIndex -= this.initElemsCount
}
}
else if( dir === 'left' ) {
LIndex = this.current - 1;
CIndex = this.current;
RIndex = this.current + 1;
if( LIndex < 0 ) {
LIndex = this.initElemsCount - 1
}
}
if( RIndex >= this.initElemsCount ) {
RIndex -= this.initElemsCount
}
var $elL = $items.eq( LIndex ).clone().show(),
$elC = $items.eq( CIndex ).clone().show(),
$elR = $items.eq( RIndex ).clone().show();
// изменение размеров изображений
$elL.children( 'img' ).css( this.$blockL.data( 'imgstyle' ) );
$elC.children( 'img' ).css( this.$blockC.data( 'imgstyle' ) );
$elR.children( 'img' ).css( this.$blockR.data( 'imgstyle' ) );
this.$blockL.append( $elL );
this.$blockC.append( $elC );
this.$blockR.append( $elR );
// отображение новых изображений
var $slides = this.$blocks.find( 'figure:first' ).css( 'width', '0%');
if( Modernizr.csstransitions ) {
$slides.on( this.transEndEventName, function( event ) {
var $this = $( this ),
blockIdx = $this.parent().index('');
_self._slideEnd( dir, blockIdx, $elC );
$this.off( _self.transEndEventName ).remove();
} );
}
else {
$slides.each( function() {
var $this = $( this ),
blockIdx = $this.parent().index('');
_self._slideEnd( dir, blockIdx, $elC );
} );
this._slideEnd();
}
}
При помощи функции _navigate определим, какие элементы будут расположены в трех видимых блоках. Мы вставляем каждую фигуру в блоки, изменяем размеры новых изображений и изменяем ширину этого же блока к 0px. В конце мы удаляем его, оставляя новую фигуру на этом месте. Кроме того, мы показываем описание среднего блока при том, что все остальные — скрыты.
_slideEnd : function( dir, blockIdx, $main ) {
if( blockIdx === 0 ) {
if( this.current === this.initElemsCount - 1 && dir === 'right' ) {
this.current = 0;
}
else if( this.current === 0 && dir === 'left' ) {
this.current = this.initElemsCount - 1;
}
else {
( dir === 'right' ) ? ++this.current : --this.current;
}
this.isAnimating = false;
}
else if( blockIdx === 1 ) {
$main.find( 'figcaption' ).addClass( 'fs-transition' );
}
}
Последнее о чем мы должны позаботиться это изменение размеров окна. Если пользователь меняет размер окна, мы должны изменить размеры изображений соответственно. Это происходит при вызове функции_layout:
_initEvents : function() {
var _self = this;
$window.on( 'debouncedresize.imgslider', function() {
_self._resizeBlocks();
} );
},
// изменение размеров изображений
_resizeBlocks : function() {
var _self = this;
this.$blocks.each( function( i ) {
var $el = $( this ).children( 'figure' ),
$img = $el.children( 'img' ),
dim = _self._getImageDim( $img.attr( 'src' ), { width : $el.width(), height : $el.height() } );
// сохранение размеров
switch( i ) {
case 0 : _self.$blockL.data( 'imgstyle', dim ); break;
case 1 : _self.$blockC.data( 'imgstyle', dim ); break;
case 2 : _self.$blockR.data( 'imgstyle', dim ); break;
};
// применение стиля
$img.css( dim );
} );
}
Автор: Lecaw