vendor: 50,273 bytes, lines 1-1551
1/* 2 * blueimp Gallery JS 3 * https://github.com/blueimp/Gallery 4 * 5 * Copyright 2013, Sebastian Tschan 6 * https://blueimp.net 7 * 8 * Swipe implementation based on 9 * https://github.com/bradbirdsall/Swipe 10 * 11 * Licensed under the MIT license: 12 * https://opensource.org/licenses/MIT 13 */ 14 15/* global define, DocumentTouch */ 16 17/* eslint-disable no-param-reassign */ 18 19;(function (factory) { 20 'use strict' 21 if (typeof define === 'function' && define.amd) { 22 // Register as an anonymous AMD module: 23 define(['./blueimp-helper'], factory) 24 } else { 25 // Browser globals: 26 window.blueimp = window.blueimp || {} 27 window.blueimp.Gallery = factory(window.blueimp.helper || window.jQuery) 28 } 29})(function ($) { 30 'use strict' 31 32 /** 33 * Gallery constructor 34 * 35 * @class 36 * @param {Array|NodeList} list Gallery content 37 * @param {object} [options] Gallery options 38 * @returns {object} Gallery object 39 */ 40 function Gallery(list, options) { 41 if (document.body.style.maxHeight === undefined) { 42 // document.body.style.maxHeight is undefined on IE6 and lower 43 return null 44 } 45 if (!this || this.options !== Gallery.prototype.options) { 46 // Called as function instead of as constructor, 47 // so we simply return a new instance: 48 return new Gallery(list, options) 49 } 50 if (!list || !list.length) { 51 this.console.log( 52 'blueimp Gallery: No or empty list provided as first argument.', 53 list 54 ) 55 return 56 } 57 this.list = list 58 this.num = list.length 59 this.initOptions(options) 60 this.initialize() 61 } 62 63 $.extend(Gallery.prototype, { 64 options: { 65 // The Id, element or querySelector of the gallery widget: 66 container: '#blueimp-gallery', 67 // The tag name, Id, element or querySelector of the slides container: 68 slidesContainer: 'div', 69 // The tag name, Id, element or querySelector of the title element: 70 titleElement: 'h3', 71 // The class to add when the gallery is visible: 72 displayClass: 'blueimp-gallery-display', 73 // The class to add when the gallery controls are visible: 74 controlsClass: 'blueimp-gallery-controls', 75 // The class to add when the gallery only displays one element: 76 singleClass: 'blueimp-gallery-single', 77 // The class to add when the left edge has been reached: 78 leftEdgeClass: 'blueimp-gallery-left', 79 // The class to add when the right edge has been reached: 80 rightEdgeClass: 'blueimp-gallery-right', 81 // The class to add when the automatic slideshow is active: 82 playingClass: 'blueimp-gallery-playing', 83 // The class to add when the browser supports SVG as img (or background): 84 svgasimgClass: 'blueimp-gallery-svgasimg', 85 // The class to add when the browser supports SMIL (animated SVGs): 86 smilClass: 'blueimp-gallery-smil', 87 // The class for all slides: 88 slideClass: 'slide', 89 // The slide class for the active (current index) slide: 90 slideActiveClass: 'slide-active', 91 // The slide class for the previous (before current index) slide: 92 slidePrevClass: 'slide-prev', 93 // The slide class for the next (after current index) slide: 94 slideNextClass: 'slide-next', 95 // The slide class for loading elements: 96 slideLoadingClass: 'slide-loading', 97 // The slide class for elements that failed to load: 98 slideErrorClass: 'slide-error', 99 // The class for the content element loaded into each slide: 100 slideContentClass: 'slide-content', 101 // The class for the "toggle" control: 102 toggleClass: 'toggle', 103 // The class for the "prev" control: 104 prevClass: 'prev', 105 // The class for the "next" control: 106 nextClass: 'next', 107 // The class for the "close" control: 108 closeClass: 'close', 109 // The class for the "play-pause" toggle control: 110 playPauseClass: 'play-pause', 111 // The list object property (or data attribute) with the object type: 112 typeProperty: 'type', 113 // The list object property (or data attribute) with the object title: 114 titleProperty: 'title', 115 // The list object property (or data attribute) with the object alt text: 116 altTextProperty: 'alt', 117 // The list object property (or data attribute) with the object URL: 118 urlProperty: 'href', 119 // The list object property (or data attribute) with the object srcset: 120 srcsetProperty: 'srcset', 121 // The list object property (or data attribute) with the object sizes: 122 sizesProperty: 'sizes', 123 // The list object property (or data attribute) with the object sources: 124 sourcesProperty: 'sources', 125 // The gallery listens for transitionend events before triggering the 126 // opened and closed events, unless the following option is set to false: 127 displayTransition: true, 128 // Defines if the gallery slides are cleared from the gallery modal, 129 // or reused for the next gallery initialization: 130 clearSlides: true, 131 // Toggle the controls on pressing the Enter key: 132 toggleControlsOnEnter: true, 133 // Toggle the controls on slide click: 134 toggleControlsOnSlideClick: true, 135 // Toggle the automatic slideshow interval on pressing the Space key: 136 toggleSlideshowOnSpace: true, 137 // Navigate the gallery by pressing the ArrowLeft and ArrowRight keys: 138 enableKeyboardNavigation: true, 139 // Close the gallery on pressing the Escape key: 140 closeOnEscape: true, 141 // Close the gallery when clicking on an empty slide area: 142 closeOnSlideClick: true, 143 // Close the gallery by swiping up or down: 144 closeOnSwipeUpOrDown: true, 145 // Close the gallery when the URL hash changes: 146 closeOnHashChange: true, 147 // Emulate touch events on mouse-pointer devices such as desktop browsers: 148 emulateTouchEvents: true, 149 // Stop touch events from bubbling up to ancestor elements of the Gallery: 150 stopTouchEventsPropagation: false, 151 // Hide the page scrollbars: 152 hidePageScrollbars: true, 153 // Stops any touches on the container from scrolling the page: 154 disableScroll: true, 155 // Carousel mode (shortcut for carousel specific options): 156 carousel: false, 157 // Allow continuous navigation, moving from last to first 158 // and from first to last slide: 159 continuous: true, 160 // Remove elements outside of the preload range from the DOM: 161 unloadElements: true, 162 // Start with the automatic slideshow: 163 startSlideshow: false, 164 // Delay in milliseconds between slides for the automatic slideshow: 165 slideshowInterval: 5000, 166 // The direction the slides are moving: ltr=LeftToRight or rtl=RightToLeft 167 slideshowDirection: 'ltr', 168 // The starting index as integer. 169 // Can also be an object of the given list, 170 // or an equal object with the same url property: 171 index: 0, 172 // The number of elements to load around the current index: 173 preloadRange: 2, 174 // The transition duration between slide changes in milliseconds: 175 transitionDuration: 300, 176 // The transition duration for automatic slide changes, set to an integer 177 // greater 0 to override the default transition duration: 178 slideshowTransitionDuration: 500, 179 // The event object for which the default action will be canceled 180 // on Gallery initialization (e.g. the click event to open the Gallery): 181 event: undefined, 182 // Callback function executed when the Gallery is initialized. 183 // Is called with the gallery instance as "this" object: 184 onopen: undefined, 185 // Callback function executed when the Gallery has been initialized 186 // and the initialization transition has been completed. 187 // Is called with the gallery instance as "this" object: 188 onopened: undefined, 189 // Callback function executed on slide change. 190 // Is called with the gallery instance as "this" object and the 191 // current index and slide as arguments: 192 onslide: undefined, 193 // Callback function executed after the slide change transition. 194 // Is called with the gallery instance as "this" object and the 195 // current index and slide as arguments: 196 onslideend: undefined, 197 // Callback function executed on slide content load. 198 // Is called with the gallery instance as "this" object and the 199 // slide index and slide element as arguments: 200 onslidecomplete: undefined, 201 // Callback function executed when the Gallery is about to be closed. 202 // Is called with the gallery instance as "this" object: 203 onclose: undefined, 204 // Callback function executed when the Gallery has been closed 205 // and the closing transition has been completed. 206 // Is called with the gallery instance as "this" object: 207 onclosed: undefined 208 }, 209 210 carouselOptions: { 211 hidePageScrollbars: false, 212 toggleControlsOnEnter: false, 213 toggleSlideshowOnSpace: false, 214 enableKeyboardNavigation: false, 215 closeOnEscape: false, 216 closeOnSlideClick: false, 217 closeOnSwipeUpOrDown: false, 218 closeOnHashChange: false, 219 disableScroll: false, 220 startSlideshow: true 221 }, 222 223 console: 224 window.console && typeof window.console.log === 'function' 225 ? window.console 226 : { log: function () {} }, 227 228 // Detect touch, transition, transform and background-size support: 229 support: (function (element) { 230 var support = { 231 source: !!window.HTMLSourceElement, 232 picture: !!window.HTMLPictureElement, 233 svgasimg: document.implementation.hasFeature( 234 'http://www.w3.org/TR/SVG11/feature#Image', 235 '1.1' 236 ), 237 smil: 238 !!document.createElementNS && 239 /SVGAnimate/.test( 240 document 241 .createElementNS('http://www.w3.org/2000/svg', 'animate') 242 .toString() 243 ), 244 touch: 245 window.ontouchstart !== undefined || 246 (window.DocumentTouch && document instanceof DocumentTouch) 247 } 248 var transitions = { 249 webkitTransition: { 250 end: 'webkitTransitionEnd', 251 prefix: '-webkit-' 252 }, 253 MozTransition: { 254 end: 'transitionend', 255 prefix: '-moz-' 256 }, 257 OTransition: { 258 end: 'otransitionend', 259 prefix: '-o-' 260 }, 261 transition: { 262 end: 'transitionend', 263 prefix: '' 264 } 265 } 266 var prop 267 for (prop in transitions) { 268 if ( 269 Object.prototype.hasOwnProperty.call(transitions, prop) && 270 element.style[prop] !== undefined 271 ) { 272 support.transition = transitions[prop] 273 support.transition.name = prop 274 break 275 } 276 } 277 /** 278 * Tests browser support 279 */ 280 function elementTests() { 281 var transition = support.transition 282 var prop 283 var translateZ 284 document.body.appendChild(element) 285 if (transition) { 286 prop = transition.name.slice(0, -9) + 'ransform' 287 if (element.style[prop] !== undefined) { 288 element.style[prop] = 'translateZ(0)' 289 translateZ = window 290 .getComputedStyle(element) 291 .getPropertyValue(transition.prefix + 'transform') 292 support.transform = { 293 prefix: transition.prefix, 294 name: prop, 295 translate: true, 296 translateZ: !!translateZ && translateZ !== 'none' 297 } 298 } 299 } 300 document.body.removeChild(element) 301 } 302 if (document.body) { 303 elementTests() 304 } else { 305 $(document).on('DOMContentLoaded', elementTests) 306 } 307 return support 308 // Test element, has to be standard HTML and must not be hidden 309 // for the CSS3 tests using window.getComputedStyle to be applicable: 310 })(document.createElement('div')), 311 312 requestAnimationFrame: 313 window.requestAnimationFrame || 314 window.webkitRequestAnimationFrame || 315 window.mozRequestAnimationFrame, 316 317 cancelAnimationFrame: 318 window.cancelAnimationFrame || 319 window.webkitCancelRequestAnimationFrame || 320 window.webkitCancelAnimationFrame || 321 window.mozCancelAnimationFrame, 322 323 initialize: function () { 324 this.initStartIndex() 325 if (this.initWidget() === false) { 326 return false 327 } 328 this.initEventListeners() 329 // Load the slide at the given index: 330 this.onslide(this.index) 331 // Manually trigger the slideend event for the initial slide: 332 this.ontransitionend() 333 // Start the automatic slideshow if applicable: 334 if (this.options.startSlideshow) { 335 this.play() 336 } 337 }, 338 339 slide: function (to, duration) { 340 window.clearTimeout(this.timeout) 341 var index = this.index 342 var direction 343 var naturalDirection 344 var diff 345 if (index === to || this.num === 1) { 346 return 347 } 348 if (!duration) { 349 duration = this.options.transitionDuration 350 } 351 if (this.support.transform) { 352 if (!this.options.continuous) { 353 to = this.circle(to) 354 } 355 // 1: backward, -1: forward: 356 direction = Math.abs(index - to) / (index - to) 357 // Get the actual position of the slide: 358 if (this.options.continuous) { 359 naturalDirection = direction 360 direction = -this.positions[this.circle(to)] / this.slideWidth 361 // If going forward but to < index, use to = slides.length + to 362 // If going backward but to > index, use to = -slides.length + to 363 if (direction !== naturalDirection) { 364 to = -direction * this.num + to 365 } 366 } 367 diff = Math.abs(index - to) - 1 368 // Move all the slides between index and to in the right direction: 369 while (diff) { 370 diff -= 1 371 this.move( 372 this.circle((to > index ? to : index) - diff - 1), 373 this.slideWidth * direction, 374 0 375 ) 376 } 377 to = this.circle(to) 378 this.move(index, this.slideWidth * direction, duration) 379 this.move(to, 0, duration) 380 if (this.options.continuous) { 381 this.move( 382 this.circle(to - direction), 383 -(this.slideWidth * direction), 384 0 385 ) 386 } 387 } else { 388 to = this.circle(to) 389 this.animate(index * -this.slideWidth, to * -this.slideWidth, duration) 390 } 391 this.onslide(to) 392 }, 393 394 getIndex: function () { 395 return this.index 396 }, 397 398 getNumber: function () { 399 return this.num 400 }, 401 402 prev: function () { 403 if (this.options.continuous || this.index) { 404 this.slide(this.index - 1) 405 } 406 }, 407 408 next: function () { 409 if (this.options.continuous || this.index < this.num - 1) { 410 this.slide(this.index + 1) 411 } 412 }, 413 414 play: function (time) { 415 var that = this 416 var nextIndex = 417 this.index + (this.options.slideshowDirection === 'rtl' ? -1 : 1) 418 window.clearTimeout(this.timeout) 419 this.interval = time || this.options.slideshowInterval 420 if (this.elements[this.index] > 1) { 421 this.timeout = this.setTimeout( 422 (!this.requestAnimationFrame && this.slide) || 423 function (to, duration) { 424 that.animationFrameId = that.requestAnimationFrame.call( 425 window, 426 function () { 427 that.slide(to, duration) 428 } 429 ) 430 }, 431 [nextIndex, this.options.slideshowTransitionDuration], 432 this.interval 433 ) 434 } 435 this.container.addClass(this.options.playingClass) 436 this.slidesContainer[0].setAttribute('aria-live', 'off') 437 if (this.playPauseElement.length) { 438 this.playPauseElement[0].setAttribute('aria-pressed', 'true') 439 } 440 }, 441 442 pause: function () { 443 window.clearTimeout(this.timeout) 444 this.interval = null 445 if (this.cancelAnimationFrame) { 446 this.cancelAnimationFrame.call(window, this.animationFrameId) 447 this.animationFrameId = null 448 } 449 this.container.removeClass(this.options.playingClass) 450 this.slidesContainer[0].setAttribute('aria-live', 'polite') 451 if (this.playPauseElement.length) { 452 this.playPauseElement[0].setAttribute('aria-pressed', 'false') 453 } 454 }, 455 456 add: function (list) { 457 var i 458 if (!list.concat) { 459 // Make a real array out of the list to add: 460 list = Array.prototype.slice.call(list) 461 } 462 if (!this.list.concat) { 463 // Make a real array out of the Gallery list: 464 this.list = Array.prototype.slice.call(this.list) 465 } 466 this.list = this.list.concat(list) 467 this.num = this.list.length 468 if (this.num > 2 && this.options.continuous === null) { 469 this.options.continuous = true 470 this.container.removeClass(this.options.leftEdgeClass) 471 } 472 this.container 473 .removeClass(this.options.rightEdgeClass) 474 .removeClass(this.options.singleClass) 475 for (i = this.num - list.length; i < this.num; i += 1) { 476 this.addSlide(i) 477 this.positionSlide(i) 478 } 479 this.positions.length = this.num 480 this.initSlides(true) 481 }, 482 483 resetSlides: function () { 484 this.slidesContainer.empty() 485 this.unloadAllSlides() 486 this.slides = [] 487 }, 488 489 handleClose: function () { 490 var options = this.options 491 this.destroyEventListeners() 492 // Cancel the slideshow: 493 this.pause() 494 this.container[0].style.display = 'none' 495 this.container 496 .removeClass(options.displayClass) 497 .removeClass(options.singleClass) 498 .removeClass(options.leftEdgeClass) 499 .removeClass(options.rightEdgeClass) 500 if (options.hidePageScrollbars) { 501 document.body.style.overflow = this.bodyOverflowStyle 502 } 503 if (this.options.clearSlides) { 504 this.resetSlides() 505 } 506 if (this.options.onclosed) { 507 this.options.onclosed.call(this) 508 } 509 }, 510 511 close: function () { 512 var that = this 513 /** 514 * Close handler 515 * 516 * @param {event} event Close event 517 */ 518 function closeHandler(event) { 519 if (event.target === that.container[0]) { 520 that.container.off(that.support.transition.end, closeHandler) 521 that.handleClose() 522 } 523 } 524 if (this.options.onclose) { 525 this.options.onclose.call(this) 526 } 527 if (this.support.transition && this.options.displayTransition) { 528 this.container.on(this.support.transition.end, closeHandler) 529 this.container.removeClass(this.options.displayClass) 530 } else { 531 this.handleClose() 532 } 533 }, 534 535 circle: function (index) { 536 // Always return a number inside of the slides index range: 537 return (this.num + (index % this.num)) % this.num 538 }, 539 540 move: function (index, dist, duration) { 541 this.translateX(index, dist, duration) 542 this.positions[index] = dist 543 }, 544 545 translate: function (index, x, y, duration) { 546 if (!this.slides[index]) return 547 var style = this.slides[index].style 548 var transition = this.support.transition 549 var transform = this.support.transform 550 style[transition.name + 'Duration'] = duration + 'ms' 551 style[transform.name] = 552 'translate(' + 553 x + 554 'px, ' + 555 y + 556 'px)' + 557 (transform.translateZ ? ' translateZ(0)' : '') 558 }, 559 560 translateX: function (index, x, duration) { 561 this.translate(index, x, 0, duration) 562 }, 563 564 translateY: function (index, y, duration) { 565 this.translate(index, 0, y, duration) 566 }, 567 568 animate: function (from, to, duration) { 569 if (!duration) { 570 this.slidesContainer[0].style.left = to + 'px' 571 return 572 } 573 var that = this 574 var start = new Date().getTime() 575 var timer = window.setInterval(function () { 576 var timeElap = new Date().getTime() - start 577 if (timeElap > duration) { 578 that.slidesContainer[0].style.left = to + 'px' 579 that.ontransitionend() 580 window.clearInterval(timer) 581 return 582 } 583 that.slidesContainer[0].style.left = 584 (to - from) * (Math.floor((timeElap / duration) * 100) / 100) + 585 from + 586 'px' 587 }, 4) 588 }, 589 590 preventDefault: function (event) { 591 if (event.preventDefault) { 592 event.preventDefault() 593 } else { 594 event.returnValue = false 595 } 596 }, 597 598 stopPropagation: function (event) { 599 if (event.stopPropagation) { 600 event.stopPropagation() 601 } else { 602 event.cancelBubble = true 603 } 604 }, 605 606 onresize: function () { 607 this.initSlides(true) 608 }, 609 610 onhashchange: function () { 611 if (this.options.closeOnHashChange) { 612 this.close() 613 } 614 }, 615 616 onmousedown: function (event) { 617 // Trigger on clicks of the left mouse button only 618 // and exclude video & audio elements: 619 if ( 620 event.which && 621 event.which === 1 && 622 event.target.nodeName !== 'VIDEO' && 623 event.target.nodeName !== 'AUDIO' 624 ) { 625 // Preventing the default mousedown action is required 626 // to make touch emulation work with Firefox: 627 event.preventDefault() 628 ;(event.originalEvent || event).touches = [ 629 { 630 pageX: event.pageX, 631 pageY: event.pageY 632 } 633 ] 634 this.ontouchstart(event) 635 } 636 }, 637 638 onmousemove: function (event) { 639 if (this.touchStart) { 640 ;(event.originalEvent || event).touches = [ 641 { 642 pageX: event.pageX, 643 pageY: event.pageY 644 } 645 ] 646 this.ontouchmove(event) 647 } 648 }, 649 650 onmouseup: function (event) { 651 if (this.touchStart) { 652 this.ontouchend(event) 653 delete this.touchStart 654 } 655 }, 656 657 onmouseout: function (event) { 658 if (this.touchStart) { 659 var target = event.target 660 var related = event.relatedTarget 661 if (!related || (related !== target && !$.contains(target, related))) { 662 this.onmouseup(event) 663 } 664 } 665 }, 666 667 ontouchstart: function (event) { 668 if (this.options.stopTouchEventsPropagation) { 669 this.stopPropagation(event) 670 } 671 // jQuery doesn't copy touch event properties by default, 672 // so we have to access the originalEvent object: 673 var touch = (event.originalEvent || event).touches[0] 674 this.touchStart = { 675 // Remember the initial touch coordinates: 676 x: touch.pageX, 677 y: touch.pageY, 678 // Store the time to determine touch duration: 679 time: Date.now() 680 } 681 // Helper variable to detect scroll movement: 682 this.isScrolling = undefined 683 // Reset delta values: 684 this.touchDelta = {} 685 }, 686 687 ontouchmove: function (event) { 688 if (this.options.stopTouchEventsPropagation) { 689 this.stopPropagation(event) 690 } 691 // jQuery doesn't copy touch event properties by default, 692 // so we have to access the originalEvent object: 693 var touches = (event.originalEvent || event).touches 694 var touch = touches[0] 695 var scale = (event.originalEvent || event).scale 696 var index = this.index 697 var touchDeltaX 698 var indices 699 // Ensure this is a one touch swipe and not, e.g. a pinch: 700 if (touches.length > 1 || (scale && scale !== 1)) { 701 return 702 } 703 if (this.options.disableScroll) { 704 event.preventDefault() 705 } 706 // Measure change in x and y coordinates: 707 this.touchDelta = { 708 x: touch.pageX - this.touchStart.x, 709 y: touch.pageY - this.touchStart.y 710 } 711 touchDeltaX = this.touchDelta.x 712 // Detect if this is a vertical scroll movement (run only once per touch): 713 if (this.isScrolling === undefined) { 714 this.isScrolling = 715 this.isScrolling || 716 Math.abs(touchDeltaX) < Math.abs(this.touchDelta.y) 717 } 718 if (!this.isScrolling) { 719 // Always prevent horizontal scroll: 720 event.preventDefault() 721 // Stop the slideshow: 722 window.clearTimeout(this.timeout) 723 if (this.options.continuous) { 724 indices = [this.circle(index + 1), index, this.circle(index - 1)] 725 } else { 726 // Increase resistance if first slide and sliding left 727 // or last slide and sliding right: 728 this.touchDelta.x = touchDeltaX = 729 touchDeltaX / 730 ((!index && touchDeltaX > 0) || 731 (index === this.num - 1 && touchDeltaX < 0) 732 ? Math.abs(touchDeltaX) / this.slideWidth + 1 733 : 1) 734 indices = [index] 735 if (index) { 736 indices.push(index - 1) 737 } 738 if (index < this.num - 1) { 739 indices.unshift(index + 1) 740 } 741 } 742 while (indices.length) { 743 index = indices.pop() 744 this.translateX(index, touchDeltaX + this.positions[index], 0) 745 } 746 } else if (!this.options.carousel) { 747 this.translateY(index, this.touchDelta.y + this.positions[index], 0) 748 } 749 }, 750 751 ontouchend: function (event) { 752 if (this.options.stopTouchEventsPropagation) { 753 this.stopPropagation(event) 754 } 755 var index = this.index 756 var absTouchDeltaX = Math.abs(this.touchDelta.x) 757 var slideWidth = this.slideWidth 758 var duration = Math.ceil( 759 (this.options.transitionDuration * (1 - absTouchDeltaX / slideWidth)) / 760 2 761 ) 762 // Determine if slide attempt triggers next/prev slide: 763 var isValidSlide = absTouchDeltaX > 20 764 // Determine if slide attempt is past start or end: 765 var isPastBounds = 766 (!index && this.touchDelta.x > 0) || 767 (index === this.num - 1 && this.touchDelta.x < 0) 768 var isValidClose = 769 !isValidSlide && 770 this.options.closeOnSwipeUpOrDown && 771 Math.abs(this.touchDelta.y) > 20 772 var direction 773 var indexForward 774 var indexBackward 775 var distanceForward 776 var distanceBackward 777 if (this.options.continuous) { 778 isPastBounds = false 779 } 780 // Determine direction of swipe (true: right, false: left): 781 direction = this.touchDelta.x < 0 ? -1 : 1 782 if (!this.isScrolling) { 783 if (isValidSlide && !isPastBounds) { 784 indexForward = index + direction 785 indexBackward = index - direction 786 distanceForward = slideWidth * direction 787 distanceBackward = -slideWidth * direction 788 if (this.options.continuous) { 789 this.move(this.circle(indexForward), distanceForward, 0) 790 this.move(this.circle(index - 2 * direction), distanceBackward, 0) 791 } else if (indexForward >= 0 && indexForward < this.num) { 792 this.move(indexForward, distanceForward, 0) 793 } 794 this.move(index, this.positions[index] + distanceForward, duration) 795 this.move( 796 this.circle(indexBackward), 797 this.positions[this.circle(indexBackward)] + distanceForward, 798 duration 799 ) 800 index = this.circle(indexBackward) 801 this.onslide(index) 802 } else { 803 // Move back into position 804 if (this.options.continuous) { 805 this.move(this.circle(index - 1), -slideWidth, duration) 806 this.move(index, 0, duration) 807 this.move(this.circle(index + 1), slideWidth, duration) 808 } else { 809 if (index) { 810 this.move(index - 1, -slideWidth, duration) 811 } 812 this.move(index, 0, duration) 813 if (index < this.num - 1) { 814 this.move(index + 1, slideWidth, duration) 815 } 816 } 817 } 818 } else { 819 if (isValidClose) { 820 this.close() 821 } else { 822 // Move back into position 823 this.translateY(index, 0, duration) 824 } 825 } 826 }, 827 828 ontouchcancel: function (event) { 829 if (this.touchStart) { 830 this.ontouchend(event) 831 delete this.touchStart 832 } 833 }, 834 835 ontransitionend: function (event) { 836 var slide = this.slides[this.index] 837 if (!event || slide === event.target) { 838 if (this.interval) { 839 this.play() 840 } 841 this.setTimeout(this.options.onslideend, [this.index, slide]) 842 } 843 }, 844 845 oncomplete: function (event) { 846 var target = event.target || event.srcElement 847 var parent = target && target.parentNode 848 var index 849 if (!target || !parent) { 850 return 851 } 852 index = this.getNodeIndex(parent) 853 $(parent).removeClass(this.options.slideLoadingClass) 854 if (event.type === 'error') { 855 $(parent).addClass(this.options.slideErrorClass) 856 this.elements[index] = 3 // Fail 857 } else { 858 this.elements[index] = 2 // Done 859 } 860 // Fix for IE7's lack of support for percentage max-height: 861 if (target.clientHeight > this.container[0].clientHeight) { 862 target.style.maxHeight = this.container[0].clientHeight 863 } 864 if (this.interval && this.slides[this.index] === parent) { 865 this.play() 866 } 867 this.setTimeout(this.options.onslidecomplete, [index, parent]) 868 }, 869 870 onload: function (event) { 871 this.oncomplete(event) 872 }, 873 874 onerror: function (event) { 875 this.oncomplete(event) 876 }, 877 878 onkeydown: function (event) { 879 switch (event.which || event.keyCode) { 880 case 13: // Enter 881 if (this.options.toggleControlsOnEnter) { 882 this.preventDefault(event) 883 this.toggleControls() 884 } 885 break 886 case 27: // Escape 887 if (this.options.closeOnEscape) { 888 this.close() 889 // prevent Escape from closing other things 890 event.stopImmediatePropagation() 891 } 892 break 893 case 32: // Space 894 if (this.options.toggleSlideshowOnSpace) { 895 this.preventDefault(event) 896 this.toggleSlideshow() 897 } 898 break 899 case 37: // ArrowLeft 900 if (this.options.enableKeyboardNavigation) { 901 this.preventDefault(event) 902 this.prev() 903 } 904 break 905 case 39: // ArrowRight 906 if (this.options.enableKeyboardNavigation) { 907 this.preventDefault(event) 908 this.next() 909 } 910 break 911 } 912 }, 913 914 handleClick: function (event) { 915 var options = this.options 916 var target = event.target || event.srcElement 917 var parent = target.parentNode 918 /** 919 * Checks if the target from the close has the given class 920 * 921 * @param {string} className Class name 922 * @returns {boolean} Returns true if the target has the class name 923 */ 924 function isTarget(className) { 925 return $(target).hasClass(className) || $(parent).hasClass(className) 926 } 927 if (isTarget(options.toggleClass)) { 928 // Click on "toggle" control 929 this.preventDefault(event) 930 this.toggleControls() 931 } else if (isTarget(options.prevClass)) { 932 // Click on "prev" control 933 this.preventDefault(event) 934 this.prev() 935 } else if (isTarget(options.nextClass)) { 936 // Click on "next" control 937 this.preventDefault(event) 938 this.next() 939 } else if (isTarget(options.closeClass)) { 940 // Click on "close" control 941 this.preventDefault(event) 942 this.close() 943 } else if (isTarget(options.playPauseClass)) { 944 // Click on "play-pause" control 945 this.preventDefault(event) 946 this.toggleSlideshow() 947 } else if (parent === this.slidesContainer[0]) { 948 // Click on slide background 949 if (options.closeOnSlideClick) { 950 this.preventDefault(event) 951 this.close() 952 } else if (options.toggleControlsOnSlideClick) { 953 this.preventDefault(event) 954 this.toggleControls() 955 } 956 } else if ( 957 parent.parentNode && 958 parent.parentNode === this.slidesContainer[0] 959 ) { 960 // Click on displayed element 961 if (options.toggleControlsOnSlideClick) { 962 this.preventDefault(event) 963 this.toggleControls() 964 } 965 } 966 }, 967 968 onclick: function (event) { 969 if ( 970 this.options.emulateTouchEvents && 971 this.touchDelta && 972 (Math.abs(this.touchDelta.x) > 20 || Math.abs(this.touchDelta.y) > 20) 973 ) { 974 delete this.touchDelta 975 return 976 } 977 return this.handleClick(event) 978 }, 979 980 updateEdgeClasses: function (index) { 981 if (!index) { 982 this.container.addClass(this.options.leftEdgeClass) 983 } else { 984 this.container.removeClass(this.options.leftEdgeClass) 985 } 986 if (index === this.num - 1) { 987 this.container.addClass(this.options.rightEdgeClass) 988 } else { 989 this.container.removeClass(this.options.rightEdgeClass) 990 } 991 }, 992 993 updateActiveSlide: function (oldIndex, newIndex) { 994 var slides = this.slides 995 var options = this.options 996 var list = [ 997 { 998 index: newIndex, 999 method: 'addClass', 1000 hidden: false 1001 }, 1002 { 1003 index: oldIndex, 1004 method: 'removeClass', 1005 hidden: true 1006 } 1007 ] 1008 var item, index 1009 while (list.length) { 1010 item = list.pop() 1011 $(slides[item.index])[item.method](options.slideActiveClass) 1012 index = this.circle(item.index - 1) 1013 if (options.continuous || index < item.index) { 1014 $(slides[index])[item.method](options.slidePrevClass) 1015 } 1016 index = this.circle(item.index + 1) 1017 if (options.continuous || index > item.index) { 1018 $(slides[index])[item.method](options.slideNextClass) 1019 } 1020 } 1021 this.slides[oldIndex].setAttribute('aria-hidden', 'true') 1022 this.slides[newIndex].removeAttribute('aria-hidden') 1023 }, 1024 1025 handleSlide: function (oldIndex, newIndex) { 1026 if (!this.options.continuous) { 1027 this.updateEdgeClasses(newIndex) 1028 } 1029 this.updateActiveSlide(oldIndex, newIndex) 1030 this.loadElements(newIndex) 1031 if (this.options.unloadElements) { 1032 this.unloadElements(oldIndex, newIndex) 1033 } 1034 this.setTitle(newIndex) 1035 }, 1036 1037 onslide: function (index) { 1038 this.handleSlide(this.index, index) 1039 this.index = index 1040 this.setTimeout(this.options.onslide, [index, this.slides[index]]) 1041 }, 1042 1043 setTitle: function (index) { 1044 var firstChild = this.slides[index].firstChild 1045 var text = firstChild.title || firstChild.alt 1046 var titleElement = this.titleElement 1047 if (titleElement.length) { 1048 this.titleElement.empty() 1049 if (text) { 1050 titleElement[0].appendChild(document.createTextNode(text)) 1051 } 1052 } 1053 }, 1054 1055 setTimeout: function (func, args, wait) { 1056 var that = this 1057 return ( 1058 func && 1059 window.setTimeout(function () { 1060 func.apply(that, args || []) 1061 }, wait || 0) 1062 ) 1063 }, 1064 1065 imageFactory: function (obj, callback) { 1066 var options = this.options 1067 var that = this 1068 var url = obj 1069 var img = this.imagePrototype.cloneNode(false) 1070 var picture 1071 var called 1072 var sources 1073 var srcset 1074 var sizes 1075 var title 1076 var altText 1077 var i 1078 /** 1079 * Wraps the callback function for the load/error event 1080 * 1081 * @param {event} event load/error event 1082 * @returns {number} timeout ID 1083 */ 1084 function callbackWrapper(event) { 1085 if (!called) { 1086 event = { 1087 type: event.type, 1088 target: picture || img 1089 } 1090 if (!event.target.parentNode) { 1091 // Fix for browsers (e.g. IE7) firing the load event for 1092 // cached images before the element could 1093 // be added to the DOM: 1094 return that.setTimeout(callbackWrapper, [event]) 1095 } 1096 called = true 1097 $(img).off('load error', callbackWrapper) 1098 callback(event) 1099 } 1100 } 1101 if (typeof url !== 'string') { 1102 url = this.getItemProperty(obj, options.urlProperty) 1103 sources = 1104 this.support.picture && 1105 this.support.source && 1106 this.getItemProperty(obj, options.sourcesProperty) 1107 srcset = this.getItemProperty(obj, options.srcsetProperty) 1108 sizes = this.getItemProperty(obj, options.sizesProperty) 1109 title = this.getItemProperty(obj, options.titleProperty) 1110 altText = this.getItemProperty(obj, options.altTextProperty) || title 1111 } 1112 img.draggable = false 1113 if (title) { 1114 img.title = title 1115 } 1116 if (altText) { 1117 img.alt = altText 1118 } 1119 $(img).on('load error', callbackWrapper) 1120 if (sources && sources.length) { 1121 picture = this.picturePrototype.cloneNode(false) 1122 for (i = 0; i < sources.length; i += 1) { 1123 picture.appendChild( 1124 $.extend(this.sourcePrototype.cloneNode(false), sources[i]) 1125 ) 1126 } 1127 picture.appendChild(img) 1128 $(picture).addClass(options.toggleClass) 1129 } 1130 if (srcset) { 1131 if (sizes) { 1132 img.sizes = sizes 1133 } 1134 img.srcset = srcset 1135 } 1136 img.src = url 1137 if (picture) return picture 1138 return img 1139 }, 1140 1141 createElement: function (obj, callback) { 1142 var type = obj && this.getItemProperty(obj, this.options.typeProperty) 1143 var factory = 1144 (type && this[type.split('/')[0] + 'Factory']) || this.imageFactory 1145 var element = obj && factory.call(this, obj, callback) 1146 if (!element) { 1147 element = this.elementPrototype.cloneNode(false) 1148 this.setTimeout(callback, [ 1149 { 1150 type: 'error', 1151 target: element 1152 } 1153 ]) 1154 } 1155 $(element).addClass(this.options.slideContentClass) 1156 return element 1157 }, 1158 1159 iteratePreloadRange: function (index, func) { 1160 var num = this.num 1161 var options = this.options 1162 var limit = Math.min(num, options.preloadRange * 2 + 1) 1163 var j = index 1164 var i 1165 for (i = 0; i < limit; i += 1) { 1166 // First iterate to the current index (0), 1167 // then the next one (+1), 1168 // then the previous one (-1), 1169 // then the next after next (+2), 1170 // then the one before the previous one (-2), etc.: 1171 j += i * (i % 2 === 0 ? -1 : 1) 1172 if (j < 0 || j >= num) { 1173 if (!options.continuous) continue 1174 // Connect the ends of the list to load slide elements for 1175 // continuous iteration: 1176 j = this.circle(j) 1177 } 1178 func.call(this, j) 1179 } 1180 }, 1181 1182 loadElement: function (index) { 1183 if (!this.elements[index]) { 1184 if (this.slides[index].firstChild) { 1185 this.elements[index] = $(this.slides[index]).hasClass( 1186 this.options.slideErrorClass 1187 ) 1188 ? 3 1189 : 2 1190 } else { 1191 this.elements[index] = 1 // Loading 1192 $(this.slides[index]).addClass(this.options.slideLoadingClass) 1193 this.slides[index].appendChild( 1194 this.createElement(this.list[index], this.proxyListener) 1195 ) 1196 } 1197 } 1198 }, 1199 1200 loadElements: function (index) { 1201 this.iteratePreloadRange(index, this.loadElement) 1202 }, 1203 1204 unloadElements: function (oldIndex, newIndex) { 1205 var preloadRange = this.options.preloadRange 1206 this.iteratePreloadRange(oldIndex, function (i) { 1207 var diff = Math.abs(i - newIndex) 1208 if (diff > preloadRange && diff + preloadRange < this.num) { 1209 this.unloadSlide(i) 1210 delete this.elements[i] 1211 } 1212 }) 1213 }, 1214 1215 addSlide: function (index) { 1216 var slide = this.slidePrototype.cloneNode(false) 1217 slide.setAttribute('data-index', index) 1218 slide.setAttribute('aria-hidden', 'true') 1219 this.slidesContainer[0].appendChild(slide) 1220 this.slides.push(slide) 1221 }, 1222 1223 positionSlide: function (index) { 1224 var slide = this.slides[index] 1225 slide.style.width = this.slideWidth + 'px' 1226 if (this.support.transform) { 1227 slide.style.left = index * -this.slideWidth + 'px' 1228 this.move( 1229 index, 1230 this.index > index 1231 ? -this.slideWidth 1232 : this.index < index 1233 ? this.slideWidth 1234 : 0, 1235 0 1236 ) 1237 } 1238 }, 1239 1240 initSlides: function (reload) { 1241 var clearSlides, i 1242 if (!reload) { 1243 this.positions = [] 1244 this.positions.length = this.num 1245 this.elements = {} 1246 this.picturePrototype = 1247 this.support.picture && document.createElement('picture') 1248 this.sourcePrototype = 1249 this.support.source && document.createElement('source') 1250 this.imagePrototype = document.createElement('img') 1251 this.elementPrototype = document.createElement('div') 1252 this.slidePrototype = this.elementPrototype.cloneNode(false) 1253 $(this.slidePrototype).addClass(this.options.slideClass) 1254 this.slides = this.slidesContainer[0].children 1255 clearSlides = 1256 this.options.clearSlides || this.slides.length !== this.num 1257 } 1258 this.slideWidth = this.container[0].clientWidth 1259 this.slideHeight = this.container[0].clientHeight 1260 this.slidesContainer[0].style.width = this.num * this.slideWidth + 'px' 1261 if (clearSlides) { 1262 this.resetSlides() 1263 } 1264 for (i = 0; i < this.num; i += 1) { 1265 if (clearSlides) { 1266 this.addSlide(i) 1267 } 1268 this.positionSlide(i) 1269 } 1270 // Reposition the slides before and after the given index: 1271 if (this.options.continuous && this.support.transform) { 1272 this.move(this.circle(this.index - 1), -this.slideWidth, 0) 1273 this.move(this.circle(this.index + 1), this.slideWidth, 0) 1274 } 1275 if (!this.support.transform) { 1276 this.slidesContainer[0].style.left = 1277 this.index * -this.slideWidth + 'px' 1278 } 1279 }, 1280 1281 unloadSlide: function (index) { 1282 var slide, firstChild 1283 slide = this.slides[index] 1284 firstChild = slide.firstChild 1285 if (firstChild !== null) { 1286 slide.removeChild(firstChild) 1287 } 1288 }, 1289 1290 unloadAllSlides: function () { 1291 var i, len 1292 for (i = 0, len = this.slides.length; i < len; i++) { 1293 this.unloadSlide(i) 1294 } 1295 }, 1296 1297 toggleControls: function () { 1298 var controlsClass = this.options.controlsClass 1299 if (this.container.hasClass(controlsClass)) { 1300 this.container.removeClass(controlsClass) 1301 } else { 1302 this.container.addClass(controlsClass) 1303 } 1304 }, 1305 1306 toggleSlideshow: function () { 1307 if (!this.interval) { 1308 this.play() 1309 } else { 1310 this.pause() 1311 } 1312 }, 1313 1314 getNodeIndex: function (element) { 1315 return parseInt(element.getAttribute('data-index'), 10) 1316 }, 1317 1318 getNestedProperty: function (obj, property) { 1319 property.replace( 1320 // Matches native JavaScript notation in a String, 1321 // e.g. '["doubleQuoteProp"].dotProp[2]' 1322 // eslint-disable-next-line no-useless-escape 1323 /\[(?:'([^']+)'|"([^"]+)"|(\d+))\]|(?:(?:^|\.)([^\.\[]+))/g, 1324 function (str, singleQuoteProp, doubleQuoteProp, arrayIndex, dotProp) { 1325 var prop = 1326 dotProp || 1327 singleQuoteProp || 1328 doubleQuoteProp || 1329 (arrayIndex && parseInt(arrayIndex, 10)) 1330 if (str && obj) { 1331 obj = obj[prop] 1332 } 1333 } 1334 ) 1335 return obj 1336 }, 1337 1338 getDataProperty: function (obj, property) { 1339 var key 1340 var prop 1341 if (obj.dataset) { 1342 key = property.replace(/-([a-z])/g, function (_, b) { 1343 return b.toUpperCase() 1344 }) 1345 prop = obj.dataset[key] 1346 } else if (obj.getAttribute) { 1347 prop = obj.getAttribute( 1348 'data-' + property.replace(/([A-Z])/g, '-$1').toLowerCase() 1349 ) 1350 } 1351 if (typeof prop === 'string') { 1352 // eslint-disable-next-line no-useless-escape 1353 if ( 1354 /^(true|false|null|-?\d+(\.\d+)?|\{[\s\S]*\}|\[[\s\S]*\])$/.test(prop) 1355 ) { 1356 try { 1357 return $.parseJSON(prop) 1358 } catch (ignore) { 1359 // ignore JSON parsing errors 1360 } 1361 } 1362 return prop 1363 } 1364 }, 1365 1366 getItemProperty: function (obj, property) { 1367 var prop = this.getDataProperty(obj, property) 1368 if (prop === undefined) { 1369 prop = obj[property] 1370 } 1371 if (prop === undefined) { 1372 prop = this.getNestedProperty(obj, property) 1373 } 1374 return prop 1375 }, 1376 1377 initStartIndex: function () { 1378 var index = this.options.index 1379 var urlProperty = this.options.urlProperty 1380 var i 1381 // Check if the index is given as a list object: 1382 if (index && typeof index !== 'number') { 1383 for (i = 0; i < this.num; i += 1) { 1384 if ( 1385 this.list[i] === index || 1386 this.getItemProperty(this.list[i], urlProperty) === 1387 this.getItemProperty(index, urlProperty) 1388 ) { 1389 index = i 1390 break 1391 } 1392 } 1393 } 1394 // Make sure the index is in the list range: 1395 this.index = this.circle(parseInt(index, 10) || 0) 1396 }, 1397 1398 initEventListeners: function () { 1399 var that = this 1400 var slidesContainer = this.slidesContainer 1401 /** 1402 * Proxy listener 1403 * 1404 * @param {event} event original event 1405 */ 1406 function proxyListener(event) { 1407 var type = 1408 that.support.transition && that.support.transition.end === event.type 1409 ? 'transitionend' 1410 : event.type 1411 that['on' + type](event) 1412 } 1413 $(window).on('resize', proxyListener) 1414 $(window).on('hashchange', proxyListener) 1415 $(document.body).on('keydown', proxyListener) 1416 this.container.on('click', proxyListener) 1417 if (this.support.touch) { 1418 slidesContainer.on( 1419 'touchstart touchmove touchend touchcancel', 1420 proxyListener 1421 ) 1422 } else if (this.options.emulateTouchEvents && this.support.transition) { 1423 slidesContainer.on( 1424 'mousedown mousemove mouseup mouseout', 1425 proxyListener 1426 ) 1427 } 1428 if (this.support.transition) { 1429 slidesContainer.on(this.support.transition.end, proxyListener) 1430 } 1431 this.proxyListener = proxyListener 1432 }, 1433 1434 destroyEventListeners: function () { 1435 var slidesContainer = this.slidesContainer 1436 var proxyListener = this.proxyListener 1437 $(window).off('resize', proxyListener) 1438 $(document.body).off('keydown', proxyListener) 1439 this.container.off('click', proxyListener) 1440 if (this.support.touch) { 1441 slidesContainer.off( 1442 'touchstart touchmove touchend touchcancel', 1443 proxyListener 1444 ) 1445 } else if (this.options.emulateTouchEvents && this.support.transition) { 1446 slidesContainer.off( 1447 'mousedown mousemove mouseup mouseout', 1448 proxyListener 1449 ) 1450 } 1451 if (this.support.transition) { 1452 slidesContainer.off(this.support.transition.end, proxyListener) 1453 } 1454 }, 1455 1456 handleOpen: function () { 1457 if (this.options.onopened) { 1458 this.options.onopened.call(this) 1459 } 1460 }, 1461 1462 initWidget: function () { 1463 var that = this 1464 /** 1465 * Open handler 1466 * 1467 * @param {event} event Gallery open event 1468 */ 1469 function openHandler(event) { 1470 if (event.target === that.container[0]) { 1471 that.container.off(that.support.transition.end, openHandler) 1472 that.handleOpen() 1473 } 1474 } 1475 this.container = $(this.options.container) 1476 if (!this.container.length) { 1477 this.console.log( 1478 'blueimp Gallery: Widget container not found.', 1479 this.options.container 1480 ) 1481 return false 1482 } 1483 this.slidesContainer = this.container 1484 .find(this.options.slidesContainer) 1485 .first() 1486 if (!this.slidesContainer.length) { 1487 this.console.log( 1488 'blueimp Gallery: Slides container not found.', 1489 this.options.slidesContainer 1490 ) 1491 return false 1492 } 1493 this.titleElement = this.container.find(this.options.titleElement).first() 1494 this.playPauseElement = this.container 1495 .find('.' + this.options.playPauseClass) 1496 .first() 1497 if (this.num === 1) { 1498 this.container.addClass(this.options.singleClass) 1499 } 1500 if (this.support.svgasimg) { 1501 this.container.addClass(this.options.svgasimgClass) 1502 } 1503 if (this.support.smil) { 1504 this.container.addClass(this.options.smilClass) 1505 } 1506 if (this.options.onopen) { 1507 this.options.onopen.call(this) 1508 } 1509 if (this.support.transition && this.options.displayTransition) { 1510 this.container.on(this.support.transition.end, openHandler) 1511 } else { 1512 this.handleOpen() 1513 } 1514 if (this.options.hidePageScrollbars) { 1515 // Hide the page scrollbars: 1516 this.bodyOverflowStyle = document.body.style.overflow 1517 document.body.style.overflow = 'hidden' 1518 } 1519 this.container[0].style.display = 'block' 1520 this.initSlides() 1521 this.container.addClass(this.options.displayClass) 1522 }, 1523 1524 initOptions: function (options) { 1525 // Create a copy of the prototype options: 1526 this.options = $.extend({}, this.options) 1527 // Check if carousel mode is enabled: 1528 if ( 1529 (options && options.carousel) || 1530 (this.options.carousel && (!options || options.carousel !== false)) 1531 ) { 1532 $.extend(this.options, this.carouselOptions) 1533 } 1534 // Override any given options: 1535 $.extend(this.options, options) 1536 if (this.num < 3) { 1537 // 1 or 2 slides cannot be displayed continuous, 1538 // remember the original option by setting to null instead of false: 1539 this.options.continuous = this.options.continuous ? null : false 1540 } 1541 if (!this.support.transition) { 1542 this.options.emulateTouchEvents = false 1543 } 1544 if (this.options.event) { 1545 this.preventDefault(this.options.event) 1546 } 1547 } 1548 }) 1549 1550 return Gallery 1551})
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.