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