PageSourceSearch

https://olokuti.com/cdn/shop/t/69/assets/photoswipe.min.js?v=14839465386805463181728897719

js olokuti.com collected 2026-09-25 00:07:15 UTC 105,506 bytes, 3,219 lines download raw bytes

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

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.