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