diff --git a/Gruntfile.js b/Gruntfile.js
index e945b9d4805b7e8eb3a72f3fc9229f643bea9061..09e71c4a2512cb8c63ae7b144a513ee4ce5b7444 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -140,6 +140,7 @@ module.exports = function (grunt) {
         src: [
           'docs/assets/js/vendor/holder.js',
           'docs/assets/js/vendor/ZeroClipboard.min.js',
+          'docs/assets/js/vendor/smartscroll.js',
           'docs/assets/js/src/application.js'
         ],
         dest: 'docs/assets/js/docs.min.js'
diff --git a/docs/_includes/footer.html b/docs/_includes/footer.html
index ef68da3ebab3e6d310c40160cfb6a55eb554e011..aeb1f6183b352a5debfc37c4af2511ff68157cb7 100644
--- a/docs/_includes/footer.html
+++ b/docs/_includes/footer.html
@@ -43,6 +43,7 @@
 {% else %}
 <script src="../assets/js/vendor/holder.js"></script>
 <script src="../assets/js/vendor/ZeroClipboard.min.js"></script>
+<script src="../assets/js/vendor/smartscroll.js"></script>
 <script src="../assets/js/src/application.js"></script>
 {% endif %}
 {% if page.slug == "customize" %}
diff --git a/docs/assets/css/src/docs.css b/docs/assets/css/src/docs.css
index 0e43d1f55ca661513f402a983510ccbf2f64a9c7..0490b5d92bf95db1087d1b7da0447c31d5b87473 100644
--- a/docs/assets/css/src/docs.css
+++ b/docs/assets/css/src/docs.css
@@ -1537,3 +1537,11 @@ h1[id] {
   -webkit-box-shadow: 0 0 8px rgba(82,168,236,.6);
           box-shadow: 0 0 8px rgba(82,168,236,.6);
 }
+
+/* Utility class to pause infinite animation loops (#14409) */
+.js-pause {
+  -webkit-animation-play-state: paused;
+      -ms-animation-play-state: paused;
+       -o-animation-play-state: paused;
+          animation-play-state: paused;
+}
diff --git a/docs/assets/js/src/application.js b/docs/assets/js/src/application.js
index cfcfa2b503dccd1f7943c610c35ab276bfe72543..e4a2c868df7ccfcdc2968559165d881055d6753e 100644
--- a/docs/assets/js/src/application.js
+++ b/docs/assets/js/src/application.js
@@ -109,6 +109,47 @@
       }, 3000)
     })
 
+    // Pause infinite animations until they are within the viewport
+    // @see: https://github.com/twbs/bootstrap/issues/14409
+    var isInViewport = function (elem) {
+      // Get the scroll position of the page.
+      var viewportTop     = $(window).scrollTop()
+      var viewportBottom  = viewportTop + $(window).height()
+
+      // Get the position of the element on the page.
+      var elemTop     = Math.round($(elem).offset().top)
+      var elemBottom  = elemTop + $(elem).height()
+
+      return ((elemTop < viewportBottom) && (elemBottom > viewportTop))
+    }
+
+    // Check if it's time to start the animation.
+    var checkAnimatedElems = function () {
+      return $infinitelyAnimatedElems.each(function () {
+        var $this = $(this)
+
+        if (isInViewport(this)) {
+          // Start the animation
+          $this.removeClass('js-pause')
+        } else {
+          // Pause the animation
+          $this.addClass('js-pause')
+        }
+      })
+    }
+
+    // Define what elems have infinite animations
+    var $infinitelyAnimatedElems = $('.progress-bar.active')
+
+    if ($infinitelyAnimatedElems.length > 0) {
+      // Check for infinitely animated elems in viewport on scroll (debounced)
+      $(window).smartscroll(function () {
+        checkAnimatedElems()
+      })
+      // Check for infinitely animated elems in viewport on load
+      checkAnimatedElems()
+    }
+
 
     // Config ZeroClipboard
     ZeroClipboard.config({
diff --git a/docs/assets/js/vendor/smartscroll.js b/docs/assets/js/vendor/smartscroll.js
new file mode 100644
index 0000000000000000000000000000000000000000..f1fa39e41d069a7ed297128c716a14bd7bfc88ed
--- /dev/null
+++ b/docs/assets/js/vendor/smartscroll.js
@@ -0,0 +1,38 @@
+/*!
+* Debounced scroll event library for jQuery based on Paul Irish's smartresize
+* http://paulirish.com/2009/throttled-smartresize-jquery-event-handler/
+* https://github.com/peschee/jquery-smartscroll
+*/
+
+(function($, sscr){
+  'use strict';
+
+  var debounce = function (func, threshold, execAsap) {
+    var timeout;
+
+    return function debounced () {
+      var obj = this,
+         args = arguments
+
+      function delayed () {
+        if (!execAsap)
+          func.apply(obj, args)
+
+        timeout = null
+      }
+
+      if (timeout)
+        clearTimeout(timeout)
+      else if (execAsap)
+        func.apply(obj, args)
+
+      timeout = setTimeout(delayed, threshold || 100)
+    }
+
+  }
+
+  jQuery.fn[sscr] = function (fn) {
+    return fn ? this.bind('scroll', debounce(fn)) : this.trigger(sscr)
+  }
+
+})(jQuery, 'smartscroll');