JAVASCRIPT   22

carousel js

Guest on 6th July 2022 01:11:31 AM

  1. /**
  2.  * --------------------------------------------------------------------------
  3.  * Bootstrap (v4.3.1): carousel.js
  4.  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
  5.  * --------------------------------------------------------------------------
  6.  */
  7.  
  8. import $ from 'jquery'
  9. import Util from './util'
  10.  
  11. /**
  12.  * ------------------------------------------------------------------------
  13.  * Constants
  14.  * ------------------------------------------------------------------------
  15.  */
  16.  
  17. const NAME                   = 'carousel'
  18. const VERSION                = '4.3.1'
  19. const DATA_KEY               = 'bs.carousel'
  20. const EVENT_KEY              = `.${DATA_KEY}`
  21. const DATA_API_KEY           = '.data-api'
  22. const JQUERY_NO_CONFLICT     = $.fn[NAME]
  23. const ARROW_LEFT_KEYCODE     = 37 // KeyboardEvent.which value for left arrow key
  24. const ARROW_RIGHT_KEYCODE    = 39 // KeyboardEvent.which value for right arrow key
  25. const TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch
  26. const SWIPE_THRESHOLD        = 40
  27.  
  28. const Default = {
  29.   interval : 5000,
  30.   keyboard : true,
  31.   slide    : false,
  32.   pause    : 'hover',
  33.   wrap     : true,
  34.   touch    : true
  35. }
  36.  
  37. const DefaultType = {
  38.   interval : '(number|boolean)',
  39.   keyboard : 'boolean',
  40.   slide    : '(boolean|string)',
  41.   pause    : '(string|boolean)',
  42.   wrap     : 'boolean',
  43.   touch    : 'boolean'
  44. }
  45.  
  46. const Direction = {
  47.   NEXT     : 'next',
  48.   PREV     : 'prev',
  49.   LEFT     : 'left',
  50.   RIGHT    : 'right'
  51. }
  52.  
  53. const Event = {
  54.   SLIDE          : `slide${EVENT_KEY}`,
  55.   SLID           : `slid${EVENT_KEY}`,
  56.   KEYDOWN        : `keydown${EVENT_KEY}`,
  57.   MOUSEENTER     : `mouseenter${EVENT_KEY}`,
  58.   MOUSELEAVE     : `mouseleave${EVENT_KEY}`,
  59.   TOUCHSTART     : `touchstart${EVENT_KEY}`,
  60.   TOUCHMOVE      : `touchmove${EVENT_KEY}`,
  61.   TOUCHEND       : `touchend${EVENT_KEY}`,
  62.   POINTERDOWN    : `pointerdown${EVENT_KEY}`,
  63.   POINTERUP      : `pointerup${EVENT_KEY}`,
  64.   DRAG_START     : `dragstart${EVENT_KEY}`,
  65.   LOAD_DATA_API  : `load${EVENT_KEY}${DATA_API_KEY}`,
  66.   CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`
  67. }
  68.  
  69. const ClassName = {
  70.   CAROUSEL      : 'carousel',
  71.   ACTIVE        : 'active',
  72.   SLIDE         : 'slide',
  73.   RIGHT         : 'carousel-item-right',
  74.   LEFT          : 'carousel-item-left',
  75.   NEXT          : 'carousel-item-next',
  76.   PREV          : 'carousel-item-prev',
  77.   ITEM          : 'carousel-item',
  78.   POINTER_EVENT : 'pointer-event'
  79. }
  80.  
  81. const Selector = {
  82.   ACTIVE      : '.active',
  83.   ACTIVE_ITEM : '.active.carousel-item',
  84.   ITEM        : '.carousel-item',
  85.   ITEM_IMG    : '.carousel-item img',
  86.   NEXT_PREV   : '.carousel-item-next, .carousel-item-prev',
  87.   INDICATORS  : '.carousel-indicators',
  88.   DATA_SLIDE  : '[data-slide], [data-slide-to]',
  89.   DATA_RIDE   : '[data-ride="carousel"]'
  90. }
  91.  
  92. const PointerType = {
  93.   TOUCH : 'touch',
  94.   PEN   : 'pen'
  95. }
  96.  
  97. /**
  98.  * ------------------------------------------------------------------------
  99.  * Class Definition
  100.  * ------------------------------------------------------------------------
  101.  */
  102. class Carousel {
  103.   constructor(element, config) {
  104.     this._items         = null
  105.     this._interval      = null
  106.     this._activeElement = null
  107.     this._isPaused      = false
  108.     this._isSliding     = false
  109.     this.touchTimeout   = null
  110.     this.touchStartX    = 0
  111.     this.touchDeltaX    = 0
  112.  
  113.     this._config            = this._getConfig(config)
  114.     this._element           = element
  115.     this._indicatorsElement = this._element.querySelector(Selector.INDICATORS)
  116.     this._touchSupported    = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0
  117.     this._pointerEvent      = Boolean(window.PointerEvent || window.MSPointerEvent)
  118.  
  119.     this._addEventListeners()
  120.   }
  121.  
  122.   // Getters
  123.  
  124.   static get VERSION() {
  125.     return VERSION
  126.   }
  127.  
  128.   static get Default() {
  129.     return Default
  130.   }
  131.  
  132.   // Public
  133.  
  134.   next() {
  135.     if (!this._isSliding) {
  136.       this._slide(Direction.NEXT)
  137.     }
  138.   }
  139.  
  140.   nextWhenVisible() {
  141.     // Don't call next when the page isn't visible
  142.     // or the carousel or its parent isn't visible
  143.     if (!document.hidden &&
  144.       ($(this._element).is(':visible') && $(this._element).css('visibility') !== 'hidden')) {
  145.       this.next()
  146.     }
  147.   }
  148.  
  149.   prev() {
  150.     if (!this._isSliding) {
  151.       this._slide(Direction.PREV)
  152.     }
  153.   }
  154.  
  155.   pause(event) {
  156.     if (!event) {
  157.       this._isPaused = true
  158.     }
  159.  
  160.     if (this._element.querySelector(Selector.NEXT_PREV)) {
  161.       Util.triggerTransitionEnd(this._element)
  162.       this.cycle(true)
  163.     }
  164.  
  165.     clearInterval(this._interval)
  166.     this._interval = null
  167.   }
  168.  
  169.   cycle(event) {
  170.     if (!event) {
  171.       this._isPaused = false
  172.     }
  173.  
  174.     if (this._interval) {
  175.       clearInterval(this._interval)
  176.       this._interval = null
  177.     }
  178.  
  179.     if (this._config.interval && !this._isPaused) {
  180.       this._interval = setInterval(
  181.         (document.visibilityState ? this.nextWhenVisible : this.next).bind(this),
  182.         this._config.interval
  183.       )
  184.     }
  185.   }
  186.  
  187.   to(index) {
  188.     this._activeElement = this._element.querySelector(Selector.ACTIVE_ITEM)
  189.  
  190.     const activeIndex = this._getItemIndex(this._activeElement)
  191.  
  192.     if (index > this._items.length - 1 || index < 0) {
  193.       return
  194.     }
  195.  
  196.     if (this._isSliding) {
  197.       $(this._element).one(Event.SLID, () => this.to(index))
  198.       return
  199.     }
  200.  
  201.     if (activeIndex === index) {
  202.       this.pause()
  203.       this.cycle()
  204.       return
  205.     }
  206.  
  207.     const direction = index > activeIndex
  208.       ? Direction.NEXT
  209.       : Direction.PREV
  210.  
  211.     this._slide(direction, this._items[index])
  212.   }
  213.  
  214.   dispose() {
  215.     $(this._element).off(EVENT_KEY)
  216.     $.removeData(this._element, DATA_KEY)
  217.  
  218.     this._items             = null
  219.     this._config            = null
  220.     this._element           = null
  221.     this._interval          = null
  222.     this._isPaused          = null
  223.     this._isSliding         = null
  224.     this._activeElement     = null
  225.     this._indicatorsElement = null
  226.   }
  227.  
  228.   // Private
  229.  
  230.   _getConfig(config) {
  231.     config = {
  232.       ...Default,
  233.       ...config
  234.     }
  235.     Util.typeCheckConfig(NAME, config, DefaultType)
  236.     return config
  237.   }
  238.  
  239.   _handleSwipe() {
  240.     const absDeltax = Math.abs(this.touchDeltaX)
  241.  
  242.     if (absDeltax <= SWIPE_THRESHOLD) {
  243.       return
  244.     }
  245.  
  246.     const direction = absDeltax / this.touchDeltaX
  247.  
  248.     // swipe left
  249.     if (direction > 0) {
  250.       this.prev()
  251.     }
  252.  
  253.     // swipe right
  254.     if (direction < 0) {
  255.       this.next()
  256.     }
  257.   }
  258.  
  259.   _addEventListeners() {
  260.     if (this._config.keyboard) {
  261.       $(this._element)
  262.         .on(Event.KEYDOWN, (event) => this._keydown(event))
  263.     }
  264.  
  265.     if (this._config.pause === 'hover') {
  266.       $(this._element)
  267.         .on(Event.MOUSEENTER, (event) => this.pause(event))
  268.         .on(Event.MOUSELEAVE, (event) => this.cycle(event))
  269.     }
  270.  
  271.     if (this._config.touch) {
  272.       this._addTouchEventListeners()
  273.     }
  274.   }
  275.  
  276.   _addTouchEventListeners() {
  277.     if (!this._touchSupported) {
  278.       return
  279.     }
  280.  
  281.     const start = (event) => {
  282.       if (this._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) {
  283.         this.touchStartX = event.originalEvent.clientX
  284.       } else if (!this._pointerEvent) {
  285.         this.touchStartX = event.originalEvent.touches[0].clientX
  286.       }
  287.     }
  288.  
  289.     const move = (event) => {
  290.       // ensure swiping with one touch and not pinching
  291.       if (event.originalEvent.touches && event.originalEvent.touches.length > 1) {
  292.         this.touchDeltaX = 0
  293.       } else {
  294.         this.touchDeltaX = event.originalEvent.touches[0].clientX - this.touchStartX
  295.       }
  296.     }
  297.  
  298.     const end = (event) => {
  299.       if (this._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) {
  300.         this.touchDeltaX = event.originalEvent.clientX - this.touchStartX
  301.       }
  302.  
  303.       this._handleSwipe()
  304.       if (this._config.pause === 'hover') {
  305.         // If it's a touch-enabled device, mouseenter/leave are fired as
  306.         // part of the mouse compatibility events on first tap - the carousel
  307.         // would stop cycling until user tapped out of it;
  308.         // here, we listen for touchend, explicitly pause the carousel
  309.         // (as if it's the second time we tap on it, mouseenter compat event
  310.         // is NOT fired) and after a timeout (to allow for mouse compatibility
  311.         // events to fire) we explicitly restart cycling
  312.  
  313.         this.pause()
  314.         if (this.touchTimeout) {
  315.           clearTimeout(this.touchTimeout)
  316.         }
  317.         this.touchTimeout = setTimeout((event) => this.cycle(event), TOUCHEVENT_COMPAT_WAIT + this._config.interval)
  318.       }
  319.     }
  320.  
  321.     $(this._element.querySelectorAll(Selector.ITEM_IMG)).on(Event.DRAG_START, (e) => e.preventDefault())
  322.     if (this._pointerEvent) {
  323.       $(this._element).on(Event.POINTERDOWN, (event) => start(event))
  324.       $(this._element).on(Event.POINTERUP, (event) => end(event))
  325.  
  326.       this._element.classList.add(ClassName.POINTER_EVENT)
  327.     } else {
  328.       $(this._element).on(Event.TOUCHSTART, (event) => start(event))
  329.       $(this._element).on(Event.TOUCHMOVE, (event) => move(event))
  330.       $(this._element).on(Event.TOUCHEND, (event) => end(event))
  331.     }
  332.   }
  333.  
  334.   _keydown(event) {
  335.     if (/input|textarea/i.test(event.target.tagName)) {
  336.       return
  337.     }
  338.  
  339.     switch (event.which) {
  340.       case ARROW_LEFT_KEYCODE:
  341.         event.preventDefault()
  342.         this.prev()
  343.         break
  344.       case ARROW_RIGHT_KEYCODE:
  345.         event.preventDefault()
  346.         this.next()
  347.         break
  348.       default:
  349.     }
  350.   }
  351.  
  352.   _getItemIndex(element) {
  353.     this._items = element && element.parentNode
  354.       ? [].slice.call(element.parentNode.querySelectorAll(Selector.ITEM))
  355.       : []
  356.     return this._items.indexOf(element)
  357.   }
  358.  
  359.   _getItemByDirection(direction, activeElement) {
  360.     const isNextDirection = direction === Direction.NEXT
  361.     const isPrevDirection = direction === Direction.PREV
  362.     const activeIndex     = this._getItemIndex(activeElement)
  363.     const lastItemIndex   = this._items.length - 1
  364.     const isGoingToWrap   = isPrevDirection && activeIndex === 0 ||
  365.                             isNextDirection && activeIndex === lastItemIndex
  366.  
  367.     if (isGoingToWrap && !this._config.wrap) {
  368.       return activeElement
  369.     }
  370.  
  371.     const delta     = direction === Direction.PREV ? -1 : 1
  372.     const itemIndex = (activeIndex + delta) % this._items.length
  373.  
  374.     return itemIndex === -1
  375.       ? this._items[this._items.length - 1] : this._items[itemIndex]
  376.   }
  377.  
  378.   _triggerSlideEvent(relatedTarget, eventDirectionName) {
  379.     const targetIndex = this._getItemIndex(relatedTarget)
  380.     const fromIndex = this._getItemIndex(this._element.querySelector(Selector.ACTIVE_ITEM))
  381.     const slideEvent = $.Event(Event.SLIDE, {
  382.       relatedTarget,
  383.       direction: eventDirectionName,
  384.       from: fromIndex,
  385.       to: targetIndex
  386.     })
  387.  
  388.     $(this._element).trigger(slideEvent)
  389.  
  390.     return slideEvent
  391.   }
  392.  
  393.   _setActiveIndicatorElement(element) {
  394.     if (this._indicatorsElement) {
  395.       const indicators = [].slice.call(this._indicatorsElement.querySelectorAll(Selector.ACTIVE))
  396.       $(indicators)
  397.         .removeClass(ClassName.ACTIVE)
  398.  
  399.       const nextIndicator = this._indicatorsElement.children[
  400.         this._getItemIndex(element)
  401.       ]
  402.  
  403.       if (nextIndicator) {
  404.         $(nextIndicator).addClass(ClassName.ACTIVE)
  405.       }
  406.     }
  407.   }
  408.  
  409.   _slide(direction, element) {
  410.     const activeElement = this._element.querySelector(Selector.ACTIVE_ITEM)
  411.     const activeElementIndex = this._getItemIndex(activeElement)
  412.     const nextElement   = element || activeElement &&
  413.       this._getItemByDirection(direction, activeElement)
  414.     const nextElementIndex = this._getItemIndex(nextElement)
  415.     const isCycling = Boolean(this._interval)
  416.  
  417.     let directionalClassName
  418.     let orderClassName
  419.     let eventDirectionName
  420.  
  421.     if (direction === Direction.NEXT) {
  422.       directionalClassName = ClassName.LEFT
  423.       orderClassName = ClassName.NEXT
  424.       eventDirectionName = Direction.LEFT
  425.     } else {
  426.       directionalClassName = ClassName.RIGHT
  427.       orderClassName = ClassName.PREV
  428.       eventDirectionName = Direction.RIGHT
  429.     }
  430.  
  431.     if (nextElement && $(nextElement).hasClass(ClassName.ACTIVE)) {
  432.       this._isSliding = false
  433.       return
  434.     }
  435.  
  436.     const slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName)
  437.     if (slideEvent.isDefaultPrevented()) {
  438.       return
  439.     }
  440.  
  441.     if (!activeElement || !nextElement) {
  442.       // Some weirdness is happening, so we bail
  443.       return
  444.     }
  445.  
  446.     this._isSliding = true
  447.  
  448.     if (isCycling) {
  449.       this.pause()
  450.     }
  451.  
  452.     this._setActiveIndicatorElement(nextElement)
  453.  
  454.     const slidEvent = $.Event(Event.SLID, {
  455.       relatedTarget: nextElement,
  456.       direction: eventDirectionName,
  457.       from: activeElementIndex,
  458.       to: nextElementIndex
  459.     })
  460.  
  461.     if ($(this._element).hasClass(ClassName.SLIDE)) {
  462.       $(nextElement).addClass(orderClassName)
  463.  
  464.       Util.reflow(nextElement)
  465.  
  466.       $(activeElement).addClass(directionalClassName)
  467.       $(nextElement).addClass(directionalClassName)
  468.  
  469.       const nextElementInterval = parseInt(nextElement.getAttribute('data-interval'), 10)
  470.       if (nextElementInterval) {
  471.         this._config.defaultInterval = this._config.defaultInterval || this._config.interval
  472.         this._config.interval = nextElementInterval
  473.       } else {
  474.         this._config.interval = this._config.defaultInterval || this._config.interval
  475.       }
  476.  
  477.       const transitionDuration = Util.getTransitionDurationFromElement(activeElement)
  478.  
  479.       $(activeElement)
  480.         .one(Util.TRANSITION_END, () => {
  481.           $(nextElement)
  482.             .removeClass(`${directionalClassName} ${orderClassName}`)
  483.             .addClass(ClassName.ACTIVE)
  484.  
  485.           $(activeElement).removeClass(`${ClassName.ACTIVE} ${orderClassName} ${directionalClassName}`)
  486.  
  487.           this._isSliding = false
  488.  
  489.           setTimeout(() => $(this._element).trigger(slidEvent), 0)
  490.         })
  491.         .emulateTransitionEnd(transitionDuration)
  492.     } else {
  493.       $(activeElement).removeClass(ClassName.ACTIVE)
  494.       $(nextElement).addClass(ClassName.ACTIVE)
  495.  
  496.       this._isSliding = false
  497.       $(this._element).trigger(slidEvent)
  498.     }
  499.  
  500.     if (isCycling) {
  501.       this.cycle()
  502.     }
  503.   }
  504.  
  505.   // Static
  506.  
  507.   static _jQueryInterface(config) {
  508.     return this.each(function () {
  509.       let data = $(this).data(DATA_KEY)
  510.       let _config = {
  511.         ...Default,
  512.         ...$(this).data()
  513.       }
  514.  
  515.       if (typeof config === 'object') {
  516.         _config = {
  517.           ..._config,
  518.           ...config
  519.         }
  520.       }
  521.  
  522.       const action = typeof config === 'string' ? config : _config.slide
  523.  
  524.       if (!data) {
  525.         data = new Carousel(this, _config)
  526.         $(this).data(DATA_KEY, data)
  527.       }
  528.  
  529.       if (typeof config === 'number') {
  530.         data.to(config)
  531.       } else if (typeof action === 'string') {
  532.         if (typeof data[action] === 'undefined') {
  533.           throw new TypeError(`No method named "${action}"`)
  534.         }
  535.         data[action]()
  536.       } else if (_config.interval && _config.ride) {
  537.         data.pause()
  538.         data.cycle()
  539.       }
  540.     })
  541.   }
  542.  
  543.   static _dataApiClickHandler(event) {
  544.     const selector = Util.getSelectorFromElement(this)
  545.  
  546.     if (!selector) {
  547.       return
  548.     }
  549.  
  550.     const target = $(selector)[0]
  551.  
  552.     if (!target || !$(target).hasClass(ClassName.CAROUSEL)) {
  553.       return
  554.     }
  555.  
  556.     const config = {
  557.       ...$(target).data(),
  558.       ...$(this).data()
  559.     }
  560.     const slideIndex = this.getAttribute('data-slide-to')
  561.  
  562.     if (slideIndex) {
  563.       config.interval = false
  564.     }
  565.  
  566.     Carousel._jQueryInterface.call($(target), config)
  567.  
  568.     if (slideIndex) {
  569.       $(target).data(DATA_KEY).to(slideIndex)
  570.     }
  571.  
  572.     event.preventDefault()
  573.   }
  574. }
  575.  
  576. /**
  577.  * ------------------------------------------------------------------------
  578.  * Data Api implementation
  579.  * ------------------------------------------------------------------------
  580.  */
  581.  
  582. $(document)
  583.   .on(Event.CLICK_DATA_API, Selector.DATA_SLIDE, Carousel._dataApiClickHandler)
  584.  
  585. $(window).on(Event.LOAD_DATA_API, () => {
  586.   const carousels = [].slice.call(document.querySelectorAll(Selector.DATA_RIDE))
  587.   for (let i = 0, len = carousels.length; i < len; i++) {
  588.     const $carousel = $(carousels[i])
  589.     Carousel._jQueryInterface.call($carousel, $carousel.data())
  590.   }
  591. })
  592.  
  593. /**
  594.  * ------------------------------------------------------------------------
  595.  * jQuery
  596.  * ------------------------------------------------------------------------
  597.  */
  598.  
  599. $.fn[NAME] = Carousel._jQueryInterface
  600. $.fn[NAME].Constructor = Carousel
  601. $.fn[NAME].noConflict = () => {
  602.   $.fn[NAME] = JQUERY_NO_CONFLICT
  603.   return Carousel._jQueryInterface
  604. }
  605.  
  606. export default Carousel

Raw Paste


Login or Register to edit or fork this paste. It's free.