PageSourceSearch

https://campus.w3schools.com/cdn/shop/t/68/assets/vendor.photoswipe.min.js?v=169650331104647059261785919678

js w3schools.com collected 2026-09-24 08:24:22 UTC 105,329 bytes, 3,218 lines download raw bytes

1/*! PhotoSwipe - v4.1.3 - 2019-01-08
2 * http://photoswipe.com
3 * Copyright (c) 2019 Dmitry Semenov; */
4// Minor modifications by @cshold
5; (function (root, factory) {
6  root.PhotoSwipe = factory()
7})(window, function () {
8  'use strict'
9  var PhotoSwipe = function (template, UiClass, items, options) {
10    var framework = {
11      features: null,
12      bind: function (target, type, listener, unbind) {
13        var methodName = (unbind ? 'remove' : 'add') + 'EventListener'
14        type = type.split(' ')
15        for (var i = 0; i < type.length; i++) {
16          if (type[i]) {
17            target[methodName](type[i], listener, false)
18          }
19        }
20      },
21      isArray: function (obj) {
22        return obj instanceof Array
23      },
24      createEl: function (classes, tag) {
25        var el = document.createElement(tag || 'div')
26        if (classes) {
27          el.className = classes
28        }
29        return el
30      },
31      getScrollY: function () {
32        var yOffset = window.pageYOffset
33        return yOffset !== undefined ? yOffset : document.documentElement.scrollTop
34      },
35      unbind: function (target, type, listener) {
36        framework.bind(target, type, listener, true)
37      },
38      removeClass: function (el, className) {
39        var reg = new RegExp('(\\s|^)' + className + '(\\s|$)')
40        el.className = el.className
41          .replace(reg, ' ')
42          .replace(/^\s\s*/, '')
43          .replace(/\s\s*$/, '')
44      },
45      addClass: function (el, className) {
46        if (!framework.hasClass(el, className)) {
47          el.className += (el.className ? ' ' : '') + className
48        }
49      },
50      hasClass: function (el, className) {
51        return el.className && new RegExp('(^|\\s)' + className + '(\\s|$)').test(el.className)
52      },
53      getChildByClass: function (parentEl, childClassName) {
54        var node = parentEl.firstChild
55        while (node) {
56          if (framework.hasClass(node, childClassName)) {
57            return node
58          }
59          node = node.nextSibling
60        }
61      },
62      arraySearch: function (array, value, key) {
63        var i = array.length
64        while (i--) {
65          if (array[i][key] === value) {
66            return i
67          }
68        }
69        return -1
70      },
71      extend: function (o1, o2, preventOverwrite) {
72        for (var prop in o2) {
73          if (o2.hasOwnProperty(prop)) {
74            if (preventOverwrite && o1.hasOwnProperty(prop)) {
75              continue
76            }
77            o1[prop] = o2[prop]
78          }
79        }
80      },
81      easing: {
82        sine: {
83          out: function (k) {
84            return Math.sin(k * (Math.PI / 2))
85          },
86          inOut: function (k) {
87            return -(Math.cos(Math.PI * k) - 1) / 2
88          }
89        },
90        cubic: {
91          out: function (k) {
92            return --k * k * k + 1
93          }
94        }
95      },
96
97      /**
98       *
99       * @return {object}
100       *
101       * {
102       *  raf : request animation frame function
103       *  caf : cancel animation frame function
104       *  transfrom : transform property key (with vendor), or null if not supported
105       *  oldIE : IE8 or below
106       * }
107       *
108       */
109      detectFeatures: function () {
110        if (framework.features) {
111          return framework.features
112        }
113        var helperEl = framework.createEl(),
114          helperStyle = helperEl.style,
115          vendor = '',
116          features = {}
117
118        // IE8 and below
119        features.oldIE = document.all && !document.addEventListener
120
121        features.touch = 'ontouchstart' in window
122
123        if (window.requestAnimationFrame) {
124          features.raf = window.requestAnimationFrame
125          features.caf = window.cancelAnimationFrame
126        }
127
128        features.pointerEvent = !!window.PointerEvent || navigator.msPointerEnabled
129
130        // fix false-positive detection of old Android in new IE
131        // (IE11 ua string contains "Android 4.0")
132
133        if (!features.pointerEvent) {
134          var ua = navigator.userAgent
135
136          // Detect if device is iPhone or iPod and if it's older than 
136iOS 8
137          // http://stackoverflow.com/a/14223920
138          //
139          // This detection is made because of buggy top/bottom toolbars
140          // that don't trigger window.resize event.
141          // For more info refer to _isFixedPosition variable in core.js
142
143          if (/iP(hone|od)/.test(navigator.platform)) {
144            var v = navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/)
145            if (v && v.length > 0) {
146              v = parseInt(v[1], 10)
147              if (v >= 1 && v < 8) {
148                features.isOldIOSPhone = true
149              }
150            }
151          }
152
153          // Detect old Android (before KitKat)
154          // due to bugs related to position:fixed
155          // http://stackoverflow.com/questions/7184573/pick-up-the-android-version-in-the-browser-by-javascript
156
157          var match = ua.match(/Android\s([0-9\.]*)/)
158          var androidversion = match ? match[1] : 0
159          androidversion = parseFloat(androidversion)
160          if (androidversion >= 1) {
161            if (androidversion < 4.4) {
162              features.isOldAndroid = true // for fixed position bug & performance
163            }
164            features.androidVersion = androidversion // for touchend bug
165          }
166          features.isMobileOpera = /opera mini|opera mobi/i.test(ua)
167
168          // p.s. yes, yes, UA sniffing is bad, propose your solution for above bugs.
169        }
170
171        var styleChecks = ['transform', 'perspective', 'animationName'],
172          vendors = ['', 'webkit', 'Moz', 'ms', 'O'],
173          styleCheckItem,
174          styleName
175
176        for (var i = 0; i < 4; i++) {
177          vendor = vendors[i]
178
179          for (var a = 0; a < 3; a++) {
180            styleCheckItem = styleChecks[a]
181
182            // uppercase first letter of property name, if vendor is present
183            styleName =
184              vendor + (vendor ? styleCheckItem.charAt(0).toUpperCase() + styleCheckItem.slice(1) : styleCheckItem)
185
186            if (!features[styleCheckItem] && styleName in helperStyle) {
187              features[styleCheckItem] = styleName
188            }
189          }
190
191          if (vendor && !features.raf) {
192            vendor = vendor.toLowerCase()
193            features.raf = window[vendor + 'RequestAnimationFrame']
194            if (features.raf) {
195              features.caf = window[vendor + 'CancelAnimationFrame'] || window[vendor + 'CancelRequestAnimationFrame']
196            }
197          }
198        }
199
200        if (!features.raf) {
201          var lastTime = 0
202          features.raf = function (fn) {
203            var currTime = new Date().getTime()
204            var timeToCall = Math.max(0, 16 - (currTime - lastTime))
205            var id = window.setTimeout(function () {
206              fn(currTime + timeToCall)
207            }, timeToCall)
208            lastTime = currTime + timeToCall
209            return id
210          }
211          features.caf = function (id) {
212            clearTimeout(id)
213          }
214        }
215
216        // Detect SVG support
217        features.svg =
218          !!document.createElementNS && !!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect
219
220        framework.features = features
221
222        return features
223      }
224    }
225
226    framework.detectFeatures()
227
228    // Override addEventListener for old versions of IE
229    if (framework.features.oldIE) {
230      framework.bind = function (target, type, listener, unbind) {
231        type = type.split(' ')
232
233        var methodName = (unbind ? 'detach' : 'attach') + 'Event',
234          evName,
235          _handleEv = function () {
236            listener.handleEvent.call(listener)
237          }
238
239        for (var i = 0; i < type.length; i++) {
240          evName = type[i]
241          if (evName) {
242            if (typeof listener === 'object' && listener.handleEvent) {
243              if (!unbind) {
244                listener['oldIE' + evName] = _handleEv
245              } else {
246                if (!listener['oldIE' + evName]) {
247                  return false
248                }
249              }
250
251              target[methodName]('on' + evName, listener['oldIE' + evName])
252            } else {
253              target[methodName]('on' + evName, listener)
254            }
255          }
256        }
257      }
258    }
259
260    /*>>framework-bridge*/
261
262    /*>>core*/
263    //function(template, UiClass, items, options)
264
265    var self = this
266
267    /**
268     * Static vars, don't change unless you know what you're doing.
269     */
270    var DOUBLE_TAP_RADIUS = 25,
271      NUM_HOLDERS = 3
272
273    /**
274     * Options
275     */
276    var _options = {
277      allowPanToNext: true,
278      spacing: 0.12,
279      bgOpacity: 1,
280      mouseUsed: false,
281      loop: true,
282      pinchToClose: true,
283      closeOnScroll: true,
284      closeOnVerticalDrag: true,
285      verticalDragRange: 0.75,
286      hideAnimationDuration: 333,
287      showAnimationDuration: 333,
288      showHideOpacity: false,
289      focus: true,
290      escKey: true,
291      arrowKeys: true,
292      mainScrollEndFriction: 0.35,
293      panEndFriction: 0.35,
294      isClickableElement: function (el) {
295        return el.tagName === 'A'
296      },
297      getDoubleTapZoom: function (isMouseClick, item) {
298        if (isMouseClick) {
299          return 1
300        } else {
301          return item.initialZoomLevel < 0.7 ? 1 : 1.33
302        }
303      },
304      maxSpreadZoom: 1.33,
305      modal: true,
306
307      // not fully implemented yet
308      scaleMode: 'fit' // TODO
309    }
310    framework.extend(_options, options)
311
312    /**
313     * Private helper variables & functions
314     */
315
316    var _getEmptyPoint = function () {
317      return { x: 0, y: 0 }
318    }
319
320    var _isOpen,
321      _isDestroying,
322      _closedByScroll,
323      _currentItemIndex,
324      _containerStyle,
325      _containerShiftIndex,
326      _currPanDist = _getEmptyPoint(),
327      _startPanOffset = _getEmptyPoint(),
328      _panOffset = _getEmptyPoint(),
329      _upMoveEvents, // drag move, drag end & drag cancel events array
330      _downEvents, // drag start events array
331      _globalEventHandlers,
332      _viewportSize = {},
333      _currZoomLevel,
334      _startZoomLevel,
335      _translatePrefix,
336      _translateSufix,
337      _updateSizeInterval,
338      _itemsNeedUpdate,
339      _currPositionIndex = 0,
340      _offset = {},
341      _slideSize = _getEmptyPoint(), // size of slide area, including spacing
342      _itemHolders,
343      _prevItemIndex,
344      _indexDiff = 0, // difference of indexes since last content update
345      _dragStartEvent,
346      _dragMoveEvent,
347      _dragEndEvent,
348      _dragCancelEvent,
349      _transformKey,
350      _pointerEventEnabled,
351      _isFixedPosition = true,
352      _likelyTouchDevice,
353      _modules = [],
354      _requestAF,
355      _cancelAF,
356      _initalClassName,
357      _initalWindowScrollY,
358      _oldIE,
359      _currentWindowScrollY,
360      _features,
361      _windowVisibleSize = {},
362      _renderMaxResolution = false,
363      _orientationChangeTimeout,
364      // Registers PhotoSWipe module (History, Controller ...)
365      _registerModule = function (name, module) {
366        framework.extend(self, module.publicMethods)
367        _modules.push(name)
368      },
369      _getLoopedId = function (index) {
370        var numSlides = _getNumItems()
371        if (index > numSlides - 1) {
372          return index - numSlides
373        } else if (index < 0) {
374          return numSlides + index
375        }
376        return index
377      },
378      // Micro bind/trigger
379      _listeners = {},
380      _listen = function (name, fn) {
381        if (!_listeners[name]) {
382          _listeners[name] = []
383        }
384        return _listeners[name].push(fn)
385      },
386      _shout = function (name) {
387        var listeners = _listeners[name]
388
389        if (listeners) {
390          var args = Array.prototype.slice.call(arguments)
391          args.shift()
392
393          for (var i = 0; i < listeners.length; i++) {
394            listeners[i].apply(self, args)
395          }
396        }
397      },
398      _getCurrentTime = function () {
399        return new Date().getTime()
400      },
401      _applyBgOpacity = function (opacity) {
402        _bgOpacity = opacity
403        self.bg.style.opacity = opacity * _options.bgOpacity
404      },
405      _applyZoomTransform = function (styleObj, x, y, zoom, item) {
406        if (!_renderMaxResolution || (item && item !== self.currItem)) {
407          zoom = zoom / (item ? item.fitRatio : self.currItem.fitRatio)
408        }
409
410        styleObj[_transformKey] = _translatePrefix + x + 'px, ' + y + 'px' + _translateSufix + ' scale(' + zoom + ')'
411      },
412      _applyCurrentZoomPan = function (allowRenderResolution) {
413        if (_currZoomElementStyle) {
414          if (allowRenderResolution) {
415            if (_currZoomLevel > self.currItem.fitRatio) {
416              if (!_renderMaxResolution) {
417                _setImageSize(self.currItem, false, true)
418                _renderMaxResolution = true
419              }
420            } else {
421              if (_renderMaxResolution) {
422                _setImageSize(self.currItem)
423                _renderMaxResolution = false
424              }
425            }
426          }
427
428          _applyZoomTransform(_currZoomElementStyle, _panOffset.x, _panOffset.
428y, _currZoomLevel)
429        }
430      },
431      _applyZoomPanToItem = function (item) {
432        if (item.container) {
433          _applyZoomTransform(
434            item.container.style,
435            item.initialPosition.x,
436            item.initialPosition.y,
437            item.initialZoomLevel,
438            item
439          )
440        }
441      },
442      _setTranslateX = function (x, elStyle) {
443        elStyle[_transformKey] = _translatePrefix + x + 'px, 0px' + _translateSufix
444      },
445      _moveMainScroll = function (x, dragging) {
446        if (!_options.loop && dragging) {
447          var newSlideIndexOffset = _currentItemIndex + (_slideSize.x * _currPositionIndex - x) / _slideSize.x,
448            delta = Math.round(x - _mainScrollPos.x)
449
450          if ((newSlideIndexOffset < 0 && delta > 0) || (newSlideIndexOffset >= _getNumItems() - 1 && delta < 0)) {
451            x = _mainScrollPos.x + delta * _options.mainScrollEndFriction
452          }
453        }
454
455        _mainScrollPos.x = x
456        _setTranslateX(x, _containerStyle)
457      },
458      _calculatePanOffset = function (axis, zoomLevel) {
459        var m = _midZoomPoint[axis] - _offset[axis]
460        return _startPanOffset[axis] + _currPanDist[axis] + m - m * (zoomLevel / _startZoomLevel)
461      },
462      _equalizePoints = function (p1, p2) {
463        p1.x = p2.x
464        p1.y = p2.y
465        if (p2.id) {
466          p1.id = p2.id
467        }
468      },
469      _roundPoint = function (p) {
470        p.x = Math.round(p.x)
471        p.y = Math.round(p.y)
472      },
473      _mouseMoveTimeout = null,
474      _onFirstMouseMove = function () {
475        // Wait until mouse move event is fired at least twice during 100ms
476        // We do this, because some mobile browsers trigger it on touchstart
477        if (_mouseMoveTimeout) {
478          framework.unbind(document, 'mousemove', _onFirstMouseMove)
479          framework.addClass(template, 'pswp--has_mouse')
480          _options.mouseUsed = true
481          _shout('mouseUsed')
482        }
483        _mouseMoveTimeout = setTimeout(function () {
484          _mouseMoveTimeout = null
485        }, 100)
486      },
487      _bindEvents = function () {
488        framework.bind(document, 'keydown', self)
489
490        if (_features.transform) {
491          // don't bind click event in browsers that don't support transform (mostly IE8)
492          framework.bind(self.scrollWrap, 'click', self)
493        }
494
495        if (!_options.mouseUsed) {
496          framework.bind(document, 'mousemove', _onFirstMouseMove)
497        }
498
499        framework.bind(window, 'resize scroll orientationchange', self)
500
501        _shout('bindEvents')
502      },
503      _unbindEvents = function () {
504        framework.unbind(window, 'resize scroll orientationchange', self)
505        framework.unbind(window, 'scroll', _globalEventHandlers.scroll)
506        framework.unbind(document, 'keydown', self)
507        framework.unbind(document, 'mousemove', _onFirstMouseMove)
508
509        if (_features.transform) {
510          framework.unbind(self.scrollWrap, 'click', self)
511        }
512
513        if (_isDragging) {
514          framework.unbind(window, _upMoveEvents, self)
515        }
516
517        clearTimeout(_orientationChangeTimeout)
518
519        _shout('unbindEvents')
520      },
521      _calculatePanBounds = function (zoomLevel, update) {
522        var bounds = _calculateItemSize(self.currItem, _viewportSize, zoomLevel)
523        if (update) {
524          _currPanBounds = bounds
525        }
526        return bounds
527      },
528      _getMinZoomLevel = function (item) {
529        if (!item) {
530          item = self.currItem
531        }
532        return item.initialZoomLevel
533      },
534      _getMaxZoomLevel = function (item) {
535        if (!item) {
536          item = self.currItem
537        }
538        return item.w > 0 ? _options.maxSpreadZoom : 1
539      },
540      // Return true if offset is out of the bounds
541      _modifyDestPanOffset = function (axis, destPanBounds, destPanOffset, destZoomLevel) {
542        if (destZoomLevel === self.currItem.initialZoomLevel) {
543          destPanOffset[axis] = self.currItem.initialPosition[axis]
544          return true
545        } else {
546          destPanOffset[axis] = _calculatePanOffset(axis, destZoomLevel)
547
548          if (destPanOffset[axis] > destPanBounds.min[axis]) {
549            destPanOffset[axis] = destPanBounds.min[axis]
550            return true
551          } else if (destPanOffset[axis] < destPanBounds.max[axis]) {
552            destPanOffset[axis] = destPanBounds.max[axis]
553            return true
554          }
555        }
556        return false
557      },
558      _setupTransforms = function () {
559        if (_transformKey) {
560          // setup 3d transforms
561          var allow3dTransform = _features.perspective && !_likelyTouchDevice
562          _translatePrefix = 'translate' + (allow3dTransform ? '3d(' : '(')
563          _translateSufix = _features.perspective ? ', 0px)' : ')'
564          return
565        }
566
567        // Override zoom/pan/move functions in case old browser is used (most likely IE)
568        // (so they use left/top/width/height, instead of CSS transform)
569
570        _transformKey = 'left'
571        framework.addClass(template, 'pswp--ie')
572
573        _setTranslateX = function (x, elStyle) {
574          elStyle.left = x + 'px'
575        }
576        _applyZoomPanToItem = function (item) {
577          var zoomRatio = item.fitRatio > 1 ? 1 : item.fitRatio,
578            s = item.container.style,
579            w = zoomRatio * item.w,
580            h = zoomRatio * item.h
581
582          s.width = w + 'px'
583          s.height = h + 'px'
584          s.left = item.initialPosition.x + 'px'
585          s.top = item.initialPosition.y + 'px'
586        }
587        _applyCurrentZoomPan = function () {
588          if (_currZoomElementStyle) {
589            var s = _currZoomElementStyle,
590              item = self.currItem,
591              zoomRatio = item.fitRatio > 1 ? 1 : item.fitRatio,
592              w = zoomRatio * item.w,
593              h = zoomRatio * item.h
594
595            s.width = w + 'px'
596            s.height = h + 'px'
597
598            s.left = _panOffset.x + 'px'
599            s.top = _panOffset.y + 'px'
600          }
601        }
602      },
603      _onKeyDown = function (e) {
604        var keydownAction = ''
605        if (_options.escKey && e.keyCode === 27) {
606          keydownAction = 'close'
607        } else if (_options.arrowKeys) {
608          if (e.keyCode === 37) {
609            keydownAction = 'prev'
610          } else if (e.keyCode === 39) {
611            keydownAction = 'next'
612          }
613        }
614
615        if (keydownAction) {
616          // don't do anything if special key pressed to prevent from overriding default browser actions
617          // e.g. in Chrome on Mac cmd+arrow-left returns to previous page
618          if (!e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey) {
619            if (e.preventDefault) {
620              e.preventDefault()
621            } else {
622              e.returnValue = false
623            }
624            self[keydownAction]()
625          }
626        }
627      },
628      _onGlobalClick = function (e) {
629        if (!e) {
630          return
631        }
632
633        // don't allow click event to pass through when triggering after drag or some other gesture
634        if (_moved || _zoomStarted || _mainScrollAnimating || _verticalDragInitiated) {
635          e.preventDefault()
636          e.stopPropagation()
637        }
638      },
639      _updatePageScrollOffset = function () {
640        self.setScrollOffset(0, framework.getScrollY())
641      }
642
643    // Micro animation engine
644    var _animations = {},
645      _numAnimations = 0,
646      _stopAnimation = function (name) {
647        if (_animations[name]) {
648          if (_animations[name].raf) {
649            _cancelAF(_animations[name].raf)
650          }
651          _numAnimations--
652          delete _animations[name]
653        }
654      },
655      _registerStartAnimation = function (name) {
656        if (_animations[name]) {
657          _stopAnimation(name)
658        }
659        if (!_animations[name]) {
660          _numAnimations++
661          _animations[name] = {}
662        }
663      },
664      _stopAllAnimations = function () {
665        for (var prop in _animations) {
666          if (_animations.hasOwnProperty(prop)) {
667            _stopAnimation(prop)
668          }
669        }
670      },
671      _animateProp = function (name, b, endProp, d, easingFn, onUpdate, onComplete) {
672        var startAnimTime = _getCurrentTime(),
673          t
674        _registerStartAnimation(name)
675
676        var animloop = function () {
677          if (_animations[name]) {
678            t = _getCurrentTime() - startAnimTime // time diff
679            //b - beginning (start prop)
680            //d - anim duration
681
682            if (t >= d) {
683              _stopAnimation(name)
684              onUpdate(endProp)
685              if (onComplete) {
686                onComplete()
687              }
688              return
689            }
690            onUpdate((endProp - b) * easingFn(t / d) + b)
691
692            _animations[name].raf = _requestAF(animloop)
693          }
694        }
695        animloop()
696      }
697
698    var publicMethods = {
699      // make a few local variables and functions public
700      shout: _shout,
701      listen: _listen,
702      viewportSize: _viewportSize,
703      options: _options,
704
705      isMainScrollAnimating: function () {
706        return _mainScrollAnimating
707      },
708      getZoomLevel: function () {
709        return _currZoomLevel
710      },
711      getCurrentIndex: function () {
712        return _currentItemIndex
713      },
714      isDragging: function () {
715        return _isDragging
716      },
717      isZooming: function () {
718        return _isZooming
719      },
720      setScrollOffset: function (x, y) {
721        _offset.x = x
722        _currentWindowScrollY = _offset.y = y
723        _shout('updateScrollOffset', _offset)
724      },
725      applyZoomPan: function (zoomLevel, panX, panY, allowRenderResolution) {
726        _panOffset.x = panX
727        _panOffset.y = panY
728        _currZoomLevel = zoomLevel
729        _applyCurrentZoomPan(allowRenderResolution)
730      },
731
732      init: function () {
733        if (_isOpen || _isDestroying) {
734          return
735        }
736
737        var i
738
739        self.framework = framework // basic functionality
740        self.template = template // root DOM element of PhotoSwipe
741        self.bg = framework.getChildByClass(template, 'pswp__bg')
742
743        _initalClassName = template.className
744        _isOpen = true
745
746        _features = framework.detectFeatures()
747        _requestAF = _features.raf
748        _cancelAF = _features.caf
749        _transformKey = _features.transform
750        _oldIE = _features.oldIE
751
752        self.scrollWrap = framework.getChildByClass(template, 'pswp__scroll-wrap')
753        self.container = framework.getChildByClass(self.scrollWrap, 'pswp__container')
754
755        _containerStyle = self.container.style // for fast access
756
757        // Objects that hold slides (there are only 3 in DOM)
758        self.itemHolders = _itemHolders = [
759          { el: self.container.children[0], wrap: 0, index: -1 },
760          { el: self.container.children[1], wrap: 0, index: -1 },
761          { el: self.container.children[2], wrap: 0, index: -1 }
762        ]
763
764        // hide nearby item holders until initial zoom animation finishes (to avoid extra Paints)
765        _itemHolders[0].el.style.display = _itemHolders[2].el.style.display = 'none'
766
767        _setupTransforms()
768
769        // Setup global events
770        _globalEventHandlers = {
771          resize: self.updateSize,
772
773          // Fixes: iOS 10.3 resize event
774          // does not update scrollWrap.clientWidth instantly after resize
775          // https://github.com/dimsemenov/PhotoSwipe/issues/1315
776          orientationchange: function () {
777            clearTimeout(_orientationChangeTimeout)
778            _orientationChangeTimeout = setTimeout(function () {
779              if (_viewportSize.x !== self.scrollWrap.clientWidth) {
780                self.updateSize()
781              }
782            }, 500)
783          },
784          scroll: _updatePageScrollOffset,
785          keydown: _onKeyDown,
786          click: _onGlobalClick
787        }
788
789        // disable show/hide effects on old browsers that don't support CSS animations or transforms,
790        // old IOS, Android and Opera mobile. Blackberry seems to work fine, even older models.
791        var oldPhone = _features.isOldIOSPhone || _features.isOldAndroid || _features.isMobileOpera
792        if (!_features.animationName || !_features.transform || oldPhone) {
793          _options.showAnimationDuration = _options.hideAnimationDuration = 0
794        }
795
796        // init modules
797        for (i = 0; i < _modules.length; i++) {
798          self['init' + _modules[i]]()
799        }
800
801        // init
802        if (UiClass) {
803          var ui = (self.ui = new UiClass(self, framework))
804          ui.init()
805        }
806
807        _shout('firstUpdate')
808        _currentItemIndex = _currentItemIndex || _options.index || 0
809        // validate index
810        if (isNaN(_currentItemIndex) || _currentItemIndex < 0 || _currentItemIndex >= _getNumItems()) {
811          _currentItemIndex = 0
812        }
813        self.currItem = _getItemAt(_currentItemIndex)
814
815        if (_features.isOldIOSPhone || _features.isOldAndroid) {
816          _isFixedPosition = false
817        }
818
819        template.setAttribute('aria-hidden', 'false')
820        if (_options.modal) {
821          if (!_isFixedPosition) {
822            template.style.position = 'absolute'
823            template.style.top = framework.getScrollY() + 'px'
824          } else {
825            template.style.position = 'fixed'
826          }
827        }
828
829        if (_currentWindowScrollY === undefined) {
830          _shout('initialLayout')
831          _currentWindowScrollY = _initalWindowScrollY = framework.getScrollY()
832        }
833
834        // add classes to root element of PhotoSwipe
835        var rootClasses = 'pswp--open '
836        if (_options.mainClass) {
837          rootClasses += _options.mainClass + ' '
838        }
839        if (_options.showHideOpacity) {
840          rootClasses += 'pswp--animate_opacity '
841        }
842        rootClasses += _likelyTouchDevice ? 'pswp--touch' : 'pswp--notouch'
843        rootClasses += _features.animationName ? ' pswp--css_animation' : ''
844        rootClasses += _features.svg ? ' pswp--svg' : ''
845        framework.addClass(template, rootClasses)
846
847        self.updateSize()
848
849        // initial update
850        _containerShiftIndex = -1
851        _indexDiff = null
852        for (i = 0; i < NUM_HOLDERS; i++) {
853          _setTranslateX((i + _containerShiftIndex) * _slideSize.x, _itemHolders[i].el.style)
854        }
855
856        if (!_oldIE) {
857          framework.bind(self.scrollWrap, _downEvents, self) // no dragging for old IE
858        }
859
860        _listen('initialZoomInEnd', function () {
861          self.setContent(_itemHolders[0], _currentItemIndex - 1)
862          self.setContent(_itemHolders[2], _currentItemIndex + 1)
863
864          _itemHolders[0].el.style.display = _itemHolders[2].el.style.display = 'block'
865
866          if (_options.focus) {
867            // focus causes layout,
868            // which causes lag during the animation,
869            // that's why we delay it untill the initial zoom transition ends
870            template.focus()
871          }
872
873          _bindEvents()
874        })
875
876        // set content for center slide (first time)
877        self.setContent(_itemHolders[1], _currentItemIndex)
878
879        self.updateCurrItem()
880
881        _shout('afterInit')
882
883        if (!_isFixedPosition) {
884          // On all versions of iOS lower than 8.0, we check size of viewport every second.
885          //
886          // This is done to detect when Safari top & bottom bars appear,
887          // as this action doesn't trigger any events (like resize).
888          //
889          // On iOS8 they fixed this.
890          //
891          // 10 Nov 2014: iOS 7 usage ~40%. iOS 8 usage 56%.
892
893          _updateSizeInterval = setInterval(function () {
894            if (!_numAnimations && !_isDragging && !_isZooming && _currZoomLevel === self.currItem.initialZoomLevel) {
895              self.updateSize()
896            }
897          }, 1000)
898        }
899
900        framework.addClass(template, 'pswp--visible')
901      },
902
903      // Close the gallery, then destroy it
904      close: function () {
905        if (!_isOpen) {
906          return
907        }
908
909        _isOpen = false
910        _isDestroying = true
911        _shout('close')
912        _unbindEvents()
913
914        _showOrHide(self.currItem, null, true, self.destroy)
915
916        setTimeout(() => {
917          document.body.classList.remove('photoswipe-open')
918        }, 100)
919      },
920
921      // destroys the gallery (unbinds events, cleans up intervals and timeouts to avoid memory leaks)
922      destroy: function () {
923        _shout('destroy')
924
925        if (_showOrHideTimeout) {
926          clearTimeout(_showOrHideTimeout)
927        }
928
929        template.setAttribute('aria-hidden', 'true')
930        template.className = _initalClassName
931        template.classList.remove('pswp--open')
932
933        if (_updateSizeInterval) {
934          clearInterval(_updateSizeInterval)
935        }
936
937        framework.unbind(self.scrollWrap, _downEvents, self)
938
939        // we unbind scroll event at the end, as closing animation may depend on it
940        framework.unbind(window, 'scroll', self)
941
942        _stopDragUpdateLoop()
943
944        _stopAllAnimations()
945
946        _listeners = null
947      },
948
949      /**
950       * Pan image to position
951       * @param {Number} x
952       * @param {Number} y
953       * @param {Boolean} force Will ignore bounds if set to true.
954       */
955      panTo: function (x, y, force) {
956        if (!force) {
957          if (x > _currPanBounds.min.x) {
958            x = _currPanBounds.min.x
959          } else if (x < _currPanBounds.max.x) {
960            x = _currPanBounds.max.x
961          }
962
963          if (y > _currPanBounds.min.y) {
964            y = _currPanBounds.min.y
965          } else if (y < _currPanBounds.max.y) {
966            y = _currPanBounds.max.y
967          }
968        }
969
970        _panOffset.x = x
971        _panOffset.y = y
972        _applyCurrentZoomPan()
973      },
974
975      handleEvent: function (e) {
976        e = e || window.event
977        if (_globalEventHandlers[e.type]) {
978          _globalEventHandlers[e.type](e)
979        }
980      },
981
982      goTo: function (index) {
983        index = _getLoopedId(index)
984
985        var diff = index - _currentItemIndex
986        _indexDiff = diff
987
988        _currentItemIndex = index
989        self.currItem = _getItemAt(_currentItemIndex)
990        _currPositionIndex -= diff
991
992        _moveMainScroll(_slideSize.x * _currPositionIndex)
993
994        _stopAllAnimations()
995        _mainScrollAnimating = false
996
997        self.updateCurrItem()
998      },
999      next: function () {
1000        self.goTo(_currentItemIndex + 1)
1001      },
1002      prev: function () {
1003        self.goTo(_currentItemIndex - 1)
1004      },
1005
1006      // update current zoom/pan objects
1007      updateCurrZoomItem: function (emulateSetContent) {
1008        if (emulateSetContent) {
1009          _shout('beforeChange', 0)
1010        }
1011
1012        // itemHolder[1] is middle (current) item
1013        if (_itemHolders[1].el.children.length) {
1014          var zoomElement = _itemHolders[1].el.children[0]
1015          if (framework.hasClass(zoomElement, 'pswp__zoom-wrap')) {
1016            _currZoomElementStyle = zoomElement.style
1017          } else {
1018            _currZoomElementStyle = null
1019          }
1020        } else {
1021          _currZoomElementStyle = null
1022        }
1023
1024        _currPanBounds = self.currItem.bounds
1025        _startZoomLevel = _currZoomLevel = self.currItem.initialZoomLevel
1026
1027        _panOffset.x = _currPanBounds.center.x
1028        _panOffset.y = _currPanBounds.center.y
1029
1030        if (emulateSetContent) {
1031          _shout('afterChange')
1032        }
1033      },
1034
1035      invalidateCurrItems: function () {
1036        _itemsNeedUpdate = true
1037        for (var i = 0; i < NUM_HOLDERS; i++) {
1038          if (_itemHolders[i].item) {
1039            _itemHolders[i].item.needsUpdate = true
1040          }
1041        }
1042      },
1043
1044      updateCurrItem: function (beforeAnimation) {
1045        if (_indexDiff === 0) {
1046          return
1047        }
1048
1049        var diffAbs = Math.abs(_indexDiff),
1050          tempHolder
1051
1052        if (beforeAnimation && diffAbs < 2) {
1053          return
1054        }
1055
1056        self.currItem = _getItemAt(_currentItemIndex)
1057        _renderMaxResolution = false
1058
1059        _shout('beforeChange', _indexDiff)
1060
1061        if (diffAbs >= NUM_HOLDERS) {
1062          _containerShiftIndex += _indexDiff + (_indexDiff > 0 ? -NUM_HOLDERS : NUM_HOLDERS)
1063          diffAbs = NUM_HOLDERS
1064        }
1065        for (var i = 0; i < diffAbs; i++) {
1066          if (_indexDiff > 0) {
1067            tempHolder = _itemHolders.shift()
1068            _itemHolders[NUM_HOLDERS - 1] = tempHolder // move first to last
1069
1070            _containerShiftIndex++
1071            _setTranslateX((_containerShiftIndex + 2) * _slideSize.x, tempHolder.el.style)
1072            self.setContent(tempHolder, _currentItemIndex - diffAbs + i + 1 + 1)
1073          } else {
1074            tempHolder = _itemHolders.pop()
1075            _itemHolders.unshift(tempHolder) // move last to first
1076
1077            _containerShiftIndex--
1078            _setTranslateX(_containerShiftIndex * _slideSize.x, tempHolder.el.style)
1079            self.setContent(tempHolder, _currentItemIndex + diffAbs - i - 1 - 1)
1080          }
1081        }
1082
1083        // reset zoom/pan on previous item
1084        if (_currZoomElementStyle && Math.abs(_indexDiff) === 1) {
1085          var prevItem = _getItemAt(_prevItemIndex)
1086          if (prevItem.initialZoomLevel !== _currZoomLevel) {
1087            _calculateItemSize(prevItem, _viewportSize)
1088            _setImageSize(prevItem)
1089            _applyZoomPanToItem(prevItem)
1090          }
1091        }
1092
1093        // reset diff after update
1094        _indexDiff = 0
1095
1096        self.updateCurrZoomItem()
1097
1098        _prevItemIndex = _currentItemIndex
1099
1100        _shout('afterChange')
1101      },
1102
1103      updateSize: function (force) {
1104        if (!_isFixedPosition && _options.modal) {
1105          var windowScrollY = framework.getScrollY()
1106          if (_currentWindowScrollY !== windowScrollY) {
1107            template.style.top = windowScrollY + 'px'
1108            _currentWindowScrollY = windowScrollY
1109          }
1110          if (!force && _windowVisibleSize.x === window.innerWidth && _windowVisibleSize.y === window.innerHeight) {
1111            return
1112          }
1113          _windowVisibleSize.x = window.innerWidth
1114          _windowVisibleSize.y = window.innerHeight
1115
1116          //template.style.width = _windowVisibleSize.x + 'px';
1117          template.style.height = _windowVisibleSize.y + 'px'
1118        }
1119
1120        _viewportSize.x = self.scrollWrap.clientWidth
1121        _viewportSize.y = self.scrollWrap.clientHeight
1122
1123        _updatePageScrollOffset()
1124
1125        _slideSize.x = _viewportSize.x + Math.round(_viewportSize.x * _options.spacing)
1126        _slideSize.y = _viewportSize.y
1127
1128        _moveMainScroll(_slideSize.x * _currPositionIndex)
1129
1130        _shout('beforeResize') // even may be used for example to switch image sources
1131
1132        // don't re-calculate size on inital size update
1133        if (_containerShiftIndex !== undefined) {
1134          var holder, item, hIndex
1135
1136          for (var i = 0; i < NUM_HOLDERS; i++) {
1137            holder = _itemHolders[i]
1138            _setTranslateX((i + _containerShiftIndex) * _slideSize.x, holder.el.style)
1139
1140            hIndex = _currentItemIndex + i - 1
1141
1142            if (_options.loop && _getNumItems() > 2) {
1143              hIndex = _getLoopedId(hIndex)
1144            }
1145
1146            // update zoom level on items and refresh source (if needsUpdate)
1147            item = _getItemAt(hIndex)
1148
1149            // re-render gallery item if `needsUpdate`,
1150            // or doesn't have `bounds` (entirely new slide object)
1151            if (item && (_itemsNeedUpdate || item.needsUpdate || !item.bounds)) {
1152              self.cleanSlide(item)
1153
1154              self.setContent(holder, hIndex)
1155
1156              // if "center" slide
1157              if (i === 1) {
1158                self.currItem = item
1159                self.updateCurrZoomItem(true)
1160              }
1161
1162              item.needsUpdate = false
1163            } else if (holder.index === -1 && hIndex >= 0) {
1164              // add content first time
1165              self.setContent(holder, hIndex)
1166            }
1167            if (item && item.container) {
1168              _calculateItemSize(item, _viewportSize)
1169              _setImageSize(item)
1170              _applyZoomPanToItem(item)
1171            }
1172          }
1173          _itemsNeedUpdate = false
1174        }
1175
1176        _startZoomLevel = _currZoomLevel = self.currItem.initialZoomLevel
1177        _currPanBounds = self.currItem.bounds
1178
1179        if (_currPanBounds) {
1180          _panOffset.x = _currPanBounds.center.x
1181          _panOffset.y = _currPanBounds.center.y
1182          _applyCurrentZoomPan(true)
1183        }
1184
1185        _shout('resize')
1186      },
1187
1188      // Zoom current item to
1189      zoomTo: function (destZoomLevel, centerPoint, speed, easingFn, updateFn) {
1190        /*
1191      if(destZoomLevel === 'fit') {
1192        destZoomLevel = self.currItem.fitRatio;
1193      } else if(destZoomLevel === 'fill') {
1194        destZoomLevel = self.currItem.fillRatio;
1195      }
1196    */
1197
1198        if (centerPoint) {
1199          _startZoomLevel = _currZoomLevel
1200          _midZoomPoint.x = Math.abs(centerPoint.x) - _panOffset.x
1201          _midZoomPoint.y = Math.abs(centerPoint.y) - _panOffset.y
1202          _equalizePoints(_startPanOffset, _panOffset)
1203        }
1204
1205        var destPanBounds = _calculatePanBounds(destZoomLevel, false),
1206          destPanOffset = {}
1207
1208        _modifyDestPanOffset('x', destPanBounds, destPanOffset, destZoomLevel)
1209        _modifyDestPanOffset('y', destPanBounds, destPanOffset, destZoomLevel)
1210
1211        var initialZoomLevel = _currZoomLevel
1212        var initialPanOffset = {
1213          x: _panOffset.x,
1214          y: _panOffset.y
1215        }
1216
1217        _roundPoint(destPanOffset)
1218
1219        var onUpdate = function (now) {
1220          if (now === 1) {
1221            _currZoomLevel = destZoomLevel
1222            _panOffset.x = destPanOffset.x
1223            _panOffset.y = destPanOffset.y
1224          } else {
1225            _currZoomLevel = (destZoomLevel - initialZoomLevel) * now + initialZoomLevel
1226            _panOffset.x = (destPanOffset.x - initialPanOffset.x) * now + initialPanOffset.x
1227            _panOffset.y = (destPanOffset.y - initialPanOffset.y) * now + initialPanOffset.y
1228          }
1229
1230          if (updateFn) {
1231            updateFn(now)
1232          }
1233
1234          _applyCurrentZoomPan(now === 1)
1235        }
1236
1237        if (speed) {
1238          _animateProp('customZoomTo', 0, 1, speed, easingFn || framework.easing.sine.inOut, onUpdate)
1239        } else {
1240          onUpdate(1)
1241        }
1242      }
1243    }
1244
1245    /*>>core*/
1246
1247    /*>>gestures*/
1248    /**
1249     * Mouse/touch/pointer event handlers.
1250     *
1251     * separated from @core.js for readability
1252     */
1253
1254    var MIN_SWIPE_DISTANCE = 30,
1255      DIRECTION_CHECK_OFFSET = 10 // amount of pixels to drag to determine direction of swipe
1256
1257    var _gestureStartTime,
1258      _gestureCheckSpeedTime,
1259      // pool of objects that are used during dragging of zooming
1260      p = {}, // first point
1261      p2 = {}, // second point (for zoom gesture)
1262      delta = {},
1263      _currPoint = {},
1264      _startPoint = {},
1265      _currPointers = [],
1266      _startMainScrollPos = {},
1267      _releaseAnimData,
1268      _posPoints = [], // array of points during dragging, used to determine type of gesture
1269      _tempPoint = {},
1270      _isZoomingIn,
1271      _verticalDragInitiated,
1272      _oldAndroidTouchEndTimeout,
1273      _currZoomedItemIndex = 0,
1274      _centerPoint = _getEmptyPoint(),
1275      _lastReleaseTime = 0,
1276      _isDragging, // at least one pointer is down
1277      _isMultitouch, // at least two _pointers are down
1278      _zoomStarted, // zoom level changed during zoom gesture
1279      _moved,
1280      _dragAnimFrame,
1281      _mainScrollShifted,
1282      _currentPoints, // array of current touch points
1283      _isZooming,
1284      _currPointsDistance,
1285      _startPointsDistance,
1286      _currPanBounds,
1287      _mainScrollPos = _getEmptyPoint(),
1288      _currZoomElementStyle,
1289      _mainScrollAnimating, // true, if animation after swipe gesture is running
1290      _midZoomPoint = _getEmptyPoint(),
1291      _currCenterPoint = _getEmptyPoint(),
1292      _direction,
1293      _isFirstMove,
1294      _opacityChanged,
1295      _bgOpacity,
1296      _wasOverInitialZoom,
1297      _isEqualPoints = function (p1, p2) {
1298        return p1.x === p2.x && p1.y === p2.y
1299      },
1300      _isNearbyPoints = function (touch0, touch1) {
1301        return Math.abs(touch0.x - touch1.x) < DOUBLE_TAP_RADIUS && Math.abs(touch0.y - touch1.y) < DOUBLE_TAP_RADIUS
1302      },
1303      _calculatePointsDistance = function (p1, p2) {
1304        _tempPoint.x = Math.abs(p1.x - p2.x)
1305        _tempPoint.y = Math.abs(p1.y - p2.y)
1306        return Math.sqrt(_tempPoint.x * _tempPoint.x + _tempPoint.y * _tempPoint.y)
1307      },
1308      _stopDragUpdateLoop = function () {
1309        if (_dragAnimFrame) {
1310          _cancelAF(_dragAnimFrame)
1311          _dragAnimFrame = null
1312        }
1313      },
1314      _dragUpdateLoop = function () {
1315        if (_isDragging) {
1316          _dragAnimFrame = _requestAF(_dragUpdateLoop)
1317          _renderMovement()
1318        }
1319      },
1320      _canPan = function () {
1321        return !(_options.scaleMode === 'fit' && _currZoomLevel === self.currItem.initialZoomLevel)
1322      },
1323      // find the closest parent DOM element
1324      _closestElement = function (el, fn) {
1325        if (!el || el === document) {
1326          return false
1327        }
1328
1329        // don't search elements above pswp__scroll-wrap
1330        if (el.getAttribute('class') && el.getAttribute('class').indexOf('pswp__scroll-wrap') > -1) {
1331          return false
1332        }
1333
1334        if (fn(el)) {
1335          return el
1336        }
1337
1338        return _closestElement(el.parentNode, fn)
1339      },
1340      _preventObj = {},
1341      _preventDefaultEventBehaviour = function (e, isDown) {
1342        _preventObj.prevent = !_closestElement(e.target, _options.isClickableElement)
1343
1344        _shout('preventDragEvent', e, isDown, _preventObj)
1345        return _preventObj.prevent
1346      },
1347      _convertTouchToPoint = function (touch, p) {
1348        p.x = touch.pageX
1349        p.y = touch.pageY
1350        p.id = touch.identifier
1351        return p
1352      },
1353      _findCenterOfPoints = function (p1, p2, pCenter) {
1354        pCenter.x = (p1.x + p2.x) * 0.5
1355        pCenter.y = (p1.y + p2.y) * 0.5
1356      },
1357      _pushPosPoint = function (time, x, y) {
1358        if (time - _gestureCheckSpeedTime > 50) {
1359          var o = _posPoints.length > 2 ? _posPoints.shift() : {}
1360          o.x = x
1361          o.y = y
1362          _posPoints.push(o)
1363          _gestureCheckSpeedTime = time
1364        }
1365      },
1366      _calculateVerticalDragOpacityRatio = function () {
1367        var yOffset = _panOffset.y - self.currItem.initialPosition.y // difference between initial and current position
1368        return 1 - Math.abs(yOffset / (_viewportSize.y / 2))
1369      },
1370      // points pool, reused during touch events
1371      _ePoint1 = {},
1372      _ePoint2 = {},
1373      _tempPointsArr = [],
1374      _tempCounter,
1375      _getTouchPoints = function (e) {
1376        // clean up previous points, without recreating array
1377        while (_tempPointsArr.length > 0) {
1378          _tempPointsArr.pop()
1379        }
1380
1381        if (!_pointerEventEnabled) {
1382          if (e.type.indexOf('touch') > -1) {
1383            if (e.touches && e.touches.length > 0) {
1384              _tempPointsArr[0] = _convertTouchToPoint(e.touches[0], _ePoint1)
1385              if (e.touches.length > 1) {
1386                _tempPointsArr[1] = _convertTouchToPoint(e.touches[1], _ePoint2)
1387              }
1388            }
1389          } else {
1390            _ePoint1.x = e.pageX
1391            _ePoint1.y = e.pageY
1392            _ePoint1.id = ''
1393            _tempPointsArr[0] = _ePoint1 //_ePoint1;
1394          }
1395        } else {
1396          _tempCounter = 0
1397          // we can use forEach, as pointer events are supported only in modern browsers
1398          _currPointers.forEach(function (p) {
1399            if (_tempCounter === 0) {
1400              _tempPointsArr[0] = p
1401            } else if (_tempCounter === 1) {
1402              _tempPointsArr[1] = p
1403            }
1404            _tempCounter++
1405          })
1406        }
1407        return _tempPointsArr
1408      },
1409      _panOrMoveMainScroll = function (axis, delta) {
1410        var panFriction,
1411          overDiff = 0,
1412          newOffset = _panOffset[axis] + delta[axis],
1413          startOverDiff,
1414          dir = delta[axis] > 0,
1415          newMainScrollPosition = _mainScrollPos.x + delta.x,
1416          mainScrollDiff = _mainScrollPos.x - _startMainScrollPos.x,
1417          newPanPos,
1418          newMainScrollPos
1419
1420        // calculate fdistance over the bounds and friction
1421        if (newOffset > _currPanBounds.min[axis] || newOffset < _currPanBounds.max[axis]) {
1422          panFriction = _options.panEndFriction
1423          // Linear increasing of friction, so at 1/4 of viewport it's at max value.
1424          // Looks not as nice as was expected. Left for history.
1425          // panFriction = (1 - (_panOffset[axis] + delta[axis] + panBounds.min[axis]) / (_viewportSize[axis] / 4) );
1426        } else {
1427          panFriction = 1
1428        }
1429
1430        newOffset = _panOffset[axis] + delta[axis] * panFriction
1431
1432        // move main scroll or start panning
1433        if (_options.allowPanToNext || _currZoomLevel === self.currItem.initialZoomLevel) {
1434          if (!_currZoomElementStyle) {
1435            newMainScrollPos = newMainScrollPosition
1436          } else if (_direction === 'h' && axis === 'x' && !_zoomStarted) {
1437            if (dir) {
1438              if (newOffset > _currPanBounds.min[axis]) {
1439                panFriction = _options.panEndFriction
1440                overDiff = _currPanBounds.min[axis] - newOffset
1441                startOverDiff = _currPanBounds.min[axis] - _startPanOffset[axis]
1442              }
1443
1444              // drag right
1445              if ((startOverDiff <= 0 || mainScrollDiff < 0) && _getNumItems() > 1) {
1446                newMainScrollPos = newMainScrollPosition
1447                if (mainScrollDiff < 0 && newMainScrollPosition > _startMainScrollPos.x) {
1448                  newMainScrollPos = _startMainScrollPos.x
1449                }
1450              } else {
1451                if (_currPanBounds.min.x !== _currPanBounds.max.x) {
1452                  newPanPos = newOffset
1453                }
1454              }
1455            } else {
1456              if (newOffset < _currPanBounds.max[axis]) {
1457                panFriction = _options.panEndFriction
1458                overDiff = newOffset - _currPanBounds.max[axis]
1459                startOverDiff = _startPanOffset[axis] - _currPanBounds.max[axis]
1460              }
1461
1462              if ((startOverDiff <= 0 || mainScrollDiff > 0) && _getNumItems() > 1) {
1463                newMainScrollPos = newMainScrollPosition
1464
1465                if (mainScrollDiff > 0 && newMainScrollPosition < _startMainScrollPos.x) {
1466                  newMainScrollPos = _startMainScrollPos.x
1467                }
1468              } else {
1469                if (_currPanBounds.min.x !== _currPanBounds.max.x) {
1470                  newPanPos = newOffset
1471                }
1472              }
1473            }
1474
1475            //
1476          }
1477
1478          if (axis === 'x') {
1479            if (newMainScrollPos !== undefined) {
1480              _moveMainScroll(newMainScrollPos, true)
1481              if (newMainScrollPos === _startMainScrollPos.x) {
1482                _mainScrollShifted = false
1483              } else {
1484                _mainScrollShifted = true
1485              }
1486            }
1487
1488            if (_currPanBounds.min.x !== _currPanBounds.max.x) {
1489              if (newPanPos !== undefined) {
1490                _panOffset.x = newPanPos
1491              } else if (!_mainScrollShifted) {
1492                _panOffset.x += delta.x * panFriction
1493              }
1494            }
1495
1496            return newMainScrollPos !== undefined
1497          }
1498        }
1499
1500        if (!_mainScrollAnimating) {
1501          if (!_mainScrollShifted) {
1502            if (_currZoomLevel > self.currItem.fitRatio) {
1503              _panOffset[axis] += delta[axis] * panFriction
1504            }
1505          }
1506        }
1507      },
1508      // Pointerdown/touchstart/mousedown handler
1509      _onDragStart = function (e) {
1510        // Allow dragging only via left mouse button.
1511        // As this handler is not added in IE8 - we ignore e.which
1512        //
1513        // http://www.quirksmode.org/js/events_properties.html
1514        // https://developer.mozilla.org/en-US/docs/Web/API/event.button
1515        if (e.type === 'mousedown' && e.button > 0) {
1516          return
1517        }
1518
1519        if (_initialZoomRunning) {
1520          e.preventDefault()
1521          return
1522        }
1523
1524        if (_oldAndroidTouchEndTimeout && e.type === 'mousedown') {
1525          return
1526        }
1527
1528        if (_preventDefaultEventBehaviour(e, true)) {
1529          e.preventDefault()
1530        }
1531
1532        _shout('pointerDown')
1533
1534        if (_pointerEventEnabled) {
1535          var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id')
1536          if (pointerIndex < 0) {
1537            pointerIndex = _currPointers.length
1538          }
1539          _currPointers[pointerIndex] = { x: e.pageX, y: e.pageY, id: e.pointerId }
1540        }
1541
1542        var startPointsList = _getTouchPoints(e),
1543          numPoints = startPointsList.length
1544
1545        _currentPoints = null
1546
1547        _stopAllAnimations()
1548
1549        // init drag
1550        if (!_isDragging || numPoints === 1) {
1551          _isDragging = _isFirstMove = true
1552          framework.bind(window, _upMoveEvents, self)
1553
1554          _isZoomingIn =
1555            _wasOverInitialZoom =
1556            _opacityChanged =
1557            _verticalDragInitiated =
1558            _mainScrollShifted =
1559            _moved =
1560            _isMultitouch =
1561            _zoomStarted =
1562            false
1563
1564          _direction = null
1565
1566          _shout('firstTouchStart', startPointsList)
1567
1568          _equalizePoints(_startPanOffset, _panOffset)
1569
1570          _currPanDist.x = _currPanDist.y = 0
1571          _equalizePoints(_currPoint, startPointsList[0])
1572          _equalizePoints(_startPoint, _currPoint)
1573
1574          //_equalizePoints(_startMainScrollPos, _mainScrollPos);
1575          _startMainScrollPos.x = _slideSize.x * _currPositionIndex
1576
1577          _posPoints = [
1578            {
1579              x: _currPoint.x,
1580              y: _currPoint.y
1581            }
1582          ]
1583
1584          _gestureCheckSpeedTime = _gestureStartTime = _getCurrentTime()
1585
1586          //_mainScrollAnimationEnd(true);
1587          _calculatePanBounds(_currZoomLevel, true)
1588
1589          // Start rendering
1590          _stopDragUpdateLoop()
1591          _dragUpdateLoop()
1592        }
1593
1594        // init zoom
1595        if (!_isZooming && numPoints > 1 && !_mainScrollAnimating && !_mainScrollShifted) {
1596          _startZoomLevel = _currZoomLevel
1597          _zoomStarted = false // true if zoom changed at least once
1598
1599          _isZooming = _isMultitouch = true
1600          _currPanDist.y = _currPanDist.x = 0
1601
1602          _equalizePoints(_startPanOffset, _panOffset)
1603
1604          _equalizePoints(p, startPointsList[0])
1605          _equalizePoints(p2, startPointsList[1])
1606
1607          _findCenterOfPoints(p, p2, _currCenterPoint)
1608
1609          _midZoomPoint.x = Math.abs(_currCenterPoint.x) - _panOffset.x
1610          _midZoomPoint.y = Math.abs(_currCenterPoint.y) - _panOffset.y
1611          _currPointsDistance = _startPointsDistance = _calculatePointsDistance(p, p2)
1612        }
1613      },
1614      // Pointermove/touchmove/mousemove handler
1615      _onDragMove = function (e) {
1616        e.preventDefault()
1617
1618        if (_pointerEventEnabled) {
1619          var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id')
1620          if (pointerIndex > -1) {
1621            var p = _currPointers[pointerIndex]
1622            p.x = e.pageX
1623            p.y = e.pageY
1624          }
1625        }
1626
1627        if (_isDragging) {
1628          var touchesList = _getTouchPoints(e)
1629          if (!_direction && !_moved && !_isZooming) {
1630            if (_mainScrollPos.x !== _slideSize.x * _currPositionIndex) {
1631              // if main scroll position is shifted – direction is always horizontal
1632              _direction = 'h'
1633            } else {
1634              var diff = Math.abs(touchesList[0].x - _currPoint.x) - Math.abs(touchesList[0].y - _currPoint.y)
1635              // check the direction of movement
1636              if (Math.abs(diff) >= DIRECTION_CHECK_OFFSET) {
1637                _direction = diff > 0 ? 'h' : 'v'
1638                _currentPoints = touchesList
1639              }
1640            }
1641          } else {
1642            _currentPoints = touchesList
1643          }
1644        }
1645      },
1646      //
1647      _renderMovement = function () {
1648        if (!_currentPoints) {
1649          return
1650        }
1651
1652        var numPoints = _currentPoints.length
1653
1654        if (numPoints === 0) {
1655          return
1656        }
1657
1658        _equalizePoints(p, _currentPoints[0])
1659
1660        delta.x = p.x - _currPoint.x
1661        delta.y = p.y - _currPoint.y
1662
1663        if (_isZooming && numPoints > 1) {
1664          // Handle behaviour for more than 1 point
1665
1666          _currPoint.x = p.x
1667          _currPoint.y = p.y
1668
1669          // check if one of two points changed
1670          if (!delta.x && !delta.y && _isEqualPoints(_currentPoints[1], p2)) {
1671            return
1672          }
1673
1674          _equalizePoints(p2, _currentPoints[1])
1675
1676          if (!_zoomStarted) {
1677            _zoomStarted = true
1678            _shout('zoomGestureStarted')
1679          }
1680
1681          // Distance between two points
1682          var pointsDistance = _calculatePointsDistance(p, p2)
1683
1684          var zoomLevel = _calculateZoomLevel(pointsDistance)
1685
1686          // slightly over the of initial zoom level
1687          if (zoomLevel > self.currItem.initialZoomLevel + self.currItem.initialZoomLevel / 15) {
1688            _wasOverInitialZoom = true
1689          }
1690
1691          // Apply the friction if zoom level is out of the bounds
1692          var zoomFriction = 1,
1693            minZoomLevel = _getMinZoomLevel(),
1694            maxZoomLevel = _getMaxZoomLevel()
1695
1696          if (zoomLevel < minZoomLevel) {
1697            if (_options.pinchToClose && !_wasOverInitialZoom && _startZoomLevel <= self.currItem.initialZoomLevel) {
1698              // fade out background if zooming out
1699              var minusDiff = minZoomLevel - zoomLevel
1700              var percent = 1 - minusDiff / (minZoomLevel / 1.2)
1701
1702              _applyBgOpacity(percent)
1703              _shout('onPinchClose', percent)
1704              _opacityChanged = true
1705            } else {
1706              zoomFriction = (minZoomLevel - zoomLevel) / minZoomLevel
1707              if (zoomFriction > 1) {
1708                zoomFriction = 1
1709              }
1710              zoomLevel = minZoomLevel - zoomFriction * (minZoomLevel / 3)
1711            }
1712          } else if (zoomLevel > maxZoomLevel) {
1713            // 1.5 - extra zoom level above the max. E.g. if max is x6, real max 6 + 1.5 = 7.5
1714            zoomFriction = (zoomLevel - maxZoomLevel) / (minZoomLevel * 6)
1715            if (zoomFriction > 1) {
1716              zoomFriction = 1
1717            }
1718            zoomLevel = maxZoomLevel + zoomFriction * minZoomLevel
1719          }
1720
1721          if (zoomFriction < 0) {
1722            zoomFriction = 0
1723          }
1724
1725          // distance between touch points after friction is applied
1726          _currPointsDistance = pointsDistance
1727
1728          // _centerPoint - The point in the middle of two pointers
1729          _findCenterOfPoints(p, p2, _centerPoint)
1730
1731          // paning with two pointers pressed
1732          _currPanDist.x += _centerPoint.x - _currCenterPoint.x
1733          _currPanDist.y += _centerPoint.y - _currCenterPoint.y
1734          _equalizePoints(_currCenterPoint, _centerPoint)
1735
1736          _panOffset.x = _calculatePanOffset('x', zoomLevel)
1737          _panOffset.y = _calculatePanOffset('y', zoomLevel)
1738
1739          _isZoomingIn = zoomLevel > _currZoomLevel
1740          _currZoomLevel = zoomLevel
1741          _applyCurrentZoomPan()
1742        } else {
1743          // handle behaviour for one point (dragging or panning)
1744
1745          if (!_direction) {
1746            return
1747          }
1748
1749          if (_isFirstMove) {
1750            _isFirstMove = false
1751
1752            // subtract drag distance that was used during the detection direction
1753
1754            if (Math.abs(delta.x) >= DIRECTION_CHECK_OFFSET) {
1755              delta.x -= _currentPoints[0].x - _startPoint.x
1756            }
1757
1758            if (Math.abs(delta.y) >= DIRECTION_CHECK_OFFSET) {
1759              delta.y -= _currentPoints[0].y - _startPoint.y
1760            }
1761          }
1762
1763          _currPoint.x = p.x
1764          _currPoint.y = p.y
1765
1766          // do nothing if pointers position hasn't changed
1767          if (delta.x === 0 && delta.y === 0) {
1768            return
1769          }
1770
1771          if (_direction === 'v' && _options.closeOnVerticalDrag) {
1772            if (!_canPan()) {
1773              _currPanDist.y += delta.y
1774              _panOffset.y += delta.y
1775
1776              var opacityRatio = _calculateVerticalDragOpacityRatio()
1777
1778              _verticalDragInitiated = true
1779              _shout('onVerticalDrag', opacityRatio)
1780
1781              _applyBgOpacity(opacityRatio)
1782              _applyCurrentZoomPan()
1783              return
1784            }
1785          }
1786
1787          _pushPosPoint(_getCurrentTime(), p.x, p.y)
1788
1789          _moved = true
1790          _currPanBounds = self.currItem.bounds
1791
1792          var mainScrollChanged = _panOrMoveMainScroll('x', delta)
1793          if (!mainScrollChanged) {
1794            _panOrMoveMainScroll('y', delta)
1795
1796            _roundPoint(_panOffset)
1797            _applyCurrentZoomPan()
1798          }
1799        }
1800      },
1801      // Pointerup/pointercancel/touchend/touchcancel/mouseup event handler
1802      _onDragRelease = function (e) {
1803        if (_features.isOldAndroid) {
1804          if (_oldAndroidTouchEndTimeout && e.type === 'mouseup') {
1805            return
1806          }
1807
1808          // on Android (v4.1, 4.2, 4.3 & possibly older)
1809          // ghost mousedown/up event isn't preventable via e.preventDefault,
1810          // which causes fake mousedown event
1811          // so we block mousedown/up for 600ms
1812          if (e.type.indexOf('touch') > -1) {
1813            clearTimeout(_oldAndroidTouchEndTimeout)
1814            _oldAndroidTouchEndTimeout = setTimeout(function () {
1815              _oldAndroidTouchEndTimeout = 0
1816            }, 600)
1817          }
1818        }
1819
1820        _shout('pointerUp')
1821
1822        if (_preventDefaultEventBehaviour(e, false)) {
1823          e.preventDefault()
1824        }
1825
1826        var releasePoint
1827
1828        if (_pointerEventEnabled) {
1829          var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id')
1830
1831          if (pointerIndex > -1) {
1832            releasePoint = _currPointers.splice(pointerIndex, 1)[0]
1833
1834            if (navigator.msPointerEnabled) {
1835              var MSPOINTER_TYPES = {
1836                4: 'mouse', // event.MSPOINTER_TYPE_MOUSE
1837                2: 'touch', // event.MSPOINTER_TYPE_TOUCH
1838                3: 'pen' // event.MSPOINTER_TYPE_PEN
1839              }
1840              releasePoint.type = MSPOINTER_TYPES[e.pointerType]
1841
1842              if (!releasePoint.type) {
1843                releasePoint.type = e.pointerType || 'mouse'
1844              }
1845            } else {
1846              releasePoint.type = e.pointerType || 'mouse'
1847            }
1848          }
1849        }
1850
1851        var touchList = _getTouchPoints(e),
1852          gestureType,
1853          numPoints = touchList.length
1854
1855        if (e.type === 'mouseup') {
1856          numPoints = 0
1857        }
1858
1859        // Do nothing if there were 3 touch points or more
1860        if (numPoints === 2) {
1861          _currentPoints = null
1862          return true
1863        }
1864
1865        // if second pointer released
1866        if (numPoints === 1) {
1867          _equalizePoints(_startPoint, touchList[0])
1868        }
1869
1870        // pointer hasn't moved, send "tap release" point
1871        if (numPoints === 0 && !_direction && !_mainScrollAnimating) {
1872          if (!releasePoint) {
1873            if (e.type === 'mouseup') {
1874              releasePoint = { x: e.pageX, y: e.pageY, type: 'mouse' }
1875            } else if (e.changedTouches && e.changedTouches[0]) {
1876              releasePoint = { x: e.changedTouches[0].pageX, y: e.changedTouches[0].pageY, type: 'touch' }
1877            }
1878          }
1879
1880          _shout('touchRelease', e, releasePoint)
1881        }
1882
1883        // Difference in time between releasing of two last touch points (zoom gesture)
1884        var releaseTimeDiff = -1
1885
1886        // Gesture completed, no pointers left
1887        if (numPoints === 0) {
1888          _isDragging = false
1889          framework.unbind(window, _upMoveEvents, self)
1890
1891          _stopDragUpdateLoop()
1892
1893          if (_isZooming) {
1894            // Two points released at the same time
1895            releaseTimeDiff = 0
1896          } else if (_lastReleaseTime !== -1) {
1897            releaseTimeDiff = _getCurrentTime() - _lastReleaseTime
1898          }
1899        }
1900        _lastReleaseTime = numPoints === 1 ? _getCurrentTime() : -1
1901
1902        if (releaseTimeDiff !== -1 && releaseTimeDiff < 150) {
1903          gestureType = 'zoom'
1904        } else {
1905          gestureType = 'swipe'
1906        }
1907
1908        if (_isZooming && numPoints < 2) {
1909          _isZooming = false
1910
1911          // Only second point released
1912          if (numPoints === 1) {
1913            gestureType = 'zoomPointerUp'
1914          }
1915          _shout('zoomGestureEnded')
1916        }
1917
1918        _currentPoints = null
1919        if (!_moved && !_zoomStarted && !_mainScrollAnimating && !_verticalDragInitiated) {
1920          // nothing to animate
1921          return
1922        }
1923
1924        _stopAllAnimations()
1925
1926        if (!_releaseAnimData) {
1927          _releaseAnimData = _initDragReleaseAnimationData()
1928        }
1929
1930        _releaseAnimData.calculateSwipeSpeed('x')
1931
1932        if (_verticalDragInitiated) {
1933          var opacityRatio = _calculateVerticalDragOpacityRatio()
1934
1935          if (opacityRatio < _options.verticalDragRange) {
1936            self.close()
1937          } else {
1938            var initalPanY = _panOffset.y,
1939              initialBgOpacity = _bgOpacity
1940
1941            _animateProp('verticalDrag', 0, 1, 300, framework.easing.cubic.out, function (now) {
1942              _panOffset.y = (self.currItem.initialPosition.y - initalPanY) * now + initalPanY
1943
1944              _applyBgOpacity((1 - initialBgOpacity) * now + initialBgOpacity)
1945              _applyCurrentZoomPan()
1946            })
1947
1948            _shout('onVerticalDrag', 1)
1949          }
1950
1951          return
1952        }
1953
1954        // main scroll
1955        if ((_mainScrollShifted || _mainScrollAnimating) && numPoints === 0) {
1956          var itemChanged = _finishSwipeMainScrollGesture(gestureType, _releaseAnimData)
1957          if (itemChanged) {
1958            return
1959          }
1960          gestureType = 'zoomPointerUp'
1961        }
1962
1963        // prevent zoom/pan animation when main scroll animation runs
1964        if (_mainScrollAnimating) {
1965          return
1966        }
1967
1968        // Complete simple zoom gesture (reset zoom level if it's out of the bounds)
1969        if (gestureType !== 'swipe') {
1970          _completeZoomGesture()
1971          return
1972        }
1973
1974        // Complete pan gesture if main scroll is not shifted, and it's possible to pan current image
1975        if (!_mainScrollShifted && _currZoomLevel > self.currItem.fitRatio) {
1976          _completePanGesture(_releaseAnimData)
1977        }
1978      },
1979      // Returns object with data about gesture
1980      // It's created only once and then reused
1981      _initDragReleaseAnimationData = function () {
1982        // temp local vars
1983        var lastFlickDuration, tempReleasePos
1984
1985        // s = this
1986        var s = {
1987          lastFlickOffset: {},
1988          lastFlickDist: {},
1989          lastFlickSpeed: {},
1990          slowDownRatio: {},
1991          slowDownRatioReverse: {},
1992          speedDecelerationRatio: {},
1993          speedDecelerationRatioAbs: {},
1994          distanceOffset: {},
1995          backAnimDestination: {},
1996          backAnimStarted: {},
1997          calculateSwipeSpeed: function (axis) {
1998            if (_posPoints.length > 1) {
1999              lastFlickDuration = _getCurrentTime() - _gestureCheckSpeedTime + 50
2000              tempReleasePos = _posPoints[_posPoints.length - 2][axis]
2001            } else {
2002              lastFlickDuration = _getCurrentTime() - _gestureStartTime // total gesture duration
2003              tempReleasePos = _startPoint[axis]
2004            }
2005            s.lastFlickOffset[axis] = _currPoint[axis] - tempReleasePos
2006            s.lastFlickDist[axis] = Math.abs(s.lastFlickOffset[axis])
2007            if (s.lastFlickDist[axis] > 20) {
2008              s.lastFlickSpeed[axis] = s.lastFlickOffset[axis] / lastFlickDuration
2009            } else {
2010              s.lastFlickSpeed[axis] = 0
2011            }
2012            if (Math.abs(s.lastFlickSpeed[axis]) < 0.1) {
2013              s.lastFlickSpeed[axis] = 0
2014            }
2015
2016            s.slowDownRatio[axis] = 0.95
2017            s.slowDownRatioReverse[axis] = 1 - s.slowDownRatio[axis]
2018            s.speedDecelerationRatio[axis] = 1
2019          },
2020
2021          calculateOverBoundsAnimOffset: function (axis, speed) {
2022            if (!s.backAnimStarted[axis]) {
2023              if (_panOffset[axis] > _currPanBounds.min[axis]) {
2024                s.backAnimDestination[axis] = _currPanBounds.min[axis]
2025              } else if (_panOffset[axis] < _currPanBounds.max[axis]) {
2026                s.backAnimDestination[axis] = _currPanBounds.max[axis]
2027              }
2028
2029              if (s.backAnimDestination[axis] !== undefined) {
2030                s.slowDownRatio[axis] = 0.7
2031                s.slowDownRatioReverse[axis] = 1 - s.slowDownRatio[axis]
2032                if (s.speedDecelerationRatioAbs[axis] < 0.05) {
2033                  s.lastFlickSpeed[axis] = 0
2034                  s.backAnimStarted[axis] = true
2035
2036                  _animateProp(
2037                    'bounceZoomPan' + axis,
2038                    _panOffset[axis],
2039                    s.backAnimDestination[axis],
2040                    speed || 300,
2041                    framework.easing.sine.out,
2042                    function (pos) {
2043                      _panOffset[axis] = pos
2044                      _applyCurrentZoomPan()
2045                    }
2046                  )
2047                }
2048              }
2049            }
2050          },
2051
2052          // Reduces the speed by slowDownRatio (per 10ms)
2053          calculateAnimOffset: function (axis) {
2054            if (!s.backAnimStarted[axis]) {
2055              s.speedDecelerationRatio[axis] =
2056                s.speedDecelerationRatio[axis] *
2057                (s.slowDownRatio[axis] +
2058                  s.slowDownRatioReverse[axis] -
2059                  (s.slowDownRatioReverse[axis] * s.timeDiff) / 10)
2060
2061              s.speedDecelerationRatioAbs[axis] = Math.abs(s.lastFlickSpeed[axis] * s.speedDecelerationRatio[axis])
2062              s.distanceOffset[axis] = s.lastFlickSpeed[axis] * s.speedDecelerationRatio[axis] * s.timeDiff
2063              _panOffset[axis] += s.distanceOffset[axis]
2064            }
2065          },
2066
2067          panAnimLoop: function () {
2068            if (_animations.zoomPan) {
2069              _animations.zoomPan.raf = _requestAF(s.panAnimLoop)
2070
2071              s.now = _getCurrentTime()
2072              s.timeDiff = s.now - s.lastNow
2073              s.lastNow = s.now
2074
2075              s.calculateAnimOffset('x')
2076              s.calculateAnimOffset('y')
2077
2078              _applyCurrentZoomPan()
2079
2080              s.calculateOverBoundsAnimOffset('x')
2081              s.calculateOverBoundsAnimOffset('y')
2082
2083              if (s.speedDecelerationRatioAbs.x < 0.05 && s.speedDecelerationRatioAbs.y < 0.05) {
2084                // round pan position
2085                _panOffset.x = Math.round(_panOffset.x)
2086                _panOffset.y = Math.round(_panOffset.y)
2087                _applyCurrentZoomPan()
2088
2089                _stopAnimation('zoomPan')
2090                return
2091              }
2092            }
2093          }
2094        }
2095        return s
2096      },
2097      _completePanGesture = function (animData) {
2098        // calculate swipe speed for Y axis (paanning)
2099        animData.calculateSwipeSpeed('y')
2100
2101        _currPanBounds = self.currItem.bounds
2102
2103        animData.backAnimDestination = {}
2104        animData.backAnimStarted = {}
2105
2106        // Avoid acceleration animation if speed is too low
2107        if (Math.abs(animData.lastFlickSpeed.x) <= 0.05 && Math.abs(animData.lastFlickSpeed.y) <= 0.05) {
2108          animData.speedDecelerationRatioAbs.x = animData.speedDecelerationRatioAbs.y = 0
2109
2110          // Run pan drag release animation. E.g. if you drag image and release finger without momentum.
2111          animData.calculateOverBoundsAnimOffset('x')
2112          animData.calculateOverBoundsAnimOffset('y')
2113          return true
2114        }
2115
2116        // Animation loop that controls the acceleration after pan gesture ends
2117        _registerStartAnimation('zoomPan')
2118        animData.lastNow = _getCurrentTime()
2119        animData.panAnimLoop()
2120      },
2121      _finishSwipeMainScrollGesture = function (gestureType, _releaseAnimData) {
2122        var itemChanged
2123        if (!_mainScrollAnimating) {
2124          _currZoomedItemIndex = _currentItemIndex
2125        }
2126
2127        var itemsDiff
2128
2129        if (gestureType === 'swipe') {
2130          var totalShiftDist = _currPoint.x - _startPoint.x,
2131            isFastLastFlick = _releaseAnimData.lastFlickDist.x < 10
2132
2133          // if container is shifted for more than MIN_SWIPE_DISTANCE,
2134          // and last flick gesture was in right direction
2135          if (totalShiftDist > MIN_SWIPE_DISTANCE && (isFastLastFlick || _releaseAnimData.lastFlickOffset.x > 20)) {
2136            // go to prev item
2137            itemsDiff = -1
2138          } else if (
2139            totalShiftDist < -MIN_SWIPE_DISTANCE &&
2140            (isFastLastFlick || _releaseAnimData.lastFlickOffset.x < -20)
2141          ) {
2142            // go to next item
2143            itemsDiff = 1
2144          }
2145        }
2146
2147        var nextCircle
2148
2149        if (itemsDiff) {
2150          _currentItemIndex += itemsDiff
2151
2152          if (_currentItemIndex < 0) {
2153            _currentItemIndex = _options.loop ? _getNumItems() - 1 : 0
2154            nextCircle = true
2155          } else if (_currentItemIndex >= _getNumItems()) {
2156            _currentItemIndex = _options.loop ? 0 : _getNumItems() - 1
2157            nextCircle = true
2158          }
2159
2160          if (!nextCircle || _options.loop) {
2161            _indexDiff += itemsDiff
2162            _currPositionIndex -= itemsDiff
2163            itemChanged = true
2164          }
2165        }
2166
2167        var animateToX = _slideSize.x * _currPositionIndex
2168        var animateToDist = Math.abs(animateToX - _mainScrollPos.x)
2169        var finishAnimDuration
2170
2171        if (!itemChanged && animateToX > _mainScrollPos.x !== _releaseAnimData.lastFlickSpeed.x > 0) {
2172          // "return to current" duration, e.g. when dragging from slide 0 to -1
2173          finishAnimDuration = 333
2174        } else {
2175          finishAnimDuration =
2176            Math.abs(_releaseAnimData.lastFlickSpeed.x) > 0
2177              ? animateToDist / Math.abs(_releaseAnimData.lastFlickSpeed.x)
2178              : 333
2179
2180          finishAnimDuration = Math.min(finishAnimDuration, 400)
2181          finishAnimDuration = Math.max(finishAnimDuration, 250)
2182        }
2183
2184        if (_currZoomedItemIndex === _currentItemIndex) {
2185          itemChanged = false
2186        }
2187
2188        _mainScrollAnimating = true
2189
2190        _shout('mainScrollAnimStart')
2191
2192        _animateProp(
2193          'mainScroll',
2194          _mainScrollPos.x,
2195          animateToX,
2196          finishAnimDuration,
2197          framework.easing.cubic.out,
2198          _moveMainScroll,
2199          function () {
2200            _stopAllAnimations()
2201            _mainScrollAnimating = false
2202            _currZoomedItemIndex = -1
2203
2204            if (itemChanged || _currZoomedItemIndex !== _currentItemIndex) {
2205              self.updateCurrItem()
2206            }
2207
2208            _shout('mainScrollAnimComplete')
2209          }
2210        )
2211
2212        if (itemChanged) {
2213          self.updateCurrItem(true)
2214        }
2215
2216        return itemChanged
2217      },
2218      _calculateZoomLevel = function (touchesDistance) {
2219        return (1 / _startPointsDistance) * touchesDistance * _startZoomLevel
2220      },
2221      // Resets zoom if it's out of bounds
2222      _completeZoomGesture = function () {
2223        var destZoomLevel = _currZoomLevel,
2224          minZoomLevel = _getMinZoomLevel(),
2225          maxZoomLevel = _getMaxZoomLevel()
2226
2227        if (_currZoomLevel < minZoomLevel) {
2228          destZoomLevel = minZoomLevel
2229        } else if (_currZoomLevel > maxZoomLevel) {
2230          destZoomLevel = maxZoomLevel
2231        }
2232
2233        var destOpacity = 1,
2234          onUpdate,
2235          initialOpacity = _bgOpacity
2236
2237        if (_opacityChanged && !_isZoomingIn && !_wasOverInitialZoom && _currZoomLevel < minZoomLevel) {
2238          //_closedByScroll = true;
2239          self.close()
2240          return true
2241        }
2242
2243        if (_opacityChanged) {
2244          onUpdate = function (now) {
2245            _applyBgOpacity((destOpacity - initialOpacity) * now + initialOpacity)
2246          }
2247        }
2248
2249        self.zoomTo(destZoomLevel, 0, 200, framework.easing.cubic.out, onUpdate)
2250        return true
2251      }
2252
2253    _registerModule('Gestures', {
2254      publicMethods: {
2255        initGestures: function () {
2256          // helper function that builds touch/pointer/mouse events
2257          var addEventNames = function (pref, down, move, up, cancel) {
2258            _dragStartEvent = pref + down
2259            _dragMoveEvent = pref + move
2260            _dragEndEvent = pref + up
2261            if (cancel) {
2262              _dragCancelEvent = pref + cancel
2263            } else {
2264              _dragCancelEvent = ''
2265            }
2266          }
2267
2268          _pointerEventEnabled = _features.pointerEvent
2269          if (_pointerEventEnabled && _features.touch) {
2270            // we don't need touch events, if browser supports pointer events
2271            _features.touch = false
2272          }
2273
2274          if (_pointerEventEnabled) {
2275            if (navigator.msPointerEnabled) {
2276              // IE10 pointer events are case-sensitive
2277              addEventNames('MSPointer', 'Down', 'Move', 'Up', 'Cancel')
2278            } else {
2279              addEventNames('pointer', 'down', 'move', 'up', 'cancel')
2280            }
2281          } else if (_features.touch) {
2282            addEventNames('touch', 'start', 'move', 'end', 'cancel')
2283            _likelyTouchDevice = true
2284          } else {
2285            addEventNames('mouse', 'down', 'move', 'up')
2286          }
2287
2288          _upMoveEvents = _dragMoveEvent + ' ' + _dragEndEvent + ' ' + _dragCancelEvent
2289          _downEvents = _dragStartEvent
2290
2291          if (_pointerEventEnabled && !_likelyTouchDevice) {
2292            _likelyTouchDevice = navigator.maxTouchPoints > 1 || navigator.msMaxTouchPoints > 1
2293          }
2294          // make variable public
2295          self.likelyTouchDevice = _likelyTouchDevice
2296
2297          _globalEventHandlers[_dragStartEvent] = _onDragStart
2298          _globalEventHandlers[_dragMoveEvent] = _onDragMove
2299          _globalEventHandlers[_dragEndEvent] = _onDragRelease // the Kraken
2300
2301          if (_dragCancelEvent) {
2302            _globalEventHandlers[_dragCancelEvent] = _globalEventHandlers[_dragEndEvent]
2303          }
2304
2305          // Bind mouse events on device with detected hardware touch support, in case it supports multiple types of input.
2306          if (_features.touch) {
2307            _downEvents += ' mousedown'
2308            _upMoveEvents += ' mousemove mouseup'
2309            _globalEventHandlers.mousedown = _globalEventHandlers[_dragStartEvent]
2310            _globalEventHandlers.mousemove = _globalEventHandlers[_dragMoveEvent]
2311            _globalEventHandlers.mouseup = _globalEventHandlers[_dragEndEvent]
2312          }
2313
2314          if (!_likelyTouchDevice) {
2315            // don't allow pan to next slide from zoomed state on Desktop
2316            _options.allowPanToNext = false
2317          }
2318        }
2319      }
2320    })
2321
2322    /*>>gestures*/
2323
2324    /*>>show-hide-transition*/
2325    /**
2326     * show-hide-transition.js:
2327     *
2328     * Manages initial opening or closing transition.
2329     *
2330     * If you're not planning to use transition for gallery at all,
2331     * you may set options hideAnimationDuration and showAnimationDuration to 0,
2332     * and just delete startAnimation function.
2333     *
2334     */
2335
2336    var _showOrHideTimeout,
2337      _showOrHide = function (item, img, out, completeFn) {
2338        if (_showOrHideTimeout) {
2339          clearTimeout(_showOrHideTimeout)
2340        }
2341
2342        _initialZoomRunning = true
2343        _initialContentSet = true
2344
2345        // dimensions of small thumbnail {x:,y:,w:}.
2346        // Height is optional, as calculated based on large image.
2347        var thumbBounds
2348        if (item.initialLayout) {
2349          thumbBounds = item.initialLayout
2350          item.initialLayout = null
2351        } else {
2352          thumbBounds = _options.getThumbBoundsFn && _options.getThumbBoundsFn(_currentItemIndex)
2353        }
2354
2355        var duration = out ? _options.hideAnimationDuration : _options.showAnimationDuration
2356
2357        var onComplete = function () {
2358          _stopAnimation('initialZoom')
2359          if (!out) {
2360            _applyBgOpacity(1)
2361            if (img) {
2362              img.style.display = 'block'
2363            }
2364            framework.addClass(template, 'pswp--animated-in')
2365            _shout('initialZoom' + (out ? 'OutEnd' : 'InEnd'))
2366          } else {
2367            self.template.removeAttribute('style')
2368            self.bg.removeAttribute('style')
2369          }
2370
2371          if (completeFn) {
2372            completeFn()
2373          }
2374          _initialZoomRunning = false
2375        }
2376
2377        // if bounds aren't provided, just open gallery without animation
2378        if (!duration || !thumbBounds || thumbBounds.x === undefined) {
2379          _shout('initialZoom' + (out ? 'Out' : 'In'))
2380
2381          _currZoomLevel = item.initialZoomLevel
2382          _equalizePoints(_panOffset, item.initialPosition)
2383          _applyCurrentZoomPan()
2384
2385          template.style.opacity = out ? 0 : 1
2386          _applyBgOpacity(1)
2387
2388          if (duration) {
2389            setTimeout(function () {
2390              onComplete()
2391            }, duration)
2392          } else {
2393            onComplete()
2394          }
2395
2396          return
2397        }
2398
2399        var startAnimation = function () {
2400          var closeWithRaf = _closedByScroll,
2401            fadeEverything = !self.currItem.src || self.currItem.loadError || _options.showHideOpacity
2402
2403          // apply hw-acceleration to image
2404          if (item.miniImg) {
2405            item.miniImg.style.webkitBackfaceVisibility = 'hidden'
2406          }
2407
2408          if (!out) {
2409            _currZoomLevel = thumbBounds.w / item.w
2410            _panOffset.x = thumbBounds.x
2411            _panOffset.y = thumbBounds.y - _initalWindowScrollY
2412
2413            self[fadeEverything ? 'template' : 'bg'].style.opacity = 0.001
2414            _applyCurrentZoomPan()
2415          }
2416
2417          _registerStartAnimation('initialZoom')
2418
2419          if (out && !closeWithRaf) {
2420            framework.removeClass(template, 'pswp--animated-in')
2421          }
2422
2423          if (fadeEverything) {
2424            if (out) {
2425              framework[(closeWithRaf ? 'remove' : 'add') + 'Class'](template, 'pswp--animate_opacity')
2426            } else {
2427              setTimeout(function () {
2428                framework.addClass(template, 'pswp--animate_opacity')
2429              }, 30)
2430            }
2431          }
2432
2433          _showOrHideTimeout = setTimeout(
2434            function () {
2435              _shout('initialZoom' + (out ? 'Out' : 'In'))
2436
2437              if (!out) {
2438                // "in" animation always uses CSS transitions (instead of rAF).
2439                // CSS transition work faster here,
2440                // as developer may also want to animate other things,
2441                // like ui on top of sliding area, which can be animated just via CSS
2442
2443                _currZoomLevel = item.initialZoomLevel
2444                _equalizePoints(_panOffset, item.initialPosition)
2445                _applyCurrentZoomPan()
2446                _applyBgOpacity(1)
2447
2448                if (fadeEverything) {
2449                  template.style.opacity = 1
2450                } else {
2451                  _applyBgOpacity(1)
2452                }
2453
2454                _showOrHideTimeout = setTimeout(onComplete, duration + 20)
2455              } else {
2456                // "out" animation uses rAF only when PhotoSwipe is closed by browser scroll, to recalculate position
2457                var destZoomLevel = thumbBounds.w / item.w,
2458                  initialPanOffset = {
2459                    x: _panOffset.x,
2460                    y: _panOffset.y
2461                  },
2462                  initialZoomLevel = _currZoomLevel,
2463                  initalBgOpacity = _bgOpacity,
2464                  onUpdate = function (now) {
2465                    if (now === 1) {
2466                      _currZoomLevel = destZoomLevel
2467                      _panOffset.x = thumbBounds.x
2468                      _panOffset.y = thumbBounds.y - _currentWindowScrollY
2469                    } else {
2470                      _currZoomLevel = (destZoomLevel - initialZoomLevel) * now + initialZoomLevel
2471                      _panOffset.x = (thumbBounds.x - initialPanOffset.x) * now + initialPanOffset.x
2472                      _panOffset.y =
2473                        (thumbBounds.y - _currentWindowScrollY - initialPanOffset.
2473y) * now + initialPanOffset.y
2474                    }
2475
2476                    _applyCurrentZoomPan()
2477                    if (fadeEverything) {
2478                      template.style.opacity = 1 - now
2479                    } else {
2480                      _applyBgOpacity(initalBgOpacity - now * initalBgOpacity)
2481                    }
2482                  }
2483
2484                if (closeWithRaf) {
2485                  _animateProp('initialZoom', 0, 1, duration, framework.easing.cubic.out, onUpdate, onComplete)
2486                } else {
2487                  onUpdate(1)
2488                  _showOrHideTimeout = setTimeout(onComplete, duration + 20)
2489                }
2490              }
2491            },
2492            out ? 25 : 90
2493          ) // Main purpose of this delay is to give browser time to paint and
2494          // create composite layers of PhotoSwipe UI parts (background, controls, caption, arrows).
2495          // Which avoids lag at the beginning of scale transition.
2496        }
2497        startAnimation()
2498      }
2499
2500    /*>>show-hide-transition*/
2501
2502    /*>>items-controller*/
2503    /**
2504     *
2505     * Controller manages gallery items, their dimensions, and their content.
2506     *
2507     */
2508
2509    var _items,
2510      _tempPanAreaSize = {},
2511      _imagesToAppendPool = [],
2512      _initialContentSet,
2513      _initialZoomRunning,
2514      _controllerDefaultOptions = {
2515        index: 0,
2516        errorMsg:
2517          '<div class="pswp__error-msg"><a href="%url%" target="_blank">The image</a> could not be loaded.</div>',
2518        forceProgressiveLoading: false, // TODO
2519        preload: [1, 1],
2520        getNumItemsFn: function () {
2521          return _items.length
2522        }
2523      }
2524
2525    var _getItemAt,
2526      _getNumItems,
2527      _initialIsLoop,
2528      _getZeroBounds = function () {
2529        return {
2530          center: { x: 0, y: 0 },
2531          max: { x: 0, y: 0 },
2532          min: { x: 0, y: 0 }
2533        }
2534      },
2535      _calculateSingleItemPanBounds = function (item, realPanElementW, realPanElementH) {
2536        var bounds = item.bounds
2537
2538        // position of element when it's centered
2539        bounds.center.x = Math.round((_tempPanAreaSize.x - realPanElementW) / 2)
2540        bounds.center.y = Math.round((_tempPanAreaSize.y - realPanElementH) / 2) + item.vGap.top
2541
2542        // maximum pan position
2543        bounds.max.x =
2544          realPanElementW > _tempPanAreaSize.x ? Math.round(_tempPanAreaSize.x - realPanElementW) : bounds.center.x
2545
2546        bounds.max.y =
2547          realPanElementH > _tempPanAreaSize.y
2548            ? Math.round(_tempPanAreaSize.y - realPanElementH) + item.vGap.top
2549            : bounds.center.y
2550
2551        // minimum pan position
2552        bounds.min.x = realPanElementW > _tempPanAreaSize.x ? 0 : bounds.center.x
2553        bounds.min.y = realPanElementH > _tempPanAreaSize.y ? item.vGap.top : bounds.center.y
2554      },
2555      _calculateItemSize = function (item, viewportSize, zoomLevel) {
2556        if (item.src && !item.loadError) {
2557          var isInitial = !zoomLevel
2558
2559          if (isInitial) {
2560            if (!item.vGap) {
2561              item.vGap = { top: 0, bottom: 0 }
2562            }
2563            // allows overriding vertical margin for individual items
2564            _shout('parseVerticalMargin', item)
2565          }
2566
2567          _tempPanAreaSize.x = viewportSize.x
2568          _tempPanAreaSize.y = viewportSize.y - item.vGap.top - item.vGap.bottom
2569
2570          if (isInitial) {
2571            var hRatio = _tempPanAreaSize.x / item.w
2572            var vRatio = _tempPanAreaSize.y / item.h
2573
2574            item.fitRatio = hRatio < vRatio ? hRatio : vRatio
2575            //item.fillRatio = hRatio > vRatio ? hRatio : vRatio;
2576
2577            var scaleMode = _options.scaleMode
2578
2579            if (scaleMode === 'orig') {
2580              zoomLevel = 1
2581            } else if (scaleMode === 'fit') {
2582              zoomLevel = item.fitRatio
2583            } else if (scaleMode === 'zoom') {
2584              zoomLevel = Math.max(item.initialZoomLevel || 1, item.fitRatio)
2585            }
2586
2587            if (zoomLevel > 1) {
2588              zoomLevel = 1
2589            }
2590
2591            item.initialZoomLevel = zoomLevel
2592
2593            if (!item.bounds) {
2594              // reuse bounds object
2595              item.bounds = _getZeroBounds()
2596            }
2597          }
2598
2599          if (!zoomLevel) {
2600            return
2601          }
2602
2603          _calculateSingleItemPanBounds(item, item.w * zoomLevel, item.h * zoomLevel)
2604
2605          if (isInitial && zoomLevel === item.initialZoomLevel) {
2606            item.initialPosition = item.bounds.center
2607          }
2608
2609          return item.bounds
2610        } else {
2611          item.w = item.h = 0
2612          item.initialZoomLevel = item.fitRatio = 1
2613          item.bounds = _getZeroBounds()
2614          item.initialPosition = item.bounds.center
2615
2616          // if it's not image, we return zero bounds (content is not zoomable)
2617          return item.bounds
2618        }
2619      },
2620      _appendImage = function (index, item, baseDiv, img, preventAnimation, keepPlaceholder) {
2621        if (item.loadError) {
2622          return
2623        }
2624
2625        if (img) {
2626          item.imageAppended = true
2627          _setImageSize(item, img, item === self.currItem && _renderMaxResolution)
2628
2629          baseDiv.appendChild(img)
2630
2631          if (keepPlaceholder) {
2632            setTimeout(function () {
2633              if (item && item.loaded && item.placeholder) {
2634                item.placeholder.style.display = 'none'
2635                item.placeholder = null
2636              }
2637            }, 500)
2638          }
2639        }
2640      },
2641      _preloadImage = function (item) {
2642        item.loading = true
2643        item.loaded = false
2644        var img = (item.img = framework.createEl('pswp__img', 'img'))
2645        var onComplete = function () {
2646          item.loading = false
2647          item.loaded = true
2648
2649          if (item.loadComplete) {
2650            item.loadComplete(item)
2651          } else {
2652            item.img = null // no need to store image object
2653          }
2654          img.onload = img.onerror = null
2655          img = null
2656        }
2657        img.onload = onComplete
2658        img.onerror = function () {
2659          item.loadError = true
2660          onComplete()
2661        }
2662
2663        img.src = item.src // + '?a=' + Math.random();
2664
2665        return img
2666      },
2667      _checkForError = function (item, cleanUp) {
2668        if (item.src && item.loadError && item.container) {
2669          if (cleanUp) {
2670            item.container.innerHTML = ''
2671          }
2672
2673          item.container.innerHTML = _options.errorMsg.replace('%url%', item.src)
2674          return true
2675        }
2676      },
2677      _setImageSize = function (item, img, maxRes) {
2678        if (!item.src) {
2679          return
2680        }
2681
2682        if (!img) {
2683          img = item.container.lastChild
2684        }
2685
2686        var w = maxRes ? item.w : Math.round(item.w * item.fitRatio),
2687          h = maxRes ? item.h : Math.round(item.h * item.fitRatio)
2688
2689        if (item.placeholder && !item.loaded) {
2690          item.placeholder.style.width = w + 'px'
2691          item.placeholder.style.height = h + 'px'
2692        }
2693
2694        img.style.width = w + 'px'
2695        img.style.height = h + 'px'
2696      },
2697      _appendImagesPool = function () {
2698        if (_imagesToAppendPool.length) {
2699          var poolItem
2700
2701          for (var i = 0; i < _imagesToAppendPool.length; i++) {
2702            poolItem = _imagesToAppendPool[i]
2703            if (poolItem.holder.index === poolItem.index) {
2704              _appendImage(
2705                poolItem.index,
2706                poolItem.item,
2707                poolItem.baseDiv,
2708                poolItem.img,
2709                false,
2710                poolItem.clearPlaceholder
2711              )
2712            }
2713          }
2714          _imagesToAppendPool = []
2715        }
2716      }
2717
2718    _registerModule('Controller', {
2719      publicMethods: {
2720        lazyLoadItem: function (index) {
2721          index = _getLoopedId(index)
2722          var item = _getItemAt(index)
2723
2724          if (!item || ((item.loaded || item.loading) && !_itemsNeedUpdate)) {
2725            return
2726          }
2727
2728          _shout('gettingData', index, item)
2729
2730          if (!item.src) {
2731            return
2732          }
2733
2734          _preloadImage(item)
2735        },
2736        initController: function () {
2737          framework.extend(_options, _controllerDefaultOptions, true)
2738          self.items = _items = items
2739          _getItemAt = self.getItemAt
2740          _getNumItems = _options.getNumItemsFn //self.getNumItems;
2741
2742          _initialIsLoop = _options.loop
2743          if (_getNumItems() < 3) {
2744            _options.loop = false // disable loop if less then 3 items
2745          }
2746
2747          _listen('beforeChange', function (diff) {
2748            var p = _options.preload,
2749              isNext = diff === null ? true : diff >= 0,
2750              preloadBefore = Math.min(p[0], _getNumItems()),
2751              preloadAfter = Math.min(p[1], _getNumItems()),
2752              i
2753
2754            for (i = 1; i <= (isNext ? preloadAfter : preloadBefore); i++) {
2755              self.lazyLoadItem(_currentItemIndex + i)
2756            }
2757            for (i = 1; i <= (isNext ? preloadBefore : preloadAfter); i++) {
2758              self.lazyLoadItem(_currentItemIndex - i)
2759            }
2760          })
2761
2762          _listen('initialLayout', function () {
2763            self.currItem.initialLayout = _options.getThumbBoundsFn && _options.getThumbBoundsFn(_currentItemIndex)
2764          })
2765
2766          _listen('mainScrollAnimComplete', _appendImagesPool)
2767          _listen('initialZoomInEnd', _appendImagesPool)
2768
2769          _listen('destroy', function () {
2770            var item
2771            for (var i = 0; i < _items.length; i++) {
2772              item = _items[i]
2773              // remove reference to DOM elements, for GC
2774              if (item.container) {
2775                item.container = null
2776              }
2777              if (item.placeholder) {
2778                item.placeholder = null
2779              }
2780              if (item.img) {
2781                item.img = null
2782              }
2783              if (item.preloader) {
2784                item.preloader = null
2785              }
2786              if (item.loadError) {
2787                item.loaded = item.loadError = false
2788              }
2789            }
2790            _imagesToAppendPool = null
2791          })
2792        },
2793
2794        getItemAt: function (index) {
2795          if (index >= 0) {
2796            return _items[index] !== undefined ? _items[index] : false
2797          }
2798          return false
2799        },
2800
2801        allowProgressiveImg: function () {
2802          // 1. Progressive image loading isn't working on webkit/blink
2803          //    when hw-acceleration (e.g. translateZ) is applied to IMG element.
2804          //    That's why in PhotoSwipe parent element gets zoom transform, not image itself.
2805          //
2806          // 2. Progressive image loading sometimes blinks in webkit/blink when applying animation to parent element.
2807          //    That's why it's disabled on touch devices (mainly because of swipe transition)
2808          //
2809          // 3. Progressive image loading sometimes doesn't work in IE (up to 11).
2810
2811          // Don't allow progressive loading on non-large touch devices
2812          return _options.forceProgressiveLoading || !_likelyTouchDevice || _options.mouseUsed || screen.width > 1200
2813          // 1200 - to eliminate touch devices with large screen (like Chromebook Pixel)
2814        },
2815
2816        setContent: function (holder, index) {
2817          if (_options.loop) {
2818            index = _getLoopedId(index)
2819          }
2820
2821          var prevItem = self.getItemAt(holder.index)
2822          if (prevItem) {
2823            prevItem.container = null
2824          }
2825
2826          var item = self.getItemAt(index),
2827            img
2828
2829          if (!item) {
2830            holder.el.innerHTML = ''
2831            return
2832          }
2833
2834          // allow to override data
2835          _shout('gettingData', index, item)
2836
2837          holder.index = index
2838          holder.item = item
2839
2840          // base container DIV is created only once for each of 3 holders
2841          var baseDiv = (item.container = framework.createEl('pswp__zoom-wrap'))
2842
2843          if (!item.src && item.html) {
2844            if (item.html.tagName) {
2845              baseDiv.appendChild(item.html)
2846            } else {
2847              baseDiv.innerHTML = item.html
2848            }
2849          }
2850
2851          _checkForError(item)
2852
2853          _calculateItemSize(item, _viewportSize)
2854
2855          if (item.src && !item.loadError && !item.loaded) {
2856            item.loadComplete = function (item) {
2857              // gallery closed before image finished loading
2858              if (!_isOpen) {
2859                return
2860              }
2861
2862              // check if holder hasn't changed while image was loading
2863              if (holder && holder.index === index) {
2864                if (_checkForError(item, true)) {
2865                  item.loadComplete = item.img = null
2866                  _calculateItemSize(item, _viewportSize)
2867                  _applyZoomPanToItem(item)
2868
2869                  if (holder.index === _currentItemIndex) {
2870                    // recalculate dimensions
2871                    self.updateCurrZoomItem()
2872                  }
2873                  return
2874                }
2875                if (!item.imageAppended) {
2876                  if (_features.transform && (_mainScrollAnimating || _initialZoomRunning)) {
2877                    _imagesToAppendPool.push({
2878                      item: item,
2879                      baseDiv: baseDiv,
2880                      img: item.img,
2881                      index: index,
2882                      holder: holder,
2883                      clearPlaceholder: true
2884                    })
2885                  } else {
2886                    _appendImage(index, item, baseDiv, item.img, _mainScrollAnimating || _initialZoomRunning, true)
2887                  }
2888                } else {
2889                  // remove preloader & mini-img
2890                  if (!_initialZoomRunning && item.placeholder) {
2891                    item.placeholder.style.display = 'none'
2892                    item.placeholder = null
2893                  }
2894                }
2895              }
2896
2897              item.loadComplete = null
2898              item.img = null // no need to store image element after it's added
2899
2900              _shout('imageLoadComplete', index, item)
2901            }
2902
2903            if (framework.features.transform) {
2904              var placeholderClassName = 'pswp__img pswp__img--placeholder'
2905              placeholderClassName += item.msrc ? '' : ' pswp__img--placeholder--blank'
2906
2907              var placeholder = framework.createEl(placeholderClassName, item.msrc ? 'img' : '')
2908              if (item.msrc) {
2909                placeholder.src = item.msrc
2910              }
2911
2912              _setImageSize(item, placeholder)
2913
2914              baseDiv.appendChild(placeholder)
2915              item.placeholder = placeholder
2916            }
2917
2918            if (!item.loading) {
2919              _preloadImage(item)
2920            }
2921
2922            if (self.allowProgressiveImg()) {
2923              // just append image
2924              if (!_initialContentSet && _features.transform) {
2925                _imagesToAppendPool.push({
2926                  item: item,
2927                  baseDiv: baseDiv,
2928                  img: item.img,
2929                  index: index,
2930                  holder: holder
2931                })
2932              } else {
2933                _appendImage(index, item, baseDiv, item.img, true, true)
2934              }
2935            }
2936          } else if (item.src && !item.loadError) {
2937            // image object is created every time, due to bugs of image loading & delay when switching images
2938            img = framework.createEl('pswp__img', 'img')
2939            img.style.opacity = 1
2940            img.src = item.src
2941            _setImageSize(item, img)
2942            _appendImage(index, item, baseDiv, img, true)
2943          }
2944
2945          if (!_initialContentSet && index === _currentItemIndex) {
2946            _currZoomElementStyle = baseDiv.style
2947            _showOrHide(item, img || item.img)
2948          } else {
2949            _applyZoomPanToItem(item)
2950          }
2951
2952          holder.el.innerHTML = ''
2953          holder.el.appendChild(baseDiv)
2954        },
2955
2956        cleanSlide: function (item) {
2957          if (item.img) {
2958            item.img.onload = item.img.onerror = null
2959          }
2960          item.loaded = item.loading = item.img = item.imageAppended = false
2961        }
2962      }
2963    })
2964
2965    /*>>items-controller*/
2966
2967    /*>>tap*/
2968    /**
2969     * tap.js:
2970     *
2971     * Displatches tap and double-tap events.
2972     *
2973     */
2974
2975    var tapTimer,
2976      tapReleasePoint = {},
2977      _dispatchTapEvent = function (origEvent, releasePoint, pointerType) {
2978        var e = document.createEvent('CustomEvent'),
2979          eDetail = {
2980            origEvent: origEvent,
2981            target: origEvent.target,
2982            releasePoint: releasePoint,
2983            pointerType: pointerType || 'touch'
2984          }
2985
2986        e.initCustomEvent('pswpTap', true, true, eDetail)
2987        origEvent.target.dispatchEvent(e)
2988      }
2989
2990    _registerModule('Tap', {
2991      publicMethods: {
2992        initTap: function () {
2993          _listen('firstTouchStart', self.onTapStart)
2994          _listen('touchRelease', self.onTapRelease)
2995          _listen('destroy', function () {
2996            tapReleasePoint = {}
2997            tapTimer = null
2998          })
2999        },
3000        onTapStart: function (touchList) {
3001          if (touchList.length > 1) {
3002            clearTimeout(tapTimer)
3003            tapTimer = null
3004          }
3005        },
3006        onTapRelease: function (e, releasePoint) {
3007          if (!releasePoint) {
3008            return
3009          }
3010
3011          if (!_moved && !_isMultitouch && !_numAnimations) {
3012            var p0 = releasePoint
3013            if (tapTimer) {
3014              clearTimeout(tapTimer)
3015              tapTimer = null
3016
3017              // Check if taped on the same place
3018              if (_isNearbyPoints(p0, tapReleasePoint)) {
3019                _shout('doubleTap', p0)
3020                return
3021              }
3022            }
3023
3024            if (releasePoint.type === 'mouse') {
3025              _dispatchTapEvent(e, releasePoint, 'mouse')
3026              return
3027            }
3028
3029            var clickedTagName = e.target.tagName.toUpperCase()
3030            // avoid double tap delay on buttons and elements that have class pswp__single-tap
3031            if (clickedTagName === 'BUTTON' || framework.hasClass(e.target, 'pswp__single-tap')) {
3032              _dispatchTapEvent(e, releasePoint)
3033              return
3034            }
3035
3036            _equalizePoints(tapReleasePoint, p0)
3037
3038            tapTimer = setTimeout(function () {
3039              _dispatchTapEvent(e, releasePoint)
3040              tapTimer = null
3041            }, 300)
3042          }
3043        }
3044      }
3045    })
3046
3047    /*>>tap*/
3048
3049    /*>>desktop-zoom*/
3050    /**
3051     *
3052     * desktop-zoom.js:
3053     *
3054     * - Binds mousewheel event for paning zoomed image.
3055     * - Manages "dragging", "zoomed-in", "zoom-out" classes.
3056     *   (which are used for cursors and zoom icon)
3057     * - Adds toggleDesktopZoom function.
3058     *
3059     */
3060
3061    var _wheelDelta
3062
3063    _registerModule('DesktopZoom', {
3064      publicMethods: {
3065        initDesktopZoom: function () {
3066          if (_oldIE) {
3067            // no zoom for old IE (<=8)
3068            return
3069          }
3070
3071          if (_likelyTouchDevice) {
3072            // if detected hardware touch support, we wait until mouse is used,
3073            // and only then apply desktop-zoom features
3074            _listen('mouseUsed', function () {
3075              self.setupDesktopZoom()
3076            })
3077          } else {
3078            self.setupDesktopZoom(true)
3079          }
3080        },
3081
3082        setupDesktopZoom: function (onInit) {
3083          _wheelDelta = {}
3084
3085          var events = 'wheel mousewheel DOMMouseScroll'
3086
3087          _listen('bindEvents', function () {
3088            framework.bind(template, events, self.handleMouseWheel)
3089          })
3090
3091          _listen('unbindEvents', function () {
3092            if (_wheelDelta) {
3093              framework.unbind(template, events, self.handleMouseWheel)
3094            }
3095          })
3096
3097          self.mouseZoomedIn = false
3098
3099          var hasDraggingClass,
3100            updateZoomable = function () {
3101              if (self.mouseZoomedIn) {
3102                framework.removeClass(template, 'pswp--zoomed-in')
3103                self.mouseZoomedIn = false
3104              }
3105              if (_currZoomLevel < 1) {
3106                framework.addClass(template, 'pswp--zoom-allowed')
3107              } else {
3108                framework.removeClass(template, 'pswp--zoom-allowed')
3109              }
3110              removeDraggingClass()
3111            },
3112            removeDraggingClass = function () {
3113              if (hasDraggingClass) {
3114                framework.removeClass(template, 'pswp--dragging')
3115                hasDraggingClass = false
3116              }
3117            }
3118
3119          _listen('resize', updateZoomable)
3120          _listen('afterChange', updateZoomable)
3121          _listen('pointerDown', function () {
3122            if (self.mouseZoomedIn) {
3123              hasDraggingClass = true
3124              framework.addClass(template, 'pswp--dragging')
3125            }
3126          })
3127          _listen('pointerUp', removeDraggingClass)
3128
3129          if (!onInit) {
3130            updateZoomable()
3131          }
3132        },
3133
3134        handleMouseWheel: function (e) {
3135          if (_currZoomLevel <= self.currItem.fitRatio) {
3136            if (_options.modal) {
3137              if (!_options.closeOnScroll || _numAnimations || _isDragging) {
3138                e.preventDefault()
3139              } else if (_transformKey && Math.abs(e.deltaY) > 2) {
3140                // close PhotoSwipe
3141                // if browser supports transforms & scroll changed enough
3142                _closedByScroll = true
3143                self.close()
3144              }
3145            }
3146            return true
3147          }
3148
3149          // allow just one event to fire
3150          e.stopPropagation()
3151
3152          // https://developer.mozilla.org/en-US/docs/Web/Events/wheel
3153          _wheelDelta.x = 0
3154
3155          if ('deltaX' in e) {
3156            if (e.deltaMode === 1 /* DOM_DELTA_LINE */) {
3157              // 18 - average line height
3158              _wheelDelta.x = e.deltaX * 18
3159              _wheelDelta.y = e.deltaY * 18
3160            } else {
3161              _wheelDelta.x = e.deltaX
3162              _wheelDelta.y = e.deltaY
3163            }
3164          } else if ('wheelDelta' in e) {
3165            if (e.wheelDeltaX) {
3166              _wheelDelta.x = -0.16 * e.wheelDeltaX
3167            }
3168            if (e.wheelDeltaY) {
3169              _wheelDelta.y = -0.16 * e.wheelDeltaY
3170            } else {
3171              _wheelDelta.y = -0.16 * e.wheelDelta
3172            }
3173          } else if ('detail' in e) {
3174            _wheelDelta.y = e.detail
3175          } else {
3176            return
3177          }
3178
3179          _calculatePanBounds(_currZoomLevel, true)
3180
3181          var newPanX = _panOffset.x - _wheelDelta.x,
3182            newPanY = _panOffset.y - _wheelDelta.y
3183
3184          // only prevent scrolling in nonmodal mode when not at edges
3185          if (
3186            _options.modal ||
3187            (newPanX <= _currPanBounds.min.x &&
3188              newPanX >= _currPanBounds.max.x &&
3189              newPanY <= _currPanBounds.min.y &&
3190              newPanY >= _currPanBounds.max.y)
3191          ) {
3192            e.preventDefault()
3193          }
3194
3195          // TODO: use rAF instead of mousewheel?
3196          self.panTo(newPanX, newPanY)
3197        },
3198
3199        toggleDesktopZoom: function (centerPoint) {
3200          centerPoint = centerPoint || { x: _viewportSize.x / 2 + _offset.x, y: _viewportSize.y / 2 + _offset.y }
3201
3202          var doubleTapZoomLevel = _options.getDoubleTapZoom(true, self.currItem)
3203          var zoomOut = _currZoomLevel === doubleTapZoomLevel
3204
3205          self.mouseZoomedIn = !zoomOut
3206
3207          self.zoomTo(zoomOut ? self.currItem.initialZoomLevel : doubleTapZoomLevel, centerPoint, 333)
3208          framework[(!zoomOut ? 'add' : 'remove') + 'Class'](template, 'pswp--zoomed-in')
3209        }
3210      }
3211    })
3212
3213    /*>>desktop-zoom*/
3214
3215    framework.extend(self, publicMethods)
3216  }
3217  return PhotoSwipe
3218})

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.