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