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