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