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