以下是 棱镜效果JS滑块切换特效代码 的示例演示效果:
部分效果截图:
HTML代码(index.html):
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="gb2312" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>棱镜效果JS滑块切换特效</title>
<link rel="stylesheet" type="text/css" href="css/component.css" />
<!--[if IE]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body class="demo-1">
<div class="container">
<div class="content">
<!-- Start Demo -->
<div class="cache">
<!-- masks -->
<img src="img/masks/prism-a.svg">
<img src="img/masks/prism-b.svg">
<img src="img/masks/prism-c.svg">
<!-- photos -->
<img src="img/bird-a.jpg">
<img src="img/bird-b.jpg">
<img src="img/bird-c.jpg">
<img src="img/bird-d.jpg">
</div>
<div class="wrapper">
<div class="prism-slider">
<ul class="navigation"></ul>
</div>
</div>
<!-- End Demo -->
</div>
</div><!-- /container -->
<!-- JS -->
<script src="js/utils/rAF.js"></script>
<script src="js/utils/easing.js"></script>
<script src="js/PrismSlider.js"></script>
<script src="js/slideshow1.js"></script>
</body>
</html>
JS代码(PrismSlider.js):
/** * The PrismSlider. */
var PrismSlider = (function(window,undefined){
'use strict';
/** * The PrismSlider. * @param{
Object}
settings The PrismSlider settings. * @constructor */
function PrismSlider(settings){
/** * Get settings object of this instance. * @type{
Object}
*/
this.settings = settings;
/** * Store main container DOM element. * @type{
Element}
*/
this.container = settings.container.element;
/** * Store array of photos,note that we use slice in order to * clone the array and avoid conficts when we'll later replace * the image path with the actual JavaScript <img> object. * @type{
Array}
*/
this.slides = settings.slides.slice();
/** * Count slides * @type{
Number}
*/
this.slidesLength = this.slides.length;
/** * The mask source and effects. * @type{
Object}
*/
this.mask = settings.mask;
/** * The sliding duration. * @type{
Number}
*/
this.duration = settings.duration;
/** * The global easing function. * @type{
Object}
*/
this.easing = settings.easing;
/** * The slides index. * @type{
Number}
*/
this.slidesIndex = 0;
/** * The previous slides index,needed to animate through * more than one slide. * @type{
Number}
*/
this.prevSlidesIndex = 0;
/** * The difference between the prevSlideIndex and slideIndex,* needed to animate through more than one slide. * @type{
Number}
*/
this.indexOffset = 1;
/** * Flag to detect when an animation is in progress. * @type{
Boolean}
*/
this.isAnimated = false;
}
/** * Initialise PrismSlider. */
PrismSlider.prototype.init = function(){
this.addCanvas_();
// Add mask only if there is any. if (this.mask) this.addMask_();
// Timeout to fix first time render bug on firefox. setTimeout(this.addSlides_.bind(this),0);
}
;
/** * Create canvas element,get context,set sizes * and append to main container. */
PrismSlider.prototype.addCanvas_ = function(){
this.canvas = document.createElement('canvas');
this.context = this.canvas.getContext('2d');
this.canvas.width = this.settings.container.sizes.w;
this.canvas.height = this.settings.container.sizes.h;
this.container.appendChild(this.canvas);
}
;
/** * Add Mask. * Call loadImage method with path and callback,* once the loading will be completed we'll replace * the string path (this.mask.source) reference with * the actual <img> object. */
PrismSlider.prototype.addMask_ = function(){
var path = this.mask.source;
var callback = this.renderMask_.bind(this);
// Replace image path with <img> object. this.mask.source = this.loadImage_(path,callback);
}
;
/** * Add Slides. * Call loadImage method for each image path in the slides array,* only when it's the first slide pass render callback,* when loading completed replace image path with <img> object. */
PrismSlider.prototype.addSlides_ = function(){
this.slides.forEach(function(path,i){
// Render only first slide. var callback = (i === 0) ? this.renderSlide_.bind(this,i):null;
// Replace image path with <img> object. this.slides[i] = this.loadImage_(path,callback);
}
,this);
}
;
/** * Load image source from path and fire given callback,* return loaded <img> object. * @param{
String}
path The path of the file. * @param{
Function}
callback The callback to be executed when * loading completed. * @return{
Object}
The JavaScript <img> object. */
PrismSlider.prototype.loadImage_ = function(path,callback){
var image = new Image();
image.onload = callback;
// Path always after callback. image.src = path;
return image;
}
;
/** * Draw mask. * Calculate center position and draw mask,width and height at 100% of * the container sizes. */
PrismSlider.prototype.renderMask_ = function(){
var centerX = this.canvas.width / 2 - this.settings.container.sizes.w / 2;
var centerY = this.canvas.height / 2 - this.settings.container.sizes.h / 2;
var w = this.settings.container.sizes.w;
var h = this.settings.container.sizes.h;
this.context.drawImage(this.mask.source,centerX,centerY,w,h);
}
;
/** * Draw Slide. * Calculate frame position,apply composite operation * and effects on the image when there is a mask. * @param{
Number}
i The index used to get the img to render. * @param{
Number}
progress The progress value. */
PrismSlider.prototype.renderSlide_ = function(i,progress){
// Set progress to 0 if Not a Number or undefined. progress = (isNaN(progress) || progress === undefined) ? 0:progress;
// Get img object from array. var slide = this.slides[i];
// Calculate X position. var x = this.canvas.width * (i - progress);
var y = 0;
var w = this.canvas.width;
var h = this.canvas.height;
// Apply composite operation. if (this.mask) this.context.globalCompositeOperation = 'source-atop';
this.context.save();
if (this.mask) this.applyEffects_();
// Draw slide. this.context.drawImage(slide,x,y,w,h);
this.context.restore();
}
;
/** * Apply effects. * Check mask object parameters and select effect. */
PrismSlider.prototype.applyEffects_ = function(){
if (this.mask.effects.flip) this.flip_();
if (this.mask.effects.rotate > 0) this.rotate_();
}
;
/** * Flip Effect. */
PrismSlider.prototype.flip_ = function(){
// Get axes. var axes = this.mask.effects.flip;
if (axes === 'X'){
// Invert x position. this.context.translate(this.canvas.width,0);
// Flip context horizontally. this.context.scale(-1,1);
}
if (axes === 'Y'){
// Invert y position. this.context.translate(0,this.canvas.height);
// Flip context vertically. this.context.scale(1,-1);
}
}
;
/** * Rotate Effect. */
PrismSlider.prototype.rotate_ = function(){
// Convert degrees to radians. var radians = this.mask.effects.rotate * (Math.PI / 180);
// Move registration point to the center of the canvas. this.context.translate(this.canvas.width / 2,this.canvas.height / 2);
// Apply rotation. this.context.rotate(radians);
// Move registration point back to the top left corner of canvas. this.context.translate(-this.canvas.width / 2,-this.canvas.height / 2);
}
;
/** * Slide To. * @param{
Number}
index The destination slide index. */
PrismSlider.prototype.slideTo = function(index){
// Prevent when animation is in progres or if same bullet is clicked. if (this.isAnimated || index === this.slidesIndex) return;
// Store current (start) index. this.prevSlidesIndex = this.slidesIndex;
// Set destination (end) index. this.slidesIndex = index;
// Calculate how many slides between current (start) and destination (end). var indexOffset = (this.prevSlidesIndex - this.slidesIndex) * -1;
// Store offset always converted to positive number. this.indexOffset = (indexOffset > 0) ? indexOffset:indexOffset * -1;
// Kickstart animation. this.animate_();
}
;
/** * Animate. */
PrismSlider.prototype.animate_ = function(){
// Calculate end time. var end = Date.now() + this.duration;
// Mark animation as in progress. this.isAnimated = true;
// Kickstart frames ticker. this.ticker_(end);
}
;
/** * Ticker called for each frame of the animation. * @param{
Number}
end The end time of the animation. */
PrismSlider.prototype.ticker_ = function(end){
// Start time. var now = Date.now();
// Update time left in the animation. var remaining = end - now;
// Retrieve easing and multiply for number of slides between stars // and end,in order to jump through N slides in one ease. var easing = this.easing(remaining / this.duration) * this.indexOffset;
var i,progress,slide;
// Select sliding direction. if (this.slidesIndex > this.prevSlidesIndex){
// Sliding forward. progress = this.slidesIndex - easing;
// Loop offset and render slides from start to end. for (i = 0;
i <= this.indexOffset;
i++){
slide = this.slidesIndex - i;
this.renderSlide_(slide,progress);
}
}
else{
// Sliding backward. progress = this.slidesIndex + easing;
// Loop offset and render slides from start to end. for (i = 0;
i <= this.indexOffset;
i++){
slide = this.slidesIndex + i;
this.renderSlide_(slide,progress);
}
}
// Under 50 milliseconds reset and stop. if (remaining < 50){
// Set default value. this.indexOffset = 1;
// Make sure slide is perfectly aligned. this.renderSlide_(this.slidesIndex);
// Mark animation as finished. this.isAnimated = false;
// Stop. return;
}
// Kickstart rAF with updated end. window.requestAnimationFrame(this.ticker_.bind(this,end));
}
;
return PrismSlider;
}
)(window);
JS代码(slideshow1.js):
/** * The slideshow controller. * Get settings and initialise PrismSlider for each layer,* add controls and events,then call slideTo method on click. * @return{
Object}
The set of public methods. */
var slideshow = (function(window,undefined){
'use strict';
/** * Enum navigation classes,attributes and * provide navigation DOM element container. */
var navigation ={
selector:'.navigation',element:null,bullet:'li',attrs:{
active:'active',index:'data-index'}
}
;
/** * Enum main element,sizes and provide * main DOM element container. * @type{
Object}
*/
var container ={
selector:'.prism-slider',element:null,sizes:{
w:1200,h:960}
}
;
/** * Set of images to be used. * @type{
Array}
*/
var slides = [ 'img/bird-a.jpg','img/bird-b.jpg','img/bird-c.jpg','img/bird-d.jpg' ];
/** * Set of masks with related effects. * @type{
Array}
*/
var masks = [{
source:'img/masks/prism-a.svg',effects:{
flip:'X',rotate:45 // degrees}
}
,{
source:'img/masks/prism-b.svg',effects:{
flip:false,rotate:45 // degrees}
}
,{
source:'img/masks/prism-c.svg',effects:{
flip:false,rotate:180 // degrees}
}
];
/** * Set global easing. * @type{
Function(currentTime)}
*/
var easing = Easing.easeInOutQuint;
/** * Set global duration. * @type{
Number}
*/
var duration = 1000;
/** * Container for PrismSlider instances. * @type{
Object}
*/
var instances ={
}
;
/** * Init. */
function init(){
getContainer_();
initSlider_();
initPrism_();
addNavigation_();
addEvents_();
}
/** * Get main container element,and store in container element. */
function getContainer_(){
container.element = document.querySelector(container.selector);
}
/** * Init Slides. * Create and initialise main background slider (first layer). * Since we'll use this as main slider no mask is given. */
function initSlider_(){
instances.slider = new PrismSlider({
container:container,slides:slides,mask:false,duration:duration,easing:easing}
);
// Initialise instance. instances.slider.init();
}
/** * Init Masks. * Loop masks variable and create a new layer for each mask object. */
function initPrism_(){
masks.forEach(function(mask,i){
// Generate reference name. var name = 'mask_' + i;
instances[name] = new PrismSlider({
container:container,slides:slides,mask:mask,// Here is the mask object. duration:duration,easing:easing}
);
// Initialise instance. instances[name].init();
}
);
}
/** * Add Navigation. * Create a new bullet for each slide and add it to navigation (ul) * with data-index reference. */
function addNavigation_(){
// Store navigation element. navigation.element = document.querySelector(navigation.selector);
slides.forEach(function(slide,i){
var bullet = document.createElement(navigation.bullet);
bullet.setAttribute(navigation.attrs.index,i);
// When it's first bullet set class as active. if (i === 0) bullet.className = navigation.attrs.active;
navigation.element.appendChild(bullet);
}
);
}
/** * Add Events. * Bind click on bullets. */
function addEvents_(){
// Detect click on navigation elment (ul). navigation.element.addEventListener('click',function(e){
// Get clicked element. var bullet = e.target;
// Detect if the clicked element is actually a bullet (li). var isBullet = bullet.nodeName === navigation.bullet.toUpperCase();
// Check bullet and prevent action if animation is in progress. if (isBullet && !instances.slider.isAnimated){
// Remove active class from all bullets. for (var i = 0;
i < navigation.element.childNodes.length;
i++){
navigation.element.childNodes[i].className = '';
}
// Add active class to clicked bullet. bullet.className = navigation.attrs.active;
// Get index from data attribute and convert string to number. var index = Number(bullet.getAttribute(navigation.attrs.index));
// Call slideAllTo method with index. slideAllTo_(index);
}
}
);
}
/** * Call slideTo method of each instance. * In order to sync sliding of all layers we'll loop through the * instances object and call the slideTo method for each instance. * @param{
Number}
index The index of the destination slide. */
function slideAllTo_(index){
// Loop PrismSlider instances. for (var key in instances){
if (instances.hasOwnProperty(key)){
// Call slideTo for current instance. instances[key].slideTo(index);
}
}
}
return{
init:init}
;
}
)(window);
/** * Bootstrap slideshow plugin. * For demo purposes images are preloaded inside a div hidden with css,* the plugin initialisation is delayed through window.onload,in a real life * scenario would be better to preload images asynchronously with javascript. */
window.onload = slideshow.init;
CSS代码(component.css):
/* Article Layout */
.wrapper{width:100%;background:white;}
.wrapper:after{content:'';clear:both;display:block;}
.wrapper .copy{width:40%;float:left;position:relative;}
.demo-2 .wrapper .copy{float:right;}
.wrapper .copy:before{width:0;height:0;float:left;padding-bottom:119%;content:'';}
.wrapper .copy article{margin:auto;padding:4vw;position:absolute;top:0;right:0;bottom:0;left:0;}
.wrapper .copy h2{margin:0;font-size:3em;font-size:4vw;}
.wrapper .copy p:first-of-type{color:#b1b6ba;font-size:1.5em;font-size:2vw;margin-bottom:1.5em;}
.wrapper .prism-slider{width:60%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;}
/* PrismSlider */
.cache{display:none;}
.prism-slider{width:1200px;max-width:100%;height:0;padding-bottom:48%;position:relative;}
.prism-slider canvas{width:100%;position:absolute;top:0;left:0;}
.navigation{width:100%;position:absolute;bottom:5%;text-align:center;list-style:none;z-index:1;}
.navigation li{border:3px solid #eceff1;width:18px;height:18px;margin:0 5px;background:#52525a;border-radius:50%;display:inline-block;cursor:pointer;}
.navigation .active{background:#eceff1;}
/* Media Queries */
@media screen and (max-width:1024px){.wrapper:after{display:none;}
.wrapper .copy,.wrapper .prism-slider{width:100%;float:none;}
.wrapper .copy:before{display:none;}
.wrapper .copy article{position:relative;padding:50px;display:block;}
.wrapper .copy h2{font-size:8vw;}
.wrapper .copy p:first-of-type{font-size:4vw;}
.wrapper .prism-slider{padding-bottom:80%;}
.navigation li{width:24px;height:24px;}
}