Option name | Type | Description |
---|---|---|
el | Element | |
params | Object |
Carousel constructor.
var Carousel = function(el, params) {
params = params || {};
if (!el) {
return;
}
this._init(el);
};
Carousel.prototype = {
Include common functionality.
_setParams: Base.setParams,
_toggleClass: Base.toggleClass,
_addClass: Base.addClass,
_removeClass: Base.removeClass,
_hasClass: Base.hasClass,
_getElementMatchingParent: Base.getElementMatchingParent,
_debounce: Debounce,
_transform: Transform,
removeB: Base.remove,
Default values for internal properties we will be setting.
These are set on each construction so we don't leak properties
into the prototype chain.
defaults: {
el: null
},
Whitelisted parameters which can be set on construction.
_whitelistedParams: [],
Option name | Type | Description |
---|---|---|
el | Element | Node to initalize as carouselItem |
parent | Object | reference to the parent carousel |
dot | Element | Node to use as dot element |
order | Number | original order in the markup |
Carousel Item Contructor, exposes access to functions setTransform, setSelected, and currentPosition
_carouselItem: function(el, parent, dot, order) {
var a = {};
a.el = el;
a.parent = parent;
a.dot = dot;
a.order = order;
a.addTransform = function(x) {
a.setTransform(a.transform.x + x);
};
a.setTransform = function(x) {
x = x ? x : 0;
a.transform = {
'x': x
};
a.el.setAttribute('style', a.parent._transform('translate3d', a.transform.x + 'px, 0px, 0px'));
};
a.setSelected = function(b) {
if (a.setSelected.selected === b && typeof a.setSelected.selected !== 'undefined') {
return;
}
if (b) {
a.parent._addClass(a.el, 'selected');
a.parent._addClass(a.dot, 'selected');
a.setSelected.selected = true;
}
else {
a.parent._removeClass(a.el, 'selected');
a.parent._removeClass(a.dot, 'selected');
a.setSelected.selected = false;
}
};
a.currentPosition = function() {
return parent.transform.x + a.transform.x + a.dims.left + (a.width / 2) - parent.dims.left;
};
a.setTransform();
a.dims = el.getBoundingClientRect();
a.width = a.dims.width;
a.el.sparkcarouselitem = a;
a.dot.sparkcarouselitemdot = a;
return a;
},
Option name | Type | Description |
---|---|---|
el | Element | Node to initalize as the carousel |
Scans element and sets up or resets the carousel based on configuration
_init: function(el) {
//cache elements and controls
this.el = el;
this.el.sparkcarousel = this;
this.backe = this.el.querySelector('.spark-carousel__back');
this.forwarde = this.el.querySelector('.spark-carousel__forward');
this.outerContainer = this.el.querySelector('.spark-carousel__outer-container');
this.containerMask = this.el.querySelector('.spark-carousel__container-mask');
this.container = this.el.querySelector('.spark-carousel__container');
this.dots = this.el.querySelector('.spark-carousel__dots');
this.pauseEl = this.el.querySelector('.spark-carousel__pause');
//get the options from the element
this.opts = {};
this.opts.wrapItems = this.el.attributes['data-spark-carousel-wrap-items'] ? true : false;
this.opts.startingVelocity = this.el.attributes['data-spark-carousel-scroll-velocity'] ? this.el.attributes['data-spark-carousel-scroll-velocity'].value : 10;
this.opts.smoothScroll = this.el.attributes['data-spark-carousel-smooth-scroll'] ? true : false;
this.opts.smoothScrollCenterItems = this.el.attributes['data-spark-carousel-smooth-scroll-center'] ? true : false;
this.opts.panelScroll = this.el.attributes['data-spark-carousel-panel'] ? true : false;
this.opts.edgeScroll = this.opts.panelScroll || this.el.attributes['data-spark-carousel-edge'] ? true : false;
this.opts.autoAdvance = this.el.attributes['data-spark-carousel-auto-advance'] ? this.el.attributes['data-spark-carousel-auto-advance'].value : false;
//setup autoAdvance
if (this.opts.autoAdvance && !this.autoAdvance && !this.pause) {
this.autoAdvance = window.setTimeout(function() {
this._autoAdvance();
}.bind(this), this.opts.autoAdvance * 1000);
}
//conditionally bind pause handlers.
//needs to be done here so that resetting the carousel will work correctly
if (this.opts.autoAdvance && !this.pauseH) {
this.pauseH = this._pause.bind(this);
this.pauseEl.addEventListener('click', this.pauseH);
}
//collect items and cache sizing
var a = this.el.querySelectorAll('.spark-carousel__item');
this.items = [];
this.totalItemWidth = 0;
this.dims = this.containerMask.getBoundingClientRect();
this.width = this.dims.width;
this.height = this.dims.height;
var dots = document.createDocumentFragment();
var b;
//create our carouselItems
for (var i = 0; i < a.length; i++) {
b = document.createElement('div');
dots.appendChild(b);
this.items.push(new this._carouselItem(a[i], this, b, i));
this.totalItemWidth += this.items[i].width;
}
//if we're resetting we need to empty out the exisiting elements first
while (this.dots.firstChild) {
this.dots.removeChild(this.dots.firstChild);
}
this.dots.appendChild(dots);
//this is to test if we're dealing with 2011 flexbox (IE10) and need to do an adjustment
//this is because ms-flex-pack: center doesn't work like 2012 flexbox center-pack
if (typeof this.container.style.msFlexAlign !== 'undefined') {
this._transformItems(-(this.totalItemWidth - this.width) / 2);
}
//setup inital transform
this._setTransform();
//need to bind this event handler here as we are always going to need to be listening
//for this event, in order to react to container visibility changing
this._handleVisibleChildren = this._handleVisibleChildrenH.bind(this);
document.addEventListener('spark.visible-children', this._handleVisibleChildren, true);
if (!this._rafHandler) {
this._rafHandler = this._rafHandlerH.bind(this);
}
//need to also listen to resize events, even if we don't have items overflowing
if (!this.resizeH) {
this.resizeH = this._debounce(this._resize.bind(this), 100);
window.addEventListener('resize', this.resizeH);
}
//if we haven't already init'd event listerers, and we have items overflowing
if (this.totalItemWidth > this.width) {
if (!this.touchstartH) {
this._removeClass(this.el, 'noscroll');
this._setupListeners();
//center the first item
this._addTransform(-this.items[0].currentPosition() + (this.width / 2));
}
}
//if we don't have overflowing items, then disable scrolling and remove listeners
else {
this._addClass(this.el, 'noscroll');
this._removeListeners();
}
//set the new selected item
this._updateSelected();
//finally, display the element
this._addClass(this.el, 'ready');
},
Option name | Type | Description |
---|---|---|
leaveElement | Boolean | Falsey value will remove the DOM element as well as the component instance |
Tears down the component, removes listeners, and conditionally delete the DOM element
remove: function(leaveElement) {
window.removeEventListener('resize', this.resizeH);
delete this.resizeH;
document.removeEventListener('spark.visible-children', this._handleVisibleChildren, true);
delete this._handleVisibleChildren;
this.removeB(leaveElement);
},
Pause/unpause the autoAdvance feature
_pause: function() {
if (this.pause) {
delete this.moves;
this.autoAdvance = window.setTimeout(function() {
this._autoAdvance();
}.bind(this), this.opts.autoAdvance * 1000);
}
else {
this._rafCancel();
if (!this.opts.smoothScroll || this.opts.smoothScrollCenterItems) {
this._scrollTo(this._selectedItem());
}
window.clearTimeout(this.autoAdvance);
delete this.autoAdvance;
}
this._setPause(!this.pause);
},
Option name | Type | Description |
---|---|---|
b | Boolean | Truthy value will give the element the pause class |
toggles the pause class on the element
_setPause: function(b) {
if (typeof b === 'undefined') {
this.pause = typeof this.pause === 'undefined' ? false : this.pause;
return this.pause;
}
else {
if (b) {
this._addClass(this.el, 'pause');
}
else {
this._removeClass(this.el, 'pause');
}
this.pause = b;
return this.pause;
}
},
function called by window.setTimeout, will check first to see if element is in use before triggering a slide advance
_autoAdvance: function() {
if (!this.moves && !this._laststart && !this.paused) {
this._rafCancel();
var a = this.items.indexOf(this._selectedItem());
this._scrollToItem = true;
this._scrollTo(this.items[a === this.items.length - 1 ? 0 : a + 1]);
this.autoAdvance = window.setTimeout(function() {
this._autoAdvance();
}.bind(this), this.opts.autoAdvance * 1000);
}
},
initalize and bind even listeners
_setupListeners: function() {
this.touchstartH = this._touchstart.bind(this);
this.container.addEventListener('touchstart', this.touchstartH);
this.touchmoveH = this._touchmove.bind(this);
window.addEventListener('touchmove', this.touchmoveH);
this.touchendH = this._touchend.bind(this);
window.addEventListener('touchend', this.touchendH);
this.mousedownH = this._mousedown.bind(this);
this.container.addEventListener('mousedown', this.mousedownH);
this.mousemoveH = this._mousemove.bind(this);
window.addEventListener('mousemove', this.mousemoveH);
this.mouseupH = this._mouseup.bind(this);
window.addEventListener('mouseup', this.mouseupH);
this.forwardH = this._forward.bind(this);
this.forwarde.addEventListener('click', this.forwardH);
this.backH = this._back.bind(this);
this.backe.addEventListener('click', this.backH);
this.clickH = this._click.bind(this);
this.el.addEventListener('click', this.clickH);
this._focusHandler = this._scrollToClicked.bind(this);
this.container.addEventListener('focus', this._focusHandler, true);
},
Removes non-essential event listeners, called when tearing down the component, or our content
does not exceed the width of our element
_removeListeners: function() {
this.el.removeEventListener('touchstart', this.touchstartH);
delete this.touchstartH;
window.removeEventListener('touchmove', this.touchmoveH);
delete this.touchmoveH;
window.removeEventListener('touchend', this.touchendH);
delete this.touchendH;
this.el.removeEventListener('mousedown', this.mousedownH);
delete this.mousedownH;
window.removeEventListener('mousemove', this.mousemoveH);
delete this.mousemoveH;
window.removeEventListener('mouseup', this.mouseupH);
delete this.mouseupH;
this.forwarde.removeEventListener('click', this.forwardH);
delete this.forwardH;
this.backe.removeEventListener('click', this.backH);
delete this.backH;
this.el.removeEventListener('click', this.clickH);
delete this.clickH;
this.container.removeEventListener('focus', this._focusHandler, true);
delete this._focusHandler;
if (this.pauseEl) {
this.pauseEl.removeEventListener('click', this.pauseH);
delete this.pauseH;
}
},
Option name | Type | Description |
---|---|---|
e | Event | The spark.visible-children event |
Event handler for the spark.visible-children event, just call the change function to handle any
visibility or sizing changes
_handleVisibleChildrenH: function(e) {
if (e.target.contains(this.el)) {
window.setTimeout(function() {
this.change();
}.bind(this), 0);
}
},
Option name | Type | Description |
---|---|---|
e | Event | The click event |
Forward button click handler triggers a scrollTo to the "next" element
_forward: function(e) {
var s = this.items.indexOf(this._selectedItem());
s++;
if (s > this.items.length - 1) {
if (this.opts.wrapItems) {
s = 0;
}
else {
s--;
}
}
delete this.moves;
var a = this._startingVelocity;
a = a < -this.opts.startingVelocity ? a : -this.opts.startingVelocity;
this._rafCancel();
this._scrollToItem = true;
this._scrollTo(this.items[s], a);
if (e) {
e.preventDefault();
}
},
Option name | Type | Description |
---|---|---|
e | Event | The click event |
Back button click handler triggers a scrollTo to the "previous" element
_back: function(e) {
var s = this.items.indexOf(this._selectedItem());
s--;
if (s < 0) {
if (this.opts.wrapItems) {
s = this.items.length - 1;
}
else {
s++;
}
}
delete this.moves;
var a = this._startingVelocity;
a = a > this.opts.startingVelocity ? a : this.opts.startingVelocity;
this._rafCancel();
this._scrollToItem = true;
this._scrollTo(this.items[s], a);
if (e) {
e.preventDefault();
}
},
Option name | Type | Description |
---|---|---|
e | Object | The start event |
Move start handler, handles both touchstart and mousedown events
_movestart: function(e) {
this._rafCancel();
this.moves = [];
this.moves.push(e);
},
Option name | Type | Description |
---|---|---|
e | Object | The move event |
Move handler, handles internal move event objects
_move: function(e) {
if (this.moves && this.moves.length > 1) {
this._addTransform(e.pageX - this.moves[this.moves.length - 1].pageX);
this.moves.push(e);
e.preventDefault = true;
}
else {
if (this.moves && this.moves[0]) {
if (Math.abs(this.moves[0].pageX - e.pageX) > Math.abs(this.moves[0].pageY - e.pageY) && Math.abs(this.moves[0].pageX - e.pageX) > 5 && e.cancelable) {
this._addTransform(e.pageX - this.moves[0].pageX);
this.moves.push(e);
e.preventDefault = true;
}
else {
if (Math.abs(this.moves[0].pageX - e.pageX) > 5) {
this.moves[0] = e;
}
}
}
if (e.type === 'touchend' || e.type === 'mouseup') {
delete this.moves;
}
}
},
Option name | Type | Description |
---|---|---|
e | Object | The moveend event |
Move end handler, handles both touchend and mouseup events
_moveend: function(e) {
this._move(e);
if (!this.opts.smoothScroll) {
this._settle(this.moves);
}
else {
this._interiaScroll(this.moves);
}
return e;
},
Resize event handler, calls change to handle any element dimension changes
_resize: function() {
this.change();
},
Calling the change function will handle updating the element to take into account
any styling, sizing, or visibility changes, and the addition or removal of any carouselItems
change: function() {
var dims = this.el.getBoundingClientRect();
if (dims.width !== this.width || dims.height !== this.height) {
if (this.autoAdvance) {
window.clearTimeout(this.autoAdvance);
delete this.autoAdvance;
}
this._rafCancel();
var c = this._selectedItem();
this._setTransform(0);
this._setTransformItems(0);
this._init(this.el);
if (this.items.indexOf(c.el.sparkcarouselitem) > -1 && this.totalItemWidth > this.width) {
if (this.opts.wrapItems) {
this._addTransform(-this.totalItemWidth + (-c.el.sparkcarouselitem.currentPosition() + (this.width / 2)));
}
else {
this._addTransform(-c.el.sparkcarouselitem.currentPosition() + (this.width / 2));
}
}
}
},
Option name | Type | Description |
---|---|---|
e | Object | The touchstart event |
Touchstart event handler, passes necessary data points to the movestart function
_touchstart: function(e) {
var a = {
'type': e.type,
'pageX': e.touches[0].pageX,
'pageY': e.touches[0].pageY,
'timeStamp': e.timeStamp
};
this._movestart(a);
},
Option name | Type | Description |
---|---|---|
e | Object | The touchmove event |
Touchmove event handler, passes necessary data points to the move function
_touchmove: function(e) {
var a = {
'type': e.type,
'pageX': e.touches[0].pageX,
'pageY': e.touches[0].pageY,
'timeStamp': e.timeStamp,
'cancelable': e.cancelable
};
this._move(a);
if (a.preventDefault) {
e.preventDefault();
}
},
Option name | Type | Description |
---|---|---|
e | Object | The touchend event |
Touchend event handler, passes necessary data points to the moveend function
_touchend: function(e) {
if (this.moves && this.moves.length > 2 && e.cancelable) {
var a = {
'type': e.type,
'pageX': this.moves[this.moves.length - 1].pageX,
'pageY': this.moves[this.moves.length - 1].pageY,
'timeStamp': e.timeStamp
};
this._moveend(a);
} else {
delete this.moves;
}
},
Option name | Type | Description |
---|---|---|
e | Object | The mousedown event |
Mousedown event handler, passes necessary data points to the movestart function
_mousedown: function(e) {
if (e.button !== 0) {
return e;
}
this.isMouseDown = true;
var a = {
'type': e.type,
'pageX': e.pageX,
'pageY': e.pageY,
'timeStamp': e.timeStamp
};
this._movestart(a);
e.preventDefault();
},
Option name | Type | Description |
---|---|---|
e | Object | The mousemove event |
Mousemove event handler, passes necessary data points to the move function
_mousemove: function(e) {
if (this.isMouseDown) {
var a = {
'type': e.type,
'pageX': e.clientX,
'pageY': e.clientY,
'timeStamp': e.timeStamp,
//this was changed to correct an issue in safari - it doesn't report cancelable correctly
'cancelable': true
};
this._move(a);
if (a.preventDefault) {
e.preventDefault();
}
}
},
Option name | Type | Description |
---|---|---|
e | Object | The mouseup event |
Mouseup event handler, passes necessary data points to the moveend function
_mouseup: function(e) {
if (this.moves && this.moves.length > 2) {
var a = {
'type': e.type,
'pageX': e.pageX,
'pageY': e.pageY,
'timeStamp': e.timeStamp
};
this._moveend(a);
this.mouseUpHandled = true;
}
else {
delete this.moves;
this._scrollToClicked(e);
}
this.isMouseDown = false;
},
Option name | Type | Description |
---|---|---|
e | Object | The click event |
Click event handler
_click: function(e) {
//if we are already tracking moves, then this will be handled by the mouseend event handler and we should prevent the default action
if (this.moves) {
e.preventDefault();
}
//if it has already been handled by mouseup handler, prevent the action
if (this.mouseUpHandled) {
e.preventDefault();
}
//reset our handled state
delete this.mouseUpHandled;
//checking both this.moves and this.mouseUpHandled ensures we capture events correctly in all browsers, where the order of the mouseup/click events can vary
},
Option name | Type | Description |
---|---|---|
moves | Array | The array of cursor positions |
Calculate the user's recent cursor/finger velocity
_velocity: function(moves) {
var avg = 0;
var m = Math.min(6, moves.length - 1);
for (var i = 1; i < m; i++) {
if (moves[moves.length - i].timeStamp === moves[moves.length - i - 1].timeStamp) {
avg += avg / i;
}
else {
avg += (10 * (moves[moves.length - i].pageX - moves[moves.length - i - 1].pageX) / (moves[moves.length - i].timeStamp - moves[moves.length - i - 1].timeStamp)) / m;
}
}
return avg;
},
Option name | Type | Description |
---|---|---|
e | Event | The click event |
Handles click events on items and dots, scrolling to the clicked item
_scrollToClicked: function(e) {
var tar = e.target;
if (this.el.contains(tar)) {
while (!tar.sparkcarousel) {
if (tar.sparkcarouselitem) {
this.containerMask.scrollLeft = 0;
delete this.moves;
this._rafCancel();
this._scrollTo(tar.sparkcarouselitem);
e.preventDefault();
break;
}
if (tar.sparkcarouselitemdot) {
this.containerMask.scrollLeft = 0;
delete this.moves;
this._rafCancel();
var v = tar.sparkcarouselitemdot.order < this._selectedItem().order ? this.opts.startingVelocity : -this.opts.startingVelocity;
this._scrollTo(tar.sparkcarouselitemdot, v);
e.preventDefault();
break;
}
tar = tar.parentNode;
}
}
},
Option name | Type | Description |
---|---|---|
item | Object | The carouselItem to scroll to |
startingVelocity | Number | The startingVelocity of the scroll animation |
Scroll to the carouselItem, with specified startingVelocity, auto determines default velocity if not specified
_scrollTo: function(item, startingVelocity) {
var offset = this.width / 2;
var currentPosition = item.currentPosition();
if (!startingVelocity) {
startingVelocity = offset - item.currentPosition() > 0 ? this.opts.startingVelocity : -this.opts.startingVelocity;
}
if (this.opts.wrapItems) {
if (startingVelocity > 0) {
//left
if (currentPosition > offset) {
this._totalDistance = offset + this.totalItemWidth - currentPosition;
}
else {
this._totalDistance = offset - currentPosition;
}
}
else {
//right
if (currentPosition < offset) {
this._totalDistance = -(this.totalItemWidth + currentPosition - offset);
}
else {
this._totalDistance = offset - currentPosition;
}
}
}
else {
this._totalDistance = offset - currentPosition;
}
this._startingVelocity = startingVelocity;
delete this.moves;
this._scrollToItem = true;
this._raf = window.requestAnimationFrame(this._rafHandler);
},
Option name | Type | Description |
---|---|---|
t | Number | The timestamp for the current animation frame |
This is the animator function, it examines the options set on the carousel object
and selectively adds transform and requests addtional animation frames if necesary
_rafHandlerH: function(t) {
if (this.opts.autoAdvance && this.autoAdvance) {
window.clearTimeout(this.autoAdvance);
delete this.autoAdvance;
}
var frames;
if (this.moves || !this._startingVelocity) {
this._rafCancel();
return;
}
if (!this._laststart) {
this._laststart = t;
}
if (!this._remainingDistance) {
this._remainingDistance = this._totalDistance;
}
if (!this._lastframe) {
this._lastframe = t;
frames = 1;
}
else {
frames = (t - this._lastframe) / ((1 / 60) * 1000);
}
var d = this._startingVelocity * frames;
if (this.opts.smoothScroll && !this._scrollToItem) {
this._addTransform(d);
this._startingVelocity *= Math.pow(0.97, frames);
if (this.opts.smoothScrollCenterItems && Math.abs(this._startingVelocity) < 1) {
this._scrollTo(this._selectedItem());
}
if (Math.abs(this._startingVelocity) < 0.5) {
if ((this._startingVelocity > 0 && this.transform.x > ((this.totalItemWidth / 2) - (this.items[0].width / 2))) ||
(this._startingVelocity < 0 && this.transform.x < (-((this.totalItemWidth / 2) - (this.items[this.items.length - 1].width / 2))))) {
this._scrollToItem = true;
this._scrollTo(this._selectedItem());
}
else {
this._rafCancel();
}
}
else {
this._raf = window.requestAnimationFrame(this._rafHandler);
}
}
else {
if (this._startingVelocity > 0) {
if (d < this._remainingDistance) {
this._addTransform(d);
this._remainingDistance -= d;
if (this._remainingDistance > this._totalDistance / 2) {
this._startingVelocity *= Math.pow(1.15, frames);
}
else {
this._startingVelocity *= Math.pow(0.9, frames);
this._startingVelocity = this._startingVelocity > 2 ? this._startingVelocity : 2;
}
this._raf = window.requestAnimationFrame(this._rafHandler);
}
else {
this._addTransform(this._remainingDistance);
this._rafCancel();
}
}
else {
if (d > this._remainingDistance) {
this._addTransform(d);
this._remainingDistance -= d;
if (this._remainingDistance < this._totalDistance / 2) {
this._startingVelocity *= Math.pow(1.15, frames);
}
else {
this._startingVelocity *= Math.pow(0.9, frames);
this._startingVelocity = this._startingVelocity < -2 ? this._startingVelocity : -2;
}
this._raf = window.requestAnimationFrame(this._rafHandler);
}
else {
this._addTransform(this._remainingDistance);
this._rafCancel();
}
}
}
this._lastframe = t;
},
This is the animator clearing function
it clears values used during animation, and selectively enables autoAdvance
_rafCancel: function() {
if (this.opts.autoAdvance && !this.autoAdvance && !this.pause) {
this.autoAdvance = window.setTimeout(function() {
this._autoAdvance();
}.bind(this), this.opts.autoAdvance * 1000);
}
window.cancelAnimationFrame(this._raf);
delete this._scrollToItem;
delete this._laststart;
delete this._startingVelocity;
delete this._remainingDistance;
delete this._totalDistance;
delete this._lastframe;
},
Option name | Type | Description |
---|---|---|
moves | Array | The captured move events |
This computes values necessary to start an animation frame when the carousel is
configured to use smoothScroll
_interiaScroll: function(moves) {
if (moves[moves.length - 1].timeStamp - moves[moves.length - 2].timeStamp > 100 || moves.length < 3) {
if(this.opts.smoothScrollCenterItems) {
return this._scrollTo(this._selectedItem());
}
return;
}
this._startingVelocity = this._velocity(moves);
delete this.moves;
this._raf = window.requestAnimationFrame(this._rafHandler);
},
Option name | Type | Description |
---|---|---|
moves | Array | The captured move events |
This determines which carousel item should be focused based on the previous moves
made by the user
_settle: function(moves) {
if (moves && moves.length > 3) {
if (moves[moves.length - 1].timeStamp - moves[moves.length - 2].timeStamp > 80) {
return this._scrollTo(this._selectedItem());
}
var v1 = 10 * (moves[moves.length - 3].pageX - moves[moves.length - 4].pageX) / (moves[moves.length - 3].timeStamp - moves[moves.length - 4].timeStamp);
var v2 = 10 * (moves[moves.length - 2].pageX - moves[moves.length - 3].pageX) / (moves[moves.length - 2].timeStamp - moves[moves.length - 3].timeStamp);
if (Math.abs(v1) < Math.abs(v2) || Math.abs(v2) > 0.5 && Math.abs(v2) > 0.5) {
//user is probably trying to go to next or prev item
var s = this.items.indexOf(this._selectedItem());
if (v2 > 0) {
//prev
if (s > 0) {
this._scrollTo(this.items[s - 1], v2);
}
else {
if (this.opts.wrapItems) {
this._scrollTo(this.items[this.items.length - 1], v2);
}
else {
this._scrollTo(this.items[0]);
}
}
}
else {
//next
if (s < this.items.length - 1) {
this._scrollTo(this.items[s + 1], v2);
}
else {
if (this.opts.wrapItems) {
this._scrollTo(this.items[0], v2);
}
else {
this._scrollTo(this.items[this.items.length - 1]);
}
}
}
}
else {
if (this._selectedItem().currentPosition() > this.width / 2) {
this._scrollTo(this._selectedItem(), -this.opts.startingVelocity);
}
else {
this._scrollTo(this._selectedItem(), this.opts.startingVelocity);
}
}
}
},
Option name | Type | Description |
---|---|---|
x | Number | The pixel value to transform |
Transforms the position of all carouselItems
_transformItems: function(x) {
for (var i = 0; i < this.items.length; i++) {
this.items[i].addTransform(x);
}
},
Option name | Type | Description |
---|---|---|
x | Number | The pixel value to transform |
Sets the transform position of all carouselItems
_setTransformItems: function(x) {
for (var i = 0; i < this.items.length; i++) {
this.items[i].setTransform(x);
}
},
Option name | Type | Description |
---|---|---|
x | Number | The pixel value to transform |
Adds transform to the container element, does checking for bounds conditions and
wraps items if necessary and configured
_addTransform: function(x) {
var a;
if ((this.opts.smoothScrollCenterItems || !this.opts.smoothScroll) && !this.opts.wrapItems && !this.opts.edgeScroll) {
var l = this.items.indexOf(this.selectedItem);
if (l === this.items.length - 1) {
this._leftbound(true);
}
else {
this._leftbound(false);
}
if (l === 0) {
this._rightbound(true);
}
else {
this._rightbound(false);
}
}
else {
this._leftbound(false);
this._rightbound(false);
}
if (this.transform.x + x < 0 && x < 0) {
if (this.opts.wrapItems) {
//wrap items until we have covered the visible area
while (this.transform.x + x < -(this.totalItemWidth - this.width) / 2 && (this.totalItemWidth < this.width ? this.transform.x + x < -this.totalItemWidth / 2 : true)) {
a = this.items.shift();
this.items.push(a);
a.addTransform(this.totalItemWidth);
this._transformItems(-a.width);
x += a.width;
}
}
else {
//there is a 1 pixel adjustment to account for some math rounding
if (this.opts.edgeScroll && x < 0 && this.transform.x + x - 1 <= -(this.totalItemWidth - this.width) / 2) {
this._leftbound(true);
return this._setTransform(-(this.totalItemWidth - this.width) / 2);
}
//progressively reduce scrolling when no more items to the right
if (x < 0 && (this.transform.x + x) < (-((this.totalItemWidth / 2) - (this.items[this.items.length - 1].width / 2)))) {
x = x * (((this.totalItemWidth / 2) + (this.items[this.items.length - 1].width / 2) + (this.transform.x + x)) / this.items[this.items.length - 1].width);
x = x > 0 ? 0 : x;
}
}
return this._setTransform(this.transform.x + x);
}
else {
if (this.transform.x + x > 0 && x > 0) {
if (this.opts.wrapItems) {
//wrap items until we have covered the visible area
while (this.transform.x + x > -(this.width - this.totalItemWidth) / 2 && (this.totalItemWidth < this.width ? this.transform.x + x > this.totalItemWidth / 2 : true)) {
a = this.items.pop();
this.items.unshift(a);
a.addTransform(-this.totalItemWidth);
this._transformItems(a.width);
x -= a.width;
}
}
else {
//there is a 1 pixel adjustment to account for some math rounding
if (this.opts.edgeScroll && x > 0 && this.transform.x + x + 1 >= (this.totalItemWidth - this.width) / 2) {
this._rightbound(true);
return this._setTransform((this.totalItemWidth - this.width) / 2);
}
//progressively reduce scrolling when no more items to the left
if (x > 0 && (this.transform.x + x) > ((this.totalItemWidth / 2) - (this.items[0].width / 2))) {
x = x * (((this.totalItemWidth / 2) + (this.items[0].width / 2) - (this.transform.x + x)) / this.items[0].width);
x = x < 0 ? 0 : x;
}
}
}
return this._setTransform(this.transform.x + x);
}
},
Option name | Type | Description |
---|---|---|
b | Boolean | Set or unset the leftbound class |
Sets the leftbound class
_leftbound: function(b) {
if (typeof b === 'undefined') {
this.leftbound = typeof this.leftbound === 'undefined' ? false : this.leftbound;
return this.leftbound;
}
else {
if (b) {
this._addClass(this.el, 'leftbound');
}
else {
this._removeClass(this.el, 'leftbound');
}
this.leftbound = b;
return this.leftbound;
}
},
Option name | Type | Description |
---|---|---|
b | Boolean | Set or unset the rightbound class |
Sets the rightbound class
_rightbound: function(b) {
if (typeof b === 'undefined') {
this.rightbound = typeof this.rightbound === 'undefined' ? false : this.rightbound;
return this.rightbound;
}
else {
if (b) {
this._addClass(this.el, 'rightbound');
}
else {
this._removeClass(this.el, 'rightbound');
}
this.rightbound = b;
return this.rightbound;
}
},
Updates the selected item, by seeing which item has its center closest
to the center of the carousel
_updateSelected: function() {
var tar = this.width / 2;
var i = -1;
var a = 1;
var b = 0;
while (a > b) {
i++;
if (i > this.items.length - 2) {
break;
}
a = Math.abs(tar - this.items[i].currentPosition());
b = Math.abs(tar - this.items[i + 1].currentPosition());
}
return this._selectedItem(this.items[i]);
},
Option name | Type | Description |
---|---|---|
item | Object | Optional: the new item select, if omitted it will return the currently selected item. |
Stores the selected item for the carousel, and updates the previously
selected item and newly selected item to have the correct states
Conditionally sets the leftbound/rightbound states depending on configuration
_selectedItem: function(item) {
if (typeof item !== 'object') {
if (this.selectedItem) {
return this.selectedItem;
}
else {
return this._updateSelected();
}
}
else {
if (this.selectedItem) {
this.selectedItem.setSelected(false);
}
this.selectedItem = item;
if ((this.opts.smoothScrollCenterItems || !this.opts.smoothScroll) && !this.opts.wrapItems && !this.opts.edgeScroll) {
var l = this.items.indexOf(this.selectedItem);
if (l === this.items.length - 1) {
this._leftbound(true);
}
else {
this._leftbound(false);
}
if (l === 0) {
this._rightbound(true);
}
else {
this._rightbound(false);
}
}
this.selectedItem.setSelected(true);
}
},
Option name | Type | Description |
---|---|---|
x | Number | The pixel value to transform |
Sets the transform for the carousel container
_setTransform: function(x) {
x = x ? x : 0;
this.transform = {
'x': x
};
this.container.setAttribute('style', this._transform('translate3d', x + 'px, 0px, 0px'));
this._updateSelected();
return x;
}
};
Base.exportjQuery(Carousel, 'Carousel');
return Carousel;
}));