Слайдер изображений с тремя 3D панелями на jQuery

в 9:48, , рубрики: jquery, веб-дизайн, Веб-разработка, Программирование, слайдер

image

В этом уроке мы будем создавать слайдер изображений с тремя 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

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js