flickity.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929
  1. // Flickity main
  2. ( function( window, factory ) {
  3. // universal module definition
  4. /* jshint strict: false */
  5. if ( typeof define == 'function' && define.amd ) {
  6. // AMD
  7. define( [
  8. 'ev-emitter/ev-emitter',
  9. 'get-size/get-size',
  10. 'fizzy-ui-utils/utils',
  11. './cell',
  12. './slide',
  13. './animate'
  14. ], function( EvEmitter, getSize, utils, Cell, Slide, animatePrototype ) {
  15. return factory( window, EvEmitter, getSize, utils, Cell, Slide, animatePrototype );
  16. });
  17. } else if ( typeof module == 'object' && module.exports ) {
  18. // CommonJS
  19. module.exports = factory(
  20. window,
  21. require('ev-emitter'),
  22. require('get-size'),
  23. require('fizzy-ui-utils'),
  24. require('./cell'),
  25. require('./slide'),
  26. require('./animate')
  27. );
  28. } else {
  29. // browser global
  30. var _Flickity = window.Flickity;
  31. window.Flickity = factory(
  32. window,
  33. window.EvEmitter,
  34. window.getSize,
  35. window.fizzyUIUtils,
  36. _Flickity.Cell,
  37. _Flickity.Slide,
  38. _Flickity.animatePrototype
  39. );
  40. }
  41. }( window, function factory( window, EvEmitter, getSize,
  42. utils, Cell, Slide, animatePrototype ) {
  43. 'use strict';
  44. // vars
  45. var jQuery = window.jQuery;
  46. var getComputedStyle = window.getComputedStyle;
  47. var console = window.console;
  48. function moveElements( elems, toElem ) {
  49. elems = utils.makeArray( elems );
  50. while ( elems.length ) {
  51. toElem.appendChild( elems.shift() );
  52. }
  53. }
  54. // -------------------------- Flickity -------------------------- //
  55. // globally unique identifiers
  56. var GUID = 0;
  57. // internal store of all Flickity intances
  58. var instances = {};
  59. function Flickity( element, options ) {
  60. var queryElement = utils.getQueryElement( element );
  61. if ( !queryElement ) {
  62. if ( console ) {
  63. console.error( 'Bad element for Flickity: ' + ( queryElement || element ) );
  64. }
  65. return;
  66. }
  67. this.element = queryElement;
  68. // do not initialize twice on same element
  69. if ( this.element.flickityGUID ) {
  70. var instance = instances[ this.element.flickityGUID ];
  71. instance.option( options );
  72. return instance;
  73. }
  74. // add jQuery
  75. if ( jQuery ) {
  76. this.$element = jQuery( this.element );
  77. }
  78. // options
  79. this.options = utils.extend( {}, this.constructor.defaults );
  80. this.option( options );
  81. // kick things off
  82. this._create();
  83. }
  84. Flickity.defaults = {
  85. accessibility: true,
  86. // adaptiveHeight: false,
  87. cellAlign: 'center',
  88. // cellSelector: undefined,
  89. // contain: false,
  90. freeScrollFriction: 0.075, // friction when free-scrolling
  91. friction: 0.28, // friction when selecting
  92. namespaceJQueryEvents: true,
  93. // initialIndex: 0,
  94. percentPosition: true,
  95. resize: true,
  96. selectedAttraction: 0.025,
  97. setGallerySize: true
  98. // watchCSS: false,
  99. // wrapAround: false
  100. };
  101. // hash of methods triggered on _create()
  102. Flickity.createMethods = [];
  103. var proto = Flickity.prototype;
  104. // inherit EventEmitter
  105. utils.extend( proto, EvEmitter.prototype );
  106. proto._create = function() {
  107. // add id for Flickity.data
  108. var id = this.guid = ++GUID;
  109. this.element.flickityGUID = id; // expando
  110. instances[ id ] = this; // associate via id
  111. // initial properties
  112. this.selectedIndex = 0;
  113. // how many frames slider has been in same position
  114. this.restingFrames = 0;
  115. // initial physics properties
  116. this.x = 0;
  117. this.velocity = 0;
  118. this.originSide = this.options.rightToLeft ? 'right' : 'left';
  119. // create viewport & slider
  120. this.viewport = document.createElement('div');
  121. this.viewport.className = 'flickity-viewport';
  122. this._createSlider();
  123. if ( this.options.resize || this.options.watchCSS ) {
  124. window.addEventListener( 'resize', this );
  125. }
  126. // add listeners from on option
  127. for ( var eventName in this.options.on ) {
  128. var listener = this.options.on[ eventName ];
  129. this.on( eventName, listener );
  130. }
  131. Flickity.createMethods.forEach( function( method ) {
  132. this[ method ]();
  133. }, this );
  134. if ( this.options.watchCSS ) {
  135. this.watchCSS();
  136. } else {
  137. this.activate();
  138. }
  139. };
  140. /**
  141. * set options
  142. * @param {Object} opts
  143. */
  144. proto.option = function( opts ) {
  145. utils.extend( this.options, opts );
  146. };
  147. proto.activate = function() {
  148. if ( this.isActive ) {
  149. return;
  150. }
  151. this.isActive = true;
  152. this.element.classList.add('flickity-enabled');
  153. if ( this.options.rightToLeft ) {
  154. this.element.classList.add('flickity-rtl');
  155. }
  156. this.getSize();
  157. // move initial cell elements so they can be loaded as cells
  158. var cellElems = this._filterFindCellElements( this.element.children );
  159. moveElements( cellElems, this.slider );
  160. this.viewport.appendChild( this.slider );
  161. this.element.appendChild( this.viewport );
  162. // get cells from children
  163. this.reloadCells();
  164. if ( this.options.accessibility ) {
  165. // allow element to focusable
  166. this.element.tabIndex = 0;
  167. // listen for key presses
  168. this.element.addEventListener( 'keydown', this );
  169. }
  170. this.emitEvent('activate');
  171. this.selectInitialIndex();
  172. // flag for initial activation, for using initialIndex
  173. this.isInitActivated = true;
  174. // ready event. #493
  175. this.dispatchEvent('ready');
  176. };
  177. // slider positions the cells
  178. proto._createSlider = function() {
  179. // slider element does all the positioning
  180. var slider = document.createElement('div');
  181. slider.className = 'flickity-slider';
  182. slider.style[ this.originSide ] = 0;
  183. this.slider = slider;
  184. };
  185. proto._filterFindCellElements = function( elems ) {
  186. return utils.filterFindElements( elems, this.options.cellSelector );
  187. };
  188. // goes through all children
  189. proto.reloadCells = function() {
  190. // collection of item elements
  191. this.cells = this._makeCells( this.slider.children );
  192. this.positionCells();
  193. this._getWrapShiftCells();
  194. this.setGallerySize();
  195. };
  196. /**
  197. * turn elements into Flickity.Cells
  198. * @param {Array or NodeList or HTMLElement} elems
  199. * @returns {Array} items - collection of new Flickity Cells
  200. */
  201. proto._makeCells = function( elems ) {
  202. var cellElems = this._filterFindCellElements( elems );
  203. // create new Flickity for collection
  204. var cells = cellElems.map( function( cellElem ) {
  205. return new Cell( cellElem, this );
  206. }, this );
  207. return cells;
  208. };
  209. proto.getLastCell = function() {
  210. return this.cells[ this.cells.length - 1 ];
  211. };
  212. proto.getLastSlide = function() {
  213. return this.slides[ this.slides.length - 1 ];
  214. };
  215. // positions all cells
  216. proto.positionCells = function() {
  217. // size all cells
  218. this._sizeCells( this.cells );
  219. // position all cells
  220. this._positionCells( 0 );
  221. };
  222. /**
  223. * position certain cells
  224. * @param {Integer} index - which cell to start with
  225. */
  226. proto._positionCells = function( index ) {
  227. index = index || 0;
  228. // also measure maxCellHeight
  229. // start 0 if positioning all cells
  230. this.maxCellHeight = index ? this.maxCellHeight || 0 : 0;
  231. var cellX = 0;
  232. // get cellX
  233. if ( index > 0 ) {
  234. var startCell = this.cells[ index - 1 ];
  235. cellX = startCell.x + startCell.size.outerWidth;
  236. }
  237. var len = this.cells.length;
  238. for ( var i=index; i < len; i++ ) {
  239. var cell = this.cells[i];
  240. cell.setPosition( cellX );
  241. cellX += cell.size.outerWidth;
  242. this.maxCellHeight = Math.max( cell.size.outerHeight, this.maxCellHeight );
  243. }
  244. // keep track of cellX for wrap-around
  245. this.slideableWidth = cellX;
  246. // slides
  247. this.updateSlides();
  248. // contain slides target
  249. this._containSlides();
  250. // update slidesWidth
  251. this.slidesWidth = len ? this.getLastSlide().target - this.slides[0].target : 0;
  252. };
  253. /**
  254. * cell.getSize() on multiple cells
  255. * @param {Array} cells
  256. */
  257. proto._sizeCells = function( cells ) {
  258. cells.forEach( function( cell ) {
  259. cell.getSize();
  260. });
  261. };
  262. // -------------------------- -------------------------- //
  263. proto.updateSlides = function() {
  264. this.slides = [];
  265. if ( !this.cells.length ) {
  266. return;
  267. }
  268. var slide = new Slide( this );
  269. this.slides.push( slide );
  270. var isOriginLeft = this.originSide == 'left';
  271. var nextMargin = isOriginLeft ? 'marginRight' : 'marginLeft';
  272. var canCellFit = this._getCanCellFit();
  273. this.cells.forEach( function( cell, i ) {
  274. // just add cell if first cell in slide
  275. if ( !slide.cells.length ) {
  276. slide.addCell( cell );
  277. return;
  278. }
  279. var slideWidth = ( slide.outerWidth - slide.firstMargin ) +
  280. ( cell.size.outerWidth - cell.size[ nextMargin ] );
  281. if ( canCellFit.call( this, i, slideWidth ) ) {
  282. slide.addCell( cell );
  283. } else {
  284. // doesn't fit, new slide
  285. slide.updateTarget();
  286. slide = new Slide( this );
  287. this.slides.push( slide );
  288. slide.addCell( cell );
  289. }
  290. }, this );
  291. // last slide
  292. slide.updateTarget();
  293. // update .selectedSlide
  294. this.updateSelectedSlide();
  295. };
  296. proto._getCanCellFit = function() {
  297. var groupCells = this.options.groupCells;
  298. if ( !groupCells ) {
  299. return function() {
  300. return false;
  301. };
  302. } else if ( typeof groupCells == 'number' ) {
  303. // group by number. 3 -> [0,1,2], [3,4,5], ...
  304. var number = parseInt( groupCells, 10 );
  305. return function( i ) {
  306. return ( i % number ) !== 0;
  307. };
  308. }
  309. // default, group by width of slide
  310. // parse '75%
  311. var percentMatch = typeof groupCells == 'string' &&
  312. groupCells.match(/^(\d+)%$/);
  313. var percent = percentMatch ? parseInt( percentMatch[1], 10 ) / 100 : 1;
  314. return function( i, slideWidth ) {
  315. return slideWidth <= ( this.size.innerWidth + 1 ) * percent;
  316. };
  317. };
  318. // alias _init for jQuery plugin .flickity()
  319. proto._init =
  320. proto.reposition = function() {
  321. this.positionCells();
  322. this.positionSliderAtSelected();
  323. };
  324. proto.getSize = function() {
  325. this.size = getSize( this.element );
  326. this.setCellAlign();
  327. this.cursorPosition = this.size.innerWidth * this.cellAlign;
  328. };
  329. var cellAlignShorthands = {
  330. // cell align, then based on origin side
  331. center: {
  332. left: 0.5,
  333. right: 0.5
  334. },
  335. left: {
  336. left: 0,
  337. right: 1
  338. },
  339. right: {
  340. right: 0,
  341. left: 1
  342. }
  343. };
  344. proto.setCellAlign = function() {
  345. var shorthand = cellAlignShorthands[ this.options.cellAlign ];
  346. this.cellAlign = shorthand ? shorthand[ this.originSide ] : this.options.cellAlign;
  347. };
  348. proto.setGallerySize = function() {
  349. if ( this.options.setGallerySize ) {
  350. var height = this.options.adaptiveHeight && this.selectedSlide ?
  351. this.selectedSlide.height : this.maxCellHeight;
  352. this.viewport.style.height = height + 'px';
  353. }
  354. };
  355. proto._getWrapShiftCells = function() {
  356. // only for wrap-around
  357. if ( !this.options.wrapAround ) {
  358. return;
  359. }
  360. // unshift previous cells
  361. this._unshiftCells( this.beforeShiftCells );
  362. this._unshiftCells( this.afterShiftCells );
  363. // get before cells
  364. // initial gap
  365. var gapX = this.cursorPosition;
  366. var cellIndex = this.cells.length - 1;
  367. this.beforeShiftCells = this._getGapCells( gapX, cellIndex, -1 );
  368. // get after cells
  369. // ending gap between last cell and end of gallery viewport
  370. gapX = this.size.innerWidth - this.cursorPosition;
  371. // start cloning at first cell, working forwards
  372. this.afterShiftCells = this._getGapCells( gapX, 0, 1 );
  373. };
  374. proto._getGapCells = function( gapX, cellIndex, increment ) {
  375. // keep adding cells until the cover the initial gap
  376. var cells = [];
  377. while ( gapX > 0 ) {
  378. var cell = this.cells[ cellIndex ];
  379. if ( !cell ) {
  380. break;
  381. }
  382. cells.push( cell );
  383. cellIndex += increment;
  384. gapX -= cell.size.outerWidth;
  385. }
  386. return cells;
  387. };
  388. // ----- contain ----- //
  389. // contain cell targets so no excess sliding
  390. proto._containSlides = function() {
  391. if ( !this.options.contain || this.options.wrapAround || !this.cells.length ) {
  392. return;
  393. }
  394. var isRightToLeft = this.options.rightToLeft;
  395. var beginMargin = isRightToLeft ? 'marginRight' : 'marginLeft';
  396. var endMargin = isRightToLeft ? 'marginLeft' : 'marginRight';
  397. var contentWidth = this.slideableWidth - this.getLastCell().size[ endMargin ];
  398. // content is less than gallery size
  399. var isContentSmaller = contentWidth < this.size.innerWidth;
  400. // bounds
  401. var beginBound = this.cursorPosition + this.cells[0].size[ beginMargin ];
  402. var endBound = contentWidth - this.size.innerWidth * ( 1 - this.cellAlign );
  403. // contain each cell target
  404. this.slides.forEach( function( slide ) {
  405. if ( isContentSmaller ) {
  406. // all cells fit inside gallery
  407. slide.target = contentWidth * this.cellAlign;
  408. } else {
  409. // contain to bounds
  410. slide.target = Math.max( slide.target, beginBound );
  411. slide.target = Math.min( slide.target, endBound );
  412. }
  413. }, this );
  414. };
  415. // ----- ----- //
  416. /**
  417. * emits events via eventEmitter and jQuery events
  418. * @param {String} type - name of event
  419. * @param {Event} event - original event
  420. * @param {Array} args - extra arguments
  421. */
  422. proto.dispatchEvent = function( type, event, args ) {
  423. var emitArgs = event ? [ event ].concat( args ) : args;
  424. this.emitEvent( type, emitArgs );
  425. if ( jQuery && this.$element ) {
  426. // default trigger with type if no event
  427. type += this.options.namespaceJQueryEvents ? '.flickity' : '';
  428. var $event = type;
  429. if ( event ) {
  430. // create jQuery event
  431. var jQEvent = jQuery.Event( event );
  432. jQEvent.type = type;
  433. $event = jQEvent;
  434. }
  435. this.$element.trigger( $event, args );
  436. }
  437. };
  438. // -------------------------- select -------------------------- //
  439. /**
  440. * @param {Integer} index - index of the slide
  441. * @param {Boolean} isWrap - will wrap-around to last/first if at the end
  442. * @param {Boolean} isInstant - will immediately set position at selected cell
  443. */
  444. proto.select = function( index, isWrap, isInstant ) {
  445. if ( !this.isActive ) {
  446. return;
  447. }
  448. index = parseInt( index, 10 );
  449. this._wrapSelect( index );
  450. if ( this.options.wrapAround || isWrap ) {
  451. index = utils.modulo( index, this.slides.length );
  452. }
  453. // bail if invalid index
  454. if ( !this.slides[ index ] ) {
  455. return;
  456. }
  457. var prevIndex = this.selectedIndex;
  458. this.selectedIndex = index;
  459. this.updateSelectedSlide();
  460. if ( isInstant ) {
  461. this.positionSliderAtSelected();
  462. } else {
  463. this.startAnimation();
  464. }
  465. if ( this.options.adaptiveHeight ) {
  466. this.setGallerySize();
  467. }
  468. // events
  469. this.dispatchEvent( 'select', null, [ index ] );
  470. // change event if new index
  471. if ( index != prevIndex ) {
  472. this.dispatchEvent( 'change', null, [ index ] );
  473. }
  474. // old v1 event name, remove in v3
  475. this.dispatchEvent('cellSelect');
  476. };
  477. // wraps position for wrapAround, to move to closest slide. #113
  478. proto._wrapSelect = function( index ) {
  479. var len = this.slides.length;
  480. var isWrapping = this.options.wrapAround && len > 1;
  481. if ( !isWrapping ) {
  482. return index;
  483. }
  484. var wrapIndex = utils.modulo( index, len );
  485. // go to shortest
  486. var delta = Math.abs( wrapIndex - this.selectedIndex );
  487. var backWrapDelta = Math.abs( ( wrapIndex + len ) - this.selectedIndex );
  488. var forewardWrapDelta = Math.abs( ( wrapIndex - len ) - this.selectedIndex );
  489. if ( !this.isDragSelect && backWrapDelta < delta ) {
  490. index += len;
  491. } else if ( !this.isDragSelect && forewardWrapDelta < delta ) {
  492. index -= len;
  493. }
  494. // wrap position so slider is within normal area
  495. if ( index < 0 ) {
  496. this.x -= this.slideableWidth;
  497. } else if ( index >= len ) {
  498. this.x += this.slideableWidth;
  499. }
  500. };
  501. proto.previous = function( isWrap, isInstant ) {
  502. this.select( this.selectedIndex - 1, isWrap, isInstant );
  503. };
  504. proto.next = function( isWrap, isInstant ) {
  505. this.select( this.selectedIndex + 1, isWrap, isInstant );
  506. };
  507. proto.updateSelectedSlide = function() {
  508. var slide = this.slides[ this.selectedIndex ];
  509. // selectedIndex could be outside of slides, if triggered before resize()
  510. if ( !slide ) {
  511. return;
  512. }
  513. // unselect previous selected slide
  514. this.unselectSelectedSlide();
  515. // update new selected slide
  516. this.selectedSlide = slide;
  517. slide.select();
  518. this.selectedCells = slide.cells;
  519. this.selectedElements = slide.getCellElements();
  520. // HACK: selectedCell & selectedElement is first cell in slide, backwards compatibility
  521. // Remove in v3?
  522. this.selectedCell = slide.cells[0];
  523. this.selectedElement = this.selectedElements[0];
  524. };
  525. proto.unselectSelectedSlide = function() {
  526. if ( this.selectedSlide ) {
  527. this.selectedSlide.unselect();
  528. }
  529. };
  530. proto.selectInitialIndex = function() {
  531. var initialIndex = this.options.initialIndex;
  532. // already activated, select previous selectedIndex
  533. if ( this.isInitActivated ) {
  534. this.select( this.selectedIndex, false, true );
  535. return;
  536. }
  537. // select with selector string
  538. if ( initialIndex && typeof initialIndex == 'string' ) {
  539. var cell = this.queryCell( initialIndex );
  540. if ( cell ) {
  541. this.selectCell( initialIndex, false, true );
  542. return;
  543. }
  544. }
  545. var index = 0;
  546. // select with number
  547. if ( initialIndex && this.slides[ initialIndex ] ) {
  548. index = initialIndex;
  549. }
  550. // select instantly
  551. this.select( index, false, true );
  552. };
  553. /**
  554. * select slide from number or cell element
  555. * @param {Element or Number} elem
  556. */
  557. proto.selectCell = function( value, isWrap, isInstant ) {
  558. // get cell
  559. var cell = this.queryCell( value );
  560. if ( !cell ) {
  561. return;
  562. }
  563. var index = this.getCellSlideIndex( cell );
  564. this.select( index, isWrap, isInstant );
  565. };
  566. proto.getCellSlideIndex = function( cell ) {
  567. // get index of slides that has cell
  568. for ( var i=0; i < this.slides.length; i++ ) {
  569. var slide = this.slides[i];
  570. var index = slide.cells.indexOf( cell );
  571. if ( index != -1 ) {
  572. return i;
  573. }
  574. }
  575. };
  576. // -------------------------- get cells -------------------------- //
  577. /**
  578. * get Flickity.Cell, given an Element
  579. * @param {Element} elem
  580. * @returns {Flickity.Cell} item
  581. */
  582. proto.getCell = function( elem ) {
  583. // loop through cells to get the one that matches
  584. for ( var i=0; i < this.cells.length; i++ ) {
  585. var cell = this.cells[i];
  586. if ( cell.element == elem ) {
  587. return cell;
  588. }
  589. }
  590. };
  591. /**
  592. * get collection of Flickity.Cells, given Elements
  593. * @param {Element, Array, NodeList} elems
  594. * @returns {Array} cells - Flickity.Cells
  595. */
  596. proto.getCells = function( elems ) {
  597. elems = utils.makeArray( elems );
  598. var cells = [];
  599. elems.forEach( function( elem ) {
  600. var cell = this.getCell( elem );
  601. if ( cell ) {
  602. cells.push( cell );
  603. }
  604. }, this );
  605. return cells;
  606. };
  607. /**
  608. * get cell elements
  609. * @returns {Array} cellElems
  610. */
  611. proto.getCellElements = function() {
  612. return this.cells.map( function( cell ) {
  613. return cell.element;
  614. });
  615. };
  616. /**
  617. * get parent cell from an element
  618. * @param {Element} elem
  619. * @returns {Flickit.Cell} cell
  620. */
  621. proto.getParentCell = function( elem ) {
  622. // first check if elem is cell
  623. var cell = this.getCell( elem );
  624. if ( cell ) {
  625. return cell;
  626. }
  627. // try to get parent cell elem
  628. elem = utils.getParent( elem, '.flickity-slider > *' );
  629. return this.getCell( elem );
  630. };
  631. /**
  632. * get cells adjacent to a slide
  633. * @param {Integer} adjCount - number of adjacent slides
  634. * @param {Integer} index - index of slide to start
  635. * @returns {Array} cells - array of Flickity.Cells
  636. */
  637. proto.getAdjacentCellElements = function( adjCount, index ) {
  638. if ( !adjCount ) {
  639. return this.selectedSlide.getCellElements();
  640. }
  641. index = index === undefined ? this.selectedIndex : index;
  642. var len = this.slides.length;
  643. if ( 1 + ( adjCount * 2 ) >= len ) {
  644. return this.getCellElements();
  645. }
  646. var cellElems = [];
  647. for ( var i = index - adjCount; i <= index + adjCount ; i++ ) {
  648. var slideIndex = this.options.wrapAround ? utils.modulo( i, len ) : i;
  649. var slide = this.slides[ slideIndex ];
  650. if ( slide ) {
  651. cellElems = cellElems.concat( slide.getCellElements() );
  652. }
  653. }
  654. return cellElems;
  655. };
  656. /**
  657. * select slide from number or cell element
  658. * @param {Element, Selector String, or Number} selector
  659. */
  660. proto.queryCell = function( selector ) {
  661. if ( typeof selector == 'number' ) {
  662. // use number as index
  663. return this.cells[ selector ];
  664. }
  665. if ( typeof selector == 'string' ) {
  666. // do not select invalid selectors from hash: #123, #/. #791
  667. if ( selector.match(/^[#\.]?[\d\/]/) ) {
  668. return;
  669. }
  670. // use string as selector, get element
  671. selector = this.element.querySelector( selector );
  672. }
  673. // get cell from element
  674. return this.getCell( selector );
  675. };
  676. // -------------------------- events -------------------------- //
  677. proto.uiChange = function() {
  678. this.emitEvent('uiChange');
  679. };
  680. // keep focus on element when child UI elements are clicked
  681. proto.childUIPointerDown = function( event ) {
  682. // HACK iOS does not allow touch events to bubble up?!
  683. if ( event.type != 'touchstart' ) {
  684. event.preventDefault();
  685. }
  686. this.focus();
  687. };
  688. // ----- resize ----- //
  689. proto.onresize = function() {
  690. this.watchCSS();
  691. this.resize();
  692. };
  693. utils.debounceMethod( Flickity, 'onresize', 150 );
  694. proto.resize = function() {
  695. if ( !this.isActive ) {
  696. return;
  697. }
  698. this.getSize();
  699. // wrap values
  700. if ( this.options.wrapAround ) {
  701. this.x = utils.modulo( this.x, this.slideableWidth );
  702. }
  703. this.positionCells();
  704. this._getWrapShiftCells();
  705. this.setGallerySize();
  706. this.emitEvent('resize');
  707. // update selected index for group slides, instant
  708. // TODO: position can be lost between groups of various numbers
  709. var selectedElement = this.selectedElements && this.selectedElements[0];
  710. this.selectCell( selectedElement, false, true );
  711. };
  712. // watches the :after property, activates/deactivates
  713. proto.watchCSS = function() {
  714. var watchOption = this.options.watchCSS;
  715. if ( !watchOption ) {
  716. return;
  717. }
  718. var afterContent = getComputedStyle( this.element, ':after' ).content;
  719. // activate if :after { content: 'flickity' }
  720. if ( afterContent.indexOf('flickity') != -1 ) {
  721. this.activate();
  722. } else {
  723. this.deactivate();
  724. }
  725. };
  726. // ----- keydown ----- //
  727. // go previous/next if left/right keys pressed
  728. proto.onkeydown = function( event ) {
  729. // only work if element is in focus
  730. var isNotFocused = document.activeElement && document.activeElement != this.element;
  731. if ( !this.options.accessibility ||isNotFocused ) {
  732. return;
  733. }
  734. var handler = Flickity.keyboardHandlers[ event.keyCode ];
  735. if ( handler ) {
  736. handler.call( this );
  737. }
  738. };
  739. Flickity.keyboardHandlers = {
  740. // left arrow
  741. 37: function() {
  742. var leftMethod = this.options.rightToLeft ? 'next' : 'previous';
  743. this.uiChange();
  744. this[ leftMethod ]();
  745. },
  746. // right arrow
  747. 39: function() {
  748. var rightMethod = this.options.rightToLeft ? 'previous' : 'next';
  749. this.uiChange();
  750. this[ rightMethod ]();
  751. },
  752. };
  753. // ----- focus ----- //
  754. proto.focus = function() {
  755. // TODO remove scrollTo once focus options gets more support
  756. // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#Browser_compatibility
  757. var prevScrollY = window.pageYOffset;
  758. this.element.focus({ preventScroll: true });
  759. // hack to fix scroll jump after focus, #76
  760. if ( window.pageYOffset != prevScrollY ) {
  761. window.scrollTo( window.pageXOffset, prevScrollY );
  762. }
  763. };
  764. // -------------------------- destroy -------------------------- //
  765. // deactivate all Flickity functionality, but keep stuff available
  766. proto.deactivate = function() {
  767. if ( !this.isActive ) {
  768. return;
  769. }
  770. this.element.classList.remove('flickity-enabled');
  771. this.element.classList.remove('flickity-rtl');
  772. this.unselectSelectedSlide();
  773. // destroy cells
  774. this.cells.forEach( function( cell ) {
  775. cell.destroy();
  776. });
  777. this.element.removeChild( this.viewport );
  778. // move child elements back into element
  779. moveElements( this.slider.children, this.element );
  780. if ( this.options.accessibility ) {
  781. this.element.removeAttribute('tabIndex');
  782. this.element.removeEventListener( 'keydown', this );
  783. }
  784. // set flags
  785. this.isActive = false;
  786. this.emitEvent('deactivate');
  787. };
  788. proto.destroy = function() {
  789. this.deactivate();
  790. window.removeEventListener( 'resize', this );
  791. this.allOff();
  792. this.emitEvent('destroy');
  793. if ( jQuery && this.$element ) {
  794. jQuery.removeData( this.element, 'flickity' );
  795. }
  796. delete this.element.flickityGUID;
  797. delete instances[ this.guid ];
  798. };
  799. // -------------------------- prototype -------------------------- //
  800. utils.extend( proto, animatePrototype );
  801. // -------------------------- extras -------------------------- //
  802. /**
  803. * get Flickity instance from element
  804. * @param {Element} elem
  805. * @returns {Flickity}
  806. */
  807. Flickity.data = function( elem ) {
  808. elem = utils.getQueryElement( elem );
  809. var id = elem && elem.flickityGUID;
  810. return id && instances[ id ];
  811. };
  812. utils.htmlInit( Flickity, 'flickity' );
  813. if ( jQuery && jQuery.bridget ) {
  814. jQuery.bridget( 'flickity', Flickity );
  815. }
  816. // set internal jQuery, for Webpack + jQuery v3, #478
  817. Flickity.setJQuery = function( jq ) {
  818. jQuery = jq;
  819. };
  820. Flickity.Cell = Cell;
  821. Flickity.Slide = Slide;
  822. return Flickity;
  823. }));