Difference between revisions of "Team:Exeter/jquery.coverflow"

(Created page with "/*jslint devel: true, bitwise: true, regexp: true, browser: true, confusion: true, unparam: true, eqeq: true, white: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 *...")
 
 
Line 5: Line 5:
 
  * Coverflow
 
  * Coverflow
 
  *
 
  *
  * Copyright (c) 2013-2015 Martijn W. van der Lee
+
  * Copyright (c) 2013 Martijn W. van der Lee
 
  * Licensed under the MIT.
 
  * Licensed under the MIT.
 
  */
 
  */
 
 
/* Lightweight and flexible coverflow effect using CSS3 transforms.
 
/* Lightweight and flexible coverflow effect using CSS3 transforms.
 
  * For modern browsers with some amount of graceful degradation.
 
  * For modern browsers with some amount of graceful degradation.
Line 21: Line 20:
 
"use strict";
 
"use strict";
  
var sign = function(number) {
+
var sign = function(number) {
return number < 0 ? -1 : 1;
+
return number < 0 ? -1 : 1;
},
+
},
scl = function(number, fromMin, fromMax, toMin, toMax) {
+
scl = function(number, fromMin, fromMax, toMin, toMax) {
return ((number - fromMin) * (toMax - toMin) / (fromMax - fromMin)) + toMin;
+
return ((number - fromMin) * (toMax - toMin) / (fromMax - fromMin)) + toMin;
},
+
};
wheelEvents = ('onwheel' in document) ? 'wheel' : 'mousewheel', // FF
+
getWheel = function(event) {
+
if ('deltaY' in event.originalEvent) {
+
return 0 - event.originalEvent.deltaY;
+
} else if ('wheelDelta' in event.originalEvent) {
+
return event.originalEvent.wheelDelta; // IE
+
}
+
};
+
  
 
$.widget("vanderlee.coverflow", {
 
$.widget("vanderlee.coverflow", {
Line 43: Line 34:
 
duration: 'normal',
 
duration: 'normal',
 
easing: undefined,
 
easing: undefined,
enableKeyboard: true,
 
enableClick: true,
 
enableWheel: true,
 
 
index: 0,
 
index: 0,
 
innerAngle: -75,
 
innerAngle: -75,
Line 62: Line 50:
 
select: undefined // Whenever index is set (also on init)
 
select: undefined // Whenever index is set (also on init)
 
},
 
},
 
_window_handler_resize: null,
 
_window_handler_keydown: null,
 
  
 
_create: function() {
 
_create: function() {
var that = this,
+
var that = this;
covers = that._getCovers(),
+
images = covers.filter('img').add('img', covers).filter(function() {
+
return !(this.complete || this.height > 0);
+
}),
+
maxHeight = covers.height(),
+
height;
+
  
 
// Internal event prefix
 
// Internal event prefix
Line 81: Line 60:
 
that.pagesize = 1;
 
that.pagesize = 1;
 
that.currentIndex = that.options.index;
 
that.currentIndex = that.options.index;
+
 
 
// Fix height
 
// Fix height
that.element.height(maxHeight);
+
that.element.height(that._getCovers().first().height());
images.load(function() {
+
height = that._getCovers().height();
+
if (height > maxHeight) {
+
maxHeight = height;
+
that.element.height(maxHeight);
+
}
+
});
+
  
 
// Hide all covers and set position to absolute
 
// Hide all covers and set position to absolute
covers.hide();
+
that._getCovers().hide().css('position', 'absolute');
  
 
// Enable click-jump
 
// Enable click-jump
that.element.on('mousedown tap', '> *', function(event) {
+
that.element.on('click', '> *', function() {
if (that.options.enableClick) {
+
var index = that._getCovers().index(this);
var index = that._getCovers().index(this);
+
if (index === that.currentIndex) {
if (index === that.currentIndex) {
+
that._callback('confirm');
that._callback('confirm', event);
+
} else {
} else {
+
that._setIndex(index, true);
that._setIndex(index, true);
+
}
+
 
}
 
}
 
});
 
});
  
// Mousewheel
+
// Refresh on resize
that.element.on(wheelEvents, function(event) {
+
$(window).resize(function() {
if (that.options.enableWheel) {
+
that.refresh();
var delta = getWheel(event) > 0 ? 1 : -1;
+
});
  
event.preventDefault();
+
// Mousewheel
that._setIndex(that.options.index - delta, true);
+
that.element.on('mousewheel', function(event, delta) {
}
+
event.preventDefault();
 +
that._setIndex(that.options.index - delta, true);
 
});
 
});
  
Line 133: Line 104:
 
);
 
);
  
// Refresh on resize
+
$(window).on('keydown', function(event) {
that._window_handler_resize = function() {
+
if (that.hovering) {
that.refresh();
+
};
+
$(window).on('resize', that._window_handler_resize);
+
+
that._window_handler_keydown = function(event) {
+
if (that.options.enableKeyboard && that.hovering) {
+
 
switch (event.which) {
 
switch (event.which) {
 
case 36: // home
 
case 36: // home
Line 175: Line 140:
 
}
 
}
 
}
 
}
};
+
});
$(window).on('keydown', that._window_handler_keydown);
+
  
 
// Initialize
 
// Initialize
 
that._setIndex(that.options.index, false, true);
 
that._setIndex(that.options.index, false, true);
 +
that.refresh();
  
 
return that;
 
return that;
},
 
 
 
/**
 
* Destroy this object
 
* @returns {undefined}
 
*/
 
_destroy: function() {
 
$(window).off('resize', this._window_handler_resize);
 
$(window).off('keydown', this._window_handler_keydown);
 
this.element.height('');
 
 
},
 
},
  
Line 212: Line 166:
  
 
_setIndex: function(index, animate, initial) {
 
_setIndex: function(index, animate, initial) {
var that = this,
+
var covers = this._getCovers();
covers = that._getCovers();
+
  
 
index = Math.max(0, Math.min(index, covers.length - 1));
 
index = Math.max(0, Math.min(index, covers.length - 1));
  
if (index !== that.options.index) {
+
if (index !== this.options.index) {
// Fix reflections
+
this.refresh(); // pre-correct for reflection/mods
covers.css('position', 'absolute');
+
this._frame(that.options.index);
+
  
if (animate === true || that.options.duration === 0) {
+
if (animate === true) {
that.options.index = Math.round(index);
+
this.currentIndex = this.options.index;
+
this.options.index = Math.round(index);
var duration = typeof that.options.duration === "number"
+
 
 +
var that = this,
 +
duration = typeof that.options.duration === "number"
 
? that.options.duration
 
? that.options.duration
: jQuery.fx.speeds[that.options.duration] || jQuery.fx.speeds._default;
+
: jQuery.fx.speeds[that.options.duration] || jQuery.fx.speeds._default,
+
timeout = null,
this.refresh(duration, that.options.index);
+
step = that.options.index > that.currentIndex ? 1 : -1,
 +
doStep = function() {
 +
var steps = Math.abs(that.options.index - that.currentIndex),
 +
time = duration / Math.max(1, steps) * 0.5;
 +
if (that.options.index !== that.currentIndex) {
 +
that.currentIndex += step;
 +
that.refresh.call(that, time, that.currentIndex);
 +
timeout = setTimeout(doStep, time);
 +
}
 +
that._callback('change');
 +
that._callback('select');
 +
};
 +
if (timeout) {
 +
clearTimeout(timeout);
 +
}
 +
if (that.currentIndex !== this.options.index) {
 +
doStep();
 +
}
 
} else {
 
} else {
that.options.index = Math.round(index);
+
this.currentIndex = this.options.index = Math.round(index);
that.refresh(0);
+
this.refresh(this.options.duration);
 +
this._callback('change');
 +
this._callback('select');
 
}
 
}
 
} else if (initial === true) {
 
} else if (initial === true) {
that.refresh();
+
this.refresh();
that._callback('select');
+
this._callback('select');
 
}
 
}
 
},
 
},
  
_callback: function(callback, event) {
+
_callback: function(callback) {
this._trigger(callback, event, [this._getCovers().get(this.currentIndex), this.currentIndex]);
+
this._trigger(callback, null, this._getCovers().get(this.currentIndex), this.currentIndex);
 
},
 
},
  
Line 248: Line 220:
 
return this.options.index;
 
return this.options.index;
 
}
 
}
 
while (index < 0) {
 
index += this._getCovers().length;
 
}
 
 
 
this._setIndex(index, true);
 
this._setIndex(index, true);
 
},
 
},
+
 
_frame: function(frame) {
+
refresh: function(duration, index) {
frame = frame.toFixed(6);
+
+
 
var that = this,
 
var that = this,
covers = that._getCovers(),
+
target = index || that.options.index,
count = covers.length,
+
count = that._getCovers().length,
 
parentWidth = that.element.innerWidth(),
 
parentWidth = that.element.innerWidth(),
coverWidth = that.options.width || covers.first().outerWidth(),
+
coverWidth = that.options.width || that._getCovers().first().outerWidth(),
 
visible = that.options.visible === 'density' ? Math.round(parentWidth * that.options.density / coverWidth)
 
visible = that.options.visible === 'density' ? Math.round(parentWidth * that.options.density / coverWidth)
 
: $.isNumeric(that.options.visible) ? that.options.visible
 
: $.isNumeric(that.options.visible) ? that.options.visible
Line 269: Line 234:
 
parentLeft = that.element.position().left - ((1 - that.options.outerScale) * coverWidth * 0.5),
 
parentLeft = that.element.position().left - ((1 - that.options.outerScale) * coverWidth * 0.5),
 
space = (parentWidth - (that.options.outerScale * coverWidth)) * 0.5;
 
space = (parentWidth - (that.options.outerScale * coverWidth)) * 0.5;
+
 
 +
duration = duration || 0;
 +
 
 
that.pagesize = visible;
 
that.pagesize = visible;
+
 
covers.removeClass('current').each(function(index, cover) {
+
that._getCovers().removeClass('current').each(function(index, cover) {
var $cover = $(cover),
+
var position = index - target,
position = index - frame,
+
offset = position / visible,
offset = Math.min(Math.max(-1., position / visible), 1),
+
isVisible = Math.abs(offset) <= 1,
isMiddle = position == 0,
+
sin = isVisible ? Math.sin(offset * Math.PI * 0.5)
zIndex = count - Math.abs(Math.round(position)),
+
: sign(offset),
isVisible = Math.abs(position) <= visible,
+
cos = isVisible ? Math.cos(offset * Math.PI * 0.5)
sin = Math.sin(offset * Math.PI * 0.5),
+
: 0,
cos = Math.cos(offset * Math.PI * 0.5),
+
isMiddle = position === 0,
left = sign(sin) * scl(Math.abs(sin), 0, 1, that.options.innerOffset * that.options.density, space),
+
zIndex = count - Math.abs(position),
scale = isVisible ? scl(Math.abs(cos), 1, 0, that.options.innerScale, that.options.outerScale) : 0,
+
left = parentLeft + space + (isMiddle ? 0 : sign(sin) * scl(Math.abs(sin), 0, 1, that.options.innerOffset * that.options.density, space)),
angle = sign(sin) * scl(Math.abs(sin), 0, 1, that.options.innerAngle, that.options.outerAngle),
+
scale = !isVisible? 0
 +
: isMiddle ? 1
 +
: scl(Math.abs(cos), 1, 0, that.options.innerScale, that.options.outerScale),
 +
angle = isMiddle ? 0
 +
: sign(sin) * scl(Math.abs(sin), 0, 1, that.options.innerAngle, that.options.outerAngle),
 +
state = {},
 
css = isMiddle ? that.options.selectedCss || {}
 
css = isMiddle ? that.options.selectedCss || {}
 
: ( $.interpolate && that.options.outerCss && !$.isEmptyObject(that.options.outerCss) ? (
 
: ( $.interpolate && that.options.outerCss && !$.isEmptyObject(that.options.outerCss) ? (
Line 291: Line 263:
 
),
 
),
 
transform;
 
transform;
 
// bad behaviour for being in the middle
 
if (Math.abs(position) < 1) {
 
angle = 0 - (0 - angle) * Math.abs(position);
 
scale = 1 - (1 - scale) * Math.abs(position);
 
left = 0 - (0 - left) * Math.abs(position);
 
}
 
 
//@todo Test CSS for middle behaviour (or does $.interpolate handle it?)
 
  
transform = 'scale(' + scale + ',' + scale + ') perspective(' + (parentWidth * 0.5) + 'px) rotateY(' + angle + 'deg)';
+
if (isVisible) {
+
$(cover).show();
$cover[isMiddle ? 'addClass' : 'removeClass']('current');
+
$cover[isVisible ? 'show' : 'hide']();
+
+
$cover.css($.extend(css, {
+
'left': parentLeft + space + left,
+
'z-index': zIndex,
+
'-webkit-transform': transform,
+
'-ms-transform': transform,
+
'transform': transform
+
}));
+
+
// Optional callback
+
that._trigger('animateStep', null, [cover, offset, isVisible, isMiddle, sin, cos]);
+
+
if (frame == that.options.index) {
+
// Optional callback
+
that._trigger('animateComplete', null, [cover, offset, isVisible, isMiddle, sin, cos]);
+
 
}
 
}
});
 
},
 
  
refresh: function(duration, index) {
+
$(cover).stop().css({
var that = this,
+
'z-index': zIndex
previous = that.currentIndex,
+
}).animate($.extend(css, {
covers = that._getCovers(),
+
'left': left,
covercount = covers.length,
+
'_sin': sin,
triggered = false;
+
'_cos': cos,
+
'_scale': scale,
covers.css('position', 'absolute');
+
'_angle': angle // must be last!
that.element.stop().animate({
+
}), {
'__coverflow_frame': index || that.options.index
+
'easing': that.options.easing,
}, {
+
'duration': duration,
'easing': that.options.easing,
+
'step': function(now, fx) {
'duration': duration || 0,
+
// Store state
'step': function(now, fx) {
+
state[fx.prop] = now;
that._frame(now);
+
 
+
// On last of the states, change all at once
that.currentIndex = Math.max(0, Math.min(Math.round(now), covercount - 1));
+
if (fx.prop === '_angle') {
if (previous !== that.currentIndex) {
+
transform = 'scale(' + state._scale + ',' + state._scale + ') perspective('+(parentWidth * 0.5)+'px) rotateY(' + state._angle + 'deg)';
previous = that.currentIndex;
+
$(this).css({
that._callback('change');
+
'-webkit-transform': transform,
if (that.currentIndex === that.options.index) {
+
'-ms-transform': transform,
triggered = true;
+
'transform': transform
 +
});
 +
 
 +
// Optional callback
 +
that._trigger('animateStep', cover, [cover, offset, isVisible, isMiddle, state._sin, state._cos]);
 
}
 
}
 +
},
 +
'complete': function() {
 +
$(this)[isMiddle ? 'addClass' : 'removeClass']('current');
 +
$(this)[isVisible ? 'show' : 'hide']();
 +
 +
// Optional callback
 +
that._trigger('animateComplete', cover, [cover, offset, isVisible, isMiddle, sin, cos]);
 
}
 
}
},
+
});
'complete': function() {
+
that.currentIndex = that.options.index;
+
if (!triggered) {
+
that._callback('change');
+
}
+
that._callback('select');
+
}
+
 
});
 
});
 
}
 
}
 
});
 
});
 
}(jQuery));
 
}(jQuery));

Latest revision as of 16:24, 12 September 2015

/*jslint devel: true, bitwise: true, regexp: true, browser: true, confusion: true, unparam: true, eqeq: true, white: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */ /*globals jQuery */

/*!

* Coverflow
*
* Copyright (c) 2013 Martijn W. van der Lee
* Licensed under the MIT.
*/

/* Lightweight and flexible coverflow effect using CSS3 transforms.

* For modern browsers with some amount of graceful degradation.
* Optional support for jQuery.interpolate() plugin.
* Optional support for .reflect() plugins.
*
* Requires jQuery 1.7+ and jQueryUI 1.9+.
* Recommended jQuery 1.8+ and jQueryUI 1.9+.
*/
(function($, undefined) {

"use strict";

var sign = function(number) { return number < 0 ? -1 : 1; }, scl = function(number, fromMin, fromMax, toMin, toMax) { return ((number - fromMin) * (toMax - toMin) / (fromMax - fromMin)) + toMin; };

$.widget("vanderlee.coverflow", { options: { animateComplete: undefined, animateStep: undefined, density: 1, duration: 'normal', easing: undefined, index: 0, innerAngle: -75, innerCss: undefined, innerOffset: 100 / 3, innerScale: 0.75, outerAngle: -30, outerCss: undefined, outerScale: 0.25, selectedCss: undefined, visible: 'density', // 'density', 'all', NNN (exact) width: undefined,

change: undefined, // Whenever index is changed confirm: undefined, // Whenever clicking on the current item select: undefined // Whenever index is set (also on init) },

_create: function() { var that = this;

// Internal event prefix that.widgetEventPrefix = 'vanderlee-coverflow';

that.hovering = false; that.pagesize = 1; that.currentIndex = that.options.index;

// Fix height that.element.height(that._getCovers().first().height());

// Hide all covers and set position to absolute that._getCovers().hide().css('position', 'absolute');

// Enable click-jump that.element.on('click', '> *', function() { var index = that._getCovers().index(this); if (index === that.currentIndex) { that._callback('confirm'); } else { that._setIndex(index, true); } });

// Refresh on resize $(window).resize(function() { that.refresh(); });

// Mousewheel that.element.on('mousewheel', function(event, delta) { event.preventDefault(); that._setIndex(that.options.index - delta, true); });

// Swipe if ($.isFunction(that.element.swipe)) { that.element.swipe({ swipe: function(event, direction, distance, duration, fingerCount) { var count = Math.round((direction === 'left' ? 1 : -1) * 1.25 * that.pagesize * distance / that.element.width()); that._setIndex(that.options.index + count, true); } }); }

// Keyboard that.element.hover( function() { that.hovering = true; } , function() { that.hovering = false; } );

$(window).on('keydown', function(event) { if (that.hovering) { switch (event.which) { case 36: // home event.preventDefault(); that._setIndex(0, true); break;

case 35: // end event.preventDefault(); that._setIndex(that._getCovers().length - 1, true); break;

case 38: // up case 37: // left event.preventDefault(); that._setIndex(that.options.index - 1, true); break;

case 40: // down case 39: // right event.preventDefault(); that._setIndex(that.options.index + 1, true); break;

case 33: // page up (towards home) event.preventDefault(); that._setIndex(that.options.index - that.pagesize, true); break;

case 34: // page down (towards end) event.preventDefault(); that._setIndex(that.options.index + that.pagesize, true); break; } } });

// Initialize that._setIndex(that.options.index, false, true); that.refresh();

return that; },

/** * Returns the currently selected cover * @returns {jQuery} jQuery object */ cover: function() { return $(this._getCovers()[this.options.index]); },

/** * * @returns {unresolved} */ _getCovers: function() { return $('> *', this.element); },

_setIndex: function(index, animate, initial) { var covers = this._getCovers();

index = Math.max(0, Math.min(index, covers.length - 1));

if (index !== this.options.index) { this.refresh(); // pre-correct for reflection/mods

if (animate === true) { this.currentIndex = this.options.index; this.options.index = Math.round(index);

var that = this, duration = typeof that.options.duration === "number" ? that.options.duration : jQuery.fx.speeds[that.options.duration] || jQuery.fx.speeds._default, timeout = null, step = that.options.index > that.currentIndex ? 1 : -1, doStep = function() { var steps = Math.abs(that.options.index - that.currentIndex), time = duration / Math.max(1, steps) * 0.5; if (that.options.index !== that.currentIndex) { that.currentIndex += step; that.refresh.call(that, time, that.currentIndex); timeout = setTimeout(doStep, time); } that._callback('change'); that._callback('select'); }; if (timeout) { clearTimeout(timeout); } if (that.currentIndex !== this.options.index) { doStep(); } } else { this.currentIndex = this.options.index = Math.round(index); this.refresh(this.options.duration); this._callback('change'); this._callback('select'); } } else if (initial === true) { this.refresh(); this._callback('select'); } },

_callback: function(callback) { this._trigger(callback, null, this._getCovers().get(this.currentIndex), this.currentIndex); },

index: function(index) { if (index === undefined) { return this.options.index; } this._setIndex(index, true); },

refresh: function(duration, index) { var that = this, target = index || that.options.index, count = that._getCovers().length, parentWidth = that.element.innerWidth(), coverWidth = that.options.width || that._getCovers().first().outerWidth(), visible = that.options.visible === 'density' ? Math.round(parentWidth * that.options.density / coverWidth) : $.isNumeric(that.options.visible) ? that.options.visible : count, parentLeft = that.element.position().left - ((1 - that.options.outerScale) * coverWidth * 0.5), space = (parentWidth - (that.options.outerScale * coverWidth)) * 0.5;

duration = duration || 0;

that.pagesize = visible;

that._getCovers().removeClass('current').each(function(index, cover) { var position = index - target, offset = position / visible, isVisible = Math.abs(offset) <= 1, sin = isVisible ? Math.sin(offset * Math.PI * 0.5) : sign(offset), cos = isVisible ? Math.cos(offset * Math.PI * 0.5) : 0, isMiddle = position === 0, zIndex = count - Math.abs(position), left = parentLeft + space + (isMiddle ? 0 : sign(sin) * scl(Math.abs(sin), 0, 1, that.options.innerOffset * that.options.density, space)), scale = !isVisible? 0 : isMiddle ? 1 : scl(Math.abs(cos), 1, 0, that.options.innerScale, that.options.outerScale), angle = isMiddle ? 0 : sign(sin) * scl(Math.abs(sin), 0, 1, that.options.innerAngle, that.options.outerAngle), state = {}, css = isMiddle ? that.options.selectedCss || {} : ( $.interpolate && that.options.outerCss && !$.isEmptyObject(that.options.outerCss) ? ( isVisible ? $.interpolate(that.options.innerCss || {}, that.options.outerCss, Math.abs(sin))  : that.options.outerCss ) : {} ), transform;

if (isVisible) { $(cover).show(); }

$(cover).stop().css({ 'z-index': zIndex }).animate($.extend(css, { 'left': left, '_sin': sin, '_cos': cos, '_scale': scale, '_angle': angle // must be last! }), { 'easing': that.options.easing, 'duration': duration, 'step': function(now, fx) { // Store state state[fx.prop] = now;

// On last of the states, change all at once if (fx.prop === '_angle') { transform = 'scale(' + state._scale + ',' + state._scale + ') perspective('+(parentWidth * 0.5)+'px) rotateY(' + state._angle + 'deg)'; $(this).css({ '-webkit-transform': transform, '-ms-transform': transform, 'transform': transform });

// Optional callback that._trigger('animateStep', cover, [cover, offset, isVisible, isMiddle, state._sin, state._cos]); } }, 'complete': function() { $(this)[isMiddle ? 'addClass' : 'removeClass']('current'); $(this)[isVisible ? 'show' : 'hide']();

// Optional callback that._trigger('animateComplete', cover, [cover, offset, isVisible, isMiddle, sin, cos]); } }); }); } }); }(jQuery));