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