Skip to content
Permalink
Newer
Older
100644 3407 lines (2834 sloc) 92.9 KB
1
/*!
2
* Flickity PACKAGED v3.0.0
Jul 8, 2016
3
* Touch, responsive, flickable carousels
Feb 26, 2015
4
*
5
* Licensed GPLv3 for open source use
6
* or Flickity Commercial License for commercial use
7
*
Mar 16, 2018
8
* https://flickity.metafizzy.co
9
* Copyright 2015-2022 Metafizzy
10
*/
11
12
/**
13
* Bridget makes jQuery widgets
14
* v3.0.1
15
* MIT license
16
*/
17
Jul 8, 2016
18
( function( window, factory ) {
19
// module definition
20
if ( typeof module == 'object' && module.exports ) {
21
// CommonJS
22
module.exports = factory(
23
window,
24
require('jquery'),
25
);
26
} else {
27
// browser global
28
window.jQueryBridget = factory(
29
window,
30
window.jQuery,
31
);
32
}
33
Jul 8, 2016
34
}( window, function factory( window, jQuery ) {
35
Jul 8, 2016
36
// ----- utils ----- //
37
38
// helper function for logging errors
39
// $.error breaks jQuery chaining
40
let console = window.console;
41
let logError = typeof console == 'undefined' ? function() {} :
42
function( message ) {
43
console.error( message );
44
};
45
Jul 8, 2016
46
// ----- jQueryBridget ----- //
47
Jul 8, 2016
48
function jQueryBridget( namespace, PluginClass, $ ) {
49
$ = $ || jQuery || window.jQuery;
50
if ( !$ ) {
51
return;
52
}
53
Jul 8, 2016
54
// add option method -> $().plugin('option', {...})
55
if ( !PluginClass.prototype.option ) {
56
// option setter
57
PluginClass.prototype.option = function( opts ) {
58
if ( !opts ) return;
59
60
this.options = Object.assign( this.options || {}, opts );
Jul 8, 2016
61
};
62
}
63
64
// make jQuery plugin
65
$.fn[ namespace ] = function( arg0, ...args ) {
Jul 8, 2016
66
if ( typeof arg0 == 'string' ) {
67
// method call $().plugin( 'methodName', { options } )
68
return methodCall( this, arg0, args );
69
}
Jul 8, 2016
70
// just $().plugin({ options })
71
plainCall( this, arg0 );
72
return this;
73
};
74
Jul 8, 2016
75
// $().plugin('methodName')
76
function methodCall( $elems, methodName, args ) {
77
let returnValue;
78
let pluginMethodStr = `$().${namespace}("${methodName}")`;
Jul 8, 2016
79
80
$elems.each( function( i, elem ) {
81
// get instance
82
let instance = $.data( elem, namespace );
Jul 8, 2016
83
if ( !instance ) {
84
logError( `${namespace} not initialized.` +
85
` Cannot call method ${pluginMethodStr}` );
Jul 8, 2016
86
return;
87
}
88
89
let method = instance[ methodName ];
90
if ( !method || methodName.charAt( 0 ) == '_' ) {
91
logError(`${pluginMethodStr} is not a valid method`);
Jul 8, 2016
92
return;
93
}
94
Jul 8, 2016
95
// apply method, get return value
96
let value = method.apply( instance, args );
Jul 8, 2016
97
// set return value if value is returned, use only first value
98
returnValue = returnValue === undefined ? value : returnValue;
99
} );
100
Jul 8, 2016
101
return returnValue !== undefined ? returnValue : $elems;
102
}
103
Jul 8, 2016
104
function plainCall( $elems, options ) {
105
$elems.each( function( i, elem ) {
106
let instance = $.data( elem, namespace );
Jul 8, 2016
107
if ( instance ) {
108
// set options & init
109
instance.option( options );
110
instance._init();
111
} else {
112
// initialize new instance
113
instance = new PluginClass( elem, options );
114
$.data( elem, namespace, instance );
115
}
116
} );
Jul 8, 2016
117
}
118
119
}
120
Jul 8, 2016
121
// ----- ----- //
122
Jul 8, 2016
123
return jQueryBridget;
124
125
} ) );
Jul 8, 2016
126
/**
127
* EvEmitter v2.1.1
Jul 8, 2016
128
* Lil' event emitter
129
* MIT License
130
*/
131
Jul 8, 2016
132
( function( global, factory ) {
133
// universal module definition
134
if ( typeof module == 'object' && module.exports ) {
Jul 8, 2016
135
// CommonJS - Browserify, Webpack
136
module.exports = factory();
137
} else {
138
// Browser globals
139
global.EvEmitter = factory();
140
}
141
Jul 8, 2016
142
}( typeof window != 'undefined' ? window : this, function() {
143
Jul 8, 2016
144
function EvEmitter() {}
145
146
let proto = EvEmitter.prototype;
147
Jul 8, 2016
148
proto.on = function( eventName, listener ) {
149
if ( !eventName || !listener ) return this;
150
Jul 8, 2016
151
// set events hash
152
let events = this._events = this._events || {};
Jul 8, 2016
153
// set listeners array
154
let listeners = events[ eventName ] = events[ eventName ] || [];
Jul 8, 2016
155
// only add once
156
if ( !listeners.includes( listener ) ) {
Jul 8, 2016
157
listeners.push( listener );
158
}
159
Jul 8, 2016
160
return this;
161
};
162
Jul 8, 2016
163
proto.once = function( eventName, listener ) {
164
if ( !eventName || !listener ) return this;
165
Jul 8, 2016
166
// add event
167
this.on( eventName, listener );
168
// set once flag
169
// set onceEvents hash
170
let onceEvents = this._onceEvents = this._onceEvents || {};
Jul 8, 2016
171
// set onceListeners object
172
let onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
Jul 8, 2016
173
// set flag
174
onceListeners[ listener ] = true;
175
Jul 8, 2016
176
return this;
177
};
178
Jul 8, 2016
179
proto.off = function( eventName, listener ) {
180
let listeners = this._events && this._events[ eventName ];
181
if ( !listeners || !listeners.length ) return this;
182
183
let index = listeners.indexOf( listener );
Jul 8, 2016
184
if ( index != -1 ) {
185
listeners.splice( index, 1 );
186
}
187
Jul 8, 2016
188
return this;
189
};
190
Jul 8, 2016
191
proto.emitEvent = function( eventName, args ) {
192
let listeners = this._events && this._events[ eventName ];
193
if ( !listeners || !listeners.length ) return this;
194
Jul 6, 2017
195
// copy over to avoid interference if .off() in listener
196
listeners = listeners.slice( 0 );
Jul 8, 2016
197
args = args || [];
198
// once stuff
199
let onceListeners = this._onceEvents && this._onceEvents[ eventName ];
Jul 8, 2016
200
201
for ( let listener of listeners ) {
202
let isOnce = onceListeners && onceListeners[ listener ];
Jul 8, 2016
203
if ( isOnce ) {
204
// remove listener
205
// remove before trigger to prevent recursion
206
this.off( eventName, listener );
207
// unset once flag
208
delete onceListeners[ listener ];
209
}
Jul 8, 2016
210
// trigger listener
211
listener.apply( this, args );
212
}
213
Jul 8, 2016
214
return this;
215
};
216
Jul 6, 2017
217
proto.allOff = function() {
218
delete this._events;
219
delete this._onceEvents;
220
return this;
Jul 6, 2017
221
};
222
Jul 8, 2016
223
return EvEmitter;
224
225
} ) );
226
/*!
227
* Infinite Scroll v2.0.4
228
* measure size of elements
229
* MIT license
230
*/
231
Jul 8, 2016
232
( function( window, factory ) {
233
if ( typeof module == 'object' && module.exports ) {
Jul 8, 2016
234
// CommonJS
235
module.exports = factory();
236
} else {
237
// browser global
238
window.getSize = factory();
239
}
240
241
} )( window, function factory() {
242
243
// -------------------------- helpers -------------------------- //
244
245
// get a number from a string, not a percentage
246
function getStyleSize( value ) {
247
let num = parseFloat( value );
248
// not a percent like '100%', and a number
249
let isValid = value.indexOf('%') == -1 && !isNaN( num );
250
return isValid && num;
251
}
252
253
// -------------------------- measurements -------------------------- //
254
255
let measurements = [
256
'paddingLeft',
257
'paddingRight',
258
'paddingTop',
259
'paddingBottom',
260
'marginLeft',
261
'marginRight',
262
'marginTop',
263
'marginBottom',
264
'borderLeftWidth',
265
'borderRightWidth',
266
'borderTopWidth',
267
'borderBottomWidth',
268
];
269
270
let measurementsLength = measurements.length;
Jul 8, 2016
271
272
function getZeroSize() {
273
let size = {
274
width: 0,
275
height: 0,
276
innerWidth: 0,
277
innerHeight: 0,
278
outerWidth: 0,
279
outerHeight: 0,
280
};
281
measurements.forEach( ( measurement ) => {
282
size[ measurement ] = 0;
283
} );
284
return size;
285
}
286
287
// -------------------------- getSize -------------------------- //
288
289
function getSize( elem ) {
290
// use querySeletor if elem is string
291
if ( typeof elem == 'string' ) elem = document.querySelector( elem );
292
293
// do not proceed on non-objects
294
let isElement = elem && typeof elem == 'object' && elem.nodeType;
295
if ( !isElement ) return;
296
297
let style = getComputedStyle( elem );
298
299
// if hidden, everything is 0
300
if ( style.display == 'none' ) return getZeroSize();
301
302
let size = {};
303
size.width = elem.offsetWidth;
304
size.height = elem.offsetHeight;
305
306
let isBorderBox = size.isBorderBox = style.boxSizing == 'border-box';
307
308
// get all measurements
309
measurements.forEach( ( measurement ) => {
310
let value = style[ measurement ];
311
let num = parseFloat( value );
312
// any 'auto', 'medium' value will be 0
313
size[ measurement ] = !isNaN( num ) ? num : 0;
314
} );
315
316
let paddingWidth = size.paddingLeft + size.paddingRight;
317
let paddingHeight = size.paddingTop + size.paddingBottom;
318
let marginWidth = size.marginLeft + size.marginRight;
319
let marginHeight = size.marginTop + size.marginBottom;
320
let borderWidth = size.borderLeftWidth + size.borderRightWidth;
321
let borderHeight = size.borderTopWidth + size.borderBottomWidth;
322
323
// overwrite width and height if we can get it from style
324
let styleWidth = getStyleSize( style.width );
325
if ( styleWidth !== false ) {
326
size.width = styleWidth +
327
// add padding and border unless it's already including it
328
( isBorderBox ? 0 : paddingWidth + borderWidth );
329
}
330
331
let styleHeight = getStyleSize( style.height );
332
if ( styleHeight !== false ) {
333
size.height = styleHeight +
334
// add padding and border unless it's already including it
335
( isBorderBox ? 0 : paddingHeight + borderHeight );
336
}
337
338
size.innerWidth = size.width - ( paddingWidth + borderWidth );
339
size.innerHeight = size.height - ( paddingHeight + borderHeight );
340
341
size.outerWidth = size.width + marginWidth;
342
size.outerHeight = size.height + marginHeight;
343
344
return size;
345
}
346
347
return getSize;
348
349
} );
350
/**
351
* Fizzy UI utils v3.0.0
Feb 3, 2015
352
* MIT license
353
*/
354
355
( function( global, factory ) {
356
// universal module definition
357
if ( typeof module == 'object' && module.exports ) {
358
// CommonJS
359
module.exports = factory( global );
360
} else {
361
// browser global
362
global.fizzyUIUtils = factory( global );
363
}
364
365
}( this, function factory( global ) {
366
367
let utils = {};
368
369
// ----- extend ----- //
370
371
// extends objects
Feb 3, 2015
372
utils.extend = function( a, b ) {
373
return Object.assign( a, b );
374
};
375
376
// ----- modulo ----- //
377
Feb 3, 2015
378
utils.modulo = function( num, div ) {
379
return ( ( num % div ) + div ) % div;
380
};
381
382
// ----- makeArray ----- //
383
384
// turn element or nodeList into an array
Feb 3, 2015
385
utils.makeArray = function( obj ) {
386
// use object if already an array
387
if ( Array.isArray( obj ) ) return obj;
388
Feb 27, 2018
389
// return empty array if undefined or null. #6
390
if ( obj === null || obj === undefined ) return [];
Feb 27, 2018
391
392
let isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';
393
// convert nodeList to array
394
if ( isArrayLike ) return [ ...obj ];
Feb 27, 2018
395
396
// array of single index
397
return [ obj ];
398
};
399
400
// ----- removeFrom ----- //
401
Feb 26, 2015
402
utils.removeFrom = function( ary, obj ) {
403
let index = ary.indexOf( obj );
404
if ( index != -1 ) {
405
ary.splice( index, 1 );
406
}
Jul 8, 2016
407
};
408
409
// ----- getParent ----- //
410
Feb 3, 2015
411
utils.getParent = function( elem, selector ) {
412
while ( elem.parentNode && elem != document.body ) {
413
elem = elem.parentNode;
414
if ( elem.matches( selector ) ) return elem;
415
}
416
};
417
418
// ----- getQueryElement ----- //
419
420
// use element as selector string
Feb 3, 2015
421
utils.getQueryElement = function( elem ) {
422
if ( typeof elem == 'string' ) {
423
return document.querySelector( elem );
424
}
425
return elem;
426
};
427
Feb 3, 2015
428
// ----- handleEvent ----- //
429
430
// enable .ontype to trigger from .addEventListener( elem, 'type' )
431
utils.handleEvent = function( event ) {
432
let method = 'on' + event.type;
Feb 3, 2015
433
if ( this[ method ] ) {
434
this[ method ]( event );
435
}
436
};
437
438
// ----- filterFindElements ----- //
439
Feb 3, 2015
440
utils.filterFindElements = function( elems, selector ) {
441
// make array of elems
Feb 3, 2015
442
elems = utils.makeArray( elems );
443
444
return elems
445
// check that elem is an actual element
446
.filter( ( elem ) => elem instanceof HTMLElement )
447
.reduce( ( ffElems, elem ) => {
448
// add elem if no selector
449
if ( !selector ) {
450
ffElems.push( elem );
451
return ffElems;
452
}
453
// filter & find items if we have a selector
454
// filter
455
if ( elem.matches( selector ) ) {
456
ffElems.push( elem );
457
}
458
// find children
459
let childElems = elem.querySelectorAll( selector );
460
// concat childElems to filterFound array
461
ffElems = ffElems.concat( ...childElems );
462
return ffElems;
463
}, [] );
464
};
465
466
// ----- debounceMethod ----- //
467
Feb 3, 2015
468
utils.debounceMethod = function( _class, methodName, threshold ) {
Feb 27, 2018
469
threshold = threshold || 100;
470
// original method
471
let method = _class.prototype[ methodName ];
472
let timeoutName = methodName + 'Timeout';
473
474
_class.prototype[ methodName ] = function() {
475
clearTimeout( this[ timeoutName ] );
476
477
let args = arguments;
478
this[ timeoutName ] = setTimeout( () => {
479
method.apply( this, args );
480
delete this[ timeoutName ];
Feb 27, 2018
481
}, threshold );
482
};
483
};
484
Jul 8, 2016
485
// ----- docReady ----- //
486
487
utils.docReady = function( onDocReady ) {
488
let readyState = document.readyState;
Jul 8, 2016
489
if ( readyState == 'complete' || readyState == 'interactive' ) {
Sep 16, 2016
490
// do async to allow for other scripts to run. metafizzy/flickity#441
491
setTimeout( onDocReady );
Jul 8, 2016
492
} else {
493
document.addEventListener( 'DOMContentLoaded', onDocReady );
Jul 8, 2016
494
}
495
};
496
497
// ----- htmlInit ----- //
498
499
// http://bit.ly/3oYLusc
Apr 27, 2015
500
utils.toDashed = function( str ) {
501
return str.replace( /(.)([A-Z])/g, function( match, $1, $2 ) {
502
return $1 + '-' + $2;
503
} ).toLowerCase();
Apr 27, 2015
504
};
505
506
let console = global.console;
507
508
// allow user to initialize classes via [data-namespace] or .js-namespace class
509
// htmlInit( Widget, 'widgetName' )
510
// options are parsed from data-namespace-options
Feb 3, 2015
511
utils.htmlInit = function( WidgetClass, namespace ) {
Jul 8, 2016
512
utils.docReady( function() {
513
let dashedNamespace = utils.toDashed( namespace );
514
let dataAttr = 'data-' + dashedNamespace;
515
let dataAttrElems = document.querySelectorAll( `[${dataAttr}]` );
516
let jQuery = global.jQuery;
517
518
[ ...dataAttrElems ].forEach( ( elem ) => {
519
let attr = elem.getAttribute( dataAttr );
520
let options;
521
try {
522
options = attr && JSON.parse( attr );
523
} catch ( error ) {
524
// log error, do not initialize
525
if ( console ) {
526
console.error( `Error parsing ${dataAttr} on ${elem.className}: ${error}` );
527
}
Jul 8, 2016
528
return;
529
}
530
// initialize
531
let instance = new WidgetClass( elem, options );
Sep 16, 2016
532
// make available via $().data('namespace')
533
if ( jQuery ) {
534
jQuery.data( elem, namespace, instance );
535
}
536
} );
Jul 8, 2016
537
538
} );
539
};
540
541
// ----- ----- //
542
Feb 3, 2015
543
return utils;
544
545
} ) );
546
/*!
547
* Unidragger v3.0.0
548
* Draggable base class
549
* MIT license
550
*/
551
552
( function( window, factory ) {
553
// universal module definition
554
if ( typeof module == 'object' && module.exports ) {
555
// CommonJS
556
module.exports = factory(
558
require('ev-emitter'),
559
);
560
} else {
561
// browser global
562
window.Unidragger = factory(
564
window.EvEmitter,
565
);
566
}
567
568
}( typeof window != 'undefined' ? window : this, function factory( window, EvEmitter ) {
569
570
function Unidragger() {}
571
572
// inherit EvEmitter
573
let proto = Unidragger.prototype = Object.create( EvEmitter.prototype );
574
575
// ----- bind start ----- //
576
577
// trigger handler methods for events
578
proto.handleEvent = function( event ) {
579
let method = 'on' + event.type;
580
if ( this[ method ] ) {
581
this[ method ]( event );
582
}
583
};
584
585
let startEvent, activeEvents;
586
if ( 'ontouchstart' in window ) {
587
// HACK prefer Touch Events as you can preventDefault on touchstart to
588
// disable scroll in iOS & mobile Chrome metafizzy/flickity#1177
589
startEvent = 'touchstart';
590
activeEvents = [ 'touchmove', 'touchend', 'touchcancel' ];
591
} else if ( window.PointerEvent ) {
592
// Pointer Events
593
startEvent = 'pointerdown';
594
activeEvents = [ 'pointermove', 'pointerup', 'pointercancel' ];
595
} else {
596
// mouse events
597
startEvent = 'mousedown';
598
activeEvents = [ 'mousemove', 'mouseup' ];
599
}
600
601
// prototype so it can be overwriteable by Flickity
602
proto.touchActionValue = 'none';
604
proto.bindHandles = function() {
605
this._bindHandles( 'addEventListener', this.touchActionValue );
606
};
607
608
proto.unbindHandles = function() {
609
this._bindHandles( 'removeEventListener', '' );
610
};
611
612
/**
613
* Add or remove start event
614
* @param {String} bindMethod - addEventListener or removeEventListener
615
* @param {String} touchAction - value for touch-action CSS property
616
*/
617
proto._bindHandles = function( bindMethod, touchAction ) {
618
this.handles.forEach( ( handle ) => {
619
handle[ bindMethod ]( startEvent, this );
620
handle[ bindMethod ]( 'click', this );
621
// touch-action: none to override browser touch gestures. metafizzy/flickity#540
622
if ( window.PointerEvent ) handle.style.touchAction = touchAction;
623
} );
624
};
625
626
proto.bindActivePointerEvents = function() {
627
activeEvents.forEach( ( eventName ) => {
628
window.addEventListener( eventName, this );
629
} );
630
};
631
632
proto.unbindActivePointerEvents = function() {
633
activeEvents.forEach( ( eventName ) => {
634
window.removeEventListener( eventName, this );
635
} );
636
};
637
638
// ----- event handler helpers ----- //
Dec 19, 2021
639
640
// trigger method with matching pointer
641
proto.withPointer = function( methodName, event ) {
642
if ( event.pointerId == this.pointerIdentifier ) {
643
this[ methodName ]( event, event );
644
}
645
};
646
647
// trigger method with matching touch
648
proto.withTouch = function( methodName, event ) {
649
let touch;
650
for ( let changedTouch of event.changedTouches ) {
651
if ( changedTouch.identifier == this.pointerIdentifier ) {
652
touch = changedTouch;
653
}
654
}
655
if ( touch ) this[ methodName ]( event, touch );
Jan 29, 2019
656
};
657
658
// ----- start event ----- //
Jan 29, 2019
659
660
proto.onmousedown = function( event ) {
661
this.pointerDown( event, event );
662
};
663
664
proto.ontouchstart = function( event ) {
665
this.pointerDown( event, event.changedTouches[0] );
666
};
667
668
proto.onpointerdown = function( event ) {
669
this.pointerDown( event, event );
670
};
671
672
// nodes that have text fields
673
const cursorNodes = [ 'TEXTAREA', 'INPUT', 'SELECT', 'OPTION' ];
674
// input types that do not have text fields
675
const clickTypes = [ 'radio', 'checkbox', 'button', 'submit', 'image', 'file' ];
676
677
/**
678
* any time you set `event, pointer` it refers to:
679
* @param {Event} event
680
* @param {Event | Touch} pointer
681
*/
682
proto.pointerDown = function( event, pointer ) {
683
// dismiss multi-touch taps, right clicks, and clicks on text fields
684
let isCursorNode = cursorNodes.includes( event.target.nodeName );
685
let isClickType = clickTypes.includes( event.target.type );
686
let isOkayElement = !isCursorNode || isClickType;
687
let isOkay = !this.isPointerDown && !event.button && isOkayElement;
688
if ( !isOkay ) return;
Jul 8, 2016
689
690
this.isPointerDown = true;
691
// save pointer identifier to match up touch events
692
this.pointerIdentifier = pointer.pointerId !== undefined ?
693
// pointerId for pointer events, touch.indentifier for touch events
694
pointer.pointerId : pointer.identifier;
695
// track position for move
696
this.pointerDownPointer = {
697
pageX: pointer.pageX,
698
pageY: pointer.pageY,
699
};
Jul 8, 2016
700
701
this.bindActivePointerEvents();
702
this.emitEvent( 'pointerDown', [ event, pointer ] );
703
};
Jul 8, 2016
704
705
// ----- move ----- //
Jul 8, 2016
706
707
proto.onmousemove = function( event ) {
708
this.pointerMove( event, event );
Jul 8, 2016
709
};
710
711
proto.onpointermove = function( event ) {
712
this.withPointer( 'pointerMove', event );
Jul 8, 2016
713
};
714
715
proto.ontouchmove = function( event ) {
716
this.withTouch( 'pointerMove', event );
Jul 8, 2016
717
};
718
719
proto.pointerMove = function( event, pointer ) {
720
let moveVector = {
721
x: pointer.pageX - this.pointerDownPointer.pageX,
722
y: pointer.pageY - this.pointerDownPointer.pageY,
723
};
724
this.emitEvent( 'pointerMove', [ event, pointer, moveVector ] );
725
// start drag if pointer has moved far enough to start drag
726
let isDragStarting = !this.isDragging && this.hasDragStarted( moveVector );
727
if ( isDragStarting ) this.dragStart( event, pointer );
728
if ( this.isDragging ) this.dragMove( event, pointer, moveVector );
Jul 8, 2016
729
};
730
731
// condition if pointer has moved far enough to start drag
732
proto.hasDragStarted = function( moveVector ) {
733
return Math.abs( moveVector.x ) > 3 || Math.abs( moveVector.y ) > 3;
Jul 8, 2016
734
};
735
736
// ----- drag ----- //
Jul 8, 2016
737
738
proto.dragStart = function( event, pointer ) {
739
this.isDragging = true;
740
this.isPreventingClicks = true; // set flag to prevent clicks
741
this.emitEvent( 'dragStart', [ event, pointer ] );
Feb 3, 2015
742
};
743
744
proto.dragMove = function( event, pointer, moveVector ) {
745
this.emitEvent( 'dragMove', [ event, pointer, moveVector ] );
746
};
747
748
// ----- end ----- //
Feb 3, 2015
749
750
proto.onmouseup = function( event ) {
751
this.pointerUp( event, event );
Jan 29, 2019
752
};
753
754
proto.onpointerup = function( event ) {
755
this.withPointer( 'pointerUp', event );
Jan 29, 2019
756
};
Feb 3, 2015
757
758
proto.ontouchend = function( event ) {
759
this.withTouch( 'pointerUp', event );
760
};
761
762
proto.pointerUp = function( event, pointer ) {
763
this.pointerDone();
764
this.emitEvent( 'pointerUp', [ event, pointer ] );
765
766
if ( this.isDragging ) {
767
this.dragEnd( event, pointer );
Feb 3, 2015
768
} else {
769
// pointer didn't move enough for drag to start
770
this.staticClick( event, pointer );
771
}
772
};
773
774
proto.dragEnd = function( event, pointer ) {
775
this.isDragging = false; // reset flag
776
// re-enable clicking async
777
setTimeout( () => delete this.isPreventingClicks );
Feb 3, 2015
778
779
this.emitEvent( 'dragEnd', [ event, pointer ] );
780
};
781
782
// triggered on pointer up & pointer cancel
783
proto.pointerDone = function() {
784
this.isPointerDown = false;
785
delete this.pointerIdentifier;
786
this.unbindActivePointerEvents();
787
this.emitEvent('pointerDone');
Feb 3, 2015
788
};
789
790
// ----- cancel ----- //
791
792
proto.onpointercancel = function( event ) {
793
this.withPointer( 'pointerCancel', event );
Feb 3, 2015
794
};
795
796
proto.ontouchcancel = function( event ) {
797
this.withTouch( 'pointerCancel', event );
Feb 3, 2015
798
};
799
800
proto.pointerCancel = function( event, pointer ) {
801
this.pointerDone();
802
this.emitEvent( 'pointerCancel', [ event, pointer ] );
Feb 3, 2015
803
};
804
805
// ----- click ----- //
Feb 3, 2015
806
807
// handle all clicks and prevent clicks when dragging
808
proto.onclick = function( event ) {
809
if ( this.isPreventingClicks ) event.preventDefault();
Jun 18, 2015
810
};
Feb 3, 2015
811
812
// triggered after pointer down & up with no/tiny movement
813
proto.staticClick = function( event, pointer ) {
814
// ignore emulated mouse up clicks
815
let isMouseup = event.type == 'mouseup';
816
if ( isMouseup && this.isIgnoringMouseUp ) return;
817
818
this.emitEvent( 'staticClick', [ event, pointer ] );
819
820
// set flag for emulated clicks 300ms after touchend
821
if ( isMouseup ) {
822
this.isIgnoringMouseUp = true;
823
// reset flag after 400ms
824
setTimeout( () => {
825
delete this.isIgnoringMouseUp;
826
}, 400 );
Feb 3, 2015
827
}
828
};
829
830
// ----- ----- //
831
832
return Unidragger;
Feb 3, 2015
833
835
/*!
836
* imagesLoaded v5.0.0
837
* JavaScript is all like "You images are done yet or what?"
838
* MIT License
839
*/
Feb 3, 2015
840
841
( function( window, factory ) {
842
// universal module definition
843
if ( typeof module == 'object' && module.exports ) {
844
// CommonJS
845
module.exports = factory( window, require('ev-emitter') );
846
} else {
847
// browser global
848
window.imagesLoaded = factory( window, window.EvEmitter );
849
}
850
851
} )( typeof window !== 'undefined' ? window : this,
852
function factory( window, EvEmitter ) {
853
854
let $ = window.jQuery;
855
let console = window.console;
856
857
// -------------------------- helpers -------------------------- //
858
859
// turn element or nodeList into an array
860
function makeArray( obj ) {
861
// use object if already an array
862
if ( Array.isArray( obj ) ) return obj;
863
864
let isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';
865
// convert nodeList to array
866
if ( isArrayLike ) return [ ...obj ];
867
868
// array of single index
869
return [ obj ];
870
}
871
872
// -------------------------- imagesLoaded -------------------------- //
873
874
/**
875
* @param {[Array, Element, NodeList, String]} elem
876
* @param {[Object, Function]} options - if function, use as callback
877
* @param {Function} onAlways - callback function
878
* @returns {ImagesLoaded}
879
*/
880
function ImagesLoaded( elem, options, onAlways ) {
881
// coerce ImagesLoaded() without new, to be new ImagesLoaded()
882
if ( !( this instanceof ImagesLoaded ) ) {
883
return new ImagesLoaded( elem, options, onAlways );
Feb 3, 2015
884
}
885
// use elem as selector string
886
let queryElem = elem;
887
if ( typeof elem == 'string' ) {
888
queryElem = document.querySelectorAll( elem );
889
}
890
// bail if bad element
891
if ( !queryElem ) {
892
console.error(`Bad element for imagesLoaded ${queryElem || elem}`);
893
return;
Aug 16, 2016
894
}
895
896
this.elements = makeArray( queryElem );
897
this.options = {};
898
// shift arguments if no options set
899
if ( typeof options == 'function' ) {
900
onAlways = options;
901
} else {
902
Object.assign( this.options, options );
Feb 3, 2015
903
}
904
905
if ( onAlways ) this.on( 'always', onAlways );
906
907
this.getImages();
908
// add jQuery Deferred object
909
if ( $ ) this.jqDeferred = new $.Deferred();
910
911
// HACK check async to allow time to bind listeners
912
setTimeout( this.check.bind( this ) );
913
}
Feb 3, 2015
914
915
ImagesLoaded.prototype = Object.create( EvEmitter.prototype );
Feb 3, 2015
916
917
ImagesLoaded.prototype.getImages = function() {
918
this.images = [];
Feb 3, 2015
919
920
// filter & find items if we have an item selector
921
this.elements.forEach( this.addElementImages, this );
Feb 9, 2015
922
};
923
924
const elementNodeTypes = [ 1, 9, 11 ];
Jun 18, 2015
925
Mar 16, 2018
926
/**
927
* @param {Node} elem
Mar 16, 2018
928
*/
929
ImagesLoaded.prototype.addElementImages = function( elem ) {
930
// filter siblings
931
if ( elem.nodeName === 'IMG' ) {
932
this.addImage( elem );
Mar 16, 2018
933
}
934
// get background image on element
935
if ( this.options.background === true ) {
936
this.addElementBackgroundImages( elem );
Feb 3, 2015
937
}
938
939
// find children
940
// no non-element nodes, #143
941
let { nodeType } = elem;
942
if ( !nodeType || !elementNodeTypes.includes( nodeType ) ) return;
Feb 3, 2015
943
944
let childImgs = elem.querySelectorAll('img');
945
// concat childElems to filterFound array
946
for ( let img of childImgs ) {
947
this.addImage( img );
Feb 3, 2015
948
}
949
950
// get child background images
951
if ( typeof this.options.background == 'string' ) {
952
let children = elem.querySelectorAll( this.options.background );
953
for ( let child of children ) {
954
this.addElementBackgroundImages( child );
955
}
Feb 19, 2015
956
}
Feb 3, 2015
957
};
958
959
const reURL = /url\((['"])?(.*?)\1\)/gi;
Feb 3, 2015
960
961
ImagesLoaded.prototype.addElementBackgroundImages = function( elem ) {
962
let style = getComputedStyle( elem );
963
// Firefox returns null if in a hidden iframe https://bugzil.la/548397
964
if ( !style ) return;
Feb 3, 2015
965
966
// get url inside url("...")
967
let matches = reURL.exec( style.backgroundImage );
968
while ( matches !== null ) {
969
let url = matches && matches[2];
970
if ( url ) {
971
this.addBackground( url, elem );
972
}
973
matches = reURL.exec( style.backgroundImage );
974
}
975
};
Feb 3, 2015
976
977
/**
978
* @param {Image} img
Feb 3, 2015
979
*/
980
ImagesLoaded.prototype.addImage = function( img ) {
981
let loadingImage = new LoadingImage( img );
982
this.images.push( loadingImage );
Feb 3, 2015
983
};
984
985
ImagesLoaded.prototype.addBackground = function( url, elem ) {
986
let background = new Background( url, elem );
987
this.images.push( background );
990
ImagesLoaded.prototype.check = function() {
991
this.progressedCount = 0;
992
this.hasAnyBroken = false;
993
// complete if no images
994
if ( !this.images.length ) {
995
this.complete();
996
return;
997
}
Feb 3, 2015
998
999
/* eslint-disable-next-line func-style */
1000
let onProgress = ( image, elem, message ) => {
1001
// HACK - Chrome triggers event before object properties have changed. #83
1002
setTimeout( () => {
1003
this.progress( image, elem, message );
1004
} );
1005
};
Feb 3, 2015
1006
1007
this.images.forEach( function( loadingImage ) {
1008
loadingImage.once( 'progress', onProgress );
1009
loadingImage.check();
1010
} );
1011
};
Feb 3, 2015
1012
1013
ImagesLoaded.prototype.progress = function( image, elem, message ) {
1014
this.progressedCount++;
1015
this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
1016
// progress event
1017
this.emitEvent( 'progress', [ this, image, elem ] );
1018
if ( this.jqDeferred && this.jqDeferred.notify ) {
1019
this.jqDeferred.notify( this, image );
1020
}
1021
// check if completed
1022
if ( this.progressedCount === this.images.length ) {
1023
this.complete();
Feb 3, 2015
1024
}
1025
1026
if ( this.options.debug && console ) {
1027
console.log( `progress: ${message}`, image, elem );
1028
}
1029
};
Feb 3, 2015
1030
1031
ImagesLoaded.prototype.complete = function() {
1032
let eventName = this.hasAnyBroken ? 'fail' : 'done';
1033
this.isComplete = true;
1034
this.emitEvent( eventName, [ this ] );
1035
this.emitEvent( 'always', [ this ] );
1036
if ( this.jqDeferred ) {
1037
let jqMethod = this.hasAnyBroken ? 'reject' : 'resolve';
1038
this.jqDeferred[ jqMethod ]( this );
1039
}
1040
};
Feb 3, 2015
1041
1042
// -------------------------- -------------------------- //
Feb 3, 2015
1043
1044
function LoadingImage( img ) {
1045
this.img = img;
1046
}
Feb 3, 2015
1047
1048
LoadingImage.prototype = Object.create( EvEmitter.prototype );
Feb 3, 2015
1049
1050
LoadingImage.prototype.check = function() {
1051
// If complete is true and browser supports natural sizes,
1052
// try to check for image status manually.
1053
let isComplete = this.getIsImageComplete();
1054
if ( isComplete ) {
1055
// report based on naturalWidth
1056
this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
1057
return;
1058
}
Feb 3, 2015
1059
1060
// If none of the checks above matched, simulate loading on detached element.
1061
this.proxyImage = new Image();
1062
// add crossOrigin attribute. #204
1063
if ( this.img.crossOrigin ) {
1064
this.proxyImage.crossOrigin = this.img.crossOrigin;
1065
}
1066
this.proxyImage.addEventListener( 'load', this );
1067
this.proxyImage.addEventListener( 'error', this );
1068
// bind to image as well for Firefox. #191
1069
this.img.addEventListener( 'load', this );
1070
this.img.addEventListener( 'error', this );
1071
this.proxyImage.src = this.img.currentSrc || this.img.src;
Feb 3, 2015
1072
};
1073
1074
LoadingImage.prototype.getIsImageComplete = function() {
1075
// check for non-zero, non-undefined naturalWidth
1076
// fixes Safari+InfiniteScroll+Masonry bug infinite-scroll#671
1077
return this.img.complete && this.img.naturalWidth;
Feb 3, 2015
1078
};
1079
1080
LoadingImage.prototype.confirm = function( isLoaded, message ) {
1081
this.isLoaded = isLoaded;
1082
let { parentNode } = this.img;
1083
// emit progress with parent <picture> or self <img>
1084
let elem = parentNode.nodeName === 'PICTURE' ? parentNode : this.img;
1085
this.emitEvent( 'progress', [ this, elem, message ] );
Feb 3, 2015
1086
};
1087
1088
// ----- events ----- //
1089
1090
// trigger specified handler for event type
1091
LoadingImage.prototype.handleEvent = function( event ) {
1092
let method = 'on' + event.type;
Feb 3, 2015
1093
if ( this[ method ] ) {
1094
this[ method ]( event );
1095
}
1096
};
1097
1098
LoadingImage.prototype.onload = function() {
1099
this.confirm( true, 'onload' );
1100
this.unbindEvents();
Feb 3, 2015
1101
};
1102
1103
LoadingImage.prototype.onerror = function() {
1104
this.confirm( false, 'onerror' );
1105
this.unbindEvents();
Feb 3, 2015
1106
};
1107
1108
LoadingImage.prototype.unbindEvents = function() {
1109
this.proxyImage.removeEventListener( 'load', this );
1110
this.proxyImage.removeEventListener( 'error', this );
1111
this.img.removeEventListener( 'load', this );
1112
this.img.removeEventListener( 'error', this );
Feb 3, 2015
1113
};
1114
1115
// -------------------------- Background -------------------------- //
Feb 3, 2015
1116
1117
function Background( url, element ) {
1118
this.url = url;
1119
this.element = element;
1120
this.img = new Image();
1121
}
Feb 3, 2015
1122
1123
// inherit LoadingImage prototype
1124
Background.prototype = Object.create( LoadingImage.prototype );
Feb 3, 2015
1125
1126
Background.prototype.check = function() {
1127
this.img.addEventListener( 'load', this );
1128
this.img.addEventListener( 'error', this );
1129
this.img.src = this.url;
1130
// check if image is already complete
1131
let isComplete = this.getIsImageComplete();
1132
if ( isComplete ) {
1133
this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
1134
this.unbindEvents();
1135
}
Feb 3, 2015
1136
};
1137
1138
Background.prototype.unbindEvents = function() {
1139
this.img.removeEventListener( 'load', this );
1140
this.img.removeEventListener( 'error', this );
Feb 3, 2015
1141
};
1142
1143
Background.prototype.confirm = function( isLoaded, message ) {
1144
this.isLoaded = isLoaded;
1145
this.emitEvent( 'progress', [ this, this.element, message ] );
Feb 3, 2015
1146
};
1147
1148
// -------------------------- jQuery -------------------------- //
Feb 3, 2015
1149
1150
ImagesLoaded.makeJQueryPlugin = function( jQuery ) {
1151
jQuery = jQuery || window.jQuery;
1152
if ( !jQuery ) return;
Feb 3, 2015
1153
1154
// set local variable
1155
$ = jQuery;
1156
// $().imagesLoaded()
1157
$.fn.imagesLoaded = function( options, onAlways ) {
1158
let instance = new ImagesLoaded( this, options, onAlways );
1159
return instance.jqDeferred.promise( $( this ) );
1160
};
Feb 3, 2015
1161
};
1162
// try making plugin
1163
ImagesLoaded.makeJQueryPlugin();
Feb 3, 2015
1164
1165
// -------------------------- -------------------------- //
Feb 3, 2015
1166
1167
return ImagesLoaded;
Feb 3, 2015
1168
1169
} );
1170
// Flickity.Cell
1171
( function( window, factory ) {
1172
// universal module definition
1173
if ( typeof module == 'object' && module.exports ) {
1174
// CommonJS
1175
module.exports = factory( require('get-size') );
1176
} else {
1177
// browser global
1178
window.Flickity = window.Flickity || {};
1179
window.Flickity.Cell = factory( window.getSize );
Feb 3, 2015
1180
}
1181
1182
}( typeof window != 'undefined' ? window : this, function factory( getSize ) {
Feb 3, 2015
1183
1184
const cellClassName = 'flickity-cell';
Feb 3, 2015
1185
1186
function Cell( elem ) {
1187
this.element = elem;
1188
this.element.classList.add( cellClassName );
Feb 3, 2015
1189
1190
this.x = 0;
1191
this.unselect();
1192
}
Feb 3, 2015
1193
1194
let proto = Cell.prototype;
Feb 3, 2015
1195
1196
proto.destroy = function() {
1197
// reset style
1198
this.unselect();
1199
this.element.classList.remove( cellClassName );
1200
this.element.style.transform = '';
1201
this.element.removeAttribute('aria-hidden');
Feb 3, 2015
1202
};
1203
1204
proto.getSize = function() {
1205
this.size = getSize( this.element );
Feb 3, 2015
1206
};
1207
1208
proto.select = function() {
1209
this.element.classList.add('is-selected');
1210
this.element.removeAttribute('aria-hidden');
Feb 3, 2015
1211
};
1212
1213
proto.unselect = function() {
1214
this.element.classList.remove('is-selected');
1215
this.element.setAttribute( 'aria-hidden', 'true' );
Feb 3, 2015
1216
};
1217
1218
proto.remove = function() {
1219
this.element.remove();
Feb 3, 2015
1220
};
1221
1222
return Cell;
Feb 3, 2015
1223
1224
} ) );
1225
// slide
1226
( function( window, factory ) {
1227
// universal module definition
1228
if ( typeof module == 'object' && module.exports ) {
1229
// CommonJS
1230
module.exports = factory();
1231
} else {
1232
// browser global
1233
window.Flickity = window.Flickity || {};
1234
window.Flickity.Slide = factory();
1235
}
Mar 29, 2018
1236
1237
}( typeof window != 'undefined' ? window : this, function factory() {
Feb 3, 2015
1238
1239
function Slide( beginMargin, endMargin, cellAlign ) {
1240
this.beginMargin = beginMargin;
1241
this.endMargin = endMargin;
1242
this.cellAlign = cellAlign;
1243
this.cells = [];
1244
this.outerWidth = 0;
1245
this.height = 0;
1246
}
Feb 3, 2015
1247
1248
let proto = Slide.prototype;
Feb 3, 2015
1249
1250
proto.addCell = function( cell ) {
1251
this.cells.push( cell );
1252
this.outerWidth += cell.size.outerWidth;
1253
this.height = Math.max( cell.size.outerHeight, this.height );
1254
// first cell stuff
1255
if ( this.cells.length === 1 ) {
1256
this.x = cell.x; // x comes from first cell
1257
this.firstMargin = cell.size[ this.beginMargin ];
Feb 3, 2015
1258
}
1259
};
1260
1261
proto.updateTarget = function() {
1262
let lastCell = this.getLastCell();
1263
let lastMargin = lastCell ? lastCell.size[ this.endMargin ] : 0;
1264
let slideWidth = this.outerWidth - ( this.firstMargin + lastMargin );
1265
this.target = this.x + this.firstMargin + slideWidth * this.cellAlign;
Feb 3, 2015
1266
};
1267
1268
proto.getLastCell = function() {
1269
return this.cells[ this.cells.length - 1 ];
Feb 3, 2015
1270
};
1271
1272
proto.select = function() {
1273
this.cells.forEach( ( cell ) => cell.select() );
Feb 3, 2015
1274
};
1275
1276
proto.unselect = function() {
1277
this.cells.forEach( ( cell ) => cell.unselect() );
Feb 3, 2015
1278
};
1279
1280
proto.getCellElements = function() {
1281
return this.cells.map( ( cell ) => cell.element );
1282
};
Feb 3, 2015
1283
1284
return Slide;
Feb 3, 2015
1285
1286
} ) );
1287
// animate
Feb 3, 2015
1288
( function( window, factory ) {
1289
// universal module definition
1290
if ( typeof module == 'object' && module.exports ) {
Feb 3, 2015
1291
// CommonJS
1292
module.exports = factory( require('fizzy-ui-utils') );
Feb 3, 2015
1293
} else {
1294
// browser global
1295
window.Flickity = window.Flickity || {};
1296
window.Flickity.animatePrototype = factory( window.fizzyUIUtils );
Feb 3, 2015
1297
}
1298
1299
}( typeof window != 'undefined' ? window : this, function factory( utils ) {
Feb 3, 2015
1300
1301
// -------------------------- animate -------------------------- //
Feb 3, 2015
1302
1303
let proto = {};
Feb 3, 2015
1304
1305
proto.startAnimation = function() {
1306
if ( this.isAnimating ) return;
Feb 3, 2015
1307
1308
this.isAnimating = true;
1309
this.restingFrames = 0;
1310
this.animate();
1311
};
Feb 3, 2015
1312
1313
proto.animate = function() {
1314
this.applyDragForce();
1315
this.applySelectedAttraction();
Feb 3, 2015
1316
1317
let previousX = this.x;
Feb 3, 2015
1318
1319
this.integratePhysics();
1320
this.positionSlider();
1321
this.settle( previousX );
1322
// animate next frame
1323
if ( this.isAnimating ) requestAnimationFrame( () => this.animate() );
Feb 3, 2015
1324
};
1325
1326
proto.positionSlider = function() {
1327
let x = this.x;
1328
// wrap position around
1329
if ( this.isWrapping ) {
1330
x = utils.modulo( x, this.slideableWidth ) - this.slideableWidth;
1331
this.shiftWrapCells( x );
Feb 3, 2015
1332
}
1333
1334
this.setTranslateX( x, this.isAnimating );
1335
this.dispatchScrollEvent();
1336
};
Jul 6, 2017
1337
1338
proto.setTranslateX = function( x, is3d ) {
1339
x += this.cursorPosition;
1340
// reverse if right-to-left and using transform
1341
if ( this.options.rightToLeft ) x = -x;
1342
let translateX = this.getPositionValue( x );
1343
// use 3D transforms for hardware acceleration on iOS
1344
// but use 2D when settled, for better font-rendering
1345
this.slider.style.transform = is3d ?
1346
`translate3d(${translateX},0,0)` : `translateX(${translateX})`;
1347
};
Feb 3, 2015
1348
1349
proto.dispatchScrollEvent = function() {
1350
let firstSlide = this.slides[0];
1351
if ( !firstSlide ) return;
1353
let positionX = -this.x - firstSlide.target;
1354
let progress = positionX / this.slidesWidth;
1355
this.dispatchEvent( 'scroll', null, [ progress, positionX ] );
Feb 3, 2015
1356
};
1357
1358
proto.positionSliderAtSelected = function() {
1359
if ( !this.cells.length ) return;
1360
1361
this.x = -this.selectedSlide.target;
1362
this.velocity = 0; // stop wobble
1363
this.positionSlider();
Mar 29, 2018
1364
};
1365
1366
proto.getPositionValue = function( position ) {
1367
if ( this.options.percentPosition ) {
1368
// percent position, round to 2 digits, like 12.34%
1369
return ( Math.round( ( position / this.size.innerWidth ) * 10000 ) * 0.01 ) + '%';
1370
} else {
1371
// pixel positioning
1372
return Math.round( position ) + 'px';
Feb 3, 2015
1373
}
1374
};
1375
1376
proto.settle = function( previousX ) {
1377
// keep track of frames where x hasn't moved
1378
let isResting = !this.isPointerDown &&
1379
Math.round( this.x * 100 ) === Math.round( previousX * 100 );
1380
if ( isResting ) this.restingFrames++;
1381
// stop animating if resting for 3 or more frames
1382
if ( this.restingFrames > 2 ) {
1383
this.isAnimating = false;
1384
delete this.isFreeScrolling;
1385
// render position with translateX when settled
1386
this.positionSlider();
1387
this.dispatchEvent( 'settle', null, [ this.selectedIndex ] );
Mar 29, 2018
1388
}
Jul 8, 2016
1389
};
1390
1391
proto.shiftWrapCells = function( x ) {
1392
// shift before cells
1393
let beforeGap = this.cursorPosition + x;
1394
this._shiftCells( this.beforeShiftCells, beforeGap, -1 );
1395
// shift after cells
1396
let afterGap = this.size.innerWidth - ( x + this.slideableWidth + this.cursorPosition );
1397
this._shiftCells( this.afterShiftCells, afterGap, 1 );
1398
};
Feb 3, 2015
1399
1400
proto._shiftCells = function( cells, gap, shift ) {
1401
cells.forEach( ( cell ) => {
1402
let cellShift = gap > 0 ? shift : 0;
1403
this._wrapShiftCell( cell, cellShift );
1404
gap -= cell.size.outerWidth;
1405
} );
Feb 3, 2015
1406
};
1407
1408
proto._unshiftCells = function( cells ) {
1409
if ( !cells || !cells.length ) return;
1410
1411
cells.forEach( ( cell ) => this._wrapShiftCell( cell, 0 ) );
Feb 3, 2015
1412
};
1413
1414
// @param {Integer} shift - 0, 1, or -1
1415
proto._wrapShiftCell = function( cell, shift ) {
1416
this._renderCellPosition( cell, cell.x + this.slideableWidth * shift );
Feb 3, 2015
1417
};
1418
1419
// -------------------------- physics -------------------------- //
Feb 3, 2015
1420
1421
proto.integratePhysics = function() {
1422
this.x += this.velocity;
1423
this.velocity *= this.getFrictionFactor();
Feb 3, 2015
1424
};
1425
1426
proto.applyForce = function( force ) {
1427
this.velocity += force;
Feb 3, 2015
1428
};
1429
1430
proto.getFrictionFactor = function() {
1431
return 1 - this.options[ this.isFreeScrolling ? 'freeScrollFriction' : 'friction' ];
Feb 3, 2015
1432
};
1433
1434
proto.getRestingPosition = function() {
1435
// my thanks to Steven Wittens, who simplified this math greatly
1436
return this.x + this.velocity / ( 1 - this.getFrictionFactor() );
Feb 3, 2015
1437
};
1438
1439
proto.applyDragForce = function() {
1440
if ( !this.isDraggable || !this.isPointerDown ) return;
Feb 3, 2015
1441
1442
// change the position to drag position by applying force
1443
let dragVelocity = this.dragX - this.x;
1444
let dragForce = dragVelocity - this.velocity;
1445
this.applyForce( dragForce );
Feb 3, 2015
1446
};
1447
1448
proto.applySelectedAttraction = function() {
1449
// do not attract if pointer down or no slides
1450
let dragDown = this.isDraggable && this.isPointerDown;
1451
if ( dragDown || this.isFreeScrolling || !this.slides.length ) return;
1452
1453
let distance = this.selectedSlide.target * -1 - this.x;
1454
let force = distance * this.options.selectedAttraction;
1455
this.applyForce( force );
Feb 3, 2015
1456
};
1457
1458
return proto;
Feb 3, 2015
1459
1460
} ) );
1461
// Flickity main
1462
/* eslint-disable max-params */
1463
( function( window, factory ) {
1464
// universal module definition
1465
if ( typeof module == 'object' && module.exports ) {
1466
// CommonJS
1467
module.exports = factory(
1468
window,
1469
require('ev-emitter'),
1470
require('get-size'),
1471
require('fizzy-ui-utils'),
1472
require('./cell'),
1473
require('./slide'),
1474
require('./animate'),
1475
);
1476
} else {
1477
// browser global
1478
let _Flickity = window.Flickity;
1479
1480
window.Flickity = factory(
1481
window,
1482
window.EvEmitter,
1483
window.getSize,
1484
window.fizzyUIUtils,
1485
_Flickity.Cell,
1486
_Flickity.Slide,
1487
_Flickity.animatePrototype,
1488
);
1489
}
Feb 3, 2015
1490
1491
}( typeof window != 'undefined' ? window : this,
1492
function factory( window, EvEmitter, getSize, utils, Cell, Slide, animatePrototype ) {
1493
/* eslint-enable max-params */
Feb 3, 2015
1494
1495
// vars
1496
const { getComputedStyle, console } = window;
1497
let { jQuery } = window;
Feb 3, 2015
1498
1499
// -------------------------- Flickity -------------------------- //
Feb 3, 2015
1500
1501
// globally unique identifiers
1502
let GUID = 0;
1503
// internal store of all Flickity intances
1504
let instances = {};
Feb 3, 2015
1505
1506
function Flickity( element, options ) {
1507
let queryElement = utils.getQueryElement( element );
1508
if ( !queryElement ) {
1509
if ( console ) console.error(`Bad element for Flickity: ${queryElement || element}`);
1510
return;
1511
}
1512
this.element = queryElement;
1513
// do not initialize twice on same element
1514
if ( this.element.flickityGUID ) {
1515
let instance = instances[ this.element.flickityGUID ];
1516
if ( instance ) instance.option( options );
1517
return instance;
1518
}
1520
// add jQuery
1521
if ( jQuery ) {
1522
this.$element = jQuery( this.element );
1524
// options
1525
this.options = { ...this.constructor.defaults };
1526
this.option( options );
Feb 3, 2015
1527
1528
// kick things off
1529
this._create();
1530
}
1531
1532
Flickity.defaults = {
1533
accessibility: true,
1534
// adaptiveHeight: false,
1535
cellAlign: 'center',
1536
// cellSelector: undefined,
1537
// contain: false,
1538
freeScrollFriction: 0.075, // friction when free-scrolling
1539
friction: 0.28, // friction when selecting
1540
namespaceJQueryEvents: true,
1541
// initialIndex: 0,
1542
percentPosition: true,
1543
resize: true,
1544
selectedAttraction: 0.025,
1545
setGallerySize: true,
1546
// watchCSS: false,
1547
// wrapAround: false
Feb 3, 2015
1548
};
1549
1550
// hash of methods triggered on _create()
1551
Flickity.create = {};
Feb 3, 2015
1552
1553
let proto = Flickity.prototype;
1554
// inherit EventEmitter
1555
Object.assign( proto, EvEmitter.prototype );
1557
proto._create = function() {
1558
let { resize, watchCSS, rightToLeft } = this.options;
1559
// add id for Flickity.data
1560
let id = this.guid = ++GUID;
1561
this.element.flickityGUID = id; // expando
1562
instances[ id ] = this; // associate via id
1563
// initial properties
1564
this.selectedIndex = 0;
1565
// how many frames slider has been in same position
1566
this.restingFrames = 0;
1567
// initial physics properties
1568
this.x = 0;
1569
this.velocity = 0;
1570
this.beginMargin = rightToLeft ? 'marginRight' : 'marginLeft';
1571
this.endMargin = rightToLeft ? 'marginLeft' : 'marginRight';
1572
// create viewport & slider
1573
this.viewport = document.createElement('div');
1574
this.viewport.className = 'flickity-viewport';
1575
this._createSlider();
1576
// used for keyboard navigation
1577
this.focusableElems = [ this.element ];
Feb 3, 2015
1578
1579
if ( resize || watchCSS ) {
1580
window.addEventListener( 'resize', this );
1581
}
Feb 3, 2015
1582
1583
// add listeners from on option
1584
for ( let eventName in this.options.on ) {
1585
let listener = this.options.on[ eventName ];
1586
this.on( eventName, listener );
1587
}
Feb 3, 2015
1588
1589
for ( let method in Flickity.create ) {
1590
Flickity.create[ method ].call( this );
1591
}
1592
1593
if ( watchCSS ) {
1594
this.watchCSS();
Feb 3, 2015
1595
} else {
1596
this.activate();
Feb 3, 2015
1597
}
1598
};
Feb 3, 2015
1599
1600
/**
1601
* set options
1602
* @param {Object} opts - options to extend
1603
*/
1604
proto.option = function( opts ) {
1605
Object.assign( this.options, opts );
1606
};
Jul 8, 2016
1607
1608
proto.activate = function() {
1609
if ( this.isActive ) return;
Jul 8, 2016
1610
1611
this.isActive = true;
1612
this.element.classList.add('flickity-enabled');
1613
if ( this.options.rightToLeft ) {
1614
this.element.classList.add('flickity-rtl');
1615
}
Feb 3, 2015
1616
1617
this.getSize();
1618
// move initial cell elements so they can be loaded as cells
1619
let cellElems = this._filterFindCellElements( this.element.children );
1620
this.slider.append( ...cellElems );
1621
this.viewport.append( this.slider );
1622
this.element.append( this.viewport );
1623
// get cells from children
1624
this.reloadCells();
Feb 3, 2015
1625
1626
if ( this.options.accessibility ) {
1627
// allow element to focusable
1628
this.element.tabIndex = 0;
1629
// listen for key presses
1630
this.element.addEventListener( 'keydown', this );
1631
}
1632
1633
this.emitEvent('activate');
1634
this.selectInitialIndex();
1635
// flag for initial activation, for using initialIndex
1636
this.isInitActivated = true;
1637
// ready event. #493
1638
this.dispatchEvent('ready');
1639
};
Feb 3, 2015
1640
1641
// slider positions the cells
1642
proto._createSlider = function() {
1643
// slider element does all the positioning
1644
let slider = document.createElement('div');
1645
slider.className = 'flickity-slider';
1646
this.slider = slider;
1647
};
Feb 3, 2015
1648
1649
proto._filterFindCellElements = function( elems ) {
1650
return utils.filterFindElements( elems, this.options.cellSelector );
1651
};
Feb 3, 2015
1652
1653
// goes through all children
1654
proto.reloadCells = function() {
1655
// collection of item elements
1656
this.cells = this._makeCells( this.slider.children );
1657
this.positionCells();
1658
this._updateWrapShiftCells();
1659
this.setGallerySize();
1660
};
Feb 3, 2015
1661
1662
/**
1663
* turn elements into Flickity.Cells
1664
* @param {[Array, NodeList, HTMLElement]} elems - elements to make into cells
1665
* @returns {Array} items - collection of new Flickity Cells
1666
*/
1667
proto._makeCells = function( elems ) {
1668
let cellElems = this._filterFindCellElements( elems );
Feb 3, 2015
1669
1670
// create new Cells for collection
1671
return cellElems.map( ( cellElem ) => new Cell( cellElem ) );
1672
};
Feb 3, 2015
1673
1674
proto.getLastCell = function() {
1675
return this.cells[ this.cells.length - 1 ];
Feb 3, 2015
1676
};
1677
1678
proto.getLastSlide = function() {
1679
return this.slides[ this.slides.length - 1 ];
Feb 3, 2015
1680
};
1681
1682
// positions all cells
1683
proto.positionCells = function() {
1684
// size all cells
1685
this._sizeCells( this.cells );
1686
// position all cells
1687
this._positionCells( 0 );
Mar 16, 2018
1688
};
1689
1690
/**
1691
* position certain cells
1692
* @param {Integer} index - which cell to start with
1693
*/
1694
proto._positionCells = function( index ) {
1695
index = index || 0;
1696
// also measure maxCellHeight
1697
// start 0 if positioning all cells
1698
this.maxCellHeight = index ? this.maxCellHeight || 0 : 0;
1699
let cellX = 0;
1700
// get cellX
1701
if ( index > 0 ) {
1702
let startCell = this.cells[ index - 1 ];
1703
cellX = startCell.x + startCell.size.outerWidth;
Mar 16, 2018
1704
}
1705
1706
this.cells.slice( index ).forEach( ( cell ) => {
1707
cell.x = cellX;
1708
this._renderCellPosition( cell, cellX );
1709
cellX += cell.size.outerWidth;
1710
this.maxCellHeight = Math.max( cell.size.outerHeight, this.maxCellHeight );
1711
} );
1712
// keep track of cellX for wrap-around
1713
this.slideableWidth = cellX;
1714
// slides
1715
this.updateSlides();
1716
// contain slides target
1717
this._containSlides();
1718
// update slidesWidth
1719
this.slidesWidth = this.cells.length ?
1720
this.getLastSlide().target - this.slides[0].target : 0;
Mar 16, 2018
1721
};
1722
1723
proto._renderCellPosition = function( cell, x ) {
1724
// render position of cell with in slider
1725
let sideOffset = this.options.rightToLeft ? -1 : 1;
1726
let renderX = x * sideOffset;
1727
if ( this.options.percentPosition ) renderX *= this.size.innerWidth / cell.size.width;
1728
let positionValue = this.getPositionValue( renderX );
1729
cell.element.style.transform = `translateX( ${positionValue} )`;
1730
};
1731
1732
/**
1733
* cell.getSize() on multiple cells
1734
* @param {Array} cells - cells to size
1735
*/
1736
proto._sizeCells = function( cells ) {
1737
cells.forEach( ( cell ) => cell.getSize() );
1738
};
1739
1740
// -------------------------- -------------------------- //
Feb 3, 2015
1741
1742
proto.updateSlides = function() {
1743
this.slides = [];
1744
if ( !this.cells.length ) return;
1746
let { beginMargin, endMargin } = this;
1747
let slide = new Slide( beginMargin, endMargin, this.cellAlign );
1748
this.slides.push( slide );
Mar 29, 2018
1749
1750
let canCellFit = this._getCanCellFit();
1752
this.cells.forEach( ( cell, i ) => {
1753
// just add cell if first cell in slide
1754
if ( !slide.cells.length ) {
1755
slide.addCell( cell );
1756
return;
1757
}
Mar 16, 2018
1758
1759
let slideWidth = ( slide.outerWidth - slide.firstMargin ) +
1760
( cell.size.outerWidth - cell.size[ endMargin ] );
1761
1762
if ( canCellFit( i, slideWidth ) ) {
1763
slide.addCell( cell );
1764
} else {
1765
// doesn't fit, new slide
1766
slide.updateTarget();
1767
1768
slide = new Slide( beginMargin, endMargin, this.cellAlign );
1769
this.slides.push( slide );
1770
slide.addCell( cell );
1771
}
1772
} );
1773
// last slide
1774
slide.updateTarget();
1775
// update .selectedSlide
1776
this.updateSelectedSlide();
1777
};
1778
1779
proto._getCanCellFit = function() {
1780
let { groupCells } = this.options;
1781
if ( !groupCells ) return () => false;
1783
if ( typeof groupCells == 'number' ) {
1784
// group by number. 3 -> [0,1,2], [3,4,5], ...
1785
let number = parseInt( groupCells, 10 );
1786
return ( i ) => ( i % number ) !== 0;
Mar 29, 2018
1787
}
1788
// default, group by width of slide
1789
let percent = 1;
1790
// parse '75%
1791
let percentMatch = typeof groupCells == 'string' && groupCells.match( /^(\d+)%$/ );
1792
if ( percentMatch ) percent = parseInt( percentMatch[1], 10 ) / 100;
1793
let groupWidth = ( this.size.innerWidth + 1 ) * percent;
1794
return ( i, slideWidth ) => slideWidth <= groupWidth;
Mar 29, 2018
1795
};
1796
1797
// alias _init for jQuery plugin .flickity()
1798
proto._init =
1799
proto.reposition = function() {
1800
this.positionCells();
1801
this.positionSliderAtSelected();
Jul 8, 2016
1802
};
1803
1804
proto.getSize = function() {
1805
this.size = getSize( this.element );
1806
this.setCellAlign();
1807
this.cursorPosition = this.size.innerWidth * this.cellAlign;
Apr 27, 2015
1808
};
1809
1810
let cellAlignShorthands = {
1811
left: 0,
1812
center: 0.5,
1813
right: 1,
Feb 3, 2015
1814
};
1815
1816
proto.setCellAlign = function() {
1817
let { cellAlign, rightToLeft } = this.options;
1818
let shorthand = cellAlignShorthands[ cellAlign ];
1819
this.cellAlign = shorthand !== undefined ? shorthand : cellAlign;
1820
if ( rightToLeft ) this.cellAlign = 1 - this.cellAlign;
Feb 3, 2015
1821
};
1822
1823
proto.setGallerySize = function() {
1824
if ( !this.options.setGallerySize ) return;
1825
1826
let height = this.options.adaptiveHeight && this.selectedSlide ?
1827
this.selectedSlide.height : this.maxCellHeight;
1828
this.viewport.style.height = `${height}px`;
Feb 3, 2015
1829
};
1830
1831
proto._updateWrapShiftCells = function() {
1832
// update isWrapping
1833
this.isWrapping = this.getIsWrapping();
1834
// only for wrap-around
1835
if ( !this.isWrapping ) return;
1836
1837
// unshift previous cells
1838
this._unshiftCells( this.beforeShiftCells );
1839
this._unshiftCells( this.afterShiftCells );
1840
// get before cells
1841
// initial gap
1842
let beforeGapX = this.cursorPosition;
1843
let lastIndex = this.cells.length - 1;
1844
this.beforeShiftCells = this._getGapCells( beforeGapX, lastIndex, -1 );
1845
// get after cells
1846
// ending gap between last cell and end of gallery viewport
1847
let afterGapX = this.size.innerWidth - this.cursorPosition;
1848
// start cloning at first cell, working forwards
1849
this.afterShiftCells = this._getGapCells( afterGapX, 0, 1 );
Jul 11, 2016
1850
};
1851
1852
proto.getIsWrapping = function() {
1853
let { wrapAround } = this.options;
1854
if ( !wrapAround || this.slides.length < 2 ) return false;
Apr 27, 2015
1855
1856
if ( wrapAround !== 'fill' ) return true;
1857
// check that slides can fit
1858
1859
let gapWidth = this.slideableWidth - this.size.innerWidth;
1860
if ( gapWidth > this.size.innerWidth ) return true; // gap * 2x big, all good
1861
// check that content width - shifting cell is bigger than viewport width
1862
for ( let cell of this.cells ) {
1863
if ( cell.size.outerWidth > gapWidth ) return false;
Feb 3, 2015
1864
}
1865
return true;
Feb 3, 2015
1866
};
1867
1868
proto._getGapCells = function( gapX, cellIndex, increment ) {
1869
// keep adding cells until the cover the initial gap
1870
let cells = [];
1871
while ( gapX > 0 ) {
1872
let cell = this.cells[ cellIndex ];
1873
if ( !cell ) break;
1874
1875
cells.push( cell );
1876
cellIndex += increment;
1877
gapX -= cell.size.outerWidth;
1878
}
1879
return cells;
1880
};
1881
1882
// ----- contain & wrap ----- //
1883
1884
// contain cell targets so no excess sliding
1885
proto._containSlides = function() {
1886
let isContaining = this.options.contain && !this.isWrapping &&
1887
this.cells.length;
1888
if ( !isContaining ) return;
1889
1890
let contentWidth = this.slideableWidth - this.getLastCell().size[ this.endMargin ];
1891
// content is less than gallery size
1892
let isContentSmaller = contentWidth < this.size.innerWidth;
1893
if ( isContentSmaller ) {
1894
// all cells fit inside gallery
1895
this.slides.forEach( ( slide ) => {
1896
slide.target = contentWidth * this.cellAlign;
1897
} );
1898
} else {
1899
// contain to bounds
1900
let beginBound = this.cursorPosition + this.cells[0].size[ this.beginMargin ];
1901
let endBound = contentWidth - this.size.innerWidth * ( 1 - this.cellAlign );
1902
this.slides.forEach( ( slide ) => {
1903
slide.target = Math.max( slide.target, beginBound );
1904
slide.target = Math.min( slide.target, endBound );
1905
} );
1906
}
1907
};
1908
1909
// ----- events ----- //
1910
Feb 3, 2015
1911
/**
1912
* emits events via eventEmitter and jQuery events
1913
* @param {String} type - name of event
1914
* @param {Event} event - original event
1915
* @param {Array} args - extra arguments
Feb 3, 2015
1916
*/
1917
proto.dispatchEvent = function( type, event, args ) {
1918
let emitArgs = event ? [ event ].concat( args ) : args;
1919
this.emitEvent( type, emitArgs );
Jun 18, 2015
1920
1921
if ( jQuery && this.$element ) {
1922
// default trigger with type if no event
1923
type += this.options.namespaceJQueryEvents ? '.flickity' : '';
1924
let $event = type;
1925
if ( event ) {
1926
// create jQuery event
1927
let jQEvent = new jQuery.Event( event );
1928
jQEvent.type = type;
1929
$event = jQEvent;
1930
}
1931
this.$element.trigger( $event, args );
Feb 3, 2015
1932
}
1933
};
1934
1935
const unidraggerEvents = [
1936
'dragStart',
1937
'dragMove',
1938
'dragEnd',
1939
'pointerDown',
1940
'pointerMove',
1941
'pointerEnd',
1942
'staticClick',
1943
];
Jul 8, 2016
1944
1945
let _emitEvent = proto.emitEvent;
1946
proto.emitEvent = function( eventName, args ) {
1947
if ( eventName === 'staticClick' ) {
1948
// add cellElem and cellIndex args to staticClick
1949
let clickedCell = this.getParentCell( args[0].target );
1950
let cellElem = clickedCell && clickedCell.element;
1951
let cellIndex = clickedCell && this.cells.indexOf( clickedCell );
1952
args = args.concat( cellElem, cellIndex );
1953
}
1954
// do regular thing
1955
_emitEvent.call( this, eventName, args );
1956
// duck-punch in jQuery events for Unidragger events
1957
let isUnidraggerEvent = unidraggerEvents.includes( eventName );
1958
if ( !isUnidraggerEvent || !jQuery || !this.$element ) return;
1959
1960
eventName += this.options.namespaceJQueryEvents ? '.flickity' : '';
1961
let event = args.shift( 0 );
1962
let jQEvent = new jQuery.Event( event );
1963
jQEvent.type = eventName;
1964
this.$element.trigger( jQEvent, args );
Jul 8, 2016
1965
};
1966
1967
// -------------------------- select -------------------------- //
Jul 8, 2016
1968
1969
/**
1970
* @param {Integer} index - index of the slide
1971
* @param {Boolean} isWrap - will wrap-around to last/first if at the end
1972
* @param {Boolean} isInstant - will immediately set position at selected cell
1973
*/
1974
proto.select = function( index, isWrap, isInstant ) {
1975
if ( !this.isActive ) return;
Feb 3, 2015
1976
1977
index = parseInt( index, 10 );
1978
this._wrapSelect( index );
Feb 3, 2015
1979
1980
if ( this.isWrapping || isWrap ) {
1981
index = utils.modulo( index, this.slides.length );
1982
}
1983
// bail if invalid index
1984
if ( !this.slides[ index ] ) return;
Feb 3, 2015
1985
1986
let prevIndex = this.selectedIndex;
1987
this.selectedIndex = index;
1988
this.updateSelectedSlide();
1989
if ( isInstant ) {
1990
this.positionSliderAtSelected();
Feb 3, 2015
1991
} else {
1992
this.startAnimation();
1993
}
1994
if ( this.options.adaptiveHeight ) {
1995
this.setGallerySize();
1996
}
Sep 16, 2016
1997
// events
1998
this.dispatchEvent( 'select', null, [ index ] );
1999
// change event if new index
2000
if ( index !== prevIndex ) {
2001
this.dispatchEvent( 'change', null, [ index ] );
2002
}
2003
};
2004
2005
// wraps position for wrapAround, to move to closest slide. #113
2006
proto._wrapSelect = function( index ) {
2007
if ( !this.isWrapping ) return;
2008
2009
const { selectedIndex, slideableWidth, slides: { length } } = this;
2010
// shift index for wrap, do not wrap dragSelect
2011
if ( !this.isDragSelect ) {
2012
let wrapIndex = utils.modulo( index, length );
2013
// go to shortest
2014
let delta = Math.abs( wrapIndex - selectedIndex );
2015
let backWrapDelta = Math.abs( ( wrapIndex + length ) - selectedIndex );
2016
let forewardWrapDelta = Math.abs( ( wrapIndex - length ) - selectedIndex );
2017
if ( backWrapDelta < delta ) {
2018
index += length;
2019
} else if ( forewardWrapDelta < delta ) {
2020
index -= length;
2021
}
2022
}
2023
2024
// wrap position so slider is within normal area
2025
if ( index < 0 ) {
2026
this.x -= slideableWidth;
2027
} else if ( index >= length ) {
2028
this.x += slideableWidth;
2029
}
2030
};
2031
2032
proto.previous = function( isWrap, isInstant ) {
2033
this.select( this.selectedIndex - 1, isWrap, isInstant );
Feb 3, 2015
2034
};
2035
2036
proto.next = function( isWrap, isInstant ) {
2037
this.select( this.selectedIndex + 1, isWrap, isInstant );
Feb 3, 2015
2038
};
2039
2040
proto.updateSelectedSlide = function() {
2041
let slide = this.slides[ this.selectedIndex ];
2042
// selectedIndex could be outside of slides, if triggered before resize()
2043
if ( !slide ) return;
Jan 29, 2019
2044
2045
// unselect previous selected slide
2046
this.unselectSelectedSlide();
2047
// update new selected slide
2048
this.selectedSlide = slide;
2049
slide.select();
2050
this.selectedCells = slide.cells;
2051
this.selectedElements = slide.getCellElements();
2052
// HACK: selectedCell & selectedElement is first cell in slide, backwards compatibility
2053
this.selectedCell = slide.cells[0];
2054
this.selectedElement = this.selectedElements[0];
2055
};
2056
2057
proto.unselectSelectedSlide = function() {
2058
if ( this.selectedSlide ) this.selectedSlide.unselect();
Feb 3, 2015
2059
};
2060
2061
proto.selectInitialIndex = function() {
2062
let initialIndex = this.options.initialIndex;
2063
// already activated, select previous selectedIndex
2064
if ( this.isInitActivated ) {
2065
this.select( this.selectedIndex, false, true );
Feb 3, 2015
2066
return;
2067
}
2068
// select with selector string
2069
if ( initialIndex && typeof initialIndex == 'string' ) {
2070
let cell = this.queryCell( initialIndex );
2071
if ( cell ) {
2072
this.selectCell( initialIndex, false, true );
2073
return;
2074
}
2075
}
2076
2077
let index = 0;
2078
// select with number
2079
if ( initialIndex && this.slides[ initialIndex ] ) {
2080
index = initialIndex;
Feb 3, 2015
2081
}
2082
// select instantly
2083
this.select( index, false, true );
2084
};
2085
2086
/**
2087
* select slide from number or cell element
2088
* @param {[Element, Number]} value - zero-based index or element to select
2089
* @param {Boolean} isWrap - enables wrapping around for extra index
2090
* @param {Boolean} isInstant - disables slide animation
2091
*/
2092
proto.selectCell = function( value, isWrap, isInstant ) {
2093
// get cell
2094
let cell = this.queryCell( value );
2095
if ( !cell ) return;
2096
2097
let index = this.getCellSlideIndex( cell );
2098
this.select( index, isWrap, isInstant );
2099
};
2100
2101
proto.getCellSlideIndex = function( cell ) {
2102
// get index of slide that has cell
2103
let cellSlide = this.slides.find( ( slide ) => slide.cells.includes( cell ) );
2104
return this.slides.indexOf( cellSlide );
2105
};
2106
2107
// -------------------------- get cells -------------------------- //
Feb 3, 2015
2108
2109
/**
2110
* get Flickity.Cell, given an Element
2111
* @param {Element} elem - matching cell element
2112
* @returns {Flickity.Cell} cell - matching cell
2113
*/
2114
proto.getCell = function( elem ) {
2115
// loop through cells to get the one that matches
2116
for ( let cell of this.cells ) {
2117
if ( cell.element === elem ) return cell;
2118
}
Feb 3, 2015
2119
};
2120
2121
/**
2122
* get collection of Flickity.Cells, given Elements
2123
* @param {[Element, Array, NodeList]} elems - multiple elements
2124
* @returns {Array} cells - Flickity.Cells
2125
*/
2126
proto.getCells = function( elems ) {
2127
elems = utils.makeArray( elems );
2128
return elems.map( ( elem ) => this.getCell( elem ) ).filter( Boolean );
2129
};
2130
2131
/**
2132
* get cell elements
2133
* @returns {Array} cellElems
2134
*/
2135
proto.getCellElements = function() {
2136
return this.cells.map( ( cell ) => cell.element );
2137
};
2138
2139
/**
2140
* get parent cell from an element
2141
* @param {Element} elem - child element
2142
* @returns {Flickit.Cell} cell - parent cell
2143
*/
2144
proto.getParentCell = function( elem ) {
2145
// first check if elem is cell
2146
let cell = this.getCell( elem );
2147
if ( cell ) return cell;
2148
2149
// try to get parent cell elem
2150
let closest = elem.closest('.flickity-slider > *');
2151
return this.getCell( closest );
2152
};
Feb 3, 2015
2153
2154
/**
2155
* get cells adjacent to a slide
2156
* @param {Integer} adjCount - number of adjacent slides
2157
* @param {Integer} index - index of slide to start
2158
* @returns {Array} cells - array of Flickity.Cells
2159
*/
2160
proto.getAdjacentCellElements = function( adjCount, index ) {
2161
if ( !adjCount ) return this.selectedSlide.getCellElements();
2162
2163
index = index === undefined ? this.selectedIndex : index;
2164
2165
let len = this.slides.length;
2166
if ( 1 + ( adjCount * 2 ) >= len ) {
2167
return this.getCellElements(); // get all
2168
}
2169
2170
let cellElems = [];
2171
for ( let i = index - adjCount; i <= index + adjCount; i++ ) {
2172
let slideIndex = this.isWrapping ? utils.modulo( i, len ) : i;
2173
let slide = this.slides[ slideIndex ];
2174
if ( slide ) {
2175
cellElems = cellElems.concat( slide.getCellElements() );
2176
}
2177
}
2178
return cellElems;
2179
};
2180
2181
/**
2182
* select slide from number or cell element
2183
* @param {[Element, String, Number]} selector - element, selector string, or index
2184
* @returns {Flickity.Cell} - matching cell
2185
*/
2186
proto.queryCell = function( selector ) {
2187
if ( typeof selector == 'number' ) {
2188
// use number as index
2189
return this.cells[ selector ];
2190
}
2191
// do not select invalid selectors from hash: #123, #/. #791
2192
let isSelectorString = typeof selector == 'string' && !selector.match( /^[#.]?[\d/]/ );
2193
if ( isSelectorString ) {
2194
// use string as selector, get element
2195
selector = this.element.querySelector( selector );
2196
}
2197
// get cell from element
2198
return this.getCell( selector );
2199
};
2200
2201
// -------------------------- events -------------------------- //
2202
2203
proto.uiChange = function() {
2204
this.emitEvent('uiChange');
2205
};
2206
2207
// ----- resize ----- //
2208
2209
proto.onresize = function() {
2210
this.watchCSS();
2211
this.resize();
2212
};
2213
2214
utils.debounceMethod( Flickity, 'onresize', 150 );
2215
2216
proto.resize = function() {
2217
// #1177 disable resize behavior when animating or dragging for iOS 15
2218
if ( !this.isActive || this.isAnimating || this.isDragging ) return;
2219
this.getSize();
2220
// wrap values
2221
if ( this.isWrapping ) {
2222
this.x = utils.modulo( this.x, this.slideableWidth );
2223
}
2224
this.positionCells();
2225
this._updateWrapShiftCells();
2226
this.setGallerySize();
2227
this.emitEvent('resize');
2228
// update selected index for group slides, instant
2229
// TODO: position can be lost between groups of various numbers
2230
let selectedElement = this.selectedElements && this.selectedElements[0];
2231
this.selectCell( selectedElement, false, true );
2232
};
2233
2234
// watches the :after property, activates/deactivates
2235
proto.watchCSS = function() {
2236
if ( !this.options.watchCSS ) return;
2237
2238
let afterContent = getComputedStyle( this.element, ':after' ).content;
2239
// activate if :after { content: 'flickity' }
2240
if ( afterContent.includes('flickity') ) {
2241
this.activate();
2242
} else {
2243
this.deactivate();
2244
}
2245
};
2246
2247
// ----- keydown ----- //
Feb 27, 2018
2248
2249
// go previous/next if left/right keys pressed
2250
proto.onkeydown = function( event ) {
2251
let { activeElement } = document;
2252
let handler = Flickity.keyboardHandlers[ event.key ];
2253
// only work if element is in focus
2254
if ( !this.options.accessibility || !activeElement || !handler ) return;
2255
2256
let isFocused = this.focusableElems.some( ( elem ) => activeElement === elem );
2257
if ( isFocused ) handler.call( this );
2258
};
2259
2260
Flickity.keyboardHandlers = {
2261
ArrowLeft: function() {
2262
this.uiChange();
2263
let leftMethod = this.options.rightToLeft ? 'next' : 'previous';
2264
this[ leftMethod ]();
2265
},
2266
ArrowRight: function() {
2267
this.uiChange();
2268
let rightMethod = this.options.rightToLeft ? 'previous' : 'next';
2269
this[ rightMethod ]();
2270
},
2271
};
2272
2273
// ----- focus ----- //
2274
2275
proto.focus = function() {
2276
this.element.focus({ preventScroll: true });
2277
};
2278
2279
// -------------------------- destroy -------------------------- //
2280
2281
// deactivate all Flickity functionality, but keep stuff available
2282
proto.deactivate = function() {
2283
if ( !this.isActive ) return;
2284
2285
this.element.classList.remove('flickity-enabled');
2286
this.element.classList.remove('flickity-rtl');
2287
this.unselectSelectedSlide();
2288
// destroy cells
2289
this.cells.forEach( ( cell ) => cell.destroy() );
2290
this.viewport.remove();
2291
// move child elements back into element
2292
this.element.append( ...this.slider.children );
2293
if ( this.options.accessibility ) {
2294
this.element.removeAttribute('tabIndex');
2295
this.element.removeEventListener( 'keydown', this );
2296
}
2297
// set flags
2298
this.isActive = false;
2299
this.emitEvent('deactivate');
2300
};
2301
2302
proto.destroy = function() {
Feb 3, 2015
2303
this.deactivate();
2304
window.removeEventListener( 'resize', this );
Jan 29, 2019
2305
this.allOff();
2306
this.emitEvent('destroy');
2307
if ( jQuery && this.$element ) {
2308
jQuery.removeData( this.element, 'flickity' );
2309
}
2310
delete this.element.flickityGUID;
2311
delete instances[ this.guid ];
Feb 3, 2015
2312
};
2313
2314
// -------------------------- prototype -------------------------- //
Feb 3, 2015
2315
2316
Object.assign( proto, animatePrototype );
Jul 8, 2016
2317
2318
// -------------------------- extras -------------------------- //
2319
2320
/**
2321
* get Flickity instance from element
2322
* @param {[Element, String]} elem - element or selector string
2323
* @returns {Flickity} - Flickity instance
2324
*/
2325
Flickity.data = function( elem ) {
2326
elem = utils.getQueryElement( elem );
2327
if ( elem ) return instances[ elem.flickityGUID ];
Feb 3, 2015
2328
};
2329
2330
utils.htmlInit( Flickity, 'flickity' );
Jul 8, 2016
2331
2332
let { jQueryBridget } = window;
2333
if ( jQuery && jQueryBridget ) {
2334
jQueryBridget( 'flickity', Flickity, jQuery );
2335
}
Feb 3, 2015
2336
2337
// set internal jQuery, for Webpack + jQuery v3, #478
2338
Flickity.setJQuery = function( jq ) {
2339
jQuery = jq;
2340
};
2341
2342
Flickity.Cell = Cell;
2343
Flickity.Slide = Slide;
Feb 3, 2015
2344
Jun 18, 2015
2345
return Flickity;
2346
2348
// drag
2349
( function( window, factory ) {
2350
// universal module definition
2351
if ( typeof module == 'object' && module.exports ) {
2352
// CommonJS
2353
module.exports = factory(
2354
window,
2355
require('./core'),
2356
require('unidragger'),
2357
require('fizzy-ui-utils'),
2358
);
2359
} else {
2360
// browser global
2361
window.Flickity = factory(
2362
window,
2363
window.Flickity,
2364
window.Unidragger,
2365
window.fizzyUIUtils,
2366
);
2367
}
2368
2369
}( typeof window != 'undefined' ? window : this,
2370
function factory( window, Flickity, Unidragger, utils ) {
2371
2372
// ----- defaults ----- //
2373
2374
Object.assign( Flickity.defaults, {
2375
draggable: '>1',
2376
dragThreshold: 3,
2377
} );
2378
2379
// -------------------------- drag prototype -------------------------- //
2380
2381
let proto = Flickity.prototype;
2382
Object.assign( proto, Unidragger.prototype ); // inherit Unidragger
2383
proto.touchActionValue = '';
2384
2385
// -------------------------- -------------------------- //
2386
2387
Flickity.create.drag = function() {
2388
this.on( 'activate', this.onActivateDrag );
2389
this.on( 'uiChange', this._uiChangeDrag );
2390
this.on( 'deactivate', this.onDeactivateDrag );
2391
this.on( 'cellChange', this.updateDraggable );
2392
this.on( 'pointerDown', this.handlePointerDown );
2393
this.on( 'pointerUp', this.handlePointerUp );
2394
this.on( 'pointerDown', this.handlePointerDone );
2395
this.on( 'dragStart', this.handleDragStart );
2396
this.on( 'dragMove', this.handleDragMove );
2397
this.on( 'dragEnd', this.handleDragEnd );
2398
this.on( 'staticClick', this.handleStaticClick );
2399
// TODO updateDraggable on resize? if groupCells & slides change
2400
};
2401
2402
proto.onActivateDrag = function() {
2403
this.handles = [ this.viewport ];
2404
this.bindHandles();
2405
this.updateDraggable();
2406
};
2407
2408
proto.onDeactivateDrag = function() {
2409
this.unbindHandles();
2410
this.element.classList.remove('is-draggable');
2411
};
2412
2413
proto.updateDraggable = function() {
2414
// disable dragging if less than 2 slides. #278
2415
if ( this.options.draggable === '>1' ) {
2416
this.isDraggable = this.slides.length > 1;
2417
} else {
2418
this.isDraggable = this.options.draggable;
Jul 8, 2016
2419
}
2420
this.element.classList.toggle( 'is-draggable', this.isDraggable );
2421
};
Jul 8, 2016
2422
2423
proto._uiChangeDrag = function() {
2424
delete this.isFreeScrolling;
2425
};
2426
2427
// -------------------------- pointer events -------------------------- //
2428
2429
proto.handlePointerDown = function( event ) {
2430
if ( !this.isDraggable ) {
2431
// proceed for staticClick
2432
this.bindActivePointerEvents( event );
Feb 3, 2015
2433
return;
2434
}
Mar 24, 2016
2435
2436
let isTouchStart = event.type === 'touchstart';
2437
let isTouchPointer = event.pointerType === 'touch';
2438
let isFocusNode = event.target.matches('input, textarea, select');
2439
if ( !isTouchStart && !isTouchPointer && !isFocusNode ) event.preventDefault();
2440
if ( !isFocusNode ) this.focus();
2441
// blur
2442
if ( document.activeElement !== this.element ) document.activeElement.blur();
2443
// stop if it was moving
2444
this.dragX = this.x;
2445
this.viewport.classList.add('is-pointer-down');
2446
// track scrolling
2447
this.pointerDownScroll = getScrollPosition();
2448
window.addEventListener( 'scroll', this );
2449
this.bindActivePointerEvents( event );
2450
};
2451
2452
// ----- move ----- //
2453
2454
proto.hasDragStarted = function( moveVector ) {
2455
return Math.abs( moveVector.x ) > this.options.dragThreshold;
2456
};
2457
2458
// ----- up ----- //
2459
2460
proto.handlePointerUp = function() {
2461
delete this.isTouchScrolling;
2462
this.viewport.classList.remove('is-pointer-down');
2463
};
2464
2465
proto.handlePointerDone = function() {
2466
window.removeEventListener( 'scroll', this );
2467
delete this.pointerDownScroll;
Jul 8, 2016
2468
};
2469
2470
// -------------------------- dragging -------------------------- //
2471
2472
proto.handleDragStart = function() {
2473
if ( !this.isDraggable ) return;
2474
2475
this.dragStartPosition = this.x;
2476
this.startAnimation();
2477
window.removeEventListener( 'scroll', this );
2478
};
2479
2480
proto.handleDragMove = function( event, pointer, moveVector ) {
2481
if ( !this.isDraggable ) return;
2482
2483
event.preventDefault();
2484
2485
this.previousDragX = this.dragX;
2486
// reverse if right-to-left
2487
let direction = this.options.rightToLeft ? -1 : 1;
2488
// wrap around move. #589
2489
if ( this.isWrapping ) moveVector.x %= this.slideableWidth;
2490
let dragX = this.dragStartPosition + moveVector.x * direction;
2491
2492
if ( !this.isWrapping ) {
2493
// slow drag
2494
let originBound = Math.max( -this.slides[0].target, this.dragStartPosition );
2495
dragX = dragX > originBound ? ( dragX + originBound ) * 0.5 : dragX;
2496
let endBound = Math.min( -this.getLastSlide().target, this.dragStartPosition );
2497
dragX = dragX < endBound ? ( dragX + endBound ) * 0.5 : dragX;
2498
}
Feb 3, 2015
2499
2500
this.dragX = dragX;
2501
this.dragMoveTime = new Date();
Feb 3, 2015
2502
};
2503
2504
proto.handleDragEnd = function() {
2505
if ( !this.isDraggable ) return;
2506
2507
let { freeScroll } = this.options;
2508
if ( freeScroll ) this.isFreeScrolling = true;
2509
// set selectedIndex based on where flick will end up
2510
let index = this.dragEndRestingSelect();
2511
2512
if ( freeScroll && !this.isWrapping ) {
2513
// if free-scroll & not wrap around
2514
// do not free-scroll if going outside of bounding slides
2515
// so bounding slides can attract slider, and keep it in bounds
2516
let restingX = this.getRestingPosition();
2517
this.isFreeScrolling = -restingX > this.slides[0].target &&
2518
-restingX < this.getLastSlide().target;
2519
} else if ( !freeScroll && index === this.selectedIndex ) {
2520
// boost selection if selected index has not changed
2521
index += this.dragEndBoostSelect();
Feb 3, 2015
2522
}
2523
delete this.previousDragX;
2524
// apply selection
2525
// HACK, set flag so dragging stays in correct direction
2526
this.isDragSelect = this.isWrapping;
2527
this.select( index );
2528
delete this.isDragSelect;
2529
};
2530
2531
proto.dragEndRestingSelect = function() {
2532
let restingX = this.getRestingPosition();
2533
// how far away from selected slide
2534
let distance = Math.abs( this.getSlideDistance( -restingX, this.selectedIndex ) );
2535
// get closet resting going up and going down
2536
let positiveResting = this._getClosestResting( restingX, distance, 1 );
2537
let negativeResting = this._getClosestResting( restingX, distance, -1 );
2538
// use closer resting for wrap-around
2539
return positiveResting.distance < negativeResting.distance ?
2540
positiveResting.index : negativeResting.index;
Mar 18, 2016
2541
};
2542
2543
/**
2544
* given resting X and distance to selected cell
2545
* get the distance and index of the closest cell
2546
* @param {Number} restingX - estimated post-flick resting position
2547
* @param {Number} distance - distance to selected cell
2548
* @param {Integer} increment - +1 or -1, going up or down
2549
* @returns {Object} - { distance: {Number}, index: {Integer} }
2550
*/
2551
proto._getClosestResting = function( restingX, distance, increment ) {
2552
let index = this.selectedIndex;
2553
let minDistance = Infinity;
2554
let condition = this.options.contain && !this.isWrapping ?
2555
// if containing, keep going if distance is equal to minDistance
2556
( dist, minDist ) => dist <= minDist :
2557
( dist, minDist ) => dist < minDist;
2558
2559
while ( condition( distance, minDistance ) ) {
2560
// measure distance to next cell
2561
index += increment;
2562
minDistance = distance;
2563
distance = this.getSlideDistance( -restingX, index );
2564
if ( distance === null ) break;
2565
2566
distance = Math.abs( distance );
2567
}
2568
return {
2569
distance: minDistance,
2570
// selected was previous index
2571
index: index - increment,
2572
};
2573
};
2574
2575
/**
2576
* measure distance between x and a slide target
2577
* @param {Number} x - horizontal position
2578
* @param {Integer} index - slide index
2579
* @returns {Number} - slide distance
2580
*/
2581
proto.getSlideDistance = function( x, index ) {
2582
let len = this.slides.length;
2583
// wrap around if at least 2 slides
2584
let isWrapAround = this.options.wrapAround && len > 1;
2585
let slideIndex = isWrapAround ? utils.modulo( index, len ) : index;
2586
let slide = this.slides[ slideIndex ];
2587
if ( !slide ) return null;
2588
2589
// add distance for wrap-around slides
2590
let wrap = isWrapAround ? this.slideableWidth * Math.floor( index/len ) : 0;
2591
return x - ( slide.target + wrap );
Mar 18, 2016
2592
};
2593
2594
proto.dragEndBoostSelect = function() {
2595
// do not boost if no previousDragX or dragMoveTime
2596
if ( this.previousDragX === undefined || !this.dragMoveTime ||
2597
// or if drag was held for 100 ms
2598
new Date() - this.dragMoveTime > 100 ) {
2599
return 0;
2600
}
Mar 18, 2016
2601
2602
let distance = this.getSlideDistance( -this.dragX, this.selectedIndex );
2603
let delta = this.previousDragX - this.dragX;
2604
if ( distance > 0 && delta > 0 ) {
2605
// boost to next if moving towards the right, and positive velocity
2606
return 1;
2607
} else if ( distance < 0 && delta < 0 ) {
2608
// boost to previous if moving towards the left, and negative velocity
2609
return -1;
2610
}
2611
return 0;
2612
};
2613
2614
// ----- scroll ----- //
2615
2616
proto.onscroll = function() {
2617
let scroll = getScrollPosition();
2618
let scrollMoveX = this.pointerDownScroll.x - scroll.x;
2619
let scrollMoveY = this.pointerDownScroll.y - scroll.y;
2620
// cancel click/tap if scroll is too much
2621
if ( Math.abs( scrollMoveX ) > 3 || Math.abs( scrollMoveY ) > 3 ) {
2622
this.pointerDone();
Dec 16, 2015
2623
}
2624
};
2625
2626
// ----- utils ----- //
2627
2628
function getScrollPosition() {
2629
return {
2630
x: window.pageXOffset,
2631
y: window.pageYOffset,
2632
};
2633
}
Feb 3, 2015
2634
2635
// ----- ----- //
Feb 3, 2015
2636
Jun 18, 2015
2637
return Flickity;
2638
2640
// prev/next buttons
2641
( function( window, factory ) {
2642
// universal module definition
2643
if ( typeof module == 'object' && module.exports ) {
2644
// CommonJS
2645
module.exports = factory( require('./core') );
2646
} else {
2647
// browser global
2648
factory( window.Flickity );
2649
}
2650
2651
}( typeof window != 'undefined' ? window : this, function factory( Flickity ) {
2652
2653
const svgURI = 'http://www.w3.org/2000/svg';
2654
2655
// -------------------------- PrevNextButton -------------------------- //
2656
2657
function PrevNextButton( increment, direction, arrowShape ) {
2658
this.increment = increment;
2659
this.direction = direction;
2660
this.isPrevious = increment === 'previous';
2661
this.isLeft = direction === 'left';
2662
this._create( arrowShape );
2663
}
2664
2665
PrevNextButton.prototype._create = function( arrowShape ) {
2666
// properties
2667
let element = this.element = document.createElement('button');
2668
element.className = `flickity-button flickity-prev-next-button ${this.increment}`;
2669
let label = this.isPrevious ? 'Previous' : 'Next';
2670
// prevent button from submitting form https://stackoverflow.com/a/10836076/182183
2671
element.setAttribute( 'type', 'button' );
2672
element.setAttribute( 'aria-label', label );
2673
// init as disabled
2674
this.disable();
2675
// create arrow
2676
let svg = this.createSVG( label, arrowShape );
2677
element.append( svg );
2678
};
Jul 8, 2016
2679
2680
PrevNextButton.prototype.createSVG = function( label, arrowShape ) {
2681
let svg = document.createElementNS( svgURI, 'svg' );
2682
svg.setAttribute( 'class', 'flickity-button-icon' );
2683
svg.setAttribute( 'viewBox', '0 0 100 100' );
2684
// add title #1189
2685
let title = document.createElementNS( svgURI, 'title' );
2686
title.append( label );
2687
// add path
2688
let path = document.createElementNS( svgURI, 'path' );
2689
let pathMovements = getArrowMovements( arrowShape );
2690
path.setAttribute( 'd', pathMovements );
2691
path.setAttribute( 'class', 'arrow' );
2692
// rotate arrow
2693
if ( !this.isLeft ) {
2694
path.setAttribute( 'transform', 'translate(100, 100) rotate(180)' );
2695
}
2696
svg.append( title, path );
2697
return svg;
2698
};
2699
2700
// get SVG path movmement
2701
function getArrowMovements( shape ) {
2702
// use shape as movement if string
2703
if ( typeof shape == 'string' ) return shape;
2704
2705
let { x0, x1, x2, x3, y1, y2 } = shape;
2706
2707
// create movement string
2708
return `M ${x0}, 50
2709
L ${x1}, ${y1 + 50}
2710
L ${x2}, ${y2 + 50}
2711
L ${x3}, 50
2712
L ${x2}, ${50 - y2}
2713
L ${x1}, ${50 - y1}
2714
Z`;
2715
}
2716
2717
// ----- ----- //
2718
2719
PrevNextButton.prototype.enable = function() {
2720
this.element.removeAttribute('disabled');
2721
};
2722
2723
PrevNextButton.prototype.disable = function() {
2724
this.element.setAttribute( 'disabled', true );
2725
};
2726
2727
// -------------------------- Flickity prototype -------------------------- //
2728
2729
Object.assign( Flickity.defaults, {
2730
prevNextButtons: true,
2731
arrowShape: {
2732
x0: 10,
2733
x1: 60, y1: 50,
2734
x2: 70, y2: 40,
2735
x3: 30,
2736
},
2737
} );
2738
2739
Flickity.create.prevNextButtons = function() {
2740
if ( !this.options.prevNextButtons ) return;
2741
2742
let { rightToLeft, arrowShape } = this.options;
2743
let prevDirection = rightToLeft ? 'right' : 'left';
2744
let nextDirection = rightToLeft ? 'left' : 'right';
2745
this.prevButton = new PrevNextButton( 'previous', prevDirection, arrowShape );
2746
this.nextButton = new PrevNextButton( 'next', nextDirection, arrowShape );
2747
this.focusableElems.push( this.prevButton.element );
2748
this.focusableElems.push( this.nextButton.element );
2749
2750
this.handlePrevButtonClick = () => {
2751
this.uiChange();
2752
this.previous();
2753
};
2754
2755
this.handleNextButtonClick = () => {
2756
this.uiChange();
2757
this.next();
2758
};
2759
2760
this.on( 'activate', this.activatePrevNextButtons );
2761
this.on( 'select', this.updatePrevNextButtons );
2762
};
2763
2764
let proto = Flickity.prototype;
2765
2766
proto.updatePrevNextButtons = function() {
2767
let lastIndex = this.slides.length ? this.slides.length - 1 : 0;
2768
this.updatePrevNextButton( this.prevButton, 0 );
2769
this.updatePrevNextButton( this.nextButton, lastIndex );
2770
};
2771
2772
proto.updatePrevNextButton = function( button, disabledIndex ) {
2773
// enable is wrapAround and at least 2 slides
2774
if ( this.isWrapping && this.slides.length > 1 ) {
2775
button.enable();
2776
return;
2777
}
2778
2779
let isEnabled = this.selectedIndex !== disabledIndex;
2780
button[ isEnabled ? 'enable' : 'disable' ]();
2781
// if disabling button that is focused,
2782
// shift focus to element to maintain keyboard accessibility
2783
let isDisabledFocused = !isEnabled && document.activeElement === button.element;
2784
if ( isDisabledFocused ) this.focus();
2785
};
2786
2787
proto.activatePrevNextButtons = function() {
2788
this.prevButton.element.addEventListener( 'click', this.handlePrevButtonClick );
2789
this.nextButton.element.addEventListener( 'click', this.handleNextButtonClick );
2790
this.element.append( this.prevButton.element, this.nextButton.element );
2791
this.on( 'deactivate', this.deactivatePrevNextButtons );
2792
};
Mar 16, 2018
2793
2794
proto.deactivatePrevNextButtons = function() {
2795
this.prevButton.element.remove();
2796
this.nextButton.element.remove();
2797
this.prevButton.element.removeEventListener( 'click', this.handlePrevButtonClick );
2798
this.nextButton.element.removeEventListener( 'click', this.handleNextButtonClick );
2799
this.off( 'deactivate', this.deactivatePrevNextButtons );
2800
};
2801
2802
// -------------------------- -------------------------- //
2803
2804
Flickity.PrevNextButton = PrevNextButton;
2805
Feb 3, 2015
2806
return Flickity;
2807
2809
// page dots
Jun 18, 2015
2810
( function( window, factory ) {
2811
// universal module definition
2812
if ( typeof module == 'object' && module.exports ) {
Jun 18, 2015
2813
// CommonJS
2814
module.exports = factory(
2815
require('./core'),
2816
require('fizzy-ui-utils'),
Jun 18, 2015
2817
);
2818
} else {
2819
// browser global
2820
factory(
2821
window.Flickity,
2822
window.fizzyUIUtils,
Jun 18, 2015
2823
);
2824
}
2825
2826
}( typeof window != 'undefined' ? window : this, function factory( Flickity, utils ) {
Jun 18, 2015
2827
2828
// -------------------------- PageDots -------------------------- //
Jun 18, 2015
2829
2830
function PageDots() {
2831
// create holder element
2832
this.holder = document.createElement('div');
2833
this.holder.className = 'flickity-page-dots';
2834
// create dots, array of elements
2835
this.dots = [];
2836
}
Jun 18, 2015
2837
2838
PageDots.prototype.setDots = function( slidesLength ) {
2839
// get difference between number of slides and number of dots
2840
let delta = slidesLength - this.dots.length;
2841
if ( delta > 0 ) {
2842
this.addDots( delta );
2843
} else if ( delta < 0 ) {
2844
this.removeDots( -delta );
Jun 18, 2015
2845
}
2846
};
2847
2848
PageDots.prototype.addDots = function( count ) {
2849
let newDots = new Array( count ).fill()
2850
.map( ( item, i ) => {
2851
let dot = document.createElement('button');
2852
dot.setAttribute( 'type', 'button' );
2853
let num = i + 1 + this.dots.length;
2854
dot.className = 'flickity-page-dot';
2855
dot.textContent = `View slide ${num}`;
2856
return dot;
2857
} );
2858
2859
this.holder.append( ...newDots );
2860
this.dots = this.dots.concat( newDots );
2861
};
2862
2863
PageDots.prototype.removeDots = function( count ) {
2864
// remove from this.dots collection
2865
let removeDots = this.dots.splice( this.dots.length - count, count );
2866
// remove from DOM
2867
removeDots.forEach( ( dot ) => dot.remove() );
2868
};
2869
2870
PageDots.prototype.updateSelected = function( index ) {
2871
// remove selected class on previous
2872
if ( this.selectedDot ) {
2873
this.selectedDot.classList.remove('is-selected');
2874
this.selectedDot.removeAttribute('aria-current');
Jun 18, 2015
2875
}
2876
// don't proceed if no dots
2877
if ( !this.dots.length ) return;
Jun 18, 2015
2878
2879
this.selectedDot = this.dots[ index ];
2880
this.selectedDot.classList.add('is-selected');
2881
this.selectedDot.setAttribute( 'aria-current', 'step' );
2882
};
Jun 18, 2015
2883
2884
Flickity.PageDots = PageDots;
2885
2886
// -------------------------- Flickity -------------------------- //
2887
2888
Object.assign( Flickity.defaults, {
2889
pageDots: true,
2890
} );
2891
2892
Flickity.create.pageDots = function() {
2893
if ( !this.options.pageDots ) return;
2894
2895
this.pageDots = new PageDots();
2896
this.handlePageDotsClick = this.onPageDotsClick.bind( this );
2897
// events
2898
this.on( 'activate', this.activatePageDots );
2899
this.on( 'select', this.updateSelectedPageDots );
2900
this.on( 'cellChange', this.updatePageDots );
2901
this.on( 'resize', this.updatePageDots );
2902
this.on( 'deactivate', this.deactivatePageDots );
2903
};
Jun 18, 2015
2904
2905
let proto = Flickity.prototype;
Jun 18, 2015
2906
2907
proto.activatePageDots = function() {
2908
this.pageDots.setDots( this.slides.length );
2909
this.focusableElems.push( ...this.pageDots.dots );
2910
this.pageDots.holder.addEventListener( 'click', this.handlePageDotsClick );
2911
this.element.append( this.pageDots.holder );
Jun 18, 2015
2912
};
2913
2914
proto.onPageDotsClick = function( event ) {
2915
let index = this.pageDots.dots.indexOf( event.target );
2916
if ( index === -1 ) return; // only dot clicks
Jun 18, 2015
2917
2918
this.uiChange();
2919
this.select( index );
Jun 18, 2015
2920
};
2921
2922
proto.updateSelectedPageDots = function() {
2923
this.pageDots.updateSelected( this.selectedIndex );
2924
};
Jun 18, 2015
2925
2926
proto.updatePageDots = function() {
2927
this.pageDots.dots.forEach( ( dot ) => {
2928
utils.removeFrom( this.focusableElems, dot );
2929
} );
2930
this.pageDots.setDots( this.slides.length );
2931
this.focusableElems.push( ...this.pageDots.dots );
2932
};
Jun 18, 2015
2933
2934
proto.deactivatePageDots = function() {
2935
this.pageDots.holder.remove();
2936
this.pageDots.holder.removeEventListener( 'click', this.handlePageDotsClick );
Jun 18, 2015
2937
};
2938
2939
// ----- ----- //
2940
2941
Flickity.PageDots = PageDots;
Jun 18, 2015
2942
2943
return Flickity;
2944
2946
// player & autoPlay
Feb 3, 2015
2947
( function( window, factory ) {
2948
// universal module definition
2949
if ( typeof module == 'object' && module.exports ) {
Feb 3, 2015
2950
// CommonJS
2951
module.exports = factory( require('./core') );
2952
} else {
Feb 3, 2015
2953
// browser global
2954
factory( window.Flickity );
2955
}
2956
2957
}( typeof window != 'undefined' ? window : this, function factory( Flickity ) {
2958
2959
// -------------------------- Player -------------------------- //
2960
2961
function Player( autoPlay, onTick ) {
2962
this.autoPlay = autoPlay;
2963
this.onTick = onTick;
2964
this.state = 'stopped';
2965
// visibility change event handler
2966
this.onVisibilityChange = this.visibilityChange.bind( this );
2967
this.onVisibilityPlay = this.visibilityPlay.bind( this );
2968
}
2969
2970
// start play
2971
Player.prototype.play = function() {
2972
if ( this.state === 'playing' ) return;
2973
2974
// do not play if page is hidden, start playing when page is visible
2975
let isPageHidden = document.hidden;
2976
if ( isPageHidden ) {
2977
document.addEventListener( 'visibilitychange', this.onVisibilityPlay );
Feb 3, 2015
2978
return;
2979
}
2980
2981
this.state = 'playing';
2982
// listen to visibility change
2983
document.addEventListener( 'visibilitychange', this.onVisibilityChange );
2984
// start ticking
2985
this.tick();
2986
};
2987
2988
Player.prototype.tick = function() {
2989
// do not tick if not playing
2990
if ( this.state !== 'playing' ) return;
Aug 16, 2016
2991
2992
// default to 3 seconds
2993
let time = typeof this.autoPlay == 'number' ? this.autoPlay : 3000;
2994
// HACK: reset ticks if stopped and started within interval
2995
this.clear();
2996
this.timeout = setTimeout( () => {
2997
this.onTick();
2998
this.tick();
2999
}, time );
Jul 8, 2016
3000
};
3001
3002
Player.prototype.stop = function() {
3003
this.state = 'stopped';
3004
this.clear();
3005
// remove visibility change event
3006
document.removeEventListener( 'visibilitychange', this.onVisibilityChange );
3007
};
3008
3009
Player.prototype.clear = function() {
3010
clearTimeout( this.timeout );
3011
};
3012
3013
Player.prototype.pause = function() {
3014
if ( this.state === 'playing' ) {
3015
this.state = 'paused';
3016
this.clear();
3017
}
Feb 9, 2015
3018
};
3019
3020
Player.prototype.unpause = function() {
3021
// re-start play if paused
3022
if ( this.state === 'paused' ) this.play();
Feb 9, 2015
3023
};
3024
3025
// pause if page visibility is hidden, unpause if visible
3026
Player.prototype.visibilityChange = function() {
3027
let isPageHidden = document.hidden;
3028
this[ isPageHidden ? 'pause' : 'unpause' ]();
Feb 9, 2015
3029
};
3030
3031
Player.prototype.visibilityPlay = function() {
3032
this.play();
3033
document.removeEventListener( 'visibilitychange', this.onVisibilityPlay );
3034
};
3035
3036
// -------------------------- Flickity -------------------------- //
Feb 3, 2015
3037
3038
Object.assign( Flickity.defaults, {
3039
pauseAutoPlayOnHover: true,
3040
} );
Feb 3, 2015
3041
3042
Flickity.create.player = function() {
3043
this.player = new Player( this.options.autoPlay, () => {
3044
this.next( true );
3045
} );
Feb 3, 2015
3046
3047
this.on( 'activate', this.activatePlayer );
3048
this.on( 'uiChange', this.stopPlayer );
3049
this.on( 'pointerDown', this.stopPlayer );
3050
this.on( 'deactivate', this.deactivatePlayer );
3051
};
Feb 3, 2015
3052
3053
let proto = Flickity.prototype;
Feb 3, 2015
3054
3055
proto.activatePlayer = function() {
3056
if ( !this.options.autoPlay ) return;
3057
3058
this.player.play();
3059
this.element.addEventListener( 'mouseenter', this );
3060
};
3061
3062
// Player API, don't hate the ... thanks I know where the door is
Feb 3, 2015
3063
3064
proto.playPlayer = function() {
3065
this.player.play();
3066
};
Feb 3, 2015
3067
3068
proto.stopPlayer = function() {
3069
this.player.stop();
3070
};
Feb 3, 2015
3071
3072
proto.pausePlayer = function() {
3073
this.player.pause();
3074
};
Feb 3, 2015
3075
3076
proto.unpausePlayer = function() {
3077
this.player.unpause();
3078
};
Feb 3, 2015
3079
3080
proto.deactivatePlayer = function() {
3081
this.player.stop();
3082
this.element.removeEventListener( 'mouseenter', this );
3083
};
Feb 3, 2015
3084
3085
// ----- mouseenter/leave ----- //
Feb 3, 2015
3086
3087
// pause auto-play on hover
3088
proto.onmouseenter = function() {
3089
if ( !this.options.pauseAutoPlayOnHover ) return;
Feb 27, 2018
3090
3091
this.player.pause();
3092
this.element.addEventListener( 'mouseleave', this );
3093
};
Feb 27, 2018
3094
3095
// resume auto-play on hover off
3096
proto.onmouseleave = function() {
3097
this.player.unpause();
3098
this.element.removeEventListener( 'mouseleave', this );
3099
};
Feb 27, 2018
3100
3101
// ----- ----- //
3102
3103
Flickity.Player = Player;
3104
3105
return Flickity;
Feb 3, 2015
3106
3107
} ) );
3108
// add, remove cell
3109
( function( window, factory ) {
3110
// universal module definition
3111
if ( typeof module == 'object' && module.exports ) {
3112
// CommonJS
3113
module.exports = factory(
3114
require('./core'),
3115
require('fizzy-ui-utils'),
3116
);
Jul 8, 2016
3117
} else {
3118
// browser global
3119
factory(
3120
window.Flickity,
3121
window.fizzyUIUtils,
3122
);
Jul 8, 2016
3123
}
Feb 3, 2015
3124
3125
}( typeof window != 'undefined' ? window : this, function factory( Flickity, utils ) {
3126
3127
// append cells to a document fragment
3128
function getCellsFragment( cells ) {
3129
let fragment = document.createDocumentFragment();
3130
cells.forEach( ( cell ) => fragment.appendChild( cell.element ) );
3131
return fragment;
Jul 8, 2016
3132
}
3133
3134
// -------------------------- add/remove cell prototype -------------------------- //
Dec 16, 2015
3135
3136
let proto = Flickity.prototype;
Dec 16, 2015
3137
Jul 8, 2016
3138
/**
3139
* Insert, prepend, or append cells
3140
* @param {[Element, Array, NodeList]} elems - Elements to insert
3141
* @param {Integer} index - Zero-based number to insert
Jul 8, 2016
3142
*/
3143
proto.insert = function( elems, index ) {
3144
let cells = this._makeCells( elems );
3145
if ( !cells || !cells.length ) return;
Dec 16, 2015
3146
3147
let len = this.cells.length;
3148
// default to append
3149
index = index === undefined ? len : index;
3150
// add cells with document fragment
3151
let fragment = getCellsFragment( cells );
3152
// append to slider
3153
let isAppend = index === len;
3154
if ( isAppend ) {
3155
this.slider.appendChild( fragment );
3156
} else {
3157
let insertCellElement = this.cells[ index ].element;
3158
this.slider.insertBefore( fragment, insertCellElement );
Jul 8, 2016
3159
}
3160
// add to this.cells
3161
if ( index === 0 ) {
3162
// prepend, add to start
3163
this.cells = cells.concat( this.cells );
3164
} else if ( isAppend ) {
3165
// append, add to end
3166
this.cells = this.cells.concat( cells );
3167
} else {
3168
// insert in this.cells
3169
let endCells = this.cells.splice( index, len - index );
3170
this.cells = this.cells.concat( cells ).concat( endCells );
Jul 8, 2016
3171
}
3172
3173
this._sizeCells( cells );
3174
this.cellChange( index );
3175
this.positionSliderAtSelected();
Jul 8, 2016
3176
};
Dec 16, 2015
3177
3178
proto.append = function( elems ) {
3179
this.insert( elems, this.cells.length );
Jul 8, 2016
3180
};
Dec 16, 2015
3181
3182
proto.prepend = function( elems ) {
3183
this.insert( elems, 0 );
Jul 8, 2016
3184
};
Feb 3, 2015
3185
Jul 8, 2016
3186
/**
3187
* Remove cells
3188
* @param {[Element, Array, NodeList]} elems - ELements to remove
Jul 8, 2016
3189
*/
3190
proto.remove = function( elems ) {
3191
let cells = this.getCells( elems );
3192
if ( !cells || !cells.length ) return;
Dec 16, 2015
3193
3194
let minCellIndex = this.cells.length - 1;
3195
// remove cells from collection & DOM
3196
cells.forEach( ( cell ) => {
3197
cell.remove();
3198
let index = this.cells.indexOf( cell );
3199
minCellIndex = Math.min( index, minCellIndex );
3200
utils.removeFrom( this.cells, cell );
3201
} );
3202
3203
this.cellChange( minCellIndex );
3204
this.positionSliderAtSelected();
Jul 8, 2016
3205
};
Feb 3, 2015
3206
3207
/**
3208
* logic to be run after a cell's size changes
3209
* @param {Element} elem - cell's element
3210
*/
3211
proto.cellSizeChange = function( elem ) {
3212
let cell = this.getCell( elem );
3213
if ( !cell ) return;
Dec 16, 2015
3214
3215
cell.getSize();
Feb 3, 2015
3216
3217
let index = this.cells.indexOf( cell );
3218
this.cellChange( index );
3219
// do not position slider after lazy load
Jul 8, 2016
3220
};
Feb 3, 2015
3221
3222
/**
3223
* logic any time a cell is changed: added, removed, or size changed
3224
* @param {Integer} changedCellIndex - index of the changed cell, optional
3225
*/
3226
proto.cellChange = function( changedCellIndex ) {
3227
let prevSelectedElem = this.selectedElement;
3228
this._positionCells( changedCellIndex );
3229
this._updateWrapShiftCells();
3230
this.setGallerySize();
3231
// update selectedIndex, try to maintain position & select previous selected element
3232
let cell = this.getCell( prevSelectedElem );
3233
if ( cell ) this.selectedIndex = this.getCellSlideIndex( cell );
3234
this.selectedIndex = Math.min( this.slides.length - 1, this.selectedIndex );
3235
3236
this.emitEvent( 'cellChange', [ changedCellIndex ] );
3237
// position slider
3238
this.select( this.selectedIndex );
Jul 8, 2016
3239
};
Dec 16, 2015
3240
3241
// ----- ----- //
3242
3243
return Flickity;
3244
3245
} ) );
3246
// lazyload
3247
( function( window, factory ) {
3248
// universal module definition
3249
if ( typeof module == 'object' && module.exports ) {
3250
// CommonJS
3251
module.exports = factory(
3252
require('./core'),
3253
require('fizzy-ui-utils'),
3254
);
3255
} else {
3256
// browser global
3257
factory(
3258
window.Flickity,
3259
window.fizzyUIUtils,
3260
);
3261
}
3262
3263
}( typeof window != 'undefined' ? window : this, function factory( Flickity, utils ) {
3264
3265
const lazyAttr = 'data-flickity-lazyload';
3266
const lazySrcAttr = `${lazyAttr}-src`;
3267
const lazySrcsetAttr = `${lazyAttr}-srcset`;
3268
const imgSelector = `img[${lazyAttr}], img[${lazySrcAttr}], ` +
3269
`img[${lazySrcsetAttr}], source[${lazySrcsetAttr}]`;
3270
3271
Flickity.create.lazyLoad = function() {
3272
this.on( 'select', this.lazyLoad );
3273
3274
this.handleLazyLoadComplete = this.onLazyLoadComplete.bind( this );
Jul 8, 2016
3275
};
3276
3277
let proto = Flickity.prototype;
3278
3279
proto.lazyLoad = function() {
3280
let { lazyLoad } = this.options;
3281
if ( !lazyLoad ) return;
3282
3283
// get adjacent cells, use lazyLoad option for adjacent count
3284
let adjCount = typeof lazyLoad == 'number' ? lazyLoad : 0;
3285
// lazy load images
3286
this.getAdjacentCellElements( adjCount )
3287
.map( getCellLazyImages )
3288
.flat()
3289
.forEach( ( img ) => new LazyLoader( img, this.handleLazyLoadComplete ) );
Jul 8, 2016
3290
};
3291
3292
function getCellLazyImages( cellElem ) {
3293
// check if cell element is lazy image
3294
if ( cellElem.matches('img') ) {
3295
let cellAttr = cellElem.getAttribute( lazyAttr );
3296
let cellSrcAttr = cellElem.getAttribute( lazySrcAttr );
3297
let cellSrcsetAttr = cellElem.getAttribute( lazySrcsetAttr );
3298
if ( cellAttr || cellSrcAttr || cellSrcsetAttr ) {
3299
return cellElem;
3300
}
3301
}
3302
// select lazy images in cell
3303
return [ ...cellElem.querySelectorAll( imgSelector ) ];
3304
}
3305
3306
proto.onLazyLoadComplete = function( img, event ) {
3307
let cell = this.getParentCell( img );
3308
let cellElem = cell && cell.element;
3309
this.cellSizeChange( cellElem );
3310
3311
this.dispatchEvent( 'lazyLoad', event, cellElem );
Jul 8, 2016
3312
};
Dec 16, 2015
3313
3314
// -------------------------- LazyLoader -------------------------- //
3315
3316
/**
3317
* class to handle loading images
3318
* @param {Image} img - Image element
3319
* @param {Function} onComplete - callback function
3320
*/
3321
function LazyLoader( img, onComplete ) {
3322
this.img = img;
3323
this.onComplete = onComplete;
3324
this.load();
Jul 8, 2016
3325
}
3326
3327
LazyLoader.prototype.handleEvent = utils.handleEvent;
Jul 8, 2016
3328
3329
LazyLoader.prototype.load = function() {
Jul 8, 2016
3330
this.img.addEventListener( 'load', this );
3331
this.img.addEventListener( 'error', this );
3332
// get src & srcset
3333
let src = this.img.getAttribute( lazyAttr ) ||
3334
this.img.getAttribute( lazySrcAttr );
3335
let srcset = this.img.getAttribute( lazySrcsetAttr );
3336
// set src & serset
3337
this.img.src = src;
3338
if ( srcset ) this.img.setAttribute( 'srcset', srcset );
3339
// remove attr
3340
this.img.removeAttribute( lazyAttr );
3341
this.img.removeAttribute( lazySrcAttr );
3342
this.img.removeAttribute( lazySrcsetAttr );
Jul 8, 2016
3343
};
Dec 16, 2015
3344
3345
LazyLoader.prototype.onload = function( event ) {
3346
this.complete( event, 'flickity-lazyloaded' );
Jul 8, 2016
3347
};
Dec 16, 2015
3348
3349
LazyLoader.prototype.onerror = function( event ) {
3350
this.complete( event, 'flickity-lazyerror' );
Jul 8, 2016
3351
};
3352
3353
LazyLoader.prototype.complete = function( event, className ) {
3354
// unbind events
3355
this.img.removeEventListener( 'load', this );
3356
this.img.removeEventListener( 'error', this );
3357
let mediaElem = this.img.parentNode.matches('picture') ? this.img.parentNode : this.img;
3358
mediaElem.classList.add( className );
Dec 16, 2015
3359
3360
this.onComplete( this.img, event );
Jul 8, 2016
3361
};
3362
3363
// ----- ----- //
3364
3365
Flickity.LazyLoader = LazyLoader;
3366
3367
return Flickity;
3368
3369
} ) );
3370
// imagesloaded
Feb 3, 2015
3371
( function( window, factory ) {
3372
// universal module definition
3373
if ( typeof module == 'object' && module.exports ) {
Feb 3, 2015
3374
// CommonJS
3375
module.exports = factory(
3376
require('./core'),
3377
require('imagesloaded'),
Feb 3, 2015
3378
);
3379
} else {
3380
// browser global
3381
factory(
3382
window.Flickity,
3383
window.imagesLoaded,
Feb 3, 2015
3384
);
3385
}
3386
3387
}( typeof window != 'undefined' ? window : this,
3388
function factory( Flickity, imagesLoaded ) {
Jul 8, 2016
3389
3390
Flickity.create.imagesLoaded = function() {
Feb 9, 2015
3391
this.on( 'activate', this.imagesLoaded );
3392
};
3393
3394
Flickity.prototype.imagesLoaded = function() {
3395
if ( !this.options.imagesLoaded ) return;
3396
3397
let onImagesLoadedProgress = ( instance, image ) => {
3398
let cell = this.getParentCell( image.img );
3399
this.cellSizeChange( cell && cell.element );
3400
if ( !this.options.freeScroll ) this.positionSliderAtSelected();
3401
};
Feb 3, 2015
3402
imagesLoaded( this.slider ).on( 'progress', onImagesLoadedProgress );
3403
};
3404
3405
return Flickity;
3406
3407
} ) );