PluginProbe
Photonic Gallery & Lightbox for Flickr, SmugMug & Others / 3.34
Photonic Gallery & Lightbox for Flickr, SmugMug & Others v3.34
3.37 3.36 3.35 3.34 3.33 2.19 2.20 2.21 2.22 2.23 2.24 2.25 2.26 2.27 2.28 2.29 2.30 2.31 2.32 2.33 2.34 2.40 2.41 2.42 2.43 All 142 releases
photonic / include / ext / photoswipe / photoswipe.js

photoswipe.js in Photonic Gallery & Lightbox for Flickr, SmugMug & Others 3.34, at include/ext/photoswipe/photoswipe.js

4,597 lines 119.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*! PhotoSwipe - v4.1.3 - 2019-01-08
2 * http://photoswipe.com
3 * Copyright (c) 2019 Dmitry Semenov; */
4 (function (root, factory) {
5 if (typeof define === 'function' && define.amd) {
6 define(factory);
7 } else if (typeof exports === 'object') {
8 module.exports = factory();
9 } else {
10 root.PhotoSwipe = factory();
11 }
12 })(this, function () {
13
14 'use strict';
15 var PhotoSwipe = function(template, UiClass, items, options){
16
17 /*>>framework-bridge*/
18 /**
19 *
20 * Set of generic functions used by gallery.
21 *
22 * You're free to modify anything here as long as functionality is kept.
23 *
24 */
25 var framework = {
26 features: null,
27 bind: function(target, type, listener, unbind) {
28 var methodName = (unbind ? 'remove' : 'add') + 'EventListener';
29 type = type.split(' ');
30 for(var i = 0; i < type.length; i++) {
31 if(type[i]) {
32 target[methodName]( type[i], listener, false);
33 }
34 }
35 },
36 isArray: function(obj) {
37 return (obj instanceof Array);
38 },
39 createEl: function(classes, tag) {
40 var el = document.createElement(tag || 'div');
41 if(classes) {
42 el.className = classes;
43 }
44 return el;
45 },
46 getScrollY: function() {
47 var yOffset = window.pageYOffset;
48 return yOffset !== undefined ? yOffset : document.documentElement.scrollTop;
49 },
50 unbind: function(target, type, listener) {
51 framework.bind(target,type,listener,true);
52 },
53 removeClass: function(el, className) {
54 var reg = new RegExp('(\\s|^)' + className + '(\\s|$)');
55 el.className = el.className.replace(reg, ' ').replace(/^\s\s*/, '').replace(/\s\s*$/, '');
56 },
57 addClass: function(el, className) {
58 if( !framework.hasClass(el,className) ) {
59 el.className += (el.className ? ' ' : '') + className;
60 }
61 },
62 hasClass: function(el, className) {
63 return el.className && new RegExp('(^|\\s)' + className + '(\\s|$)').test(el.className);
64 },
65 getChildByClass: function(parentEl, childClassName) {
66 var node = parentEl.firstChild;
67 while(node) {
68 if( framework.hasClass(node, childClassName) ) {
69 return node;
70 }
71 node = node.nextSibling;
72 }
73 },
74 arraySearch: function(array, value, key) {
75 var i = array.length;
76 while(i--) {
77 if(array[i][key] === value) {
78 return i;
79 }
80 }
81 return -1;
82 },
83 extend: function(o1, o2, preventOverwrite) {
84 for (var prop in o2) {
85 if (o2.hasOwnProperty(prop)) {
86 if(preventOverwrite && o1.hasOwnProperty(prop)) {
87 continue;
88 }
89 o1[prop] = o2[prop];
90 }
91 }
92 },
93 easing: {
94 sine: {
95 out: function(k) {
96 return Math.sin(k * (Math.PI / 2));
97 },
98 inOut: function(k) {
99 return - (Math.cos(Math.PI * k) - 1) / 2;
100 }
101 },
102 cubic: {
103 out: function(k) {
104 return --k * k * k + 1;
105 }
106 }
107 /*
108 elastic: {
109 out: function ( k ) {
110
111 var s, a = 0.1, p = 0.4;
112 if ( k === 0 ) return 0;
113 if ( k === 1 ) return 1;
114 if ( !a || a < 1 ) { a = 1; s = p / 4; }
115 else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI );
116 return ( a * Math.pow( 2, - 10 * k) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) + 1 );
117
118 },
119 },
120 back: {
121 out: function ( k ) {
122 var s = 1.70158;
123 return --k * k * ( ( s + 1 ) * k + s ) + 1;
124 }
125 }
126 */
127 },
128
129 /**
130 *
131 * @return {object}
132 *
133 * {
134 * raf : request animation frame function
135 * caf : cancel animation frame function
136 * transfrom : transform property key (with vendor), or null if not supported
137 * oldIE : IE8 or below
138 * }
139 *
140 */
141 detectFeatures: function() {
142 if(framework.features) {
143 return framework.features;
144 }
145 var helperEl = framework.createEl(),
146 helperStyle = helperEl.style,
147 vendor = '',
148 features = {};
149
150 // IE8 and below
151 features.oldIE = document.all && !document.addEventListener;
152
153 features.touch = 'ontouchstart' in window;
154
155 if(window.requestAnimationFrame) {
156 features.raf = window.requestAnimationFrame;
157 features.caf = window.cancelAnimationFrame;
158 }
159
160 features.pointerEvent = !!(window.PointerEvent) || navigator.msPointerEnabled;
161
162 // fix false-positive detection of old Android in new IE
163 // (IE11 ua string contains "Android 4.0")
164
165 if(!features.pointerEvent) {
166
167 var ua = navigator.userAgent;
168
169 // Detect if device is iPhone or iPod and if it's older than iOS 8
170 // http://stackoverflow.com/a/14223920
171 //
172 // This detection is made because of buggy top/bottom toolbars
173 // that don't trigger window.resize event.
174 // For more info refer to _isFixedPosition variable in core.js
175
176 if (/iP(hone|od)/.test(navigator.platform)) {
177 var v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/);
178 if(v && v.length > 0) {
179 v = parseInt(v[1], 10);
180 if(v >= 1 && v < 8 ) {
181 features.isOldIOSPhone = true;
182 }
183 }
184 }
185
186 // Detect old Android (before KitKat)
187 // due to bugs related to position:fixed
188 // http://stackoverflow.com/questions/7184573/pick-up-the-android-version-in-the-browser-by-javascript
189
190 var match = ua.match(/Android\s([0-9\.]*)/);
191 var androidversion = match ? match[1] : 0;
192 androidversion = parseFloat(androidversion);
193 if(androidversion >= 1 ) {
194 if(androidversion < 4.4) {
195 features.isOldAndroid = true; // for fixed position bug & performance
196 }
197 features.androidVersion = androidversion; // for touchend bug
198 }
199 features.isMobileOpera = /opera mini|opera mobi/i.test(ua);
200
201 // p.s. yes, yes, UA sniffing is bad, propose your solution for above bugs.
202 }
203
204 var styleChecks = ['transform', 'perspective', 'animationName'],
205 vendors = ['', 'webkit','Moz','ms','O'],
206 styleCheckItem,
207 styleName;
208
209 for(var i = 0; i < 4; i++) {
210 vendor = vendors[i];
211
212 for(var a = 0; a < 3; a++) {
213 styleCheckItem = styleChecks[a];
214
215 // uppercase first letter of property name, if vendor is present
216 styleName = vendor + (vendor ?
217 styleCheckItem.charAt(0).toUpperCase() + styleCheckItem.slice(1) :
218 styleCheckItem);
219
220 if(!features[styleCheckItem] && styleName in helperStyle ) {
221 features[styleCheckItem] = styleName;
222 }
223 }
224
225 if(vendor && !features.raf) {
226 vendor = vendor.toLowerCase();
227 features.raf = window[vendor+'RequestAnimationFrame'];
228 if(features.raf) {
229 features.caf = window[vendor+'CancelAnimationFrame'] ||
230 window[vendor+'CancelRequestAnimationFrame'];
231 }
232 }
233 }
234
235 if(!features.raf) {
236 var lastTime = 0;
237 features.raf = function(fn) {
238 var currTime = new Date().getTime();
239 var timeToCall = Math.max(0, 16 - (currTime - lastTime));
240 var id = window.setTimeout(function() { fn(currTime + timeToCall); }, timeToCall);
241 lastTime = currTime + timeToCall;
242 return id;
243 };
244 features.caf = function(id) { clearTimeout(id); };
245 }
246
247 // Detect SVG support
248 features.svg = !!document.createElementNS &&
249 !!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect;
250
251 framework.features = features;
252
253 return features;
254 }
255 };
256
257 framework.detectFeatures();
258
259 // Override addEventListener for old versions of IE
260 if(framework.features.oldIE) {
261
262 framework.bind = function(target, type, listener, unbind) {
263
264 type = type.split(' ');
265
266 var methodName = (unbind ? 'detach' : 'attach') + 'Event',
267 evName,
268 _handleEv = function() {
269 listener.handleEvent.call(listener);
270 };
271
272 for(var i = 0; i < type.length; i++) {
273 evName = type[i];
274 if(evName) {
275
276 if(typeof listener === 'object' && listener.handleEvent) {
277 if(!unbind) {
278 listener['oldIE' + evName] = _handleEv;
279 } else {
280 if(!listener['oldIE' + evName]) {
281 return false;
282 }
283 }
284
285 target[methodName]( 'on' + evName, listener['oldIE' + evName]);
286 } else {
287 target[methodName]( 'on' + evName, listener);
288 }
289
290 }
291 }
292 };
293
294 }
295
296 /*>>framework-bridge*/
297
298 /*>>core*/
299 //function(template, UiClass, items, options)
300
301 var self = this;
302
303 /**
304 * Static vars, don't change unless you know what you're doing.
305 */
306 var DOUBLE_TAP_RADIUS = 25,
307 NUM_HOLDERS = 3;
308
309 /**
310 * Options
311 */
312 var _options = {
313 allowPanToNext:true,
314 spacing: 0.12,
315 bgOpacity: 1,
316 mouseUsed: false,
317 loop: true,
318 pinchToClose: true,
319 closeOnScroll: true,
320 closeOnVerticalDrag: true,
321 verticalDragRange: 0.75,
322 hideAnimationDuration: 333,
323 showAnimationDuration: 333,
324 showHideOpacity: false,
325 focus: true,
326 escKey: true,
327 arrowKeys: true,
328 mainScrollEndFriction: 0.35,
329 panEndFriction: 0.35,
330 isClickableElement: function(el) {
331 return el.tagName === 'A';
332 },
333 getDoubleTapZoom: function(isMouseClick, item) {
334 if(isMouseClick) {
335 return 1;
336 } else {
337 return item.initialZoomLevel < 0.7 ? 1 : 1.33;
338 }
339 },
340 maxSpreadZoom: 1.33,
341 modal: true,
342
343 // not fully implemented yet
344 scaleMode: 'fit' // TODO
345 };
346 framework.extend(_options, options);
347
348
349 /**
350 * Private helper variables & functions
351 */
352
353 var _getEmptyPoint = function() {
354 return {x:0,y:0};
355 };
356
357 var _isOpen,
358 _isDestroying,
359 _closedByScroll,
360 _currentItemIndex,
361 _containerStyle,
362 _containerShiftIndex,
363 _currPanDist = _getEmptyPoint(),
364 _startPanOffset = _getEmptyPoint(),
365 _panOffset = _getEmptyPoint(),
366 _upMoveEvents, // drag move, drag end & drag cancel events array
367 _downEvents, // drag start events array
368 _globalEventHandlers,
369 _viewportSize = {},
370 _currZoomLevel,
371 _startZoomLevel,
372 _translatePrefix,
373 _translateSufix,
374 _updateSizeInterval,
375 _itemsNeedUpdate,
376 _currPositionIndex = 0,
377 _offset = {},
378 _slideSize = _getEmptyPoint(), // size of slide area, including spacing
379 _itemHolders,
380 _prevItemIndex,
381 _indexDiff = 0, // difference of indexes since last content update
382 _dragStartEvent,
383 _dragMoveEvent,
384 _dragEndEvent,
385 _dragCancelEvent,
386 _transformKey,
387 _pointerEventEnabled,
388 _isFixedPosition = true,
389 _likelyTouchDevice,
390 _modules = [],
391 _requestAF,
392 _cancelAF,
393 _initalClassName,
394 _initalWindowScrollY,
395 _oldIE,
396 _currentWindowScrollY,
397 _features,
398 _windowVisibleSize = {},
399 _renderMaxResolution = false,
400 _orientationChangeTimeout,
401
402
403 // Registers PhotoSWipe module (History, Controller ...)
404 _registerModule = function(name, module) {
405 framework.extend(self, module.publicMethods);
406 _modules.push(name);
407 },
408
409 _getLoopedId = function(index) {
410 var numSlides = _getNumItems();
411 if(index > numSlides - 1) {
412 return index - numSlides;
413 } else if(index < 0) {
414 return numSlides + index;
415 }
416 return index;
417 },
418
419 // Micro bind/trigger
420 _listeners = {},
421 _listen = function(name, fn) {
422 if(!_listeners[name]) {
423 _listeners[name] = [];
424 }
425 return _listeners[name].push(fn);
426 },
427 _shout = function(name) {
428 var listeners = _listeners[name];
429
430 if(listeners) {
431 var args = Array.prototype.slice.call(arguments);
432 args.shift();
433
434 for(var i = 0; i < listeners.length; i++) {
435 listeners[i].apply(self, args);
436 }
437 }
438 },
439
440 _getCurrentTime = function() {
441 return new Date().getTime();
442 },
443 _applyBgOpacity = function(opacity) {
444 _bgOpacity = opacity;
445 self.bg.style.opacity = opacity * _options.bgOpacity;
446 },
447
448 _applyZoomTransform = function(styleObj,x,y,zoom,item) {
449 if(!_renderMaxResolution || (item && item !== self.currItem) ) {
450 zoom = zoom / (item ? item.fitRatio : self.currItem.fitRatio);
451 }
452
453 styleObj[_transformKey] = _translatePrefix + x + 'px, ' + y + 'px' + _translateSufix + ' scale(' + zoom + ')';
454 },
455 _applyCurrentZoomPan = function( allowRenderResolution ) {
456 if(_currZoomElementStyle) {
457
458 if(allowRenderResolution) {
459 if(_currZoomLevel > self.currItem.fitRatio) {
460 if(!_renderMaxResolution) {
461 _setImageSize(self.currItem, false, true);
462 _renderMaxResolution = true;
463 }
464 } else {
465 if(_renderMaxResolution) {
466 _setImageSize(self.currItem);
467 _renderMaxResolution = false;
468 }
469 }
470 }
471
472
473 _applyZoomTransform(_currZoomElementStyle, _panOffset.x, _panOffset.y, _currZoomLevel);
474 }
475 },
476 _applyZoomPanToItem = function(item) {
477 if(item.container) {
478
479 _applyZoomTransform(item.container.style,
480 item.initialPosition.x,
481 item.initialPosition.y,
482 item.initialZoomLevel,
483 item);
484 }
485 },
486 _setTranslateX = function(x, elStyle) {
487 elStyle[_transformKey] = _translatePrefix + x + 'px, 0px' + _translateSufix;
488 },
489 _moveMainScroll = function(x, dragging) {
490
491 if(!_options.loop && dragging) {
492 var newSlideIndexOffset = _currentItemIndex + (_slideSize.x * _currPositionIndex - x) / _slideSize.x,
493 delta = Math.round(x - _mainScrollPos.x);
494
495 if( (newSlideIndexOffset < 0 && delta > 0) ||
496 (newSlideIndexOffset >= _getNumItems() - 1 && delta < 0) ) {
497 x = _mainScrollPos.x + delta * _options.mainScrollEndFriction;
498 }
499 }
500
501 _mainScrollPos.x = x;
502 _setTranslateX(x, _containerStyle);
503 },
504 _calculatePanOffset = function(axis, zoomLevel) {
505 var m = _midZoomPoint[axis] - _offset[axis];
506 return _startPanOffset[axis] + _currPanDist[axis] + m - m * ( zoomLevel / _startZoomLevel );
507 },
508
509 _equalizePoints = function(p1, p2) {
510 p1.x = p2.x;
511 p1.y = p2.y;
512 if(p2.id) {
513 p1.id = p2.id;
514 }
515 },
516 _roundPoint = function(p) {
517 p.x = Math.round(p.x);
518 p.y = Math.round(p.y);
519 },
520
521 _mouseMoveTimeout = null,
522 _onFirstMouseMove = function() {
523 // Wait until mouse move event is fired at least twice during 100ms
524 // We do this, because some mobile browsers trigger it on touchstart
525 if(_mouseMoveTimeout ) {
526 framework.unbind(document, 'mousemove', _onFirstMouseMove);
527 framework.addClass(template, 'pswp--has_mouse');
528 _options.mouseUsed = true;
529 _shout('mouseUsed');
530 }
531 _mouseMoveTimeout = setTimeout(function() {
532 _mouseMoveTimeout = null;
533 }, 100);
534 },
535
536 _bindEvents = function() {
537 framework.bind(document, 'keydown', self);
538
539 if(_features.transform) {
540 // don't bind click event in browsers that don't support transform (mostly IE8)
541 framework.bind(self.scrollWrap, 'click', self);
542 }
543
544
545 if(!_options.mouseUsed) {
546 framework.bind(document, 'mousemove', _onFirstMouseMove);
547 }
548
549 framework.bind(window, 'resize scroll orientationchange', self);
550
551 _shout('bindEvents');
552 },
553
554 _unbindEvents = function() {
555 framework.unbind(window, 'resize scroll orientationchange', self);
556 framework.unbind(window, 'scroll', _globalEventHandlers.scroll);
557 framework.unbind(document, 'keydown', self);
558 framework.unbind(document, 'mousemove', _onFirstMouseMove);
559
560 if(_features.transform) {
561 framework.unbind(self.scrollWrap, 'click', self);
562 }
563
564 if(_isDragging) {
565 framework.unbind(window, _upMoveEvents, self);
566 }
567
568 clearTimeout(_orientationChangeTimeout);
569
570 _shout('unbindEvents');
571 },
572
573 _calculatePanBounds = function(zoomLevel, update) {
574 var bounds = _calculateItemSize( self.currItem, _viewportSize, zoomLevel );
575 if(update) {
576 _currPanBounds = bounds;
577 }
578 return bounds;
579 },
580
581 _getMinZoomLevel = function(item) {
582 if(!item) {
583 item = self.currItem;
584 }
585 return item.initialZoomLevel;
586 },
587 _getMaxZoomLevel = function(item) {
588 if(!item) {
589 item = self.currItem;
590 }
591 return item.w > 0 ? _options.maxSpreadZoom : 1;
592 },
593
594 // Return true if offset is out of the bounds
595 _modifyDestPanOffset = function(axis, destPanBounds, destPanOffset, destZoomLevel) {
596 if(destZoomLevel === self.currItem.initialZoomLevel) {
597 destPanOffset[axis] = self.currItem.initialPosition[axis];
598 return true;
599 } else {
600 destPanOffset[axis] = _calculatePanOffset(axis, destZoomLevel);
601
602 if(destPanOffset[axis] > destPanBounds.min[axis]) {
603 destPanOffset[axis] = destPanBounds.min[axis];
604 return true;
605 } else if(destPanOffset[axis] < destPanBounds.max[axis] ) {
606 destPanOffset[axis] = destPanBounds.max[axis];
607 return true;
608 }
609 }
610 return false;
611 },
612
613 _setupTransforms = function() {
614
615 if(_transformKey) {
616 // setup 3d transforms
617 var allow3dTransform = _features.perspective && !_likelyTouchDevice;
618 _translatePrefix = 'translate' + (allow3dTransform ? '3d(' : '(');
619 _translateSufix = _features.perspective ? ', 0px)' : ')';
620 return;
621 }
622
623 // Override zoom/pan/move functions in case old browser is used (most likely IE)
624 // (so they use left/top/width/height, instead of CSS transform)
625
626 _transformKey = 'left';
627 framework.addClass(template, 'pswp--ie');
628
629 _setTranslateX = function(x, elStyle) {
630 elStyle.left = x + 'px';
631 };
632 _applyZoomPanToItem = function(item) {
633
634 var zoomRatio = item.fitRatio > 1 ? 1 : item.fitRatio,
635 s = item.container.style,
636 w = zoomRatio * item.w,
637 h = zoomRatio * item.h;
638
639 s.width = w + 'px';
640 s.height = h + 'px';
641 s.left = item.initialPosition.x + 'px';
642 s.top = item.initialPosition.y + 'px';
643
644 };
645 _applyCurrentZoomPan = function() {
646 if(_currZoomElementStyle) {
647
648 var s = _currZoomElementStyle,
649 item = self.currItem,
650 zoomRatio = item.fitRatio > 1 ? 1 : item.fitRatio,
651 w = zoomRatio * item.w,
652 h = zoomRatio * item.h;
653
654 s.width = w + 'px';
655 s.height = h + 'px';
656
657
658 s.left = _panOffset.x + 'px';
659 s.top = _panOffset.y + 'px';
660 }
661
662 };
663 },
664
665 _onKeyDown = function(e) {
666 var keydownAction = '';
667 if(_options.escKey && e.keyCode === 27) {
668 keydownAction = 'close';
669 } else if(_options.arrowKeys) {
670 if(e.keyCode === 37) {
671 keydownAction = 'prev';
672 } else if(e.keyCode === 39) {
673 keydownAction = 'next';
674 }
675 }
676
677 if(keydownAction) {
678 // don't do anything if special key pressed to prevent from overriding default browser actions
679 // e.g. in Chrome on Mac cmd+arrow-left returns to previous page
680 if( !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey ) {
681 if(e.preventDefault) {
682 e.preventDefault();
683 } else {
684 e.returnValue = false;
685 }
686 self[keydownAction]();
687 }
688 }
689 },
690
691 _onGlobalClick = function(e) {
692 if(!e) {
693 return;
694 }
695
696 // don't allow click event to pass through when triggering after drag or some other gesture
697 if(_moved || _zoomStarted || _mainScrollAnimating || _verticalDragInitiated) {
698 e.preventDefault();
699 e.stopPropagation();
700 }
701 },
702
703 _updatePageScrollOffset = function() {
704 self.setScrollOffset(0, framework.getScrollY());
705 };
706
707
708
709
710
711
712
713 // Micro animation engine
714 var _animations = {},
715 _numAnimations = 0,
716 _stopAnimation = function(name) {
717 if(_animations[name]) {
718 if(_animations[name].raf) {
719 _cancelAF( _animations[name].raf );
720 }
721 _numAnimations--;
722 delete _animations[name];
723 }
724 },
725 _registerStartAnimation = function(name) {
726 if(_animations[name]) {
727 _stopAnimation(name);
728 }
729 if(!_animations[name]) {
730 _numAnimations++;
731 _animations[name] = {};
732 }
733 },
734 _stopAllAnimations = function() {
735 for (var prop in _animations) {
736
737 if( _animations.hasOwnProperty( prop ) ) {
738 _stopAnimation(prop);
739 }
740
741 }
742 },
743 _animateProp = function(name, b, endProp, d, easingFn, onUpdate, onComplete) {
744 var startAnimTime = _getCurrentTime(), t;
745 _registerStartAnimation(name);
746
747 var animloop = function(){
748 if ( _animations[name] ) {
749
750 t = _getCurrentTime() - startAnimTime; // time diff
751 //b - beginning (start prop)
752 //d - anim duration
753
754 if ( t >= d ) {
755 _stopAnimation(name);
756 onUpdate(endProp);
757 if(onComplete) {
758 onComplete();
759 }
760 return;
761 }
762 onUpdate( (endProp - b) * easingFn(t/d) + b );
763
764 _animations[name].raf = _requestAF(animloop);
765 }
766 };
767 animloop();
768 };
769
770
771
772 var publicMethods = {
773
774 // make a few local variables and functions public
775 shout: _shout,
776 listen: _listen,
777 viewportSize: _viewportSize,
778 options: _options,
779
780 isMainScrollAnimating: function() {
781 return _mainScrollAnimating;
782 },
783 getZoomLevel: function() {
784 return _currZoomLevel;
785 },
786 getCurrentIndex: function() {
787 return _currentItemIndex;
788 },
789 isDragging: function() {
790 return _isDragging;
791 },
792 isZooming: function() {
793 return _isZooming;
794 },
795 setScrollOffset: function(x,y) {
796 _offset.x = x;
797 _currentWindowScrollY = _offset.y = y;
798 _shout('updateScrollOffset', _offset);
799 },
800 applyZoomPan: function(zoomLevel,panX,panY,allowRenderResolution) {
801 _panOffset.x = panX;
802 _panOffset.y = panY;
803 _currZoomLevel = zoomLevel;
804 _applyCurrentZoomPan( allowRenderResolution );
805 },
806
807 init: function() {
808
809 if(_isOpen || _isDestroying) {
810 return;
811 }
812
813 var i;
814
815 self.framework = framework; // basic functionality
816 self.template = template; // root DOM element of PhotoSwipe
817 self.bg = framework.getChildByClass(template, 'pswp__bg');
818
819 _initalClassName = template.className;
820 _isOpen = true;
821
822 _features = framework.detectFeatures();
823 _requestAF = _features.raf;
824 _cancelAF = _features.caf;
825 _transformKey = _features.transform;
826 _oldIE = _features.oldIE;
827
828 self.scrollWrap = framework.getChildByClass(template, 'pswp__scroll-wrap');
829 self.container = framework.getChildByClass(self.scrollWrap, 'pswp__container');
830
831 _containerStyle = self.container.style; // for fast access
832
833 // Objects that hold slides (there are only 3 in DOM)
834 self.itemHolders = _itemHolders = [
835 {el:self.container.children[0] , wrap:0, index: -1},
836 {el:self.container.children[1] , wrap:0, index: -1},
837 {el:self.container.children[2] , wrap:0, index: -1}
838 ];
839
840 // hide nearby item holders until initial zoom animation finishes (to avoid extra Paints)
841 _itemHolders[0].el.style.display = _itemHolders[2].el.style.display = 'none';
842
843 _setupTransforms();
844
845 // Setup global events
846 _globalEventHandlers = {
847 resize: self.updateSize,
848
849 // Fixes: iOS 10.3 resize event
850 // does not update scrollWrap.clientWidth instantly after resize
851 // https://github.com/dimsemenov/PhotoSwipe/issues/1315
852 orientationchange: function() {
853 clearTimeout(_orientationChangeTimeout);
854 _orientationChangeTimeout = setTimeout(function() {
855 if(_viewportSize.x !== self.scrollWrap.clientWidth) {
856 self.updateSize();
857 }
858 }, 500);
859 },
860 scroll: _updatePageScrollOffset,
861 keydown: _onKeyDown,
862 click: _onGlobalClick
863 };
864
865 // disable show/hide effects on old browsers that don't support CSS animations or transforms,
866 // old IOS, Android and Opera mobile. Blackberry seems to work fine, even older models.
867 var oldPhone = _features.isOldIOSPhone || _features.isOldAndroid || _features.isMobileOpera;
868 if(!_features.animationName || !_features.transform || oldPhone) {
869 _options.showAnimationDuration = _options.hideAnimationDuration = 0;
870 }
871
872 // init modules
873 for(i = 0; i < _modules.length; i++) {
874 self['init' + _modules[i]]();
875 }
876
877 // init
878 if(UiClass) {
879 var ui = self.ui = new UiClass(self, framework);
880 ui.init();
881 }
882
883 _shout('firstUpdate');
884 _currentItemIndex = _currentItemIndex || _options.index || 0;
885 // validate index
886 if( isNaN(_currentItemIndex) || _currentItemIndex < 0 || _currentItemIndex >= _getNumItems() ) {
887 _currentItemIndex = 0;
888 }
889 self.currItem = _getItemAt( _currentItemIndex );
890
891
892 if(_features.isOldIOSPhone || _features.isOldAndroid) {
893 _isFixedPosition = false;
894 }
895
896 template.setAttribute('aria-hidden', 'false');
897 if(_options.modal) {
898 if(!_isFixedPosition) {
899 template.style.position = 'absolute';
900 template.style.top = framework.getScrollY() + 'px';
901 } else {
902 template.style.position = 'fixed';
903 }
904 }
905
906 if(_currentWindowScrollY === undefined) {
907 _shout('initialLayout');
908 _currentWindowScrollY = _initalWindowScrollY = framework.getScrollY();
909 }
910
911 // add classes to root element of PhotoSwipe
912 var rootClasses = 'pswp--open ';
913 if(_options.mainClass) {
914 rootClasses += _options.mainClass + ' ';
915 }
916 if(_options.showHideOpacity) {
917 rootClasses += 'pswp--animate_opacity ';
918 }
919 rootClasses += _likelyTouchDevice ? 'pswp--touch' : 'pswp--notouch';
920 rootClasses += _features.animationName ? ' pswp--css_animation' : '';
921 rootClasses += _features.svg ? ' pswp--svg' : '';
922 framework.addClass(template, rootClasses);
923
924 self.updateSize();
925
926 // initial update
927 _containerShiftIndex = -1;
928 _indexDiff = null;
929 for(i = 0; i < NUM_HOLDERS; i++) {
930 _setTranslateX( (i+_containerShiftIndex) * _slideSize.x, _itemHolders[i].el.style);
931 }
932
933 if(!_oldIE) {
934 framework.bind(self.scrollWrap, _downEvents, self); // no dragging for old IE
935 }
936
937 _listen('initialZoomInEnd', function() {
938 self.setContent(_itemHolders[0], _currentItemIndex-1);
939 self.setContent(_itemHolders[2], _currentItemIndex+1);
940
941 _itemHolders[0].el.style.display = _itemHolders[2].el.style.display = 'block';
942
943 if(_options.focus) {
944 // focus causes layout,
945 // which causes lag during the animation,
946 // that's why we delay it untill the initial zoom transition ends
947 template.focus();
948 }
949
950
951 _bindEvents();
952 });
953
954 // set content for center slide (first time)
955 self.setContent(_itemHolders[1], _currentItemIndex);
956
957 self.updateCurrItem();
958
959 _shout('afterInit');
960
961 if(!_isFixedPosition) {
962
963 // On all versions of iOS lower than 8.0, we check size of viewport every second.
964 //
965 // This is done to detect when Safari top & bottom bars appear,
966 // as this action doesn't trigger any events (like resize).
967 //
968 // On iOS8 they fixed this.
969 //
970 // 10 Nov 2014: iOS 7 usage ~40%. iOS 8 usage 56%.
971
972 _updateSizeInterval = setInterval(function() {
973 if(!_numAnimations && !_isDragging && !_isZooming && (_currZoomLevel === self.currItem.initialZoomLevel) ) {
974 self.updateSize();
975 }
976 }, 1000);
977 }
978
979 framework.addClass(template, 'pswp--visible');
980 },
981
982 // Close the gallery, then destroy it
983 close: function() {
984 if(!_isOpen) {
985 return;
986 }
987
988 _isOpen = false;
989 _isDestroying = true;
990 _shout('close');
991 _unbindEvents();
992
993 _showOrHide(self.currItem, null, true, self.destroy);
994 },
995
996 // destroys the gallery (unbinds events, cleans up intervals and timeouts to avoid memory leaks)
997 destroy: function() {
998 _shout('destroy');
999
1000 if(_showOrHideTimeout) {
1001 clearTimeout(_showOrHideTimeout);
1002 }
1003
1004 template.setAttribute('aria-hidden', 'true');
1005 template.className = _initalClassName;
1006
1007 if(_updateSizeInterval) {
1008 clearInterval(_updateSizeInterval);
1009 }
1010
1011 framework.unbind(self.scrollWrap, _downEvents, self);
1012
1013 // we unbind scroll event at the end, as closing animation may depend on it
1014 framework.unbind(window, 'scroll', self);
1015
1016 _stopDragUpdateLoop();
1017
1018 _stopAllAnimations();
1019
1020 _listeners = null;
1021 },
1022
1023 /**
1024 * Pan image to position
1025 * @param {Number} x
1026 * @param {Number} y
1027 * @param {Boolean} force Will ignore bounds if set to true.
1028 */
1029 panTo: function(x,y,force) {
1030 if(!force) {
1031 if(x > _currPanBounds.min.x) {
1032 x = _currPanBounds.min.x;
1033 } else if(x < _currPanBounds.max.x) {
1034 x = _currPanBounds.max.x;
1035 }
1036
1037 if(y > _currPanBounds.min.y) {
1038 y = _currPanBounds.min.y;
1039 } else if(y < _currPanBounds.max.y) {
1040 y = _currPanBounds.max.y;
1041 }
1042 }
1043
1044 _panOffset.x = x;
1045 _panOffset.y = y;
1046 _applyCurrentZoomPan();
1047 },
1048
1049 handleEvent: function (e) {
1050 e = e || window.event;
1051 if(_globalEventHandlers[e.type]) {
1052 _globalEventHandlers[e.type](e);
1053 }
1054 },
1055
1056
1057 goTo: function(index) {
1058
1059 index = _getLoopedId(index);
1060
1061 var diff = index - _currentItemIndex;
1062 _indexDiff = diff;
1063
1064 _currentItemIndex = index;
1065 self.currItem = _getItemAt( _currentItemIndex );
1066 _currPositionIndex -= diff;
1067
1068 _moveMainScroll(_slideSize.x * _currPositionIndex);
1069
1070
1071 _stopAllAnimations();
1072 _mainScrollAnimating = false;
1073
1074 self.updateCurrItem();
1075 },
1076 next: function() {
1077 self.goTo( _currentItemIndex + 1);
1078 },
1079 prev: function() {
1080 self.goTo( _currentItemIndex - 1);
1081 },
1082
1083 // update current zoom/pan objects
1084 updateCurrZoomItem: function(emulateSetContent) {
1085 if(emulateSetContent) {
1086 _shout('beforeChange', 0);
1087 }
1088
1089 // itemHolder[1] is middle (current) item
1090 if(_itemHolders[1].el.children.length) {
1091 var zoomElement = _itemHolders[1].el.children[0];
1092 if( framework.hasClass(zoomElement, 'pswp__zoom-wrap') ) {
1093 _currZoomElementStyle = zoomElement.style;
1094 } else {
1095 _currZoomElementStyle = null;
1096 }
1097 } else {
1098 _currZoomElementStyle = null;
1099 }
1100
1101 _currPanBounds = self.currItem.bounds;
1102 _startZoomLevel = _currZoomLevel = self.currItem.initialZoomLevel;
1103
1104 _panOffset.x = _currPanBounds.center.x;
1105 _panOffset.y = _currPanBounds.center.y;
1106
1107 if(emulateSetContent) {
1108 _shout('afterChange');
1109 }
1110 },
1111
1112
1113 invalidateCurrItems: function() {
1114 _itemsNeedUpdate = true;
1115 for(var i = 0; i < NUM_HOLDERS; i++) {
1116 if( _itemHolders[i].item ) {
1117 _itemHolders[i].item.needsUpdate = true;
1118 }
1119 }
1120 },
1121
1122 updateCurrItem: function(beforeAnimation) {
1123
1124 if(_indexDiff === 0) {
1125 return;
1126 }
1127
1128 var diffAbs = Math.abs(_indexDiff),
1129 tempHolder;
1130
1131 if(beforeAnimation && diffAbs < 2) {
1132 return;
1133 }
1134
1135
1136 self.currItem = _getItemAt( _currentItemIndex );
1137 _renderMaxResolution = false;
1138
1139 _shout('beforeChange', _indexDiff);
1140
1141 if(diffAbs >= NUM_HOLDERS) {
1142 _containerShiftIndex += _indexDiff + (_indexDiff > 0 ? -NUM_HOLDERS : NUM_HOLDERS);
1143 diffAbs = NUM_HOLDERS;
1144 }
1145 for(var i = 0; i < diffAbs; i++) {
1146 if(_indexDiff > 0) {
1147 tempHolder = _itemHolders.shift();
1148 _itemHolders[NUM_HOLDERS-1] = tempHolder; // move first to last
1149
1150 _containerShiftIndex++;
1151 _setTranslateX( (_containerShiftIndex+2) * _slideSize.x, tempHolder.el.style);
1152 self.setContent(tempHolder, _currentItemIndex - diffAbs + i + 1 + 1);
1153 } else {
1154 tempHolder = _itemHolders.pop();
1155 _itemHolders.unshift( tempHolder ); // move last to first
1156
1157 _containerShiftIndex--;
1158 _setTranslateX( _containerShiftIndex * _slideSize.x, tempHolder.el.style);
1159 self.setContent(tempHolder, _currentItemIndex + diffAbs - i - 1 - 1);
1160 }
1161
1162 }
1163
1164 // reset zoom/pan on previous item
1165 if(_currZoomElementStyle && Math.abs(_indexDiff) === 1) {
1166
1167 var prevItem = _getItemAt(_prevItemIndex);
1168 if(prevItem.initialZoomLevel !== _currZoomLevel) {
1169 _calculateItemSize(prevItem , _viewportSize );
1170 _setImageSize(prevItem);
1171 _applyZoomPanToItem( prevItem );
1172 }
1173
1174 }
1175
1176 // reset diff after update
1177 _indexDiff = 0;
1178
1179 self.updateCurrZoomItem();
1180
1181 _prevItemIndex = _currentItemIndex;
1182
1183 _shout('afterChange');
1184
1185 },
1186
1187
1188
1189 updateSize: function(force) {
1190
1191 if(!_isFixedPosition && _options.modal) {
1192 var windowScrollY = framework.getScrollY();
1193 if(_currentWindowScrollY !== windowScrollY) {
1194 template.style.top = windowScrollY + 'px';
1195 _currentWindowScrollY = windowScrollY;
1196 }
1197 if(!force && _windowVisibleSize.x === window.innerWidth && _windowVisibleSize.y === window.innerHeight) {
1198 return;
1199 }
1200 _windowVisibleSize.x = window.innerWidth;
1201 _windowVisibleSize.y = window.innerHeight;
1202
1203 //template.style.width = _windowVisibleSize.x + 'px';
1204 template.style.height = _windowVisibleSize.y + 'px';
1205 }
1206
1207
1208
1209 _viewportSize.x = self.scrollWrap.clientWidth;
1210 _viewportSize.y = self.scrollWrap.clientHeight;
1211
1212 _updatePageScrollOffset();
1213
1214 _slideSize.x = _viewportSize.x + Math.round(_viewportSize.x * _options.spacing);
1215 _slideSize.y = _viewportSize.y;
1216
1217 _moveMainScroll(_slideSize.x * _currPositionIndex);
1218
1219 _shout('beforeResize'); // even may be used for example to switch image sources
1220
1221
1222 // don't re-calculate size on inital size update
1223 if(_containerShiftIndex !== undefined) {
1224
1225 var holder,
1226 item,
1227 hIndex;
1228
1229 for(var i = 0; i < NUM_HOLDERS; i++) {
1230 holder = _itemHolders[i];
1231 _setTranslateX( (i+_containerShiftIndex) * _slideSize.x, holder.el.style);
1232
1233 hIndex = _currentItemIndex+i-1;
1234
1235 if(_options.loop && _getNumItems() > 2) {
1236 hIndex = _getLoopedId(hIndex);
1237 }
1238
1239 // update zoom level on items and refresh source (if needsUpdate)
1240 item = _getItemAt( hIndex );
1241
1242 // re-render gallery item if `needsUpdate`,
1243 // or doesn't have `bounds` (entirely new slide object)
1244 if( item && (_itemsNeedUpdate || item.needsUpdate || !item.bounds) ) {
1245
1246 self.cleanSlide( item );
1247
1248 self.setContent( holder, hIndex );
1249
1250 // if "center" slide
1251 if(i === 1) {
1252 self.currItem = item;
1253 self.updateCurrZoomItem(true);
1254 }
1255
1256 item.needsUpdate = false;
1257
1258 } else if(holder.index === -1 && hIndex >= 0) {
1259 // add content first time
1260 self.setContent( holder, hIndex );
1261 }
1262 if(item && item.container) {
1263 _calculateItemSize(item, _viewportSize);
1264 _setImageSize(item);
1265 _applyZoomPanToItem( item );
1266 }
1267
1268 }
1269 _itemsNeedUpdate = false;
1270 }
1271
1272 _startZoomLevel = _currZoomLevel = self.currItem.initialZoomLevel;
1273 _currPanBounds = self.currItem.bounds;
1274
1275 if(_currPanBounds) {
1276 _panOffset.x = _currPanBounds.center.x;
1277 _panOffset.y = _currPanBounds.center.y;
1278 _applyCurrentZoomPan( true );
1279 }
1280
1281 _shout('resize');
1282 },
1283
1284 // Zoom current item to
1285 zoomTo: function(destZoomLevel, centerPoint, speed, easingFn, updateFn) {
1286 /*
1287 if(destZoomLevel === 'fit') {
1288 destZoomLevel = self.currItem.fitRatio;
1289 } else if(destZoomLevel === 'fill') {
1290 destZoomLevel = self.currItem.fillRatio;
1291 }
1292 */
1293
1294 if(centerPoint) {
1295 _startZoomLevel = _currZoomLevel;
1296 _midZoomPoint.x = Math.abs(centerPoint.x) - _panOffset.x ;
1297 _midZoomPoint.y = Math.abs(centerPoint.y) - _panOffset.y ;
1298 _equalizePoints(_startPanOffset, _panOffset);
1299 }
1300
1301 var destPanBounds = _calculatePanBounds(destZoomLevel, false),
1302 destPanOffset = {};
1303
1304 _modifyDestPanOffset('x', destPanBounds, destPanOffset, destZoomLevel);
1305 _modifyDestPanOffset('y', destPanBounds, destPanOffset, destZoomLevel);
1306
1307 var initialZoomLevel = _currZoomLevel;
1308 var initialPanOffset = {
1309 x: _panOffset.x,
1310 y: _panOffset.y
1311 };
1312
1313 _roundPoint(destPanOffset);
1314
1315 var onUpdate = function(now) {
1316 if(now === 1) {
1317 _currZoomLevel = destZoomLevel;
1318 _panOffset.x = destPanOffset.x;
1319 _panOffset.y = destPanOffset.y;
1320 } else {
1321 _currZoomLevel = (destZoomLevel - initialZoomLevel) * now + initialZoomLevel;
1322 _panOffset.x = (destPanOffset.x - initialPanOffset.x) * now + initialPanOffset.x;
1323 _panOffset.y = (destPanOffset.y - initialPanOffset.y) * now + initialPanOffset.y;
1324 }
1325
1326 if(updateFn) {
1327 updateFn(now);
1328 }
1329
1330 _applyCurrentZoomPan( now === 1 );
1331 };
1332
1333 if(speed) {
1334 _animateProp('customZoomTo', 0, 1, speed, easingFn || framework.easing.sine.inOut, onUpdate);
1335 } else {
1336 onUpdate(1);
1337 }
1338 }
1339
1340
1341 };
1342
1343
1344 /*>>core*/
1345
1346 /*>>gestures*/
1347 /**
1348 * Mouse/touch/pointer event handlers.
1349 *
1350 * separated from @core.js for readability
1351 */
1352
1353 var MIN_SWIPE_DISTANCE = 30,
1354 DIRECTION_CHECK_OFFSET = 10; // amount of pixels to drag to determine direction of swipe
1355
1356 var _gestureStartTime,
1357 _gestureCheckSpeedTime,
1358
1359 // pool of objects that are used during dragging of zooming
1360 p = {}, // first point
1361 p2 = {}, // second point (for zoom gesture)
1362 delta = {},
1363 _currPoint = {},
1364 _startPoint = {},
1365 _currPointers = [],
1366 _startMainScrollPos = {},
1367 _releaseAnimData,
1368 _posPoints = [], // array of points during dragging, used to determine type of gesture
1369 _tempPoint = {},
1370
1371 _isZoomingIn,
1372 _verticalDragInitiated,
1373 _oldAndroidTouchEndTimeout,
1374 _currZoomedItemIndex = 0,
1375 _centerPoint = _getEmptyPoint(),
1376 _lastReleaseTime = 0,
1377 _isDragging, // at least one pointer is down
1378 _isMultitouch, // at least two _pointers are down
1379 _zoomStarted, // zoom level changed during zoom gesture
1380 _moved,
1381 _dragAnimFrame,
1382 _mainScrollShifted,
1383 _currentPoints, // array of current touch points
1384 _isZooming,
1385 _currPointsDistance,
1386 _startPointsDistance,
1387 _currPanBounds,
1388 _mainScrollPos = _getEmptyPoint(),
1389 _currZoomElementStyle,
1390 _mainScrollAnimating, // true, if animation after swipe gesture is running
1391 _midZoomPoint = _getEmptyPoint(),
1392 _currCenterPoint = _getEmptyPoint(),
1393 _direction,
1394 _isFirstMove,
1395 _opacityChanged,
1396 _bgOpacity,
1397 _wasOverInitialZoom,
1398
1399 _isEqualPoints = function(p1, p2) {
1400 return p1.x === p2.x && p1.y === p2.y;
1401 },
1402 _isNearbyPoints = function(touch0, touch1) {
1403 return Math.abs(touch0.x - touch1.x) < DOUBLE_TAP_RADIUS && Math.abs(touch0.y - touch1.y) < DOUBLE_TAP_RADIUS;
1404 },
1405 _calculatePointsDistance = function(p1, p2) {
1406 _tempPoint.x = Math.abs( p1.x - p2.x );
1407 _tempPoint.y = Math.abs( p1.y - p2.y );
1408 return Math.sqrt(_tempPoint.x * _tempPoint.x + _tempPoint.y * _tempPoint.y);
1409 },
1410 _stopDragUpdateLoop = function() {
1411 if(_dragAnimFrame) {
1412 _cancelAF(_dragAnimFrame);
1413 _dragAnimFrame = null;
1414 }
1415 },
1416 _dragUpdateLoop = function() {
1417 if(_isDragging) {
1418 _dragAnimFrame = _requestAF(_dragUpdateLoop);
1419 _renderMovement();
1420 }
1421 },
1422 _canPan = function() {
1423 return !(_options.scaleMode === 'fit' && _currZoomLevel === self.currItem.initialZoomLevel);
1424 },
1425
1426 // find the closest parent DOM element
1427 _closestElement = function(el, fn) {
1428 if(!el || el === document) {
1429 return false;
1430 }
1431
1432 // don't search elements above pswp__scroll-wrap
1433 if(el.getAttribute('class') && el.getAttribute('class').indexOf('pswp__scroll-wrap') > -1 ) {
1434 return false;
1435 }
1436
1437 if( fn(el) ) {
1438 return el;
1439 }
1440
1441 return _closestElement(el.parentNode, fn);
1442 },
1443
1444 _preventObj = {},
1445 _preventDefaultEventBehaviour = function(e, isDown) {
1446 _preventObj.prevent = !_closestElement(e.target, _options.isClickableElement);
1447
1448 _shout('preventDragEvent', e, isDown, _preventObj);
1449 return _preventObj.prevent;
1450
1451 },
1452 _convertTouchToPoint = function(touch, p) {
1453 p.x = touch.pageX;
1454 p.y = touch.pageY;
1455 p.id = touch.identifier;
1456 return p;
1457 },
1458 _findCenterOfPoints = function(p1, p2, pCenter) {
1459 pCenter.x = (p1.x + p2.x) * 0.5;
1460 pCenter.y = (p1.y + p2.y) * 0.5;
1461 },
1462 _pushPosPoint = function(time, x, y) {
1463 if(time - _gestureCheckSpeedTime > 50) {
1464 var o = _posPoints.length > 2 ? _posPoints.shift() : {};
1465 o.x = x;
1466 o.y = y;
1467 _posPoints.push(o);
1468 _gestureCheckSpeedTime = time;
1469 }
1470 },
1471
1472 _calculateVerticalDragOpacityRatio = function() {
1473 var yOffset = _panOffset.y - self.currItem.initialPosition.y; // difference between initial and current position
1474 return 1 - Math.abs( yOffset / (_viewportSize.y / 2) );
1475 },
1476
1477
1478 // points pool, reused during touch events
1479 _ePoint1 = {},
1480 _ePoint2 = {},
1481 _tempPointsArr = [],
1482 _tempCounter,
1483 _getTouchPoints = function(e) {
1484 // clean up previous points, without recreating array
1485 while(_tempPointsArr.length > 0) {
1486 _tempPointsArr.pop();
1487 }
1488
1489 if(!_pointerEventEnabled) {
1490 if(e.type.indexOf('touch') > -1) {
1491
1492 if(e.touches && e.touches.length > 0) {
1493 _tempPointsArr[0] = _convertTouchToPoint(e.touches[0], _ePoint1);
1494 if(e.touches.length > 1) {
1495 _tempPointsArr[1] = _convertTouchToPoint(e.touches[1], _ePoint2);
1496 }
1497 }
1498
1499 } else {
1500 _ePoint1.x = e.pageX;
1501 _ePoint1.y = e.pageY;
1502 _ePoint1.id = '';
1503 _tempPointsArr[0] = _ePoint1;//_ePoint1;
1504 }
1505 } else {
1506 _tempCounter = 0;
1507 // we can use forEach, as pointer events are supported only in modern browsers
1508 _currPointers.forEach(function(p) {
1509 if(_tempCounter === 0) {
1510 _tempPointsArr[0] = p;
1511 } else if(_tempCounter === 1) {
1512 _tempPointsArr[1] = p;
1513 }
1514 _tempCounter++;
1515
1516 });
1517 }
1518 return _tempPointsArr;
1519 },
1520
1521 _panOrMoveMainScroll = function(axis, delta) {
1522
1523 var panFriction,
1524 overDiff = 0,
1525 newOffset = _panOffset[axis] + delta[axis],
1526 startOverDiff,
1527 dir = delta[axis] > 0,
1528 newMainScrollPosition = _mainScrollPos.x + delta.x,
1529 mainScrollDiff = _mainScrollPos.x - _startMainScrollPos.x,
1530 newPanPos,
1531 newMainScrollPos;
1532
1533 // calculate fdistance over the bounds and friction
1534 if(newOffset > _currPanBounds.min[axis] || newOffset < _currPanBounds.max[axis]) {
1535 panFriction = _options.panEndFriction;
1536 // Linear increasing of friction, so at 1/4 of viewport it's at max value.
1537 // Looks not as nice as was expected. Left for history.
1538 // panFriction = (1 - (_panOffset[axis] + delta[axis] + panBounds.min[axis]) / (_viewportSize[axis] / 4) );
1539 } else {
1540 panFriction = 1;
1541 }
1542
1543 newOffset = _panOffset[axis] + delta[axis] * panFriction;
1544
1545 // move main scroll or start panning
1546 if(_options.allowPanToNext || _currZoomLevel === self.currItem.initialZoomLevel) {
1547
1548
1549 if(!_currZoomElementStyle) {
1550
1551 newMainScrollPos = newMainScrollPosition;
1552
1553 } else if(_direction === 'h' && axis === 'x' && !_zoomStarted ) {
1554
1555 if(dir) {
1556 if(newOffset > _currPanBounds.min[axis]) {
1557 panFriction = _options.panEndFriction;
1558 overDiff = _currPanBounds.min[axis] - newOffset;
1559 startOverDiff = _currPanBounds.min[axis] - _startPanOffset[axis];
1560 }
1561
1562 // drag right
1563 if( (startOverDiff <= 0 || mainScrollDiff < 0) && _getNumItems() > 1 ) {
1564 newMainScrollPos = newMainScrollPosition;
1565 if(mainScrollDiff < 0 && newMainScrollPosition > _startMainScrollPos.x) {
1566 newMainScrollPos = _startMainScrollPos.x;
1567 }
1568 } else {
1569 if(_currPanBounds.min.x !== _currPanBounds.max.x) {
1570 newPanPos = newOffset;
1571 }
1572
1573 }
1574
1575 } else {
1576
1577 if(newOffset < _currPanBounds.max[axis] ) {
1578 panFriction =_options.panEndFriction;
1579 overDiff = newOffset - _currPanBounds.max[axis];
1580 startOverDiff = _startPanOffset[axis] - _currPanBounds.max[axis];
1581 }
1582
1583 if( (startOverDiff <= 0 || mainScrollDiff > 0) && _getNumItems() > 1 ) {
1584 newMainScrollPos = newMainScrollPosition;
1585
1586 if(mainScrollDiff > 0 && newMainScrollPosition < _startMainScrollPos.x) {
1587 newMainScrollPos = _startMainScrollPos.x;
1588 }
1589
1590 } else {
1591 if(_currPanBounds.min.x !== _currPanBounds.max.x) {
1592 newPanPos = newOffset;
1593 }
1594 }
1595
1596 }
1597
1598
1599 //
1600 }
1601
1602 if(axis === 'x') {
1603
1604 if(newMainScrollPos !== undefined) {
1605 _moveMainScroll(newMainScrollPos, true);
1606 if(newMainScrollPos === _startMainScrollPos.x) {
1607 _mainScrollShifted = false;
1608 } else {
1609 _mainScrollShifted = true;
1610 }
1611 }
1612
1613 if(_currPanBounds.min.x !== _currPanBounds.max.x) {
1614 if(newPanPos !== undefined) {
1615 _panOffset.x = newPanPos;
1616 } else if(!_mainScrollShifted) {
1617 _panOffset.x += delta.x * panFriction;
1618 }
1619 }
1620
1621 return newMainScrollPos !== undefined;
1622 }
1623
1624 }
1625
1626 if(!_mainScrollAnimating) {
1627
1628 if(!_mainScrollShifted) {
1629 if(_currZoomLevel > self.currItem.fitRatio) {
1630 _panOffset[axis] += delta[axis] * panFriction;
1631
1632 }
1633 }
1634
1635
1636 }
1637
1638 },
1639
1640 // Pointerdown/touchstart/mousedown handler
1641 _onDragStart = function(e) {
1642
1643 // Allow dragging only via left mouse button.
1644 // As this handler is not added in IE8 - we ignore e.which
1645 //
1646 // http://www.quirksmode.org/js/events_properties.html
1647 // https://developer.mozilla.org/en-US/docs/Web/API/event.button
1648 if(e.type === 'mousedown' && e.button > 0 ) {
1649 return;
1650 }
1651
1652 if(_initialZoomRunning) {
1653 e.preventDefault();
1654 return;
1655 }
1656
1657 if(_oldAndroidTouchEndTimeout && e.type === 'mousedown') {
1658 return;
1659 }
1660
1661 if(_preventDefaultEventBehaviour(e, true)) {
1662 e.preventDefault();
1663 }
1664
1665
1666
1667 _shout('pointerDown');
1668
1669 if(_pointerEventEnabled) {
1670 var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id');
1671 if(pointerIndex < 0) {
1672 pointerIndex = _currPointers.length;
1673 }
1674 _currPointers[pointerIndex] = {x:e.pageX, y:e.pageY, id: e.pointerId};
1675 }
1676
1677
1678
1679 var startPointsList = _getTouchPoints(e),
1680 numPoints = startPointsList.length;
1681
1682 _currentPoints = null;
1683
1684 _stopAllAnimations();
1685
1686 // init drag
1687 if(!_isDragging || numPoints === 1) {
1688
1689
1690
1691 _isDragging = _isFirstMove = true;
1692 framework.bind(window, _upMoveEvents, self);
1693
1694 _isZoomingIn =
1695 _wasOverInitialZoom =
1696 _opacityChanged =
1697 _verticalDragInitiated =
1698 _mainScrollShifted =
1699 _moved =
1700 _isMultitouch =
1701 _zoomStarted = false;
1702
1703 _direction = null;
1704
1705 _shout('firstTouchStart', startPointsList);
1706
1707 _equalizePoints(_startPanOffset, _panOffset);
1708
1709 _currPanDist.x = _currPanDist.y = 0;
1710 _equalizePoints(_currPoint, startPointsList[0]);
1711 _equalizePoints(_startPoint, _currPoint);
1712
1713 //_equalizePoints(_startMainScrollPos, _mainScrollPos);
1714 _startMainScrollPos.x = _slideSize.x * _currPositionIndex;
1715
1716 _posPoints = [{
1717 x: _currPoint.x,
1718 y: _currPoint.y
1719 }];
1720
1721 _gestureCheckSpeedTime = _gestureStartTime = _getCurrentTime();
1722
1723 //_mainScrollAnimationEnd(true);
1724 _calculatePanBounds( _currZoomLevel, true );
1725
1726 // Start rendering
1727 _stopDragUpdateLoop();
1728 _dragUpdateLoop();
1729
1730 }
1731
1732 // init zoom
1733 if(!_isZooming && numPoints > 1 && !_mainScrollAnimating && !_mainScrollShifted) {
1734 _startZoomLevel = _currZoomLevel;
1735 _zoomStarted = false; // true if zoom changed at least once
1736
1737 _isZooming = _isMultitouch = true;
1738 _currPanDist.y = _currPanDist.x = 0;
1739
1740 _equalizePoints(_startPanOffset, _panOffset);
1741
1742 _equalizePoints(p, startPointsList[0]);
1743 _equalizePoints(p2, startPointsList[1]);
1744
1745 _findCenterOfPoints(p, p2, _currCenterPoint);
1746
1747 _midZoomPoint.x = Math.abs(_currCenterPoint.x) - _panOffset.x;
1748 _midZoomPoint.y = Math.abs(_currCenterPoint.y) - _panOffset.y;
1749 _currPointsDistance = _startPointsDistance = _calculatePointsDistance(p, p2);
1750 }
1751
1752
1753 },
1754
1755 // Pointermove/touchmove/mousemove handler
1756 _onDragMove = function(e) {
1757
1758 e.preventDefault();
1759
1760 if(_pointerEventEnabled) {
1761 var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id');
1762 if(pointerIndex > -1) {
1763 var p = _currPointers[pointerIndex];
1764 p.x = e.pageX;
1765 p.y = e.pageY;
1766 }
1767 }
1768
1769 if(_isDragging) {
1770 var touchesList = _getTouchPoints(e);
1771 if(!_direction && !_moved && !_isZooming) {
1772
1773 if(_mainScrollPos.x !== _slideSize.x * _currPositionIndex) {
1774 // if main scroll position is shifted – direction is always horizontal
1775 _direction = 'h';
1776 } else {
1777 var diff = Math.abs(touchesList[0].x - _currPoint.x) - Math.abs(touchesList[0].y - _currPoint.y);
1778 // check the direction of movement
1779 if(Math.abs(diff) >= DIRECTION_CHECK_OFFSET) {
1780 _direction = diff > 0 ? 'h' : 'v';
1781 _currentPoints = touchesList;
1782 }
1783 }
1784
1785 } else {
1786 _currentPoints = touchesList;
1787 }
1788 }
1789 },
1790 //
1791 _renderMovement = function() {
1792
1793 if(!_currentPoints) {
1794 return;
1795 }
1796
1797 var numPoints = _currentPoints.length;
1798
1799 if(numPoints === 0) {
1800 return;
1801 }
1802
1803 _equalizePoints(p, _currentPoints[0]);
1804
1805 delta.x = p.x - _currPoint.x;
1806 delta.y = p.y - _currPoint.y;
1807
1808 if(_isZooming && numPoints > 1) {
1809 // Handle behaviour for more than 1 point
1810
1811 _currPoint.x = p.x;
1812 _currPoint.y = p.y;
1813
1814 // check if one of two points changed
1815 if( !delta.x && !delta.y && _isEqualPoints(_currentPoints[1], p2) ) {
1816 return;
1817 }
1818
1819 _equalizePoints(p2, _currentPoints[1]);
1820
1821
1822 if(!_zoomStarted) {
1823 _zoomStarted = true;
1824 _shout('zoomGestureStarted');
1825 }
1826
1827 // Distance between two points
1828 var pointsDistance = _calculatePointsDistance(p,p2);
1829
1830 var zoomLevel = _calculateZoomLevel(pointsDistance);
1831
1832 // slightly over the of initial zoom level
1833 if(zoomLevel > self.currItem.initialZoomLevel + self.currItem.initialZoomLevel / 15) {
1834 _wasOverInitialZoom = true;
1835 }
1836
1837 // Apply the friction if zoom level is out of the bounds
1838 var zoomFriction = 1,
1839 minZoomLevel = _getMinZoomLevel(),
1840 maxZoomLevel = _getMaxZoomLevel();
1841
1842 if ( zoomLevel < minZoomLevel ) {
1843
1844 if(_options.pinchToClose && !_wasOverInitialZoom && _startZoomLevel <= self.currItem.initialZoomLevel) {
1845 // fade out background if zooming out
1846 var minusDiff = minZoomLevel - zoomLevel;
1847 var percent = 1 - minusDiff / (minZoomLevel / 1.2);
1848
1849 _applyBgOpacity(percent);
1850 _shout('onPinchClose', percent);
1851 _opacityChanged = true;
1852 } else {
1853 zoomFriction = (minZoomLevel - zoomLevel) / minZoomLevel;
1854 if(zoomFriction > 1) {
1855 zoomFriction = 1;
1856 }
1857 zoomLevel = minZoomLevel - zoomFriction * (minZoomLevel / 3);
1858 }
1859
1860 } else if ( zoomLevel > maxZoomLevel ) {
1861 // 1.5 - extra zoom level above the max. E.g. if max is x6, real max 6 + 1.5 = 7.5
1862 zoomFriction = (zoomLevel - maxZoomLevel) / ( minZoomLevel * 6 );
1863 if(zoomFriction > 1) {
1864 zoomFriction = 1;
1865 }
1866 zoomLevel = maxZoomLevel + zoomFriction * minZoomLevel;
1867 }
1868
1869 if(zoomFriction < 0) {
1870 zoomFriction = 0;
1871 }
1872
1873 // distance between touch points after friction is applied
1874 _currPointsDistance = pointsDistance;
1875
1876 // _centerPoint - The point in the middle of two pointers
1877 _findCenterOfPoints(p, p2, _centerPoint);
1878
1879 // paning with two pointers pressed
1880 _currPanDist.x += _centerPoint.x - _currCenterPoint.x;
1881 _currPanDist.y += _centerPoint.y - _currCenterPoint.y;
1882 _equalizePoints(_currCenterPoint, _centerPoint);
1883
1884 _panOffset.x = _calculatePanOffset('x', zoomLevel);
1885 _panOffset.y = _calculatePanOffset('y', zoomLevel);
1886
1887 _isZoomingIn = zoomLevel > _currZoomLevel;
1888 _currZoomLevel = zoomLevel;
1889 _applyCurrentZoomPan();
1890
1891 } else {
1892
1893 // handle behaviour for one point (dragging or panning)
1894
1895 if(!_direction) {
1896 return;
1897 }
1898
1899 if(_isFirstMove) {
1900 _isFirstMove = false;
1901
1902 // subtract drag distance that was used during the detection direction
1903
1904 if( Math.abs(delta.x) >= DIRECTION_CHECK_OFFSET) {
1905 delta.x -= _currentPoints[0].x - _startPoint.x;
1906 }
1907
1908 if( Math.abs(delta.y) >= DIRECTION_CHECK_OFFSET) {
1909 delta.y -= _currentPoints[0].y - _startPoint.y;
1910 }
1911 }
1912
1913 _currPoint.x = p.x;
1914 _currPoint.y = p.y;
1915
1916 // do nothing if pointers position hasn't changed
1917 if(delta.x === 0 && delta.y === 0) {
1918 return;
1919 }
1920
1921 if(_direction === 'v' && _options.closeOnVerticalDrag) {
1922 if(!_canPan()) {
1923 _currPanDist.y += delta.y;
1924 _panOffset.y += delta.y;
1925
1926 var opacityRatio = _calculateVerticalDragOpacityRatio();
1927
1928 _verticalDragInitiated = true;
1929 _shout('onVerticalDrag', opacityRatio);
1930
1931 _applyBgOpacity(opacityRatio);
1932 _applyCurrentZoomPan();
1933 return ;
1934 }
1935 }
1936
1937 _pushPosPoint(_getCurrentTime(), p.x, p.y);
1938
1939 _moved = true;
1940 _currPanBounds = self.currItem.bounds;
1941
1942 var mainScrollChanged = _panOrMoveMainScroll('x', delta);
1943 if(!mainScrollChanged) {
1944 _panOrMoveMainScroll('y', delta);
1945
1946 _roundPoint(_panOffset);
1947 _applyCurrentZoomPan();
1948 }
1949
1950 }
1951
1952 },
1953
1954 // Pointerup/pointercancel/touchend/touchcancel/mouseup event handler
1955 _onDragRelease = function(e) {
1956
1957 if(_features.isOldAndroid ) {
1958
1959 if(_oldAndroidTouchEndTimeout && e.type === 'mouseup') {
1960 return;
1961 }
1962
1963 // on Android (v4.1, 4.2, 4.3 & possibly older)
1964 // ghost mousedown/up event isn't preventable via e.preventDefault,
1965 // which causes fake mousedown event
1966 // so we block mousedown/up for 600ms
1967 if( e.type.indexOf('touch') > -1 ) {
1968 clearTimeout(_oldAndroidTouchEndTimeout);
1969 _oldAndroidTouchEndTimeout = setTimeout(function() {
1970 _oldAndroidTouchEndTimeout = 0;
1971 }, 600);
1972 }
1973
1974 }
1975
1976 _shout('pointerUp');
1977
1978 if(_preventDefaultEventBehaviour(e, false)) {
1979 e.preventDefault();
1980 }
1981
1982 var releasePoint;
1983
1984 if(_pointerEventEnabled) {
1985 var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id');
1986
1987 if(pointerIndex > -1) {
1988 releasePoint = _currPointers.splice(pointerIndex, 1)[0];
1989
1990 if(navigator.msPointerEnabled) {
1991 var MSPOINTER_TYPES = {
1992 4: 'mouse', // event.MSPOINTER_TYPE_MOUSE
1993 2: 'touch', // event.MSPOINTER_TYPE_TOUCH
1994 3: 'pen' // event.MSPOINTER_TYPE_PEN
1995 };
1996 releasePoint.type = MSPOINTER_TYPES[e.pointerType];
1997
1998 if(!releasePoint.type) {
1999 releasePoint.type = e.pointerType || 'mouse';
2000 }
2001 } else {
2002 releasePoint.type = e.pointerType || 'mouse';
2003 }
2004
2005 }
2006 }
2007
2008 var touchList = _getTouchPoints(e),
2009 gestureType,
2010 numPoints = touchList.length;
2011
2012 if(e.type === 'mouseup') {
2013 numPoints = 0;
2014 }
2015
2016 // Do nothing if there were 3 touch points or more
2017 if(numPoints === 2) {
2018 _currentPoints = null;
2019 return true;
2020 }
2021
2022 // if second pointer released
2023 if(numPoints === 1) {
2024 _equalizePoints(_startPoint, touchList[0]);
2025 }
2026
2027
2028 // pointer hasn't moved, send "tap release" point
2029 if(numPoints === 0 && !_direction && !_mainScrollAnimating) {
2030 if(!releasePoint) {
2031 if(e.type === 'mouseup') {
2032 releasePoint = {x: e.pageX, y: e.pageY, type:'mouse'};
2033 } else if(e.changedTouches && e.changedTouches[0]) {
2034 releasePoint = {x: e.changedTouches[0].pageX, y: e.changedTouches[0].pageY, type:'touch'};
2035 }
2036 }
2037
2038 _shout('touchRelease', e, releasePoint);
2039 }
2040
2041 // Difference in time between releasing of two last touch points (zoom gesture)
2042 var releaseTimeDiff = -1;
2043
2044 // Gesture completed, no pointers left
2045 if(numPoints === 0) {
2046 _isDragging = false;
2047 framework.unbind(window, _upMoveEvents, self);
2048
2049 _stopDragUpdateLoop();
2050
2051 if(_isZooming) {
2052 // Two points released at the same time
2053 releaseTimeDiff = 0;
2054 } else if(_lastReleaseTime !== -1) {
2055 releaseTimeDiff = _getCurrentTime() - _lastReleaseTime;
2056 }
2057 }
2058 _lastReleaseTime = numPoints === 1 ? _getCurrentTime() : -1;
2059
2060 if(releaseTimeDiff !== -1 && releaseTimeDiff < 150) {
2061 gestureType = 'zoom';
2062 } else {
2063 gestureType = 'swipe';
2064 }
2065
2066 if(_isZooming && numPoints < 2) {
2067 _isZooming = false;
2068
2069 // Only second point released
2070 if(numPoints === 1) {
2071 gestureType = 'zoomPointerUp';
2072 }
2073 _shout('zoomGestureEnded');
2074 }
2075
2076 _currentPoints = null;
2077 if(!_moved && !_zoomStarted && !_mainScrollAnimating && !_verticalDragInitiated) {
2078 // nothing to animate
2079 return;
2080 }
2081
2082 _stopAllAnimations();
2083
2084
2085 if(!_releaseAnimData) {
2086 _releaseAnimData = _initDragReleaseAnimationData();
2087 }
2088
2089 _releaseAnimData.calculateSwipeSpeed('x');
2090
2091
2092 if(_verticalDragInitiated) {
2093
2094 var opacityRatio = _calculateVerticalDragOpacityRatio();
2095
2096 if(opacityRatio < _options.verticalDragRange) {
2097 self.close();
2098 } else {
2099 var initalPanY = _panOffset.y,
2100 initialBgOpacity = _bgOpacity;
2101
2102 _animateProp('verticalDrag', 0, 1, 300, framework.easing.cubic.out, function(now) {
2103
2104 _panOffset.y = (self.currItem.initialPosition.y - initalPanY) * now + initalPanY;
2105
2106 _applyBgOpacity( (1 - initialBgOpacity) * now + initialBgOpacity );
2107 _applyCurrentZoomPan();
2108 });
2109
2110 _shout('onVerticalDrag', 1);
2111 }
2112
2113 return;
2114 }
2115
2116
2117 // main scroll
2118 if( (_mainScrollShifted || _mainScrollAnimating) && numPoints === 0) {
2119 var itemChanged = _finishSwipeMainScrollGesture(gestureType, _releaseAnimData);
2120 if(itemChanged) {
2121 return;
2122 }
2123 gestureType = 'zoomPointerUp';
2124 }
2125
2126 // prevent zoom/pan animation when main scroll animation runs
2127 if(_mainScrollAnimating) {
2128 return;
2129 }
2130
2131 // Complete simple zoom gesture (reset zoom level if it's out of the bounds)
2132 if(gestureType !== 'swipe') {
2133 _completeZoomGesture();
2134 return;
2135 }
2136
2137 // Complete pan gesture if main scroll is not shifted, and it's possible to pan current image
2138 if(!_mainScrollShifted && _currZoomLevel > self.currItem.fitRatio) {
2139 _completePanGesture(_releaseAnimData);
2140 }
2141 },
2142
2143
2144 // Returns object with data about gesture
2145 // It's created only once and then reused
2146 _initDragReleaseAnimationData = function() {
2147 // temp local vars
2148 var lastFlickDuration,
2149 tempReleasePos;
2150
2151 // s = this
2152 var s = {
2153 lastFlickOffset: {},
2154 lastFlickDist: {},
2155 lastFlickSpeed: {},
2156 slowDownRatio: {},
2157 slowDownRatioReverse: {},
2158 speedDecelerationRatio: {},
2159 speedDecelerationRatioAbs: {},
2160 distanceOffset: {},
2161 backAnimDestination: {},
2162 backAnimStarted: {},
2163 calculateSwipeSpeed: function(axis) {
2164
2165
2166 if( _posPoints.length > 1) {
2167 lastFlickDuration = _getCurrentTime() - _gestureCheckSpeedTime + 50;
2168 tempReleasePos = _posPoints[_posPoints.length-2][axis];
2169 } else {
2170 lastFlickDuration = _getCurrentTime() - _gestureStartTime; // total gesture duration
2171 tempReleasePos = _startPoint[axis];
2172 }
2173 s.lastFlickOffset[axis] = _currPoint[axis] - tempReleasePos;
2174 s.lastFlickDist[axis] = Math.abs(s.lastFlickOffset[axis]);
2175 if(s.lastFlickDist[axis] > 20) {
2176 s.lastFlickSpeed[axis] = s.lastFlickOffset[axis] / lastFlickDuration;
2177 } else {
2178 s.lastFlickSpeed[axis] = 0;
2179 }
2180 if( Math.abs(s.lastFlickSpeed[axis]) < 0.1 ) {
2181 s.lastFlickSpeed[axis] = 0;
2182 }
2183
2184 s.slowDownRatio[axis] = 0.95;
2185 s.slowDownRatioReverse[axis] = 1 - s.slowDownRatio[axis];
2186 s.speedDecelerationRatio[axis] = 1;
2187 },
2188
2189 calculateOverBoundsAnimOffset: function(axis, speed) {
2190 if(!s.backAnimStarted[axis]) {
2191
2192 if(_panOffset[axis] > _currPanBounds.min[axis]) {
2193 s.backAnimDestination[axis] = _currPanBounds.min[axis];
2194
2195 } else if(_panOffset[axis] < _currPanBounds.max[axis]) {
2196 s.backAnimDestination[axis] = _currPanBounds.max[axis];
2197 }
2198
2199 if(s.backAnimDestination[axis] !== undefined) {
2200 s.slowDownRatio[axis] = 0.7;
2201 s.slowDownRatioReverse[axis] = 1 - s.slowDownRatio[axis];
2202 if(s.speedDecelerationRatioAbs[axis] < 0.05) {
2203
2204 s.lastFlickSpeed[axis] = 0;
2205 s.backAnimStarted[axis] = true;
2206
2207 _animateProp('bounceZoomPan'+axis,_panOffset[axis],
2208 s.backAnimDestination[axis],
2209 speed || 300,
2210 framework.easing.sine.out,
2211 function(pos) {
2212 _panOffset[axis] = pos;
2213 _applyCurrentZoomPan();
2214 }
2215 );
2216
2217 }
2218 }
2219 }
2220 },
2221
2222 // Reduces the speed by slowDownRatio (per 10ms)
2223 calculateAnimOffset: function(axis) {
2224 if(!s.backAnimStarted[axis]) {
2225 s.speedDecelerationRatio[axis] = s.speedDecelerationRatio[axis] * (s.slowDownRatio[axis] +
2226 s.slowDownRatioReverse[axis] -
2227 s.slowDownRatioReverse[axis] * s.timeDiff / 10);
2228
2229 s.speedDecelerationRatioAbs[axis] = Math.abs(s.lastFlickSpeed[axis] * s.speedDecelerationRatio[axis]);
2230 s.distanceOffset[axis] = s.lastFlickSpeed[axis] * s.speedDecelerationRatio[axis] * s.timeDiff;
2231 _panOffset[axis] += s.distanceOffset[axis];
2232
2233 }
2234 },
2235
2236 panAnimLoop: function() {
2237 if ( _animations.zoomPan ) {
2238 _animations.zoomPan.raf = _requestAF(s.panAnimLoop);
2239
2240 s.now = _getCurrentTime();
2241 s.timeDiff = s.now - s.lastNow;
2242 s.lastNow = s.now;
2243
2244 s.calculateAnimOffset('x');
2245 s.calculateAnimOffset('y');
2246
2247 _applyCurrentZoomPan();
2248
2249 s.calculateOverBoundsAnimOffset('x');
2250 s.calculateOverBoundsAnimOffset('y');
2251
2252
2253 if (s.speedDecelerationRatioAbs.x < 0.05 && s.speedDecelerationRatioAbs.y < 0.05) {
2254
2255 // round pan position
2256 _panOffset.x = Math.round(_panOffset.x);
2257 _panOffset.y = Math.round(_panOffset.y);
2258 _applyCurrentZoomPan();
2259
2260 _stopAnimation('zoomPan');
2261 return;
2262 }
2263 }
2264
2265 }
2266 };
2267 return s;
2268 },
2269
2270 _completePanGesture = function(animData) {
2271 // calculate swipe speed for Y axis (paanning)
2272 animData.calculateSwipeSpeed('y');
2273
2274 _currPanBounds = self.currItem.bounds;
2275
2276 animData.backAnimDestination = {};
2277 animData.backAnimStarted = {};
2278
2279 // Avoid acceleration animation if speed is too low
2280 if(Math.abs(animData.lastFlickSpeed.x) <= 0.05 && Math.abs(animData.lastFlickSpeed.y) <= 0.05 ) {
2281 animData.speedDecelerationRatioAbs.x = animData.speedDecelerationRatioAbs.y = 0;
2282
2283 // Run pan drag release animation. E.g. if you drag image and release finger without momentum.
2284 animData.calculateOverBoundsAnimOffset('x');
2285 animData.calculateOverBoundsAnimOffset('y');
2286 return true;
2287 }
2288
2289 // Animation loop that controls the acceleration after pan gesture ends
2290 _registerStartAnimation('zoomPan');
2291 animData.lastNow = _getCurrentTime();
2292 animData.panAnimLoop();
2293 },
2294
2295
2296 _finishSwipeMainScrollGesture = function(gestureType, _releaseAnimData) {
2297 var itemChanged;
2298 if(!_mainScrollAnimating) {
2299 _currZoomedItemIndex = _currentItemIndex;
2300 }
2301
2302
2303
2304 var itemsDiff;
2305
2306 if(gestureType === 'swipe') {
2307 var totalShiftDist = _currPoint.x - _startPoint.x,
2308 isFastLastFlick = _releaseAnimData.lastFlickDist.x < 10;
2309
2310 // if container is shifted for more than MIN_SWIPE_DISTANCE,
2311 // and last flick gesture was in right direction
2312 if(totalShiftDist > MIN_SWIPE_DISTANCE &&
2313 (isFastLastFlick || _releaseAnimData.lastFlickOffset.x > 20) ) {
2314 // go to prev item
2315 itemsDiff = -1;
2316 } else if(totalShiftDist < -MIN_SWIPE_DISTANCE &&
2317 (isFastLastFlick || _releaseAnimData.lastFlickOffset.x < -20) ) {
2318 // go to next item
2319 itemsDiff = 1;
2320 }
2321 }
2322
2323 var nextCircle;
2324
2325 if(itemsDiff) {
2326
2327 _currentItemIndex += itemsDiff;
2328
2329 if(_currentItemIndex < 0) {
2330 _currentItemIndex = _options.loop ? _getNumItems()-1 : 0;
2331 nextCircle = true;
2332 } else if(_currentItemIndex >= _getNumItems()) {
2333 _currentItemIndex = _options.loop ? 0 : _getNumItems()-1;
2334 nextCircle = true;
2335 }
2336
2337 if(!nextCircle || _options.loop) {
2338 _indexDiff += itemsDiff;
2339 _currPositionIndex -= itemsDiff;
2340 itemChanged = true;
2341 }
2342
2343
2344
2345 }
2346
2347 var animateToX = _slideSize.x * _currPositionIndex;
2348 var animateToDist = Math.abs( animateToX - _mainScrollPos.x );
2349 var finishAnimDuration;
2350
2351
2352 if(!itemChanged && animateToX > _mainScrollPos.x !== _releaseAnimData.lastFlickSpeed.x > 0) {
2353 // "return to current" duration, e.g. when dragging from slide 0 to -1
2354 finishAnimDuration = 333;
2355 } else {
2356 finishAnimDuration = Math.abs(_releaseAnimData.lastFlickSpeed.x) > 0 ?
2357 animateToDist / Math.abs(_releaseAnimData.lastFlickSpeed.x) :
2358 333;
2359
2360 finishAnimDuration = Math.min(finishAnimDuration, 400);
2361 finishAnimDuration = Math.max(finishAnimDuration, 250);
2362 }
2363
2364 if(_currZoomedItemIndex === _currentItemIndex) {
2365 itemChanged = false;
2366 }
2367
2368 _mainScrollAnimating = true;
2369
2370 _shout('mainScrollAnimStart');
2371
2372 _animateProp('mainScroll', _mainScrollPos.x, animateToX, finishAnimDuration, framework.easing.cubic.out,
2373 _moveMainScroll,
2374 function() {
2375 _stopAllAnimations();
2376 _mainScrollAnimating = false;
2377 _currZoomedItemIndex = -1;
2378
2379 if(itemChanged || _currZoomedItemIndex !== _currentItemIndex) {
2380 self.updateCurrItem();
2381 }
2382
2383 _shout('mainScrollAnimComplete');
2384 }
2385 );
2386
2387 if(itemChanged) {
2388 self.updateCurrItem(true);
2389 }
2390
2391 return itemChanged;
2392 },
2393
2394 _calculateZoomLevel = function(touchesDistance) {
2395 return 1 / _startPointsDistance * touchesDistance * _startZoomLevel;
2396 },
2397
2398 // Resets zoom if it's out of bounds
2399 _completeZoomGesture = function() {
2400 var destZoomLevel = _currZoomLevel,
2401 minZoomLevel = _getMinZoomLevel(),
2402 maxZoomLevel = _getMaxZoomLevel();
2403
2404 if ( _currZoomLevel < minZoomLevel ) {
2405 destZoomLevel = minZoomLevel;
2406 } else if ( _currZoomLevel > maxZoomLevel ) {
2407 destZoomLevel = maxZoomLevel;
2408 }
2409
2410 var destOpacity = 1,
2411 onUpdate,
2412 initialOpacity = _bgOpacity;
2413
2414 if(_opacityChanged && !_isZoomingIn && !_wasOverInitialZoom && _currZoomLevel < minZoomLevel) {
2415 //_closedByScroll = true;
2416 self.close();
2417 return true;
2418 }
2419
2420 if(_opacityChanged) {
2421 onUpdate = function(now) {
2422 _applyBgOpacity( (destOpacity - initialOpacity) * now + initialOpacity );
2423 };
2424 }
2425
2426 self.zoomTo(destZoomLevel, 0, 200, framework.easing.cubic.out, onUpdate);
2427 return true;
2428 };
2429
2430
2431 _registerModule('Gestures', {
2432 publicMethods: {
2433
2434 initGestures: function() {
2435
2436 // helper function that builds touch/pointer/mouse events
2437 var addEventNames = function(pref, down, move, up, cancel) {
2438 _dragStartEvent = pref + down;
2439 _dragMoveEvent = pref + move;
2440 _dragEndEvent = pref + up;
2441 if(cancel) {
2442 _dragCancelEvent = pref + cancel;
2443 } else {
2444 _dragCancelEvent = '';
2445 }
2446 };
2447
2448 _pointerEventEnabled = _features.pointerEvent;
2449 if(_pointerEventEnabled && _features.touch) {
2450 // we don't need touch events, if browser supports pointer events
2451 _features.touch = false;
2452 }
2453
2454 if(_pointerEventEnabled) {
2455 if(navigator.msPointerEnabled) {
2456 // IE10 pointer events are case-sensitive
2457 addEventNames('MSPointer', 'Down', 'Move', 'Up', 'Cancel');
2458 } else {
2459 addEventNames('pointer', 'down', 'move', 'up', 'cancel');
2460 }
2461 } else if(_features.touch) {
2462 addEventNames('touch', 'start', 'move', 'end', 'cancel');
2463 _likelyTouchDevice = true;
2464 } else {
2465 addEventNames('mouse', 'down', 'move', 'up');
2466 }
2467
2468 _upMoveEvents = _dragMoveEvent + ' ' + _dragEndEvent + ' ' + _dragCancelEvent;
2469 _downEvents = _dragStartEvent;
2470
2471 if(_pointerEventEnabled && !_likelyTouchDevice) {
2472 _likelyTouchDevice = (navigator.maxTouchPoints > 1) || (navigator.msMaxTouchPoints > 1);
2473 }
2474 // make variable public
2475 self.likelyTouchDevice = _likelyTouchDevice;
2476
2477 _globalEventHandlers[_dragStartEvent] = _onDragStart;
2478 _globalEventHandlers[_dragMoveEvent] = _onDragMove;
2479 _globalEventHandlers[_dragEndEvent] = _onDragRelease; // the Kraken
2480
2481 if(_dragCancelEvent) {
2482 _globalEventHandlers[_dragCancelEvent] = _globalEventHandlers[_dragEndEvent];
2483 }
2484
2485 // Bind mouse events on device with detected hardware touch support, in case it supports multiple types of input.
2486 if(_features.touch) {
2487 _downEvents += ' mousedown';
2488 _upMoveEvents += ' mousemove mouseup';
2489 _globalEventHandlers.mousedown = _globalEventHandlers[_dragStartEvent];
2490 _globalEventHandlers.mousemove = _globalEventHandlers[_dragMoveEvent];
2491 _globalEventHandlers.mouseup = _globalEventHandlers[_dragEndEvent];
2492 }
2493
2494 if(!_likelyTouchDevice) {
2495 // don't allow pan to next slide from zoomed state on Desktop
2496 _options.allowPanToNext = false;
2497 }
2498 }
2499
2500 }
2501 });
2502
2503
2504 /*>>gestures*/
2505
2506 /*>>show-hide-transition*/
2507 /**
2508 * show-hide-transition.js:
2509 *
2510 * Manages initial opening or closing transition.
2511 *
2512 * If you're not planning to use transition for gallery at all,
2513 * you may set options hideAnimationDuration and showAnimationDuration to 0,
2514 * and just delete startAnimation function.
2515 *
2516 */
2517
2518
2519 var _showOrHideTimeout,
2520 _showOrHide = function(item, img, out, completeFn) {
2521
2522 if(_showOrHideTimeout) {
2523 clearTimeout(_showOrHideTimeout);
2524 }
2525
2526 _initialZoomRunning = true;
2527 _initialContentSet = true;
2528
2529 // dimensions of small thumbnail {x:,y:,w:}.
2530 // Height is optional, as calculated based on large image.
2531 var thumbBounds;
2532 if(item.initialLayout) {
2533 thumbBounds = item.initialLayout;
2534 item.initialLayout = null;
2535 } else {
2536 thumbBounds = _options.getThumbBoundsFn && _options.getThumbBoundsFn(_currentItemIndex);
2537 }
2538
2539 var duration = out ? _options.hideAnimationDuration : _options.showAnimationDuration;
2540
2541 var onComplete = function() {
2542 _stopAnimation('initialZoom');
2543 if(!out) {
2544 _applyBgOpacity(1);
2545 if(img) {
2546 img.style.display = 'block';
2547 }
2548 framework.addClass(template, 'pswp--animated-in');
2549 _shout('initialZoom' + (out ? 'OutEnd' : 'InEnd'));
2550 } else {
2551 self.template.removeAttribute('style');
2552 self.bg.removeAttribute('style');
2553 }
2554
2555 if(completeFn) {
2556 completeFn();
2557 }
2558 _initialZoomRunning = false;
2559 };
2560
2561 // if bounds aren't provided, just open gallery without animation
2562 if(!duration || !thumbBounds || thumbBounds.x === undefined) {
2563
2564 _shout('initialZoom' + (out ? 'Out' : 'In') );
2565
2566 _currZoomLevel = item.initialZoomLevel;
2567 _equalizePoints(_panOffset, item.initialPosition );
2568 _applyCurrentZoomPan();
2569
2570 template.style.opacity = out ? 0 : 1;
2571 _applyBgOpacity(1);
2572
2573 if(duration) {
2574 setTimeout(function() {
2575 onComplete();
2576 }, duration);
2577 } else {
2578 onComplete();
2579 }
2580
2581 return;
2582 }
2583
2584 var startAnimation = function() {
2585 var closeWithRaf = _closedByScroll,
2586 fadeEverything = !self.currItem.src || self.currItem.loadError || _options.showHideOpacity;
2587
2588 // apply hw-acceleration to image
2589 if(item.miniImg) {
2590 item.miniImg.style.webkitBackfaceVisibility = 'hidden';
2591 }
2592
2593 if(!out) {
2594 _currZoomLevel = thumbBounds.w / item.w;
2595 _panOffset.x = thumbBounds.x;
2596 _panOffset.y = thumbBounds.y - _initalWindowScrollY;
2597
2598 self[fadeEverything ? 'template' : 'bg'].style.opacity = 0.001;
2599 _applyCurrentZoomPan();
2600 }
2601
2602 _registerStartAnimation('initialZoom');
2603
2604 if(out && !closeWithRaf) {
2605 framework.removeClass(template, 'pswp--animated-in');
2606 }
2607
2608 if(fadeEverything) {
2609 if(out) {
2610 framework[ (closeWithRaf ? 'remove' : 'add') + 'Class' ](template, 'pswp--animate_opacity');
2611 } else {
2612 setTimeout(function() {
2613 framework.addClass(template, 'pswp--animate_opacity');
2614 }, 30);
2615 }
2616 }
2617
2618 _showOrHideTimeout = setTimeout(function() {
2619
2620 _shout('initialZoom' + (out ? 'Out' : 'In') );
2621
2622
2623 if(!out) {
2624
2625 // "in" animation always uses CSS transitions (instead of rAF).
2626 // CSS transition work faster here,
2627 // as developer may also want to animate other things,
2628 // like ui on top of sliding area, which can be animated just via CSS
2629
2630 _currZoomLevel = item.initialZoomLevel;
2631 _equalizePoints(_panOffset, item.initialPosition );
2632 _applyCurrentZoomPan();
2633 _applyBgOpacity(1);
2634
2635 if(fadeEverything) {
2636 template.style.opacity = 1;
2637 } else {
2638 _applyBgOpacity(1);
2639 }
2640
2641 _showOrHideTimeout = setTimeout(onComplete, duration + 20);
2642 } else {
2643
2644 // "out" animation uses rAF only when PhotoSwipe is closed by browser scroll, to recalculate position
2645 var destZoomLevel = thumbBounds.w / item.w,
2646 initialPanOffset = {
2647 x: _panOffset.x,
2648 y: _panOffset.y
2649 },
2650 initialZoomLevel = _currZoomLevel,
2651 initalBgOpacity = _bgOpacity,
2652 onUpdate = function(now) {
2653
2654 if(now === 1) {
2655 _currZoomLevel = destZoomLevel;
2656 _panOffset.x = thumbBounds.x;
2657 _panOffset.y = thumbBounds.y - _currentWindowScrollY;
2658 } else {
2659 _currZoomLevel = (destZoomLevel - initialZoomLevel) * now + initialZoomLevel;
2660 _panOffset.x = (thumbBounds.x - initialPanOffset.x) * now + initialPanOffset.x;
2661 _panOffset.y = (thumbBounds.y - _currentWindowScrollY - initialPanOffset.y) * now + initialPanOffset.y;
2662 }
2663
2664 _applyCurrentZoomPan();
2665 if(fadeEverything) {
2666 template.style.opacity = 1 - now;
2667 } else {
2668 _applyBgOpacity( initalBgOpacity - now * initalBgOpacity );
2669 }
2670 };
2671
2672 if(closeWithRaf) {
2673 _animateProp('initialZoom', 0, 1, duration, framework.easing.cubic.out, onUpdate, onComplete);
2674 } else {
2675 onUpdate(1);
2676 _showOrHideTimeout = setTimeout(onComplete, duration + 20);
2677 }
2678 }
2679
2680 }, out ? 25 : 90); // Main purpose of this delay is to give browser time to paint and
2681 // create composite layers of PhotoSwipe UI parts (background, controls, caption, arrows).
2682 // Which avoids lag at the beginning of scale transition.
2683 };
2684 startAnimation();
2685
2686
2687 };
2688
2689 /*>>show-hide-transition*/
2690
2691 /*>>items-controller*/
2692 /**
2693 *
2694 * Controller manages gallery items, their dimensions, and their content.
2695 *
2696 */
2697
2698 var _items,
2699 _tempPanAreaSize = {},
2700 _imagesToAppendPool = [],
2701 _initialContentSet,
2702 _initialZoomRunning,
2703 _controllerDefaultOptions = {
2704 index: 0,
2705 errorMsg: '<div class="pswp__error-msg"><a href="%url%" target="_blank">The image</a> could not be loaded.</div>',
2706 forceProgressiveLoading: false, // TODO
2707 preload: [1,1],
2708 getNumItemsFn: function() {
2709 return _items.length;
2710 }
2711 };
2712
2713
2714 var _getItemAt,
2715 _getNumItems,
2716 _initialIsLoop,
2717 _getZeroBounds = function() {
2718 return {
2719 center:{x:0,y:0},
2720 max:{x:0,y:0},
2721 min:{x:0,y:0}
2722 };
2723 },
2724 _calculateSingleItemPanBounds = function(item, realPanElementW, realPanElementH ) {
2725 var bounds = item.bounds;
2726
2727 // position of element when it's centered
2728 bounds.center.x = Math.round((_tempPanAreaSize.x - realPanElementW) / 2);
2729 bounds.center.y = Math.round((_tempPanAreaSize.y - realPanElementH) / 2) + item.vGap.top;
2730
2731 // maximum pan position
2732 bounds.max.x = (realPanElementW > _tempPanAreaSize.x) ?
2733 Math.round(_tempPanAreaSize.x - realPanElementW) :
2734 bounds.center.x;
2735
2736 bounds.max.y = (realPanElementH > _tempPanAreaSize.y) ?
2737 Math.round(_tempPanAreaSize.y - realPanElementH) + item.vGap.top :
2738 bounds.center.y;
2739
2740 // minimum pan position
2741 bounds.min.x = (realPanElementW > _tempPanAreaSize.x) ? 0 : bounds.center.x;
2742 bounds.min.y = (realPanElementH > _tempPanAreaSize.y) ? item.vGap.top : bounds.center.y;
2743 },
2744 _calculateItemSize = function(item, viewportSize, zoomLevel) {
2745
2746 if (item.src && !item.loadError) {
2747 var isInitial = !zoomLevel;
2748
2749 if(isInitial) {
2750 if(!item.vGap) {
2751 item.vGap = {top:0,bottom:0};
2752 }
2753 // allows overriding vertical margin for individual items
2754 _shout('parseVerticalMargin', item);
2755 }
2756
2757
2758 _tempPanAreaSize.x = viewportSize.x;
2759 _tempPanAreaSize.y = viewportSize.y - item.vGap.top - item.vGap.bottom;
2760
2761 if (isInitial) {
2762 var hRatio = _tempPanAreaSize.x / item.w;
2763 var vRatio = _tempPanAreaSize.y / item.h;
2764
2765 item.fitRatio = hRatio < vRatio ? hRatio : vRatio;
2766 //item.fillRatio = hRatio > vRatio ? hRatio : vRatio;
2767
2768 var scaleMode = _options.scaleMode;
2769
2770 if (scaleMode === 'orig') {
2771 zoomLevel = 1;
2772 } else if (scaleMode === 'fit') {
2773 zoomLevel = item.fitRatio;
2774 }
2775
2776 if (zoomLevel > 1) {
2777 zoomLevel = 1;
2778 }
2779
2780 item.initialZoomLevel = zoomLevel;
2781
2782 if(!item.bounds) {
2783 // reuse bounds object
2784 item.bounds = _getZeroBounds();
2785 }
2786 }
2787
2788 if(!zoomLevel) {
2789 return;
2790 }
2791
2792 _calculateSingleItemPanBounds(item, item.w * zoomLevel, item.h * zoomLevel);
2793
2794 if (isInitial && zoomLevel === item.initialZoomLevel) {
2795 item.initialPosition = item.bounds.center;
2796 }
2797
2798 return item.bounds;
2799 } else {
2800 item.w = item.h = 0;
2801 item.initialZoomLevel = item.fitRatio = 1;
2802 item.bounds = _getZeroBounds();
2803 item.initialPosition = item.bounds.center;
2804
2805 // if it's not image, we return zero bounds (content is not zoomable)
2806 return item.bounds;
2807 }
2808
2809 },
2810
2811
2812
2813
2814 _appendImage = function(index, item, baseDiv, img, preventAnimation, keepPlaceholder) {
2815
2816
2817 if(item.loadError) {
2818 return;
2819 }
2820
2821 if(img) {
2822
2823 item.imageAppended = true;
2824 _setImageSize(item, img, (item === self.currItem && _renderMaxResolution) );
2825
2826 baseDiv.appendChild(img);
2827
2828 if(keepPlaceholder) {
2829 setTimeout(function() {
2830 if(item && item.loaded && item.placeholder) {
2831 item.placeholder.style.display = 'none';
2832 item.placeholder = null;
2833 }
2834 }, 500);
2835 }
2836 }
2837 },
2838
2839
2840
2841 _preloadImage = function(item) {
2842 item.loading = true;
2843 item.loaded = false;
2844 var img = item.img = framework.createEl('pswp__img', 'img');
2845 var onComplete = function() {
2846 item.loading = false;
2847 item.loaded = true;
2848
2849 if(item.loadComplete) {
2850 item.loadComplete(item);
2851 } else {
2852 item.img = null; // no need to store image object
2853 }
2854 img.onload = img.onerror = null;
2855 img = null;
2856 };
2857 img.onload = onComplete;
2858 img.onerror = function() {
2859 item.loadError = true;
2860 onComplete();
2861 };
2862
2863 img.src = item.src;// + '?a=' + Math.random();
2864
2865 return img;
2866 },
2867 _checkForError = function(item, cleanUp) {
2868 if(item.src && item.loadError && item.container) {
2869
2870 if(cleanUp) {
2871 item.container.innerHTML = '';
2872 }
2873
2874 item.container.innerHTML = _options.errorMsg.replace('%url%', item.src );
2875 return true;
2876
2877 }
2878 },
2879 _setImageSize = function(item, img, maxRes) {
2880 if(!item.src) {
2881 return;
2882 }
2883
2884 if(!img) {
2885 img = item.container.lastChild;
2886 }
2887
2888 var w = maxRes ? item.w : Math.round(item.w * item.fitRatio),
2889 h = maxRes ? item.h : Math.round(item.h * item.fitRatio);
2890
2891 if(item.placeholder && !item.loaded) {
2892 item.placeholder.style.width = w + 'px';
2893 item.placeholder.style.height = h + 'px';
2894 }
2895
2896 img.style.width = w + 'px';
2897 img.style.height = h + 'px';
2898 },
2899 _appendImagesPool = function() {
2900
2901 if(_imagesToAppendPool.length) {
2902 var poolItem;
2903
2904 for(var i = 0; i < _imagesToAppendPool.length; i++) {
2905 poolItem = _imagesToAppendPool[i];
2906 if( poolItem.holder.index === poolItem.index ) {
2907 _appendImage(poolItem.index, poolItem.item, poolItem.baseDiv, poolItem.img, false, poolItem.clearPlaceholder);
2908 }
2909 }
2910 _imagesToAppendPool = [];
2911 }
2912 };
2913
2914
2915
2916 _registerModule('Controller', {
2917
2918 publicMethods: {
2919
2920 lazyLoadItem: function(index) {
2921 index = _getLoopedId(index);
2922 var item = _getItemAt(index);
2923
2924 if(!item || ((item.loaded || item.loading) && !_itemsNeedUpdate)) {
2925 return;
2926 }
2927
2928 _shout('gettingData', index, item);
2929
2930 if (!item.src) {
2931 return;
2932 }
2933
2934 _preloadImage(item);
2935 },
2936 initController: function() {
2937 framework.extend(_options, _controllerDefaultOptions, true);
2938 self.items = _items = items;
2939 _getItemAt = self.getItemAt;
2940 _getNumItems = _options.getNumItemsFn; //self.getNumItems;
2941
2942
2943
2944 _initialIsLoop = _options.loop;
2945 if(_getNumItems() < 3) {
2946 _options.loop = false; // disable loop if less then 3 items
2947 }
2948
2949 _listen('beforeChange', function(diff) {
2950
2951 var p = _options.preload,
2952 isNext = diff === null ? true : (diff >= 0),
2953 preloadBefore = Math.min(p[0], _getNumItems() ),
2954 preloadAfter = Math.min(p[1], _getNumItems() ),
2955 i;
2956
2957
2958 for(i = 1; i <= (isNext ? preloadAfter : preloadBefore); i++) {
2959 self.lazyLoadItem(_currentItemIndex+i);
2960 }
2961 for(i = 1; i <= (isNext ? preloadBefore : preloadAfter); i++) {
2962 self.lazyLoadItem(_currentItemIndex-i);
2963 }
2964 });
2965
2966 _listen('initialLayout', function() {
2967 self.currItem.initialLayout = _options.getThumbBoundsFn && _options.getThumbBoundsFn(_currentItemIndex);
2968 });
2969
2970 _listen('mainScrollAnimComplete', _appendImagesPool);
2971 _listen('initialZoomInEnd', _appendImagesPool);
2972
2973
2974
2975 _listen('destroy', function() {
2976 var item;
2977 for(var i = 0; i < _items.length; i++) {
2978 item = _items[i];
2979 // remove reference to DOM elements, for GC
2980 if(item.container) {
2981 item.container = null;
2982 }
2983 if(item.placeholder) {
2984 item.placeholder = null;
2985 }
2986 if(item.img) {
2987 item.img = null;
2988 }
2989 if(item.preloader) {
2990 item.preloader = null;
2991 }
2992 if(item.loadError) {
2993 item.loaded = item.loadError = false;
2994 }
2995 }
2996 _imagesToAppendPool = null;
2997 });
2998 },
2999
3000
3001 getItemAt: function(index) {
3002 if (index >= 0) {
3003 return _items[index] !== undefined ? _items[index] : false;
3004 }
3005 return false;
3006 },
3007
3008 allowProgressiveImg: function() {
3009 // 1. Progressive image loading isn't working on webkit/blink
3010 // when hw-acceleration (e.g. translateZ) is applied to IMG element.
3011 // That's why in PhotoSwipe parent element gets zoom transform, not image itself.
3012 //
3013 // 2. Progressive image loading sometimes blinks in webkit/blink when applying animation to parent element.
3014 // That's why it's disabled on touch devices (mainly because of swipe transition)
3015 //
3016 // 3. Progressive image loading sometimes doesn't work in IE (up to 11).
3017
3018 // Don't allow progressive loading on non-large touch devices
3019 return _options.forceProgressiveLoading || !_likelyTouchDevice || _options.mouseUsed || screen.width > 1200;
3020 // 1200 - to eliminate touch devices with large screen (like Chromebook Pixel)
3021 },
3022
3023 setContent: function(holder, index) {
3024
3025 if(_options.loop) {
3026 index = _getLoopedId(index);
3027 }
3028
3029 var prevItem = self.getItemAt(holder.index);
3030 if(prevItem) {
3031 prevItem.container = null;
3032 }
3033
3034 var item = self.getItemAt(index),
3035 img;
3036
3037 if(!item) {
3038 holder.el.innerHTML = '';
3039 return;
3040 }
3041
3042 // allow to override data
3043 _shout('gettingData', index, item);
3044
3045 holder.index = index;
3046 holder.item = item;
3047
3048 // base container DIV is created only once for each of 3 holders
3049 var baseDiv = item.container = framework.createEl('pswp__zoom-wrap');
3050
3051
3052
3053 if(!item.src && item.html) {
3054 if(item.html.tagName) {
3055 baseDiv.appendChild(item.html);
3056 } else {
3057 baseDiv.innerHTML = item.html;
3058 }
3059 }
3060
3061 _checkForError(item);
3062
3063 _calculateItemSize(item, _viewportSize);
3064
3065 if(item.src && !item.loadError && !item.loaded) {
3066
3067 item.loadComplete = function(item) {
3068
3069 // gallery closed before image finished loading
3070 if(!_isOpen) {
3071 return;
3072 }
3073
3074 // check if holder hasn't changed while image was loading
3075 if(holder && holder.index === index ) {
3076 if( _checkForError(item, true) ) {
3077 item.loadComplete = item.img = null;
3078 _calculateItemSize(item, _viewportSize);
3079 _applyZoomPanToItem(item);
3080
3081 if(holder.index === _currentItemIndex) {
3082 // recalculate dimensions
3083 self.updateCurrZoomItem();
3084 }
3085 return;
3086 }
3087 if( !item.imageAppended ) {
3088 if(_features.transform && (_mainScrollAnimating || _initialZoomRunning) ) {
3089 _imagesToAppendPool.push({
3090 item:item,
3091 baseDiv:baseDiv,
3092 img:item.img,
3093 index:index,
3094 holder:holder,
3095 clearPlaceholder:true
3096 });
3097 } else {
3098 _appendImage(index, item, baseDiv, item.img, _mainScrollAnimating || _initialZoomRunning, true);
3099 }
3100 } else {
3101 // remove preloader & mini-img
3102 if(!_initialZoomRunning && item.placeholder) {
3103 item.placeholder.style.display = 'none';
3104 item.placeholder = null;
3105 }
3106 }
3107 }
3108
3109 item.loadComplete = null;
3110 item.img = null; // no need to store image element after it's added
3111
3112 _shout('imageLoadComplete', index, item);
3113 };
3114
3115 if(framework.features.transform) {
3116
3117 var placeholderClassName = 'pswp__img pswp__img--placeholder';
3118 placeholderClassName += (item.msrc ? '' : ' pswp__img--placeholder--blank');
3119
3120 var placeholder = framework.createEl(placeholderClassName, item.msrc ? 'img' : '');
3121 if(item.msrc) {
3122 placeholder.src = item.msrc;
3123 }
3124
3125 _setImageSize(item, placeholder);
3126
3127 baseDiv.appendChild(placeholder);
3128 item.placeholder = placeholder;
3129
3130 }
3131
3132
3133
3134
3135 if(!item.loading) {
3136 _preloadImage(item);
3137 }
3138
3139
3140 if( self.allowProgressiveImg() ) {
3141 // just append image
3142 if(!_initialContentSet && _features.transform) {
3143 _imagesToAppendPool.push({
3144 item:item,
3145 baseDiv:baseDiv,
3146 img:item.img,
3147 index:index,
3148 holder:holder
3149 });
3150 } else {
3151 _appendImage(index, item, baseDiv, item.img, true, true);
3152 }
3153 }
3154
3155 } else if(item.src && !item.loadError) {
3156 // image object is created every time, due to bugs of image loading & delay when switching images
3157 img = framework.createEl('pswp__img', 'img');
3158 img.style.opacity = 1;
3159 img.src = item.src;
3160 _setImageSize(item, img);
3161 _appendImage(index, item, baseDiv, img, true);
3162 }
3163
3164
3165 if(!_initialContentSet && index === _currentItemIndex) {
3166 _currZoomElementStyle = baseDiv.style;
3167 _showOrHide(item, (img ||item.img) );
3168 } else {
3169 _applyZoomPanToItem(item);
3170 }
3171
3172 holder.el.innerHTML = '';
3173 holder.el.appendChild(baseDiv);
3174 },
3175
3176 cleanSlide: function( item ) {
3177 if(item.img ) {
3178 item.img.onload = item.img.onerror = null;
3179 }
3180 item.loaded = item.loading = item.img = item.imageAppended = false;
3181 }
3182
3183 }
3184 });
3185
3186 /*>>items-controller*/
3187
3188 /*>>tap*/
3189 /**
3190 * tap.js:
3191 *
3192 * Displatches tap and double-tap events.
3193 *
3194 */
3195
3196 var tapTimer,
3197 tapReleasePoint = {},
3198 _dispatchTapEvent = function(origEvent, releasePoint, pointerType) {
3199 var e = document.createEvent( 'CustomEvent' ),
3200 eDetail = {
3201 origEvent:origEvent,
3202 target:origEvent.target,
3203 releasePoint: releasePoint,
3204 pointerType:pointerType || 'touch'
3205 };
3206
3207 e.initCustomEvent( 'pswpTap', true, true, eDetail );
3208 origEvent.target.dispatchEvent(e);
3209 };
3210
3211 _registerModule('Tap', {
3212 publicMethods: {
3213 initTap: function() {
3214 _listen('firstTouchStart', self.onTapStart);
3215 _listen('touchRelease', self.onTapRelease);
3216 _listen('destroy', function() {
3217 tapReleasePoint = {};
3218 tapTimer = null;
3219 });
3220 },
3221 onTapStart: function(touchList) {
3222 if(touchList.length > 1) {
3223 clearTimeout(tapTimer);
3224 tapTimer = null;
3225 }
3226 },
3227 onTapRelease: function(e, releasePoint) {
3228 if(!releasePoint) {
3229 return;
3230 }
3231
3232 if(!_moved && !_isMultitouch && !_numAnimations) {
3233 var p0 = releasePoint;
3234 if(tapTimer) {
3235 clearTimeout(tapTimer);
3236 tapTimer = null;
3237
3238 // Check if taped on the same place
3239 if ( _isNearbyPoints(p0, tapReleasePoint) ) {
3240 _shout('doubleTap', p0);
3241 return;
3242 }
3243 }
3244
3245 if(releasePoint.type === 'mouse') {
3246 _dispatchTapEvent(e, releasePoint, 'mouse');
3247 return;
3248 }
3249
3250 var clickedTagName = e.target.tagName.toUpperCase();
3251 // avoid double tap delay on buttons and elements that have class pswp__single-tap
3252 if(clickedTagName === 'BUTTON' || framework.hasClass(e.target, 'pswp__single-tap') ) {
3253 _dispatchTapEvent(e, releasePoint);
3254 return;
3255 }
3256
3257 _equalizePoints(tapReleasePoint, p0);
3258
3259 tapTimer = setTimeout(function() {
3260 _dispatchTapEvent(e, releasePoint);
3261 tapTimer = null;
3262 }, 300);
3263 }
3264 }
3265 }
3266 });
3267
3268 /*>>tap*/
3269
3270 /*>>desktop-zoom*/
3271 /**
3272 *
3273 * desktop-zoom.js:
3274 *
3275 * - Binds mousewheel event for paning zoomed image.
3276 * - Manages "dragging", "zoomed-in", "zoom-out" classes.
3277 * (which are used for cursors and zoom icon)
3278 * - Adds toggleDesktopZoom function.
3279 *
3280 */
3281
3282 var _wheelDelta;
3283
3284 _registerModule('DesktopZoom', {
3285
3286 publicMethods: {
3287
3288 initDesktopZoom: function() {
3289
3290 if(_oldIE) {
3291 // no zoom for old IE (<=8)
3292 return;
3293 }
3294
3295 if(_likelyTouchDevice) {
3296 // if detected hardware touch support, we wait until mouse is used,
3297 // and only then apply desktop-zoom features
3298 _listen('mouseUsed', function() {
3299 self.setupDesktopZoom();
3300 });
3301 } else {
3302 self.setupDesktopZoom(true);
3303 }
3304
3305 },
3306
3307 setupDesktopZoom: function(onInit) {
3308
3309 _wheelDelta = {};
3310
3311 var events = 'wheel mousewheel DOMMouseScroll';
3312
3313 _listen('bindEvents', function() {
3314 framework.bind(template, events, self.handleMouseWheel);
3315 });
3316
3317 _listen('unbindEvents', function() {
3318 if(_wheelDelta) {
3319 framework.unbind(template, events, self.handleMouseWheel);
3320 }
3321 });
3322
3323 self.mouseZoomedIn = false;
3324
3325 var hasDraggingClass,
3326 updateZoomable = function() {
3327 if(self.mouseZoomedIn) {
3328 framework.removeClass(template, 'pswp--zoomed-in');
3329 self.mouseZoomedIn = false;
3330 }
3331 if(_currZoomLevel < 1) {
3332 framework.addClass(template, 'pswp--zoom-allowed');
3333 } else {
3334 framework.removeClass(template, 'pswp--zoom-allowed');
3335 }
3336 removeDraggingClass();
3337 },
3338 removeDraggingClass = function() {
3339 if(hasDraggingClass) {
3340 framework.removeClass(template, 'pswp--dragging');
3341 hasDraggingClass = false;
3342 }
3343 };
3344
3345 _listen('resize' , updateZoomable);
3346 _listen('afterChange' , updateZoomable);
3347 _listen('pointerDown', function() {
3348 if(self.mouseZoomedIn) {
3349 hasDraggingClass = true;
3350 framework.addClass(template, 'pswp--dragging');
3351 }
3352 });
3353 _listen('pointerUp', removeDraggingClass);
3354
3355 if(!onInit) {
3356 updateZoomable();
3357 }
3358
3359 },
3360
3361 handleMouseWheel: function(e) {
3362
3363 if(_currZoomLevel <= self.currItem.fitRatio) {
3364 if( _options.modal ) {
3365
3366 if (!_options.closeOnScroll || _numAnimations || _isDragging) {
3367 e.preventDefault();
3368 } else if(_transformKey && Math.abs(e.deltaY) > 2) {
3369 // close PhotoSwipe
3370 // if browser supports transforms & scroll changed enough
3371 _closedByScroll = true;
3372 self.close();
3373 }
3374
3375 }
3376 return true;
3377 }
3378
3379 // allow just one event to fire
3380 e.stopPropagation();
3381
3382 // https://developer.mozilla.org/en-US/docs/Web/Events/wheel
3383 _wheelDelta.x = 0;
3384
3385 if('deltaX' in e) {
3386 if(e.deltaMode === 1 /* DOM_DELTA_LINE */) {
3387 // 18 - average line height
3388 _wheelDelta.x = e.deltaX * 18;
3389 _wheelDelta.y = e.deltaY * 18;
3390 } else {
3391 _wheelDelta.x = e.deltaX;
3392 _wheelDelta.y = e.deltaY;
3393 }
3394 } else if('wheelDelta' in e) {
3395 if(e.wheelDeltaX) {
3396 _wheelDelta.x = -0.16 * e.wheelDeltaX;
3397 }
3398 if(e.wheelDeltaY) {
3399 _wheelDelta.y = -0.16 * e.wheelDeltaY;
3400 } else {
3401 _wheelDelta.y = -0.16 * e.wheelDelta;
3402 }
3403 } else if('detail' in e) {
3404 _wheelDelta.y = e.detail;
3405 } else {
3406 return;
3407 }
3408
3409 _calculatePanBounds(_currZoomLevel, true);
3410
3411 var newPanX = _panOffset.x - _wheelDelta.x,
3412 newPanY = _panOffset.y - _wheelDelta.y;
3413
3414 // only prevent scrolling in nonmodal mode when not at edges
3415 if (_options.modal ||
3416 (
3417 newPanX <= _currPanBounds.min.x && newPanX >= _currPanBounds.max.x &&
3418 newPanY <= _currPanBounds.min.y && newPanY >= _currPanBounds.max.y
3419 ) ) {
3420 e.preventDefault();
3421 }
3422
3423 // TODO: use rAF instead of mousewheel?
3424 self.panTo(newPanX, newPanY);
3425 },
3426
3427 toggleDesktopZoom: function(centerPoint) {
3428 centerPoint = centerPoint || {x:_viewportSize.x/2 + _offset.x, y:_viewportSize.y/2 + _offset.y };
3429
3430 var doubleTapZoomLevel = _options.getDoubleTapZoom(true, self.currItem);
3431 var zoomOut = _currZoomLevel === doubleTapZoomLevel;
3432
3433 self.mouseZoomedIn = !zoomOut;
3434
3435 self.zoomTo(zoomOut ? self.currItem.initialZoomLevel : doubleTapZoomLevel, centerPoint, 333);
3436 framework[ (!zoomOut ? 'add' : 'remove') + 'Class'](template, 'pswp--zoomed-in');
3437 }
3438
3439 }
3440 });
3441
3442
3443 /*>>desktop-zoom*/
3444
3445 /*>>history*/
3446 /**
3447 *
3448 * history.js:
3449 *
3450 * - Back button to close gallery.
3451 *
3452 * - Unique URL for each slide: example.com/&pid=1&gid=3
3453 * (where PID is picture index, and GID and gallery index)
3454 *
3455 * - Switch URL when slides change.
3456 *
3457 */
3458
3459
3460 var _historyDefaultOptions = {
3461 history: true,
3462 galleryUID: 1
3463 };
3464
3465 var _historyUpdateTimeout,
3466 _hashChangeTimeout,
3467 _hashAnimCheckTimeout,
3468 _hashChangedByScript,
3469 _hashChangedByHistory,
3470 _hashReseted,
3471 _initialHash,
3472 _historyChanged,
3473 _closedFromURL,
3474 _urlChangedOnce,
3475 _windowLoc,
3476
3477 _supportsPushState,
3478
3479 _getHash = function() {
3480 return _windowLoc.hash.substring(1);
3481 },
3482 _cleanHistoryTimeouts = function() {
3483
3484 if(_historyUpdateTimeout) {
3485 clearTimeout(_historyUpdateTimeout);
3486 }
3487
3488 if(_hashAnimCheckTimeout) {
3489 clearTimeout(_hashAnimCheckTimeout);
3490 }
3491 },
3492
3493 // pid - Picture index
3494 // gid - Gallery index
3495 _parseItemIndexFromURL = function() {
3496 var hash = _getHash(),
3497 params = {};
3498
3499 if(hash.length < 5) { // pid=1
3500 return params;
3501 }
3502
3503 var i, vars = hash.split('&');
3504 for (i = 0; i < vars.length; i++) {
3505 if(!vars[i]) {
3506 continue;
3507 }
3508 var pair = vars[i].split('=');
3509 if(pair.length < 2) {
3510 continue;
3511 }
3512 params[pair[0]] = pair[1];
3513 }
3514 if(_options.galleryPIDs) {
3515 // detect custom pid in hash and search for it among the items collection
3516 var searchfor = params.pid;
3517 params.pid = 0; // if custom pid cannot be found, fallback to the first item
3518 for(i = 0; i < _items.length; i++) {
3519 if(_items[i].pid === searchfor) {
3520 params.pid = i;
3521 break;
3522 }
3523 }
3524 } else {
3525 params.pid = parseInt(params.pid,10)-1;
3526 }
3527 if( params.pid < 0 ) {
3528 params.pid = 0;
3529 }
3530 return params;
3531 },
3532 _updateHash = function() {
3533
3534 if(_hashAnimCheckTimeout) {
3535 clearTimeout(_hashAnimCheckTimeout);
3536 }
3537
3538
3539 if(_numAnimations || _isDragging) {
3540 // changing browser URL forces layout/paint in some browsers, which causes noticable lag during animation
3541 // that's why we update hash only when no animations running
3542 _hashAnimCheckTimeout = setTimeout(_updateHash, 500);
3543 return;
3544 }
3545
3546 if(_hashChangedByScript) {
3547 clearTimeout(_hashChangeTimeout);
3548 } else {
3549 _hashChangedByScript = true;
3550 }
3551
3552
3553 var pid = (_currentItemIndex + 1);
3554 var item = _getItemAt( _currentItemIndex );
3555 if(item.hasOwnProperty('pid')) {
3556 // carry forward any custom pid assigned to the item
3557 pid = item.pid;
3558 }
3559 var newHash = _initialHash + '&' + 'gid=' + _options.galleryUID + '&' + 'pid=' + pid;
3560
3561 if(!_historyChanged) {
3562 if(_windowLoc.hash.indexOf(newHash) === -1) {
3563 _urlChangedOnce = true;
3564 }
3565 // first time - add new hisory record, then just replace
3566 }
3567
3568 var newURL = _windowLoc.href.split('#')[0] + '#' + newHash;
3569
3570 if( _supportsPushState ) {
3571
3572 if('#' + newHash !== window.location.hash) {
3573 history[_historyChanged ? 'replaceState' : 'pushState']('', document.title, newURL);
3574 }
3575
3576 } else {
3577 if(_historyChanged) {
3578 _windowLoc.replace( newURL );
3579 } else {
3580 _windowLoc.hash = newHash;
3581 }
3582 }
3583
3584
3585
3586 _historyChanged = true;
3587 _hashChangeTimeout = setTimeout(function() {
3588 _hashChangedByScript = false;
3589 }, 60);
3590 };
3591
3592
3593
3594
3595
3596 _registerModule('History', {
3597
3598
3599
3600 publicMethods: {
3601 initHistory: function() {
3602
3603 framework.extend(_options, _historyDefaultOptions, true);
3604
3605 if( !_options.history ) {
3606 return;
3607 }
3608
3609
3610 _windowLoc = window.location;
3611 _urlChangedOnce = false;
3612 _closedFromURL = false;
3613 _historyChanged = false;
3614 _initialHash = _getHash();
3615 _supportsPushState = ('pushState' in history);
3616
3617
3618 if(_initialHash.indexOf('gid=') > -1) {
3619 _initialHash = _initialHash.split('&gid=')[0];
3620 _initialHash = _initialHash.split('?gid=')[0];
3621 }
3622
3623
3624 _listen('afterChange', self.updateURL);
3625 _listen('unbindEvents', function() {
3626 framework.unbind(window, 'hashchange', self.onHashChange);
3627 });
3628
3629
3630 var returnToOriginal = function() {
3631 _hashReseted = true;
3632 if(!_closedFromURL) {
3633
3634 if(_urlChangedOnce) {
3635 history.back();
3636 } else {
3637
3638 if(_initialHash) {
3639 _windowLoc.hash = _initialHash;
3640 } else {
3641 if (_supportsPushState) {
3642
3643 // remove hash from url without refreshing it or scrolling to top
3644 history.pushState('', document.title, _windowLoc.pathname + _windowLoc.search );
3645 } else {
3646 _windowLoc.hash = '';
3647 }
3648 }
3649 }
3650
3651 }
3652
3653 _cleanHistoryTimeouts();
3654 };
3655
3656
3657 _listen('unbindEvents', function() {
3658 if(_closedByScroll) {
3659 // if PhotoSwipe is closed by scroll, we go "back" before the closing animation starts
3660 // this is done to keep the scroll position
3661 returnToOriginal();
3662 }
3663 });
3664 _listen('destroy', function() {
3665 if(!_hashReseted) {
3666 returnToOriginal();
3667 }
3668 });
3669 _listen('firstUpdate', function() {
3670 _currentItemIndex = _parseItemIndexFromURL().pid;
3671 });
3672
3673
3674
3675
3676 var index = _initialHash.indexOf('pid=');
3677 if(index > -1) {
3678 _initialHash = _initialHash.substring(0, index);
3679 if(_initialHash.slice(-1) === '&') {
3680 _initialHash = _initialHash.slice(0, -1);
3681 }
3682 }
3683
3684
3685 setTimeout(function() {
3686 if(_isOpen) { // hasn't destroyed yet
3687 framework.bind(window, 'hashchange', self.onHashChange);
3688 }
3689 }, 40);
3690
3691 },
3692 onHashChange: function() {
3693
3694 if(_getHash() === _initialHash) {
3695
3696 _closedFromURL = true;
3697 self.close();
3698 return;
3699 }
3700 if(!_hashChangedByScript) {
3701
3702 _hashChangedByHistory = true;
3703 self.goTo( _parseItemIndexFromURL().pid );
3704 _hashChangedByHistory = false;
3705 }
3706
3707 },
3708 updateURL: function() {
3709
3710 // Delay the update of URL, to avoid lag during transition,
3711 // and to not to trigger actions like "refresh page sound" or "blinking favicon" to often
3712
3713 _cleanHistoryTimeouts();
3714
3715
3716 if(_hashChangedByHistory) {
3717 return;
3718 }
3719
3720 if(!_historyChanged) {
3721 _updateHash(); // first time
3722 } else {
3723 _historyUpdateTimeout = setTimeout(_updateHash, 800);
3724 }
3725 }
3726
3727 }
3728 });
3729
3730
3731 /*>>history*/
3732 framework.extend(self, publicMethods); };
3733 return PhotoSwipe;
3734 });
3735
3736 /*! PhotoSwipe Default UI - 4.1.3 - 2019-01-08
3737 * http://photoswipe.com
3738 * Copyright (c) 2019 Dmitry Semenov; */
3739 /**
3740 *
3741 * UI on top of main sliding area (caption, arrows, close button, etc.).
3742 * Built just using public methods/properties of PhotoSwipe.
3743 *
3744 */
3745 (function (root, factory) {
3746 if (typeof define === 'function' && define.amd) {
3747 define(factory);
3748 } else if (typeof exports === 'object') {
3749 module.exports = factory();
3750 } else {
3751 root.PhotoSwipeUI_Default = factory();
3752 }
3753 })(this, function () {
3754
3755 'use strict';
3756
3757
3758
3759 var PhotoSwipeUI_Default =
3760 function(pswp, framework) {
3761
3762 var ui = this;
3763 var _overlayUIUpdated = false,
3764 _controlsVisible = true,
3765 _fullscrenAPI,
3766 _controls,
3767 _captionContainer,
3768 _fakeCaptionContainer,
3769 _indexIndicator,
3770 _shareButton,
3771 _shareModal,
3772 _shareModalHidden = true,
3773 _initalCloseOnScrollValue,
3774 _isIdle,
3775 _listen,
3776
3777 _loadingIndicator,
3778 _loadingIndicatorHidden,
3779 _loadingIndicatorTimeout,
3780
3781 _galleryHasOneSlide,
3782
3783 _options,
3784 _defaultUIOptions = {
3785 barsSize: {top:44, bottom:'auto'},
3786 closeElClasses: ['item', 'caption', 'zoom-wrap', 'ui', 'top-bar'],
3787 timeToIdle: 4000,
3788 timeToIdleOutside: 1000,
3789 loadingIndicatorDelay: 1000, // 2s
3790
3791 addCaptionHTMLFn: function(item, captionEl /*, isFake */) {
3792 if(!item.title) {
3793 captionEl.children[0].innerHTML = '';
3794 return false;
3795 }
3796 captionEl.children[0].innerHTML = item.title;
3797 return true;
3798 },
3799
3800 closeEl:true,
3801 captionEl: true,
3802 fullscreenEl: true,
3803 zoomEl: true,
3804 shareEl: true,
3805 counterEl: true,
3806 arrowEl: true,
3807 preloaderEl: true,
3808
3809 tapToClose: false,
3810 tapToToggleControls: true,
3811
3812 clickToCloseNonZoomable: true,
3813
3814 shareButtons: [
3815 {id:'facebook', label:'Share on Facebook', url:'https://www.facebook.com/sharer/sharer.php?u={{url}}'},
3816 {id:'twitter', label:'Tweet', url:'https://twitter.com/intent/tweet?text={{text}}&url={{url}}'},
3817 {id:'pinterest', label:'Pin it', url:'http://www.pinterest.com/pin/create/button/'+
3818 '?url={{url}}&media={{image_url}}&description={{text}}'},
3819 {id:'download', label:'Download image', url:'{{raw_image_url}}', download:true}
3820 ],
3821 getImageURLForShare: function( /* shareButtonData */ ) {
3822 return pswp.currItem.src || '';
3823 },
3824 getPageURLForShare: function( /* shareButtonData */ ) {
3825 return window.location.href;
3826 },
3827 getTextForShare: function( /* shareButtonData */ ) {
3828 return pswp.currItem.title || '';
3829 },
3830
3831 indexIndicatorSep: ' / ',
3832 fitControlsWidth: 1200
3833
3834 },
3835 _blockControlsTap,
3836 _blockControlsTapTimeout;
3837
3838
3839
3840 var _onControlsTap = function(e) {
3841 if(_blockControlsTap) {
3842 return true;
3843 }
3844
3845
3846 e = e || window.event;
3847
3848 if(_options.timeToIdle && _options.mouseUsed && !_isIdle) {
3849 // reset idle timer
3850 _onIdleMouseMove();
3851 }
3852
3853
3854 var target = e.target || e.srcElement,
3855 uiElement,
3856 clickedClass = target.getAttribute('class') || '',
3857 found;
3858
3859 for(var i = 0; i < _uiElements.length; i++) {
3860 uiElement = _uiElements[i];
3861 if(uiElement.onTap && clickedClass.indexOf('pswp__' + uiElement.name ) > -1 ) {
3862 uiElement.onTap();
3863 found = true;
3864
3865 }
3866 }
3867
3868 if(found) {
3869 if(e.stopPropagation) {
3870 e.stopPropagation();
3871 }
3872 _blockControlsTap = true;
3873
3874 // Some versions of Android don't prevent ghost click event
3875 // when preventDefault() was called on touchstart and/or touchend.
3876 //
3877 // This happens on v4.3, 4.2, 4.1,
3878 // older versions strangely work correctly,
3879 // but just in case we add delay on all of them)
3880 var tapDelay = framework.features.isOldAndroid ? 600 : 30;
3881 _blockControlsTapTimeout = setTimeout(function() {
3882 _blockControlsTap = false;
3883 }, tapDelay);
3884 }
3885
3886 },
3887 _fitControlsInViewport = function() {
3888 return !pswp.likelyTouchDevice || _options.mouseUsed || screen.width > _options.fitControlsWidth;
3889 },
3890 _togglePswpClass = function(el, cName, add) {
3891 framework[ (add ? 'add' : 'remove') + 'Class' ](el, 'pswp__' + cName);
3892 },
3893
3894 // add class when there is just one item in the gallery
3895 // (by default it hides left/right arrows and 1ofX counter)
3896 _countNumItems = function() {
3897 var hasOneSlide = (_options.getNumItemsFn() === 1);
3898
3899 if(hasOneSlide !== _galleryHasOneSlide) {
3900 _togglePswpClass(_controls, 'ui--one-slide', hasOneSlide);
3901 _galleryHasOneSlide = hasOneSlide;
3902 }
3903 },
3904 _toggleShareModalClass = function() {
3905 _togglePswpClass(_shareModal, 'share-modal--hidden', _shareModalHidden);
3906 },
3907 _toggleShareModal = function() {
3908
3909 _shareModalHidden = !_shareModalHidden;
3910
3911
3912 if(!_shareModalHidden) {
3913 _toggleShareModalClass();
3914 setTimeout(function() {
3915 if(!_shareModalHidden) {
3916 framework.addClass(_shareModal, 'pswp__share-modal--fade-in');
3917 }
3918 }, 30);
3919 } else {
3920 framework.removeClass(_shareModal, 'pswp__share-modal--fade-in');
3921 setTimeout(function() {
3922 if(_shareModalHidden) {
3923 _toggleShareModalClass();
3924 }
3925 }, 300);
3926 }
3927
3928 if(!_shareModalHidden) {
3929 _updateShareURLs();
3930 }
3931 return false;
3932 },
3933
3934 _openWindowPopup = function(e) {
3935 e = e || window.event;
3936 var target = e.target || e.srcElement;
3937
3938 pswp.shout('shareLinkClick', e, target);
3939
3940 if(!target.href) {
3941 return false;
3942 }
3943
3944 if( target.hasAttribute('download') ) {
3945 return true;
3946 }
3947
3948 window.open(target.href, 'pswp_share', 'scrollbars=yes,resizable=yes,toolbar=no,'+
3949 'location=yes,width=550,height=420,top=100,left=' +
3950 (window.screen ? Math.round(screen.width / 2 - 275) : 100) );
3951
3952 if(!_shareModalHidden) {
3953 _toggleShareModal();
3954 }
3955
3956 return false;
3957 },
3958 _updateShareURLs = function() {
3959 var shareButtonOut = '',
3960 shareButtonData,
3961 shareURL,
3962 image_url,
3963 page_url,
3964 share_text;
3965
3966 for(var i = 0; i < _options.shareButtons.length; i++) {
3967 shareButtonData = _options.shareButtons[i];
3968
3969 image_url = _options.getImageURLForShare(shareButtonData);
3970 page_url = _options.getPageURLForShare(shareButtonData);
3971 share_text = _options.getTextForShare(shareButtonData);
3972
3973 shareURL = shareButtonData.url.replace('{{url}}', encodeURIComponent(page_url) )
3974 .replace('{{image_url}}', encodeURIComponent(image_url) )
3975 .replace('{{raw_image_url}}', image_url )
3976 .replace('{{text}}', encodeURIComponent(share_text) );
3977
3978 shareButtonOut += '<a href="' + shareURL + '" target="_blank" '+
3979 'class="pswp__share--' + shareButtonData.id + '"' +
3980 (shareButtonData.download ? 'download' : '') + '>' +
3981 shareButtonData.label + '</a>';
3982
3983 if(_options.parseShareButtonOut) {
3984 shareButtonOut = _options.parseShareButtonOut(shareButtonData, shareButtonOut);
3985 }
3986 }
3987 _shareModal.children[0].innerHTML = shareButtonOut;
3988 _shareModal.children[0].onclick = _openWindowPopup;
3989
3990 },
3991 _hasCloseClass = function(target) {
3992 for(var i = 0; i < _options.closeElClasses.length; i++) {
3993 if( framework.hasClass(target, 'pswp__' + _options.closeElClasses[i]) ) {
3994 return true;
3995 }
3996 }
3997 },
3998 _idleInterval,
3999 _idleTimer,
4000 _idleIncrement = 0,
4001 _onIdleMouseMove = function() {
4002 clearTimeout(_idleTimer);
4003 _idleIncrement = 0;
4004 if(_isIdle) {
4005 ui.setIdle(false);
4006 }
4007 },
4008 _onMouseLeaveWindow = function(e) {
4009 e = e ? e : window.event;
4010 var from = e.relatedTarget || e.toElement;
4011 if (!from || from.nodeName === 'HTML') {
4012 clearTimeout(_idleTimer);
4013 _idleTimer = setTimeout(function() {
4014 ui.setIdle(true);
4015 }, _options.timeToIdleOutside);
4016 }
4017 },
4018 _setupFullscreenAPI = function() {
4019 if(_options.fullscreenEl && !framework.features.isOldAndroid) {
4020 if(!_fullscrenAPI) {
4021 _fullscrenAPI = ui.getFullscreenAPI();
4022 }
4023 if(_fullscrenAPI) {
4024 framework.bind(document, _fullscrenAPI.eventK, ui.updateFullscreen);
4025 ui.updateFullscreen();
4026 framework.addClass(pswp.template, 'pswp--supports-fs');
4027 } else {
4028 framework.removeClass(pswp.template, 'pswp--supports-fs');
4029 }
4030 }
4031 },
4032 _setupLoadingIndicator = function() {
4033 // Setup loading indicator
4034 if(_options.preloaderEl) {
4035
4036 _toggleLoadingIndicator(true);
4037
4038 _listen('beforeChange', function() {
4039
4040 clearTimeout(_loadingIndicatorTimeout);
4041
4042 // display loading indicator with delay
4043 _loadingIndicatorTimeout = setTimeout(function() {
4044
4045 if(pswp.currItem && pswp.currItem.loading) {
4046
4047 if( !pswp.allowProgressiveImg() || (pswp.currItem.img && !pswp.currItem.img.naturalWidth) ) {
4048 // show preloader if progressive loading is not enabled,
4049 // or image width is not defined yet (because of slow connection)
4050 _toggleLoadingIndicator(false);
4051 // items-controller.js function allowProgressiveImg
4052 }
4053
4054 } else {
4055 _toggleLoadingIndicator(true); // hide preloader
4056 }
4057
4058 }, _options.loadingIndicatorDelay);
4059
4060 });
4061 _listen('imageLoadComplete', function(index, item) {
4062 if(pswp.currItem === item) {
4063 _toggleLoadingIndicator(true);
4064 }
4065 });
4066
4067 }
4068 },
4069 _toggleLoadingIndicator = function(hide) {
4070 if( _loadingIndicatorHidden !== hide ) {
4071 _togglePswpClass(_loadingIndicator, 'preloader--active', !hide);
4072 _loadingIndicatorHidden = hide;
4073 }
4074 },
4075 _applyNavBarGaps = function(item) {
4076 var gap = item.vGap;
4077
4078 if( _fitControlsInViewport() ) {
4079
4080 var bars = _options.barsSize;
4081 if(_options.captionEl && bars.bottom === 'auto') {
4082 if(!_fakeCaptionContainer) {
4083 _fakeCaptionContainer = framework.createEl('pswp__caption pswp__caption--fake');
4084 _fakeCaptionContainer.appendChild( framework.createEl('pswp__caption__center') );
4085 _controls.insertBefore(_fakeCaptionContainer, _captionContainer);
4086 framework.addClass(_controls, 'pswp__ui--fit');
4087 }
4088 if( _options.addCaptionHTMLFn(item, _fakeCaptionContainer, true) ) {
4089
4090 var captionSize = _fakeCaptionContainer.clientHeight;
4091 gap.bottom = parseInt(captionSize,10) || 44;
4092 } else {
4093 gap.bottom = bars.top; // if no caption, set size of bottom gap to size of top
4094 }
4095 } else {
4096 gap.bottom = bars.bottom === 'auto' ? 0 : bars.bottom;
4097 }
4098
4099 // height of top bar is static, no need to calculate it
4100 gap.top = bars.top;
4101 } else {
4102 gap.top = gap.bottom = 0;
4103 }
4104 },
4105 _setupIdle = function() {
4106 // Hide controls when mouse is used
4107 if(_options.timeToIdle) {
4108 _listen('mouseUsed', function() {
4109
4110 framework.bind(document, 'mousemove', _onIdleMouseMove);
4111 framework.bind(document, 'mouseout', _onMouseLeaveWindow);
4112
4113 _idleInterval = setInterval(function() {
4114 _idleIncrement++;
4115 if(_idleIncrement === 2) {
4116 ui.setIdle(true);
4117 }
4118 }, _options.timeToIdle / 2);
4119 });
4120 }
4121 },
4122 _setupHidingControlsDuringGestures = function() {
4123
4124 // Hide controls on vertical drag
4125 _listen('onVerticalDrag', function(now) {
4126 if(_controlsVisible && now < 0.95) {
4127 ui.hideControls();
4128 } else if(!_controlsVisible && now >= 0.95) {
4129 ui.showControls();
4130 }
4131 });
4132
4133 // Hide controls when pinching to close
4134 var pinchControlsHidden;
4135 _listen('onPinchClose' , function(now) {
4136 if(_controlsVisible && now < 0.9) {
4137 ui.hideControls();
4138 pinchControlsHidden = true;
4139 } else if(pinchControlsHidden && !_controlsVisible && now > 0.9) {
4140 ui.showControls();
4141 }
4142 });
4143
4144 _listen('zoomGestureEnded', function() {
4145 pinchControlsHidden = false;
4146 if(pinchControlsHidden && !_controlsVisible) {
4147 ui.showControls();
4148 }
4149 });
4150
4151 };
4152
4153
4154
4155 var _uiElements = [
4156 {
4157 name: 'caption',
4158 option: 'captionEl',
4159 onInit: function(el) {
4160 _captionContainer = el;
4161 }
4162 },
4163 {
4164 name: 'share-modal',
4165 option: 'shareEl',
4166 onInit: function(el) {
4167 _shareModal = el;
4168 },
4169 onTap: function() {
4170 _toggleShareModal();
4171 }
4172 },
4173 {
4174 name: 'button--share',
4175 option: 'shareEl',
4176 onInit: function(el) {
4177 _shareButton = el;
4178 },
4179 onTap: function() {
4180 _toggleShareModal();
4181 }
4182 },
4183 {
4184 name: 'button--zoom',
4185 option: 'zoomEl',
4186 onTap: pswp.toggleDesktopZoom
4187 },
4188 {
4189 name: 'counter',
4190 option: 'counterEl',
4191 onInit: function(el) {
4192 _indexIndicator = el;
4193 }
4194 },
4195 {
4196 name: 'button--close',
4197 option: 'closeEl',
4198 onTap: pswp.close
4199 },
4200 {
4201 name: 'button--arrow--left',
4202 option: 'arrowEl',
4203 onTap: pswp.prev
4204 },
4205 {
4206 name: 'button--arrow--right',
4207 option: 'arrowEl',
4208 onTap: pswp.next
4209 },
4210 {
4211 name: 'button--fs',
4212 option: 'fullscreenEl',
4213 onTap: function() {
4214 if(_fullscrenAPI.isFullscreen()) {
4215 _fullscrenAPI.exit();
4216 } else {
4217 _fullscrenAPI.enter();
4218 }
4219 }
4220 },
4221 {
4222 name: 'preloader',
4223 option: 'preloaderEl',
4224 onInit: function(el) {
4225 _loadingIndicator = el;
4226 }
4227 }
4228
4229 ];
4230
4231 var _setupUIElements = function() {
4232 var item,
4233 classAttr,
4234 uiElement;
4235
4236 var loopThroughChildElements = function(sChildren) {
4237 if(!sChildren) {
4238 return;
4239 }
4240
4241 var l = sChildren.length;
4242 for(var i = 0; i < l; i++) {
4243 item = sChildren[i];
4244 classAttr = item.className;
4245
4246 for(var a = 0; a < _uiElements.length; a++) {
4247 uiElement = _uiElements[a];
4248
4249 if(classAttr.indexOf('pswp__' + uiElement.name) > -1 ) {
4250
4251 if( _options[uiElement.option] ) { // if element is not disabled from options
4252
4253 framework.removeClass(item, 'pswp__element--disabled');
4254 if(uiElement.onInit) {
4255 uiElement.onInit(item);
4256 }
4257
4258 //item.style.display = 'block';
4259 } else {
4260 framework.addClass(item, 'pswp__element--disabled');
4261 //item.style.display = 'none';
4262 }
4263 }
4264 }
4265 }
4266 };
4267 loopThroughChildElements(_controls.children);
4268
4269 var topBar = framework.getChildByClass(_controls, 'pswp__top-bar');
4270 if(topBar) {
4271 loopThroughChildElements( topBar.children );
4272 }
4273 };
4274
4275
4276
4277
4278 ui.init = function() {
4279
4280 // extend options
4281 framework.extend(pswp.options, _defaultUIOptions, true);
4282
4283 // create local link for fast access
4284 _options = pswp.options;
4285
4286 // find pswp__ui element
4287 _controls = framework.getChildByClass(pswp.scrollWrap, 'pswp__ui');
4288
4289 // create local link
4290 _listen = pswp.listen;
4291
4292
4293 _setupHidingControlsDuringGestures();
4294
4295 // update controls when slides change
4296 _listen('beforeChange', ui.update);
4297
4298 // toggle zoom on double-tap
4299 _listen('doubleTap', function(point) {
4300 var initialZoomLevel = pswp.currItem.initialZoomLevel;
4301 if(pswp.getZoomLevel() !== initialZoomLevel) {
4302 pswp.zoomTo(initialZoomLevel, point, 333);
4303 } else {
4304 pswp.zoomTo(_options.getDoubleTapZoom(false, pswp.currItem), point, 333);
4305 }
4306 });
4307
4308 // Allow text selection in caption
4309 _listen('preventDragEvent', function(e, isDown, preventObj) {
4310 var t = e.target || e.srcElement;
4311 if(
4312 t &&
4313 t.getAttribute('class') && e.type.indexOf('mouse') > -1 &&
4314 ( t.getAttribute('class').indexOf('__caption') > 0 || (/(SMALL|STRONG|EM)/i).test(t.tagName) )
4315 ) {
4316 preventObj.prevent = false;
4317 }
4318 });
4319
4320 // bind events for UI
4321 _listen('bindEvents', function() {
4322 framework.bind(_controls, 'pswpTap click', _onControlsTap);
4323 framework.bind(pswp.scrollWrap, 'pswpTap', ui.onGlobalTap);
4324
4325 if(!pswp.likelyTouchDevice) {
4326 framework.bind(pswp.scrollWrap, 'mouseover', ui.onMouseOver);
4327 }
4328 });
4329
4330 // unbind events for UI
4331 _listen('unbindEvents', function() {
4332 if(!_shareModalHidden) {
4333 _toggleShareModal();
4334 }
4335
4336 if(_idleInterval) {
4337 clearInterval(_idleInterval);
4338 }
4339 framework.unbind(document, 'mouseout', _onMouseLeaveWindow);
4340 framework.unbind(document, 'mousemove', _onIdleMouseMove);
4341 framework.unbind(_controls, 'pswpTap click', _onControlsTap);
4342 framework.unbind(pswp.scrollWrap, 'pswpTap', ui.onGlobalTap);
4343 framework.unbind(pswp.scrollWrap, 'mouseover', ui.onMouseOver);
4344
4345 if(_fullscrenAPI) {
4346 framework.unbind(document, _fullscrenAPI.eventK, ui.updateFullscreen);
4347 if(_fullscrenAPI.isFullscreen()) {
4348 _options.hideAnimationDuration = 0;
4349 _fullscrenAPI.exit();
4350 }
4351 _fullscrenAPI = null;
4352 }
4353 });
4354
4355
4356 // clean up things when gallery is destroyed
4357 _listen('destroy', function() {
4358 if(_options.captionEl) {
4359 if(_fakeCaptionContainer) {
4360 _controls.removeChild(_fakeCaptionContainer);
4361 }
4362 framework.removeClass(_captionContainer, 'pswp__caption--empty');
4363 }
4364
4365 if(_shareModal) {
4366 _shareModal.children[0].onclick = null;
4367 }
4368 framework.removeClass(_controls, 'pswp__ui--over-close');
4369 framework.addClass( _controls, 'pswp__ui--hidden');
4370 ui.setIdle(false);
4371 });
4372
4373
4374 if(!_options.showAnimationDuration) {
4375 framework.removeClass( _controls, 'pswp__ui--hidden');
4376 }
4377 _listen('initialZoomIn', function() {
4378 if(_options.showAnimationDuration) {
4379 framework.removeClass( _controls, 'pswp__ui--hidden');
4380 }
4381 });
4382 _listen('initialZoomOut', function() {
4383 framework.addClass( _controls, 'pswp__ui--hidden');
4384 });
4385
4386 _listen('parseVerticalMargin', _applyNavBarGaps);
4387
4388 _setupUIElements();
4389
4390 if(_options.shareEl && _shareButton && _shareModal) {
4391 _shareModalHidden = true;
4392 }
4393
4394 _countNumItems();
4395
4396 _setupIdle();
4397
4398 _setupFullscreenAPI();
4399
4400 _setupLoadingIndicator();
4401 };
4402
4403 ui.setIdle = function(isIdle) {
4404 _isIdle = isIdle;
4405 _togglePswpClass(_controls, 'ui--idle', isIdle);
4406 };
4407
4408 ui.update = function() {
4409 // Don't update UI if it's hidden
4410 if(_controlsVisible && pswp.currItem) {
4411
4412 ui.updateIndexIndicator();
4413
4414 if(_options.captionEl) {
4415 _options.addCaptionHTMLFn(pswp.currItem, _captionContainer);
4416
4417 _togglePswpClass(_captionContainer, 'caption--empty', !pswp.currItem.title);
4418 }
4419
4420 _overlayUIUpdated = true;
4421
4422 } else {
4423 _overlayUIUpdated = false;
4424 }
4425
4426 if(!_shareModalHidden) {
4427 _toggleShareModal();
4428 }
4429
4430 _countNumItems();
4431 };
4432
4433 ui.updateFullscreen = function(e) {
4434
4435 if(e) {
4436 // some browsers change window scroll position during the fullscreen
4437 // so PhotoSwipe updates it just in case
4438 setTimeout(function() {
4439 pswp.setScrollOffset( 0, framework.getScrollY() );
4440 }, 50);
4441 }
4442
4443 // toogle pswp--fs class on root element
4444 framework[ (_fullscrenAPI.isFullscreen() ? 'add' : 'remove') + 'Class' ](pswp.template, 'pswp--fs');
4445 };
4446
4447 ui.updateIndexIndicator = function() {
4448 if(_options.counterEl) {
4449 _indexIndicator.innerHTML = (pswp.getCurrentIndex()+1) +
4450 _options.indexIndicatorSep +
4451 _options.getNumItemsFn();
4452 }
4453 };
4454
4455 ui.onGlobalTap = function(e) {
4456 e = e || window.event;
4457 var target = e.target || e.srcElement;
4458
4459 if(_blockControlsTap) {
4460 return;
4461 }
4462
4463 if(e.detail && e.detail.pointerType === 'mouse') {
4464
4465 // close gallery if clicked outside of the image
4466 if(_hasCloseClass(target)) {
4467 pswp.close();
4468 return;
4469 }
4470
4471 if(framework.hasClass(target, 'pswp__img')) {
4472 if(pswp.getZoomLevel() === 1 && pswp.getZoomLevel() <= pswp.currItem.fitRatio) {
4473 if(_options.clickToCloseNonZoomable) {
4474 pswp.close();
4475 }
4476 } else {
4477 pswp.toggleDesktopZoom(e.detail.releasePoint);
4478 }
4479 }
4480
4481 } else {
4482
4483 // tap anywhere (except buttons) to toggle visibility of controls
4484 if(_options.tapToToggleControls) {
4485 if(_controlsVisible) {
4486 ui.hideControls();
4487 } else {
4488 ui.showControls();
4489 }
4490 }
4491
4492 // tap to close gallery
4493 if(_options.tapToClose && (framework.hasClass(target, 'pswp__img') || _hasCloseClass(target)) ) {
4494 pswp.close();
4495 return;
4496 }
4497
4498 }
4499 };
4500 ui.onMouseOver = function(e) {
4501 e = e || window.event;
4502 var target = e.target || e.srcElement;
4503
4504 // add class when mouse is over an element that should close the gallery
4505 _togglePswpClass(_controls, 'ui--over-close', _hasCloseClass(target));
4506 };
4507
4508 ui.hideControls = function() {
4509 framework.addClass(_controls,'pswp__ui--hidden');
4510 _controlsVisible = false;
4511 };
4512
4513 ui.showControls = function() {
4514 _controlsVisible = true;
4515 if(!_overlayUIUpdated) {
4516 ui.update();
4517 }
4518 framework.removeClass(_controls,'pswp__ui--hidden');
4519 };
4520
4521 ui.supportsFullscreen = function() {
4522 var d = document;
4523 return !!(d.exitFullscreen || d.mozCancelFullScreen || d.webkitExitFullscreen || d.msExitFullscreen);
4524 };
4525
4526 ui.getFullscreenAPI = function() {
4527 var dE = document.documentElement,
4528 api,
4529 tF = 'fullscreenchange';
4530
4531 if (dE.requestFullscreen) {
4532 api = {
4533 enterK: 'requestFullscreen',
4534 exitK: 'exitFullscreen',
4535 elementK: 'fullscreenElement',
4536 eventK: tF
4537 };
4538
4539 } else if(dE.mozRequestFullScreen ) {
4540 api = {
4541 enterK: 'mozRequestFullScreen',
4542 exitK: 'mozCancelFullScreen',
4543 elementK: 'mozFullScreenElement',
4544 eventK: 'moz' + tF
4545 };
4546
4547
4548
4549 } else if(dE.webkitRequestFullscreen) {
4550 api = {
4551 enterK: 'webkitRequestFullscreen',
4552 exitK: 'webkitExitFullscreen',
4553 elementK: 'webkitFullscreenElement',
4554 eventK: 'webkit' + tF
4555 };
4556
4557 } else if(dE.msRequestFullscreen) {
4558 api = {
4559 enterK: 'msRequestFullscreen',
4560 exitK: 'msExitFullscreen',
4561 elementK: 'msFullscreenElement',
4562 eventK: 'MSFullscreenChange'
4563 };
4564 }
4565
4566 if(api) {
4567 api.enter = function() {
4568 // disable close-on-scroll in fullscreen
4569 _initalCloseOnScrollValue = _options.closeOnScroll;
4570 _options.closeOnScroll = false;
4571
4572 if(this.enterK === 'webkitRequestFullscreen') {
4573 pswp.template[this.enterK]( Element.ALLOW_KEYBOARD_INPUT );
4574 } else {
4575 return pswp.template[this.enterK]();
4576 }
4577 };
4578 api.exit = function() {
4579 _options.closeOnScroll = _initalCloseOnScrollValue;
4580
4581 return document[this.exitK]();
4582
4583 };
4584 api.isFullscreen = function() { return document[this.elementK]; };
4585 }
4586
4587 return api;
4588 };
4589
4590
4591
4592 };
4593 return PhotoSwipeUI_Default;
4594
4595
4596 });
4597