jQuery/Zepto 3D透视圆形/椭圆形旋转木马轮播插件cloud9carousel

cloud9carousel是一款与jQuery和Zepto配合使用、支持多个轮播实例、支持任何HTML元素、速度流畅、专注于性能的3D透视旋转木马插件,使用requestAnimationFrame方法固定FPS来实现平滑过渡动画、自动开启GPU来支持CSS 转换动画(自动判断是否支持),有onLoaded、onRendered、onAnimationFinished三个回调函数,支持元素倒影(需要reflection.js插件)、自动轮播、鼠标滚轮切换、点击切换。

cloud9carousel-demo-03.png

cloud9carousel-demo-02.png

cloud9carousel-demo-04.png

cloud9carousel-demo-01.png

演示:http://specious.github.io/cloud9carousel/species.html

依赖jquery插件:

  • jQuery 1.3.0+版本或Zepto 1.1.1+版本(必需)
  • 作者Christophe Beyls的reflection.js插件可以实现镜像倒影效果(可选,仅限jQuery)
  • mousewheel.js插件可以实现鼠标滚轮切换效果(可选,仅限jQuery)

通过依赖的插件可以实现更多的效果。

使用教程

HTML代码:
<div id="carousel">
	<img class="cloud9-item" src="images/1.png" alt="Item #1">
	<img class="cloud9-item" src="images/2.png" alt="Item #2">
	<img class="cloud9-item" src="images/3.png" alt="Item #3">
	<img class="cloud9-item" src="images/4.png" alt="Item #4">
	<img class="cloud9-item" src="images/5.png" alt="Item #5">
	<img class="cloud9-item" src="images/6.png" alt="Item #6">
</div>
<div id="buttons">
	<button class="left">
		←
	</button>
	<button class="right">
		→
	</button>
</div>

提示:也可以是img以外的元素,主要是class类名要跟设置的一样

CSS代码:
#carousel .cloud9-item, #buttons button {
	cursor: pointer;
}

提示:左右切换按钮的样式,可以自定义。

Javascript代码:
$("#carousel").Cloud9Carousel({
	buttonLeft: $("#buttons > .left"),
	buttonRight: $("#buttons > .right"),
	autoPlay: 1,
	bringToFront: true
});
更多参数:

上面是插件示例,可以通过下面的参数进行更多的配置

  • xOrigin - 旋转木马X轴的中心点,默认值为容器宽度的一半(container width / 2)。
  • yOrigin - 旋转木马Y轴的中心点,默认值为容器宽度的10分之1(container height / 10)。
  • xRadius - 旋转木马宽度的一半(即X轴半径),默认值为容器宽度的2.3分之1(container width / 2.3)。
  • yRadius - 旋转木马高度的一半(即Y轴半径),默认值为容器宽度的6分之1(container height / 6)。
  • farScale - 旋转木马中最远一项的缩放,范围0-1,默认值0.5.
  • mirror - 倒影插件Reflection.js的配置项目,看下文示例,默认值none。
  • transforms - 如果浏览器支持CSS3的transforms转换属性则使用它,默认true。
  • smooth - 如果浏览器支持requestAnimationFrame API就启用,实现平滑过渡效果
  • fps - 每一秒的动画帧数(如果smooth被禁用生效)
  • speed - 旋转木马的相对速度系数,取值范围:任何正数(即1~),数值越大速度越快,如1表示慢,4表示中等,10表示快,默认值为4,可根据自己的喜好进行调整
  • autoPlay - 旋转木马自动轮播,正数顺时针轮播,负数逆时针轮播,0关闭自动轮播,默认值为0。(注意:当鼠标悬停在转盘容器上时,不会执行自动播放)
  • autoPlayDelay - 自动轮播的延迟时间,单位为毫秒,默认4000。
  • mouseWheel - 使用鼠标滚轮来旋转,依赖mousewheel.js插件,可设置为true开启,默认值为false。
  • bringToFront - 点击旋转木马其实的某一项将会旋转到最前面,默认为false。
  • buttonLeft - 元素的jQuery选择器,使旋转木马左边的元素旋转到最前面(逆时针旋转),默认值为none。
  • buttonRight - 元素的jQuery选择器,使旋转木马右边的元素旋转到最前面(顺时针旋转),默认值为none。
  • itemClass - 旋转木马每一项的class,如果配置了该项,需要把HTML代码中的class修改,默认值"cloud9-item"。
  • frontItemClass - 旋转木马最前面那一项的class,默认值为none。
  • handle - 旋转木马方法要用到的句柄,如:$("#carousel").data("carousel").go(1)
插件方法

插件提供有几种常见需求的方法

  • go(count) - 旋转到指定数量的项,count是数量值(+为顺时针旋转,-为逆时针旋转)。
  • goTo(index)  - 用于旋转到特定索引的项,index是索引值。
  • nearestIndex() - 返回最靠近前面基于0的项的索引(整数),默认值为none。
  • nearestItem() - 返回对最靠近前面的项的对象(Item object),默认值为none。
  • deactivate() - 禁用旋转木马,可以用来停止旋转木马并从中释放旋转木马元素,以便于在不受旋转木马干扰的情况下操作里面的元素。
  • itemsRotated()  - 返回从初始0位置旋转的项目的内插数,在有5个项目的旋转木马中,顺时针旋转三圈,其值将为-15,如果转盘进一步旋转到下一个项目的一半,则值为-15.5(float),默认值为none。
  • floatIndex() - 返回转盘前面项目“索引”的插值。例如,如果旋转木马经过项目2的20%到达下一个项目,那么将返回2.2(float)。
方法示例
$("#carousel").data("carousel").go( 3 );

提示:其中的carousel是插件handle参数定义的,carousel是默认值,也可以定义其它的。

回调函数
  • onLoaded - 插件初始化后执行,只执行一次
  • onRendered - 每次在帧完成计算后执行
  • onAnimationFinished - 轮播结束后执行
代码示例:
// Hide carousel while items are loading
$("#carousel").css( 'visibility', 'hidden' ).Cloud9Carousel( {
	bringToFront: true,
	onLoaded: function(carousel) {
		// Show carousel
		$(carousel).css( 'visibility', 'visible' );
		alert( 'Carousel is ready!' );
	},
	onRendered: function(carousel) {
		//var item = $(carousel).data("carousel").nearestItem();//官方示例,好像是错的
		var item = carousel.nearestItem();
		console.log( "Item closest to the front: " + $(item).attr("alt") );
	}
	onAnimationFinished: function(carousel){
		/**/
	}
});
实现镜像倒影

引入reflection.js插件后,在插件的配置参数mirror可以设置倒影效果,代码如下:

mirror: {
	gap: 12,     /* 12 pixel gap between item and reflection */
	height: 0.2, /* 20% of item height */
	opacity: 0.4 /* 40% opacity at the top */
}
参数说明
  • gap - 倒影和项目之间的垂直间距,单位像素,默认值为2。
  • height - 倒影高度相对于项目高度的比例,范围0-1,默认为1/3。
  • opacity - 倒影的透明度,范围0-1,默认为0.5。

Github地址:https://github.com/specious/cloud9carousel

Cloud 9 Carousel 2.2.0版本完整代码:

/*
 * Cloud 9 Carousel 2.2.0
 *
 * Pseudo-3D carousel plugin for jQuery/Zepto focused on performance.
 *
 * Based on the original CloudCarousel by R. Cecco.
 *
 * See the demo and download the latest version:
 *   http://specious.github.io/cloud9carousel/
 *
 * Copyright (c) 2017 by Ildar Sagdejev ( http://specious.github.io )
 * Copyright (c) 2011 by R. Cecco ( http://www.professorcloud.com )
 *
 * MIT License
 *
 * Please retain this copyright header in all versions of the software
 *
 * Requires:
 *  - jQuery >= 1.3.0 or Zepto >= 1.1.1
 *
 * Optional (jQuery only):
 *  - Reflection support via reflection.js plugin by Christophe Beyls
 *     http://www.digitalia.be/software/reflectionjs-for-jquery
 *  - Mousewheel support via mousewheel plugin
 *     http://plugins.jquery.com/mousewheel/
 */

;(function($) {
  //
  // Detect CSS transform support
  //
  var transform = (function() {
    var vendors = ['webkit', 'moz', 'ms'];
    var style   = document.createElement( "div" ).style;
    var trans   = 'transform' in style ? 'transform' : undefined;

    for( var i = 0, count = vendors.length; i < count; i++ ) {
      var prop = vendors[i] + 'Transform';
      if( prop in style ) {
        trans = prop;
        break;
      }
    }

    return trans;
  })();

  var Item = function( element, options ) {
    element.item = this;
    this.element = element;

    if( element.tagName === 'IMG' ) {
      this.fullWidth = element.width;
      this.fullHeight = element.height;
    } else {
      element.style.display = "inline-block";
      this.fullWidth = element.offsetWidth;
      this.fullHeight = element.offsetHeight;
    }

    element.style.position = 'absolute';

    if( options.mirror && this.element.tagName === 'IMG' ) {
      // Wrap image in a div together with its generated reflection
      this.reflection = $(element).reflect( options.mirror ).next()[0];

      var $reflection = $(this.reflection);
      this.reflection.fullHeight = $reflection.height();
      $reflection.css( 'margin-top', options.mirror.gap + 'px' );
      $reflection.css( 'width', '100%' );
      element.style.width = "100%";

      // The item element now contains the image and reflection
      this.element = this.element.parentNode;
      this.element.item  = this;
      this.element.alt   = element.alt;
      this.element.title = element.title;
    }

    if( transform && options.transforms )
      this.element.style[transform + "Origin"] = "0 0";

    this.moveTo = function( x, y, scale ) {
      this.width = this.fullWidth * scale;
      this.height = this.fullHeight * scale;
      this.x = x;
      this.y = y;
      this.scale = scale;

      var style = this.element.style;
      style.zIndex = "" + (scale * 100) | 0;

      if( transform && options.transforms ) {
        style[transform] = "translate(" + x + "px, " + y + "px) scale(" + scale + ")";
      } else {
        // Manually resize the gap between the image and its reflection
        if( options.mirror && this.element.tagName === 'IMG' )
          this.reflection.style.marginTop = (options.mirror.gap * scale) + "px";

        style.width = this.width + "px";
        style.left = x + "px";
        style.top = y + "px";
      }
    }
  }

  var time = !window.performance || !window.performance.now ?
    function() { return +new Date() } :
    function() { return performance.now() };

  //
  // Detect requestAnimationFrame() support
  //
  // Support legacy browsers:
  //   http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
  //
  var cancelFrame = window.cancelAnimationFrame || window.cancelRequestAnimationFrame;
  var requestFrame = window.requestAnimationFrame;

  (function() {
    var vendors = ['webkit', 'moz', 'ms'];

    for( var i = 0, count = vendors.length; i < count && !cancelFrame; i++ ) {
      cancelFrame = window[vendors[i]+'CancelAnimationFrame'] || window[vendors[i]+'CancelRequestAnimationFrame'];
      requestFrame = requestFrame && window[vendors[i]+'RequestAnimationFrame'];
    }
  }());

  var Carousel = function( element, options ) {
    var self = this;
    var $container = $(element);
    this.items = [];
    this.xOrigin = (options.xOrigin === null) ? $container.width()  * 0.5 : options.xOrigin;
    this.yOrigin = (options.yOrigin === null) ? $container.height() * 0.1 : options.yOrigin;
    this.xRadius = (options.xRadius === null) ? $container.width()  / 2.3 : options.xRadius;
    this.yRadius = (options.yRadius === null) ? $container.height() / 6   : options.yRadius;
    this.farScale = options.farScale;
    this.rotation = this.destRotation = Math.PI/2; // start with the first item positioned in front
    this.speed = options.speed;
    this.smooth = options.smooth;
    this.fps = options.fps;
    this.timer = 0;
    this.autoPlayAmount = options.autoPlay;
    this.autoPlayDelay = options.autoPlayDelay;
    this.autoPlayTimer = 0;
    this.frontItemClass = options.frontItemClass;
    this.onLoaded = options.onLoaded;
    this.onRendered = options.onRendered;
    this.onAnimationFinished = options.onAnimationFinished;

    this.itemOptions = {
      transforms: options.transforms
    }

    if( options.mirror ) {
      this.itemOptions.mirror = $.extend( { gap: 2 }, options.mirror );
    }

    $container.css( { position: 'relative', overflow: 'hidden' } );

    // Rotation:
    //  *      0 : right
    //  *   Pi/2 : front
    //  *   Pi   : left
    //  * 3 Pi/2 : back
    this.renderItem = function( itemIndex, rotation ) {
      var item = this.items[itemIndex];
      var sin = Math.sin(rotation);
      var farScale = this.farScale;
      var scale = farScale + ((1-farScale) * (sin+1) * 0.5);

      item.moveTo(
        this.xOrigin + (scale * ((Math.cos(rotation) * this.xRadius) - (item.fullWidth * 0.5))),
        this.yOrigin + (scale * sin * this.yRadius),
        scale
      );

      return item;
    }

    this.render = function() {
      var count = this.items.length;
      var spacing = 2 * Math.PI / count;
      var radians = this.rotation;
      var nearest = this.nearestIndex();

      for( var i = 0; i < count; i++ ) {
        var item = this.renderItem( i, radians );

        if( i === nearest )
          $(item.element).addClass( this.frontItemClass );
        else
          $(item.element).removeClass( this.frontItemClass );

        radians += spacing;
      }

      if( typeof this.onRendered === 'function' )
        this.onRendered( this );
    }

    this.playFrame = function() {
      var rem = self.destRotation - self.rotation;
      var now = time();
      var dt = (now - self.lastTime) * 0.002;
      self.lastTime = now;

      if( Math.abs(rem) < 0.003 ) {
        self.rotation = self.destRotation;
        self.pause();

        if( typeof self.onAnimationFinished === 'function' )
          self.onAnimationFinished();
      } else {
        // Asymptotically approach the destination
        self.rotation = self.destRotation - rem / (1 + (self.speed * dt));
        self.scheduleNextFrame();
      }

      self.render();
    }

    this.scheduleNextFrame = function() {
      this.lastTime = time();

      this.timer = this.smooth && cancelFrame ?
        requestFrame( self.playFrame ) :
        setTimeout( self.playFrame, 1000 / this.fps );
    }

    this.itemsRotated = function() {
      return this.items.length * ((Math.PI/2) - this.rotation) / (2*Math.PI);
    }

    this.floatIndex = function() {
      var count = this.items.length;
      var floatIndex = this.itemsRotated() % count;

      // Make sure float-index is positive
      return (floatIndex < 0) ? floatIndex + count : floatIndex;
    }

    this.nearestIndex = function() {
      return Math.round( this.floatIndex() ) % this.items.length;
    }

    this.nearestItem = function() {
      return this.items[this.nearestIndex()];
    }

    this.play = function() {
      if( this.timer === 0 )
        this.scheduleNextFrame();
    }

    this.pause = function() {
      this.smooth && cancelFrame ? cancelFrame( this.timer ) : clearTimeout( this.timer );
      this.timer = 0;
    }

    //
    // Spin the carousel by (+-) count items
    //
    this.go = function( count ) {
      this.destRotation += (2 * Math.PI / this.items.length) * count;
      this.play();
    }

    this.goTo = function( index ) {
      var count = this.items.length;

      // Find the shortest way to rotate item to front
      var diff = index - (this.floatIndex() % count);

      if( 2 * Math.abs(diff) > count )
        diff -= (diff > 0) ? count : -count;

      // Halt any rotation already in progress
      this.destRotation = this.rotation;

      // Spin the opposite way to bring item to front
      this.go( -diff );

      // Return rotational distance (in items) to the target
      return diff;
    }

    this.deactivate = function() {
      this.pause();
      clearInterval( this.autoPlayTimer );
      if( options.buttonLeft ) options.buttonLeft.unbind( 'click' );
      if( options.buttonRight ) options.buttonRight.unbind( 'click' );
      $container.unbind( '.cloud9' );
    }

    this.autoPlay = function() {
      this.autoPlayTimer = setInterval(
        function() { self.go( self.autoPlayAmount ) },
        this.autoPlayDelay
      );
    }

    this.enableAutoPlay = function() {
      // Stop auto-play on mouse over
      $container.bind( 'mouseover.cloud9', function() {
        clearInterval( self.autoPlayTimer );
      } );

      // Resume auto-play when mouse leaves the container
      $container.bind( 'mouseout.cloud9', function() {
        self.autoPlay();
      } );

      this.autoPlay();
    }

    this.bindControls = function() {
      if( options.buttonLeft ) {
        options.buttonLeft.bind( 'click', function() {
          self.go( -1 );
          return false;
        } );
      }

      if( options.buttonRight ) {
        options.buttonRight.bind( 'click', function() {
          self.go( 1 );
          return false;
        } );
      }

      if( options.mouseWheel ) {
        $container.bind( 'mousewheel.cloud9', function( event, delta ) {
          self.go( (delta > 0) ? 1 : -1 );
          return false;
        } );
      }

      if( options.bringToFront ) {
        $container.bind( 'click.cloud9', function( event ) {
          var hits = $(event.target).closest( '.' + options.itemClass );

          if( hits.length !== 0 ) {
            var diff = self.goTo( self.items.indexOf( hits[0].item ) );

            // Suppress default browser action if the item isn't roughly in front
            if( Math.abs(diff) > 0.5 )
              event.preventDefault();
          }
        } );
      }
    }

    var items = $container.find( '.' + options.itemClass );

    this.finishInit = function() {
      //
      // Wait until all images have completely loaded
      //
      for( var i = 0; i < items.length; i++ ) {
        var item = items[i];
        if( (item.tagName === 'IMG') &&
            ((item.width === undefined) || ((item.complete !== undefined) && !item.complete)) )
          return;
      }

      clearInterval( this.initTimer );

      // Init items
      for( i = 0; i < items.length; i++ )
        this.items.push( new Item( items[i], this.itemOptions ) );

      // Disable click-dragging of items
      $container.bind( 'mousedown onselectstart', function() { return false } );

      if( this.autoPlayAmount !== 0 ) this.enableAutoPlay();
      this.bindControls();
      this.render();

      if( typeof this.onLoaded === 'function' )
        this.onLoaded( this );
    };

    this.initTimer = setInterval( function() { self.finishInit() }, 50 );
  }

  //
  // The jQuery plugin
  //
  $.fn.Cloud9Carousel = function( options ) {
    return this.each( function() {
      /* For full list of options see the README */
      options = $.extend( {
        xOrigin: null,        // null: calculated automatically
        yOrigin: null,
        xRadius: null,
        yRadius: null,
        farScale: 0.5,        // scale of the farthest item
        transforms: true,     // enable CSS transforms
        smooth: true,         // enable smooth animation via requestAnimationFrame()
        fps: 30,              // fixed frames per second (if smooth animation is off)
        speed: 4,             // positive number
        autoPlay: 0,          // [ 0: off | number of items (integer recommended, positive is clockwise) ]
        autoPlayDelay: 4000,
        bringToFront: false,
        itemClass: 'cloud9-item',
        frontItemClass: null,
        handle: 'carousel'
      }, options );

      $(this).data( options.handle, new Carousel( this, options ) );
    } );
  }
})( window.jQuery || window.Zepto );

提示:Github如果打不开,可以把上面的完整代码复制保存为cloud9carousel.js文件引用

关键词: js轮播插件