930 lines
24 KiB
JavaScript
930 lines
24 KiB
JavaScript
// Flickity main
|
|
( function( window, factory ) {
|
|
// universal module definition
|
|
/* jshint strict: false */
|
|
if ( typeof define == 'function' && define.amd ) {
|
|
// AMD
|
|
define( [
|
|
'ev-emitter/ev-emitter',
|
|
'get-size/get-size',
|
|
'fizzy-ui-utils/utils',
|
|
'./cell',
|
|
'./slide',
|
|
'./animate'
|
|
], function( EvEmitter, getSize, utils, Cell, Slide, animatePrototype ) {
|
|
return factory( window, EvEmitter, getSize, utils, Cell, Slide, animatePrototype );
|
|
});
|
|
} else if ( typeof module == 'object' && module.exports ) {
|
|
// CommonJS
|
|
module.exports = factory(
|
|
window,
|
|
require('ev-emitter'),
|
|
require('get-size'),
|
|
require('fizzy-ui-utils'),
|
|
require('./cell'),
|
|
require('./slide'),
|
|
require('./animate')
|
|
);
|
|
} else {
|
|
// browser global
|
|
var _Flickity = window.Flickity;
|
|
|
|
window.Flickity = factory(
|
|
window,
|
|
window.EvEmitter,
|
|
window.getSize,
|
|
window.fizzyUIUtils,
|
|
_Flickity.Cell,
|
|
_Flickity.Slide,
|
|
_Flickity.animatePrototype
|
|
);
|
|
}
|
|
|
|
}( window, function factory( window, EvEmitter, getSize,
|
|
utils, Cell, Slide, animatePrototype ) {
|
|
|
|
'use strict';
|
|
|
|
// vars
|
|
var jQuery = window.jQuery;
|
|
var getComputedStyle = window.getComputedStyle;
|
|
var console = window.console;
|
|
|
|
function moveElements( elems, toElem ) {
|
|
elems = utils.makeArray( elems );
|
|
while ( elems.length ) {
|
|
toElem.appendChild( elems.shift() );
|
|
}
|
|
}
|
|
|
|
// -------------------------- Flickity -------------------------- //
|
|
|
|
// globally unique identifiers
|
|
var GUID = 0;
|
|
// internal store of all Flickity intances
|
|
var instances = {};
|
|
|
|
function Flickity( element, options ) {
|
|
var queryElement = utils.getQueryElement( element );
|
|
if ( !queryElement ) {
|
|
if ( console ) {
|
|
console.error( 'Bad element for Flickity: ' + ( queryElement || element ) );
|
|
}
|
|
return;
|
|
}
|
|
this.element = queryElement;
|
|
// do not initialize twice on same element
|
|
if ( this.element.flickityGUID ) {
|
|
var instance = instances[ this.element.flickityGUID ];
|
|
instance.option( options );
|
|
return instance;
|
|
}
|
|
|
|
// add jQuery
|
|
if ( jQuery ) {
|
|
this.$element = jQuery( this.element );
|
|
}
|
|
// options
|
|
this.options = utils.extend( {}, this.constructor.defaults );
|
|
this.option( options );
|
|
|
|
// kick things off
|
|
this._create();
|
|
}
|
|
|
|
Flickity.defaults = {
|
|
accessibility: true,
|
|
// adaptiveHeight: false,
|
|
cellAlign: 'center',
|
|
// cellSelector: undefined,
|
|
// contain: false,
|
|
freeScrollFriction: 0.075, // friction when free-scrolling
|
|
friction: 0.28, // friction when selecting
|
|
namespaceJQueryEvents: true,
|
|
// initialIndex: 0,
|
|
percentPosition: true,
|
|
resize: true,
|
|
selectedAttraction: 0.025,
|
|
setGallerySize: true
|
|
// watchCSS: false,
|
|
// wrapAround: false
|
|
};
|
|
|
|
// hash of methods triggered on _create()
|
|
Flickity.createMethods = [];
|
|
|
|
var proto = Flickity.prototype;
|
|
// inherit EventEmitter
|
|
utils.extend( proto, EvEmitter.prototype );
|
|
|
|
proto._create = function() {
|
|
// add id for Flickity.data
|
|
var id = this.guid = ++GUID;
|
|
this.element.flickityGUID = id; // expando
|
|
instances[ id ] = this; // associate via id
|
|
// initial properties
|
|
this.selectedIndex = 0;
|
|
// how many frames slider has been in same position
|
|
this.restingFrames = 0;
|
|
// initial physics properties
|
|
this.x = 0;
|
|
this.velocity = 0;
|
|
this.originSide = this.options.rightToLeft ? 'right' : 'left';
|
|
// create viewport & slider
|
|
this.viewport = document.createElement('div');
|
|
this.viewport.className = 'flickity-viewport';
|
|
this._createSlider();
|
|
|
|
if ( this.options.resize || this.options.watchCSS ) {
|
|
window.addEventListener( 'resize', this );
|
|
}
|
|
|
|
// add listeners from on option
|
|
for ( var eventName in this.options.on ) {
|
|
var listener = this.options.on[ eventName ];
|
|
this.on( eventName, listener );
|
|
}
|
|
|
|
Flickity.createMethods.forEach( function( method ) {
|
|
this[ method ]();
|
|
}, this );
|
|
|
|
if ( this.options.watchCSS ) {
|
|
this.watchCSS();
|
|
} else {
|
|
this.activate();
|
|
}
|
|
|
|
};
|
|
|
|
/**
|
|
* set options
|
|
* @param {Object} opts
|
|
*/
|
|
proto.option = function( opts ) {
|
|
utils.extend( this.options, opts );
|
|
};
|
|
|
|
proto.activate = function() {
|
|
if ( this.isActive ) {
|
|
return;
|
|
}
|
|
this.isActive = true;
|
|
this.element.classList.add('flickity-enabled');
|
|
if ( this.options.rightToLeft ) {
|
|
this.element.classList.add('flickity-rtl');
|
|
}
|
|
|
|
this.getSize();
|
|
// move initial cell elements so they can be loaded as cells
|
|
var cellElems = this._filterFindCellElements( this.element.children );
|
|
moveElements( cellElems, this.slider );
|
|
this.viewport.appendChild( this.slider );
|
|
this.element.appendChild( this.viewport );
|
|
// get cells from children
|
|
this.reloadCells();
|
|
|
|
if ( this.options.accessibility ) {
|
|
// allow element to focusable
|
|
this.element.tabIndex = 0;
|
|
// listen for key presses
|
|
this.element.addEventListener( 'keydown', this );
|
|
}
|
|
|
|
this.emitEvent('activate');
|
|
this.selectInitialIndex();
|
|
// flag for initial activation, for using initialIndex
|
|
this.isInitActivated = true;
|
|
// ready event. #493
|
|
this.dispatchEvent('ready');
|
|
};
|
|
|
|
// slider positions the cells
|
|
proto._createSlider = function() {
|
|
// slider element does all the positioning
|
|
var slider = document.createElement('div');
|
|
slider.className = 'flickity-slider';
|
|
slider.style[ this.originSide ] = 0;
|
|
this.slider = slider;
|
|
};
|
|
|
|
proto._filterFindCellElements = function( elems ) {
|
|
return utils.filterFindElements( elems, this.options.cellSelector );
|
|
};
|
|
|
|
// goes through all children
|
|
proto.reloadCells = function() {
|
|
// collection of item elements
|
|
this.cells = this._makeCells( this.slider.children );
|
|
this.positionCells();
|
|
this._getWrapShiftCells();
|
|
this.setGallerySize();
|
|
};
|
|
|
|
/**
|
|
* turn elements into Flickity.Cells
|
|
* @param {Array or NodeList or HTMLElement} elems
|
|
* @returns {Array} items - collection of new Flickity Cells
|
|
*/
|
|
proto._makeCells = function( elems ) {
|
|
var cellElems = this._filterFindCellElements( elems );
|
|
|
|
// create new Flickity for collection
|
|
var cells = cellElems.map( function( cellElem ) {
|
|
return new Cell( cellElem, this );
|
|
}, this );
|
|
|
|
return cells;
|
|
};
|
|
|
|
proto.getLastCell = function() {
|
|
return this.cells[ this.cells.length - 1 ];
|
|
};
|
|
|
|
proto.getLastSlide = function() {
|
|
return this.slides[ this.slides.length - 1 ];
|
|
};
|
|
|
|
// positions all cells
|
|
proto.positionCells = function() {
|
|
// size all cells
|
|
this._sizeCells( this.cells );
|
|
// position all cells
|
|
this._positionCells( 0 );
|
|
};
|
|
|
|
/**
|
|
* position certain cells
|
|
* @param {Integer} index - which cell to start with
|
|
*/
|
|
proto._positionCells = function( index ) {
|
|
index = index || 0;
|
|
// also measure maxCellHeight
|
|
// start 0 if positioning all cells
|
|
this.maxCellHeight = index ? this.maxCellHeight || 0 : 0;
|
|
var cellX = 0;
|
|
// get cellX
|
|
if ( index > 0 ) {
|
|
var startCell = this.cells[ index - 1 ];
|
|
cellX = startCell.x + startCell.size.outerWidth;
|
|
}
|
|
var len = this.cells.length;
|
|
for ( var i=index; i < len; i++ ) {
|
|
var cell = this.cells[i];
|
|
cell.setPosition( cellX );
|
|
cellX += cell.size.outerWidth;
|
|
this.maxCellHeight = Math.max( cell.size.outerHeight, this.maxCellHeight );
|
|
}
|
|
// keep track of cellX for wrap-around
|
|
this.slideableWidth = cellX;
|
|
// slides
|
|
this.updateSlides();
|
|
// contain slides target
|
|
this._containSlides();
|
|
// update slidesWidth
|
|
this.slidesWidth = len ? this.getLastSlide().target - this.slides[0].target : 0;
|
|
};
|
|
|
|
/**
|
|
* cell.getSize() on multiple cells
|
|
* @param {Array} cells
|
|
*/
|
|
proto._sizeCells = function( cells ) {
|
|
cells.forEach( function( cell ) {
|
|
cell.getSize();
|
|
});
|
|
};
|
|
|
|
// -------------------------- -------------------------- //
|
|
|
|
proto.updateSlides = function() {
|
|
this.slides = [];
|
|
if ( !this.cells.length ) {
|
|
return;
|
|
}
|
|
|
|
var slide = new Slide( this );
|
|
this.slides.push( slide );
|
|
var isOriginLeft = this.originSide == 'left';
|
|
var nextMargin = isOriginLeft ? 'marginRight' : 'marginLeft';
|
|
|
|
var canCellFit = this._getCanCellFit();
|
|
|
|
this.cells.forEach( function( cell, i ) {
|
|
// just add cell if first cell in slide
|
|
if ( !slide.cells.length ) {
|
|
slide.addCell( cell );
|
|
return;
|
|
}
|
|
|
|
var slideWidth = ( slide.outerWidth - slide.firstMargin ) +
|
|
( cell.size.outerWidth - cell.size[ nextMargin ] );
|
|
|
|
if ( canCellFit.call( this, i, slideWidth ) ) {
|
|
slide.addCell( cell );
|
|
} else {
|
|
// doesn't fit, new slide
|
|
slide.updateTarget();
|
|
|
|
slide = new Slide( this );
|
|
this.slides.push( slide );
|
|
slide.addCell( cell );
|
|
}
|
|
}, this );
|
|
// last slide
|
|
slide.updateTarget();
|
|
// update .selectedSlide
|
|
this.updateSelectedSlide();
|
|
};
|
|
|
|
proto._getCanCellFit = function() {
|
|
var groupCells = this.options.groupCells;
|
|
if ( !groupCells ) {
|
|
return function() {
|
|
return false;
|
|
};
|
|
} else if ( typeof groupCells == 'number' ) {
|
|
// group by number. 3 -> [0,1,2], [3,4,5], ...
|
|
var number = parseInt( groupCells, 10 );
|
|
return function( i ) {
|
|
return ( i % number ) !== 0;
|
|
};
|
|
}
|
|
// default, group by width of slide
|
|
// parse '75%
|
|
var percentMatch = typeof groupCells == 'string' &&
|
|
groupCells.match(/^(\d+)%$/);
|
|
var percent = percentMatch ? parseInt( percentMatch[1], 10 ) / 100 : 1;
|
|
return function( i, slideWidth ) {
|
|
return slideWidth <= ( this.size.innerWidth + 1 ) * percent;
|
|
};
|
|
};
|
|
|
|
// alias _init for jQuery plugin .flickity()
|
|
proto._init =
|
|
proto.reposition = function() {
|
|
this.positionCells();
|
|
this.positionSliderAtSelected();
|
|
};
|
|
|
|
proto.getSize = function() {
|
|
this.size = getSize( this.element );
|
|
this.setCellAlign();
|
|
this.cursorPosition = this.size.innerWidth * this.cellAlign;
|
|
};
|
|
|
|
var cellAlignShorthands = {
|
|
// cell align, then based on origin side
|
|
center: {
|
|
left: 0.5,
|
|
right: 0.5
|
|
},
|
|
left: {
|
|
left: 0,
|
|
right: 1
|
|
},
|
|
right: {
|
|
right: 0,
|
|
left: 1
|
|
}
|
|
};
|
|
|
|
proto.setCellAlign = function() {
|
|
var shorthand = cellAlignShorthands[ this.options.cellAlign ];
|
|
this.cellAlign = shorthand ? shorthand[ this.originSide ] : this.options.cellAlign;
|
|
};
|
|
|
|
proto.setGallerySize = function() {
|
|
if ( this.options.setGallerySize ) {
|
|
var height = this.options.adaptiveHeight && this.selectedSlide ?
|
|
this.selectedSlide.height : this.maxCellHeight;
|
|
this.viewport.style.height = height + 'px';
|
|
}
|
|
};
|
|
|
|
proto._getWrapShiftCells = function() {
|
|
// only for wrap-around
|
|
if ( !this.options.wrapAround ) {
|
|
return;
|
|
}
|
|
// unshift previous cells
|
|
this._unshiftCells( this.beforeShiftCells );
|
|
this._unshiftCells( this.afterShiftCells );
|
|
// get before cells
|
|
// initial gap
|
|
var gapX = this.cursorPosition;
|
|
var cellIndex = this.cells.length - 1;
|
|
this.beforeShiftCells = this._getGapCells( gapX, cellIndex, -1 );
|
|
// get after cells
|
|
// ending gap between last cell and end of gallery viewport
|
|
gapX = this.size.innerWidth - this.cursorPosition;
|
|
// start cloning at first cell, working forwards
|
|
this.afterShiftCells = this._getGapCells( gapX, 0, 1 );
|
|
};
|
|
|
|
proto._getGapCells = function( gapX, cellIndex, increment ) {
|
|
// keep adding cells until the cover the initial gap
|
|
var cells = [];
|
|
while ( gapX > 0 ) {
|
|
var cell = this.cells[ cellIndex ];
|
|
if ( !cell ) {
|
|
break;
|
|
}
|
|
cells.push( cell );
|
|
cellIndex += increment;
|
|
gapX -= cell.size.outerWidth;
|
|
}
|
|
return cells;
|
|
};
|
|
|
|
// ----- contain ----- //
|
|
|
|
// contain cell targets so no excess sliding
|
|
proto._containSlides = function() {
|
|
if ( !this.options.contain || this.options.wrapAround || !this.cells.length ) {
|
|
return;
|
|
}
|
|
var isRightToLeft = this.options.rightToLeft;
|
|
var beginMargin = isRightToLeft ? 'marginRight' : 'marginLeft';
|
|
var endMargin = isRightToLeft ? 'marginLeft' : 'marginRight';
|
|
var contentWidth = this.slideableWidth - this.getLastCell().size[ endMargin ];
|
|
// content is less than gallery size
|
|
var isContentSmaller = contentWidth < this.size.innerWidth;
|
|
// bounds
|
|
var beginBound = this.cursorPosition + this.cells[0].size[ beginMargin ];
|
|
var endBound = contentWidth - this.size.innerWidth * ( 1 - this.cellAlign );
|
|
// contain each cell target
|
|
this.slides.forEach( function( slide ) {
|
|
if ( isContentSmaller ) {
|
|
// all cells fit inside gallery
|
|
slide.target = contentWidth * this.cellAlign;
|
|
} else {
|
|
// contain to bounds
|
|
slide.target = Math.max( slide.target, beginBound );
|
|
slide.target = Math.min( slide.target, endBound );
|
|
}
|
|
}, this );
|
|
};
|
|
|
|
// ----- ----- //
|
|
|
|
/**
|
|
* emits events via eventEmitter and jQuery events
|
|
* @param {String} type - name of event
|
|
* @param {Event} event - original event
|
|
* @param {Array} args - extra arguments
|
|
*/
|
|
proto.dispatchEvent = function( type, event, args ) {
|
|
var emitArgs = event ? [ event ].concat( args ) : args;
|
|
this.emitEvent( type, emitArgs );
|
|
|
|
if ( jQuery && this.$element ) {
|
|
// default trigger with type if no event
|
|
type += this.options.namespaceJQueryEvents ? '.flickity' : '';
|
|
var $event = type;
|
|
if ( event ) {
|
|
// create jQuery event
|
|
var jQEvent = jQuery.Event( event );
|
|
jQEvent.type = type;
|
|
$event = jQEvent;
|
|
}
|
|
this.$element.trigger( $event, args );
|
|
}
|
|
};
|
|
|
|
// -------------------------- select -------------------------- //
|
|
|
|
/**
|
|
* @param {Integer} index - index of the slide
|
|
* @param {Boolean} isWrap - will wrap-around to last/first if at the end
|
|
* @param {Boolean} isInstant - will immediately set position at selected cell
|
|
*/
|
|
proto.select = function( index, isWrap, isInstant ) {
|
|
if ( !this.isActive ) {
|
|
return;
|
|
}
|
|
index = parseInt( index, 10 );
|
|
this._wrapSelect( index );
|
|
|
|
if ( this.options.wrapAround || isWrap ) {
|
|
index = utils.modulo( index, this.slides.length );
|
|
}
|
|
// bail if invalid index
|
|
if ( !this.slides[ index ] ) {
|
|
return;
|
|
}
|
|
var prevIndex = this.selectedIndex;
|
|
this.selectedIndex = index;
|
|
this.updateSelectedSlide();
|
|
if ( isInstant ) {
|
|
this.positionSliderAtSelected();
|
|
} else {
|
|
this.startAnimation();
|
|
}
|
|
if ( this.options.adaptiveHeight ) {
|
|
this.setGallerySize();
|
|
}
|
|
// events
|
|
this.dispatchEvent( 'select', null, [ index ] );
|
|
// change event if new index
|
|
if ( index != prevIndex ) {
|
|
this.dispatchEvent( 'change', null, [ index ] );
|
|
}
|
|
// old v1 event name, remove in v3
|
|
this.dispatchEvent('cellSelect');
|
|
};
|
|
|
|
// wraps position for wrapAround, to move to closest slide. #113
|
|
proto._wrapSelect = function( index ) {
|
|
var len = this.slides.length;
|
|
var isWrapping = this.options.wrapAround && len > 1;
|
|
if ( !isWrapping ) {
|
|
return index;
|
|
}
|
|
var wrapIndex = utils.modulo( index, len );
|
|
// go to shortest
|
|
var delta = Math.abs( wrapIndex - this.selectedIndex );
|
|
var backWrapDelta = Math.abs( ( wrapIndex + len ) - this.selectedIndex );
|
|
var forewardWrapDelta = Math.abs( ( wrapIndex - len ) - this.selectedIndex );
|
|
if ( !this.isDragSelect && backWrapDelta < delta ) {
|
|
index += len;
|
|
} else if ( !this.isDragSelect && forewardWrapDelta < delta ) {
|
|
index -= len;
|
|
}
|
|
// wrap position so slider is within normal area
|
|
if ( index < 0 ) {
|
|
this.x -= this.slideableWidth;
|
|
} else if ( index >= len ) {
|
|
this.x += this.slideableWidth;
|
|
}
|
|
};
|
|
|
|
proto.previous = function( isWrap, isInstant ) {
|
|
this.select( this.selectedIndex - 1, isWrap, isInstant );
|
|
};
|
|
|
|
proto.next = function( isWrap, isInstant ) {
|
|
this.select( this.selectedIndex + 1, isWrap, isInstant );
|
|
};
|
|
|
|
proto.updateSelectedSlide = function() {
|
|
var slide = this.slides[ this.selectedIndex ];
|
|
// selectedIndex could be outside of slides, if triggered before resize()
|
|
if ( !slide ) {
|
|
return;
|
|
}
|
|
// unselect previous selected slide
|
|
this.unselectSelectedSlide();
|
|
// update new selected slide
|
|
this.selectedSlide = slide;
|
|
slide.select();
|
|
this.selectedCells = slide.cells;
|
|
this.selectedElements = slide.getCellElements();
|
|
// HACK: selectedCell & selectedElement is first cell in slide, backwards compatibility
|
|
// Remove in v3?
|
|
this.selectedCell = slide.cells[0];
|
|
this.selectedElement = this.selectedElements[0];
|
|
};
|
|
|
|
proto.unselectSelectedSlide = function() {
|
|
if ( this.selectedSlide ) {
|
|
this.selectedSlide.unselect();
|
|
}
|
|
};
|
|
|
|
proto.selectInitialIndex = function() {
|
|
var initialIndex = this.options.initialIndex;
|
|
// already activated, select previous selectedIndex
|
|
if ( this.isInitActivated ) {
|
|
this.select( this.selectedIndex, false, true );
|
|
return;
|
|
}
|
|
// select with selector string
|
|
if ( initialIndex && typeof initialIndex == 'string' ) {
|
|
var cell = this.queryCell( initialIndex );
|
|
if ( cell ) {
|
|
this.selectCell( initialIndex, false, true );
|
|
return;
|
|
}
|
|
}
|
|
|
|
var index = 0;
|
|
// select with number
|
|
if ( initialIndex && this.slides[ initialIndex ] ) {
|
|
index = initialIndex;
|
|
}
|
|
// select instantly
|
|
this.select( index, false, true );
|
|
};
|
|
|
|
/**
|
|
* select slide from number or cell element
|
|
* @param {Element or Number} elem
|
|
*/
|
|
proto.selectCell = function( value, isWrap, isInstant ) {
|
|
// get cell
|
|
var cell = this.queryCell( value );
|
|
if ( !cell ) {
|
|
return;
|
|
}
|
|
|
|
var index = this.getCellSlideIndex( cell );
|
|
this.select( index, isWrap, isInstant );
|
|
};
|
|
|
|
proto.getCellSlideIndex = function( cell ) {
|
|
// get index of slides that has cell
|
|
for ( var i=0; i < this.slides.length; i++ ) {
|
|
var slide = this.slides[i];
|
|
var index = slide.cells.indexOf( cell );
|
|
if ( index != -1 ) {
|
|
return i;
|
|
}
|
|
}
|
|
};
|
|
|
|
// -------------------------- get cells -------------------------- //
|
|
|
|
/**
|
|
* get Flickity.Cell, given an Element
|
|
* @param {Element} elem
|
|
* @returns {Flickity.Cell} item
|
|
*/
|
|
proto.getCell = function( elem ) {
|
|
// loop through cells to get the one that matches
|
|
for ( var i=0; i < this.cells.length; i++ ) {
|
|
var cell = this.cells[i];
|
|
if ( cell.element == elem ) {
|
|
return cell;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* get collection of Flickity.Cells, given Elements
|
|
* @param {Element, Array, NodeList} elems
|
|
* @returns {Array} cells - Flickity.Cells
|
|
*/
|
|
proto.getCells = function( elems ) {
|
|
elems = utils.makeArray( elems );
|
|
var cells = [];
|
|
elems.forEach( function( elem ) {
|
|
var cell = this.getCell( elem );
|
|
if ( cell ) {
|
|
cells.push( cell );
|
|
}
|
|
}, this );
|
|
return cells;
|
|
};
|
|
|
|
/**
|
|
* get cell elements
|
|
* @returns {Array} cellElems
|
|
*/
|
|
proto.getCellElements = function() {
|
|
return this.cells.map( function( cell ) {
|
|
return cell.element;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* get parent cell from an element
|
|
* @param {Element} elem
|
|
* @returns {Flickit.Cell} cell
|
|
*/
|
|
proto.getParentCell = function( elem ) {
|
|
// first check if elem is cell
|
|
var cell = this.getCell( elem );
|
|
if ( cell ) {
|
|
return cell;
|
|
}
|
|
// try to get parent cell elem
|
|
elem = utils.getParent( elem, '.flickity-slider > *' );
|
|
return this.getCell( elem );
|
|
};
|
|
|
|
/**
|
|
* get cells adjacent to a slide
|
|
* @param {Integer} adjCount - number of adjacent slides
|
|
* @param {Integer} index - index of slide to start
|
|
* @returns {Array} cells - array of Flickity.Cells
|
|
*/
|
|
proto.getAdjacentCellElements = function( adjCount, index ) {
|
|
if ( !adjCount ) {
|
|
return this.selectedSlide.getCellElements();
|
|
}
|
|
index = index === undefined ? this.selectedIndex : index;
|
|
|
|
var len = this.slides.length;
|
|
if ( 1 + ( adjCount * 2 ) >= len ) {
|
|
return this.getCellElements();
|
|
}
|
|
|
|
var cellElems = [];
|
|
for ( var i = index - adjCount; i <= index + adjCount ; i++ ) {
|
|
var slideIndex = this.options.wrapAround ? utils.modulo( i, len ) : i;
|
|
var slide = this.slides[ slideIndex ];
|
|
if ( slide ) {
|
|
cellElems = cellElems.concat( slide.getCellElements() );
|
|
}
|
|
}
|
|
return cellElems;
|
|
};
|
|
|
|
/**
|
|
* select slide from number or cell element
|
|
* @param {Element, Selector String, or Number} selector
|
|
*/
|
|
proto.queryCell = function( selector ) {
|
|
if ( typeof selector == 'number' ) {
|
|
// use number as index
|
|
return this.cells[ selector ];
|
|
}
|
|
if ( typeof selector == 'string' ) {
|
|
// do not select invalid selectors from hash: #123, #/. #791
|
|
if ( selector.match(/^[#\.]?[\d\/]/) ) {
|
|
return;
|
|
}
|
|
// use string as selector, get element
|
|
selector = this.element.querySelector( selector );
|
|
}
|
|
// get cell from element
|
|
return this.getCell( selector );
|
|
};
|
|
|
|
// -------------------------- events -------------------------- //
|
|
|
|
proto.uiChange = function() {
|
|
this.emitEvent('uiChange');
|
|
};
|
|
|
|
// keep focus on element when child UI elements are clicked
|
|
proto.childUIPointerDown = function( event ) {
|
|
// HACK iOS does not allow touch events to bubble up?!
|
|
if ( event.type != 'touchstart' ) {
|
|
event.preventDefault();
|
|
}
|
|
this.focus();
|
|
};
|
|
|
|
// ----- resize ----- //
|
|
|
|
proto.onresize = function() {
|
|
this.watchCSS();
|
|
this.resize();
|
|
};
|
|
|
|
utils.debounceMethod( Flickity, 'onresize', 150 );
|
|
|
|
proto.resize = function() {
|
|
if ( !this.isActive ) {
|
|
return;
|
|
}
|
|
this.getSize();
|
|
// wrap values
|
|
if ( this.options.wrapAround ) {
|
|
this.x = utils.modulo( this.x, this.slideableWidth );
|
|
}
|
|
this.positionCells();
|
|
this._getWrapShiftCells();
|
|
this.setGallerySize();
|
|
this.emitEvent('resize');
|
|
// update selected index for group slides, instant
|
|
// TODO: position can be lost between groups of various numbers
|
|
var selectedElement = this.selectedElements && this.selectedElements[0];
|
|
this.selectCell( selectedElement, false, true );
|
|
};
|
|
|
|
// watches the :after property, activates/deactivates
|
|
proto.watchCSS = function() {
|
|
var watchOption = this.options.watchCSS;
|
|
if ( !watchOption ) {
|
|
return;
|
|
}
|
|
|
|
var afterContent = getComputedStyle( this.element, ':after' ).content;
|
|
// activate if :after { content: 'flickity' }
|
|
if ( afterContent.indexOf('flickity') != -1 ) {
|
|
this.activate();
|
|
} else {
|
|
this.deactivate();
|
|
}
|
|
};
|
|
|
|
// ----- keydown ----- //
|
|
|
|
// go previous/next if left/right keys pressed
|
|
proto.onkeydown = function( event ) {
|
|
// only work if element is in focus
|
|
var isNotFocused = document.activeElement && document.activeElement != this.element;
|
|
if ( !this.options.accessibility ||isNotFocused ) {
|
|
return;
|
|
}
|
|
|
|
var handler = Flickity.keyboardHandlers[ event.keyCode ];
|
|
if ( handler ) {
|
|
handler.call( this );
|
|
}
|
|
};
|
|
|
|
Flickity.keyboardHandlers = {
|
|
// left arrow
|
|
37: function() {
|
|
var leftMethod = this.options.rightToLeft ? 'next' : 'previous';
|
|
this.uiChange();
|
|
this[ leftMethod ]();
|
|
},
|
|
// right arrow
|
|
39: function() {
|
|
var rightMethod = this.options.rightToLeft ? 'previous' : 'next';
|
|
this.uiChange();
|
|
this[ rightMethod ]();
|
|
},
|
|
};
|
|
|
|
// ----- focus ----- //
|
|
|
|
proto.focus = function() {
|
|
// TODO remove scrollTo once focus options gets more support
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#Browser_compatibility
|
|
var prevScrollY = window.pageYOffset;
|
|
this.element.focus({ preventScroll: true });
|
|
// hack to fix scroll jump after focus, #76
|
|
if ( window.pageYOffset != prevScrollY ) {
|
|
window.scrollTo( window.pageXOffset, prevScrollY );
|
|
}
|
|
};
|
|
|
|
// -------------------------- destroy -------------------------- //
|
|
|
|
// deactivate all Flickity functionality, but keep stuff available
|
|
proto.deactivate = function() {
|
|
if ( !this.isActive ) {
|
|
return;
|
|
}
|
|
this.element.classList.remove('flickity-enabled');
|
|
this.element.classList.remove('flickity-rtl');
|
|
this.unselectSelectedSlide();
|
|
// destroy cells
|
|
this.cells.forEach( function( cell ) {
|
|
cell.destroy();
|
|
});
|
|
this.element.removeChild( this.viewport );
|
|
// move child elements back into element
|
|
moveElements( this.slider.children, this.element );
|
|
if ( this.options.accessibility ) {
|
|
this.element.removeAttribute('tabIndex');
|
|
this.element.removeEventListener( 'keydown', this );
|
|
}
|
|
// set flags
|
|
this.isActive = false;
|
|
this.emitEvent('deactivate');
|
|
};
|
|
|
|
proto.destroy = function() {
|
|
this.deactivate();
|
|
window.removeEventListener( 'resize', this );
|
|
this.allOff();
|
|
this.emitEvent('destroy');
|
|
if ( jQuery && this.$element ) {
|
|
jQuery.removeData( this.element, 'flickity' );
|
|
}
|
|
delete this.element.flickityGUID;
|
|
delete instances[ this.guid ];
|
|
};
|
|
|
|
// -------------------------- prototype -------------------------- //
|
|
|
|
utils.extend( proto, animatePrototype );
|
|
|
|
// -------------------------- extras -------------------------- //
|
|
|
|
/**
|
|
* get Flickity instance from element
|
|
* @param {Element} elem
|
|
* @returns {Flickity}
|
|
*/
|
|
Flickity.data = function( elem ) {
|
|
elem = utils.getQueryElement( elem );
|
|
var id = elem && elem.flickityGUID;
|
|
return id && instances[ id ];
|
|
};
|
|
|
|
utils.htmlInit( Flickity, 'flickity' );
|
|
|
|
if ( jQuery && jQuery.bridget ) {
|
|
jQuery.bridget( 'flickity', Flickity );
|
|
}
|
|
|
|
// set internal jQuery, for Webpack + jQuery v3, #478
|
|
Flickity.setJQuery = function( jq ) {
|
|
jQuery = jq;
|
|
};
|
|
|
|
Flickity.Cell = Cell;
|
|
Flickity.Slide = Slide;
|
|
|
|
return Flickity;
|
|
|
|
}));
|