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