From 3dd0bde664699fc7a191d0d3569f4f0ba8f06028 Mon Sep 17 00:00:00 2001
From: Martijn Cuppens <martijn.cuppens@gmail.com>
Date: Mon, 19 Feb 2018 10:10:25 +0100
Subject: [PATCH] Fix carousel transition duration (#25218)

---
 docs/4.0/components/carousel.md |  4 +++
 js/src/carousel.js              | 60 ++++++++++++++++++++++-----------
 js/tests/visual/carousel.html   | 15 +++++++--
 scss/_variables.scss            |  2 +-
 4 files changed, 58 insertions(+), 23 deletions(-)

diff --git a/docs/4.0/components/carousel.md b/docs/4.0/components/carousel.md
index 0aa132ed77..e12f83cebb 100644
--- a/docs/4.0/components/carousel.md
+++ b/docs/4.0/components/carousel.md
@@ -325,3 +325,7 @@ $('#myCarousel').on('slide.bs.carousel', function () {
   // do something…
 })
 {% endhighlight %}
+
+### Change transition duration
+
+The transition duration of `.carousel-item` can be changed with the `$carousel-transition` Sass variable before compiling or custom styles if you're using the compiled CSS. If multiple transitions are applied, make sure the transform transition is defined first (eg. `transition: transform 2s ease, opacity .5s ease-out`). The transition duration must be the same for each carousel item.
diff --git a/js/src/carousel.js b/js/src/carousel.js
index 9d3be6f19d..4a64e7cc8f 100644
--- a/js/src/carousel.js
+++ b/js/src/carousel.js
@@ -15,16 +15,16 @@ const Carousel = (($) => {
    * ------------------------------------------------------------------------
    */
 
-  const NAME                   = 'carousel'
-  const VERSION                = '4.0.0'
-  const DATA_KEY               = 'bs.carousel'
-  const EVENT_KEY              = `.${DATA_KEY}`
-  const DATA_API_KEY           = '.data-api'
-  const JQUERY_NO_CONFLICT     = $.fn[NAME]
-  const TRANSITION_DURATION    = 600
-  const ARROW_LEFT_KEYCODE     = 37 // KeyboardEvent.which value for left arrow key
-  const ARROW_RIGHT_KEYCODE    = 39 // KeyboardEvent.which value for right arrow key
-  const TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch
+  const NAME                    = 'carousel'
+  const VERSION                 = '4.0.0'
+  const DATA_KEY                = 'bs.carousel'
+  const EVENT_KEY               = `.${DATA_KEY}`
+  const DATA_API_KEY            = '.data-api'
+  const JQUERY_NO_CONFLICT      = $.fn[NAME]
+  const ARROW_LEFT_KEYCODE      = 37 // KeyboardEvent.which value for left arrow key
+  const ARROW_RIGHT_KEYCODE     = 39 // KeyboardEvent.which value for right arrow key
+  const TOUCHEVENT_COMPAT_WAIT  = 500 // Time for mouse compat events to fire after touch
+  const MILLISECONDS_MULTIPLIER = 1000
 
   const Default = {
     interval : 5000,
@@ -89,18 +89,20 @@ const Carousel = (($) => {
 
   class Carousel {
     constructor(element, config) {
-      this._items             = null
-      this._interval          = null
-      this._activeElement     = null
+      this._items              = null
+      this._interval           = null
+      this._activeElement      = null
 
-      this._isPaused          = false
-      this._isSliding         = false
+      this._isPaused           = false
+      this._isSliding          = false
 
-      this.touchTimeout       = null
+      this.touchTimeout        = null
 
-      this._config            = this._getConfig(config)
-      this._element           = $(element)[0]
-      this._indicatorsElement = $(this._element).find(Selector.INDICATORS)[0]
+      this._config             = this._getConfig(config)
+      this._element            = $(element)[0]
+      this._indicatorsElement  = $(this._element).find(Selector.INDICATORS)[0]
+
+      this._transitionDuration = this._getTransitionDuration()
 
       this._addEventListeners()
     }
@@ -223,6 +225,24 @@ const Carousel = (($) => {
       return config
     }
 
+    _getTransitionDuration() {
+      // Get transition-duration of first element in the carousel
+      let transitionDuration = $(this._element).find(Selector.ITEM).css('transition-duration')
+
+      // Return 0 carousel item is not found
+      if (!transitionDuration) {
+        return 0
+      }
+
+      // If multiple durations are defined, take the first
+      transitionDuration = transitionDuration.split(',')[0]
+
+      // Multiply by 1000 if transition-duration is defined in seconds
+      return transitionDuration.indexOf('ms') > -1
+        ? parseFloat(transitionDuration)
+        : parseFloat(transitionDuration) * MILLISECONDS_MULTIPLIER
+    }
+
     _addEventListeners() {
       if (this._config.keyboard) {
         $(this._element)
@@ -398,7 +418,7 @@ const Carousel = (($) => {
 
             setTimeout(() => $(this._element).trigger(slidEvent), 0)
           })
-          .emulateTransitionEnd(TRANSITION_DURATION)
+          .emulateTransitionEnd(this._transitionDuration)
       } else {
         $(activeElement).removeClass(ClassName.ACTIVE)
         $(nextElement).addClass(ClassName.ACTIVE)
diff --git a/js/tests/visual/carousel.html b/js/tests/visual/carousel.html
index f81dc951e7..e19272c2d0 100644
--- a/js/tests/visual/carousel.html
+++ b/js/tests/visual/carousel.html
@@ -5,12 +5,17 @@
     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
     <link rel="stylesheet" href="../../../dist/css/bootstrap.min.css">
     <title>Carousel</title>
+    <style>
+      .carousel-item {
+        transition: transform 2s ease, opacity .5s ease;
+      }
+    </style>
   </head>
   <body>
     <div class="container">
       <h1>Carousel <small>Bootstrap Visual Test</small></h1>
 
-      <p>Also, the carousel shouldn't slide when its window/tab is hidden. Check the console log.</p>
+      <p>The transition duration should be around 2s. Also, the carousel shouldn't slide when its window/tab is hidden. Check the console log.</p>
 
       <div id="carousel-example-generic" class="carousel slide" data-ride="carousel">
         <ol class="carousel-indicators">
@@ -46,9 +51,15 @@
 
     <script>
       $(function() {
+        var t0, t1;
+
         // Test to show that the carousel doesn't slide when the current tab isn't visible
+        // Test to show that transition-duration can be changed with css
         $('#carousel-example-generic').on('slid.bs.carousel', function(event) {
-          console.log('slid at ', event.timeStamp)
+          t1 = performance.now()
+          console.log('transition-duration took' + (t1 - t0) + 'ms, slid at ', event.timeStamp)
+        }).on('slide.bs.carousel', function() {
+          t0 = performance.now()
         })
       })
     </script>
diff --git a/scss/_variables.scss b/scss/_variables.scss
index 2178e1f9cb..53cb29c2af 100644
--- a/scss/_variables.scss
+++ b/scss/_variables.scss
@@ -870,7 +870,7 @@ $carousel-control-icon-width:       20px !default;
 $carousel-control-prev-icon-bg:     str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E"), "#", "%23") !default;
 $carousel-control-next-icon-bg:     str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E"), "#", "%23") !default;
 
-$carousel-transition:               transform .6s ease !default;
+$carousel-transition:               transform .6s ease !default; // Define transform transition first if using multiple transitons (e.g., `transform 2s ease, opacity .5s ease-out`)
 
 
 // Close
-- 
GitLab