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