diff --git a/docs/4.0/components/carousel.md b/docs/4.0/components/carousel.md index db3e0617a0..1f7ff3d824 100644 --- a/docs/4.0/components/carousel.md +++ b/docs/4.0/components/carousel.md @@ -327,4 +327,4 @@ $('#myCarousel').on('slide.bs.carousel', function () { ### 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. +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`). diff --git a/js/src/alert.js b/js/src/alert.js index a1f3c1cb7f..a072b2e4eb 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -21,7 +21,6 @@ const Alert = (($) => { const EVENT_KEY = `.${DATA_KEY}` const DATA_API_KEY = '.data-api' const JQUERY_NO_CONFLICT = $.fn[NAME] - const TRANSITION_DURATION = 150 const Selector = { DISMISS : '[data-dismiss="alert"]' @@ -109,9 +108,11 @@ const Alert = (($) => { return } + const transitionDuration = Util.getTransitionDurationFromElement(element) + $(element) .one(Util.TRANSITION_END, (event) => this._destroyElement(element, event)) - .emulateTransitionEnd(TRANSITION_DURATION) + .emulateTransitionEnd(transitionDuration) } _destroyElement(element) { diff --git a/js/src/carousel.js b/js/src/carousel.js index 4a64e7cc8f..54bb0791c9 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -15,16 +15,15 @@ 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 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 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 Default = { interval : 5000, @@ -102,8 +101,6 @@ const Carousel = (($) => { this._element = $(element)[0] this._indicatorsElement = $(this._element).find(Selector.INDICATORS)[0] - this._transitionDuration = this._getTransitionDuration() - this._addEventListeners() } @@ -225,24 +222,6 @@ 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) @@ -406,6 +385,8 @@ const Carousel = (($) => { $(activeElement).addClass(directionalClassName) $(nextElement).addClass(directionalClassName) + const transitionDuration = Util.getTransitionDurationFromElement(activeElement) + $(activeElement) .one(Util.TRANSITION_END, () => { $(nextElement) @@ -418,7 +399,7 @@ const Carousel = (($) => { setTimeout(() => $(this._element).trigger(slidEvent), 0) }) - .emulateTransitionEnd(this._transitionDuration) + .emulateTransitionEnd(transitionDuration) } else { $(activeElement).removeClass(ClassName.ACTIVE) $(nextElement).addClass(ClassName.ACTIVE) diff --git a/js/src/collapse.js b/js/src/collapse.js index 3f45d651da..ee357e37a6 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -21,7 +21,6 @@ const Collapse = (($) => { const EVENT_KEY = `.${DATA_KEY}` const DATA_API_KEY = '.data-api' const JQUERY_NO_CONFLICT = $.fn[NAME] - const TRANSITION_DURATION = 600 const Default = { toggle : true, @@ -190,10 +189,11 @@ const Collapse = (($) => { const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1) const scrollSize = `scroll${capitalizedDimension}` + const transitionDuration = Util.getTransitionDurationFromElement(this._element) $(this._element) .one(Util.TRANSITION_END, complete) - .emulateTransitionEnd(TRANSITION_DURATION) + .emulateTransitionEnd(transitionDuration) this._element.style[dimension] = `${this._element[scrollSize]}px` } @@ -252,9 +252,11 @@ const Collapse = (($) => { return } + const transitionDuration = Util.getTransitionDurationFromElement(this._element) + $(this._element) .one(Util.TRANSITION_END, complete) - .emulateTransitionEnd(TRANSITION_DURATION) + .emulateTransitionEnd(transitionDuration) } setTransitioning(isTransitioning) { diff --git a/js/src/modal.js b/js/src/modal.js index 12ab8bfd73..414359e97a 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -15,15 +15,13 @@ const Modal = (($) => { * ------------------------------------------------------------------------ */ - const NAME = 'modal' - const VERSION = '4.0.0' - const DATA_KEY = 'bs.modal' - const EVENT_KEY = `.${DATA_KEY}` - const DATA_API_KEY = '.data-api' - const JQUERY_NO_CONFLICT = $.fn[NAME] - const TRANSITION_DURATION = 300 - const BACKDROP_TRANSITION_DURATION = 150 - const ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key + const NAME = 'modal' + const VERSION = '4.0.0' + const DATA_KEY = 'bs.modal' + const EVENT_KEY = `.${DATA_KEY}` + const DATA_API_KEY = '.data-api' + const JQUERY_NO_CONFLICT = $.fn[NAME] + const ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key const Default = { backdrop : true, @@ -187,10 +185,13 @@ const Modal = (($) => { $(this._element).off(Event.CLICK_DISMISS) $(this._dialog).off(Event.MOUSEDOWN_DISMISS) + if (transition) { + const transitionDuration = Util.getTransitionDurationFromElement(this._element) + $(this._element) .one(Util.TRANSITION_END, (event) => this._hideModal(event)) - .emulateTransitionEnd(TRANSITION_DURATION) + .emulateTransitionEnd(transitionDuration) } else { this._hideModal() } @@ -263,9 +264,11 @@ const Modal = (($) => { } if (transition) { + const transitionDuration = Util.getTransitionDurationFromElement(this._element) + $(this._dialog) .one(Util.TRANSITION_END, transitionComplete) - .emulateTransitionEnd(TRANSITION_DURATION) + .emulateTransitionEnd(transitionDuration) } else { transitionComplete() } @@ -369,9 +372,11 @@ const Modal = (($) => { return } + const backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop) + $(this._backdrop) .one(Util.TRANSITION_END, callback) - .emulateTransitionEnd(BACKDROP_TRANSITION_DURATION) + .emulateTransitionEnd(backdropTransitionDuration) } else if (!this._isShown && this._backdrop) { $(this._backdrop).removeClass(ClassName.SHOW) @@ -384,9 +389,11 @@ const Modal = (($) => { if (Util.supportsTransitionEnd() && $(this._element).hasClass(ClassName.FADE)) { + const backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop) + $(this._backdrop) .one(Util.TRANSITION_END, callbackRemove) - .emulateTransitionEnd(BACKDROP_TRANSITION_DURATION) + .emulateTransitionEnd(backdropTransitionDuration) } else { callbackRemove() } diff --git a/js/src/tab.js b/js/src/tab.js index d3da382764..ebf28d76c7 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -15,13 +15,12 @@ const Tab = (($) => { * ------------------------------------------------------------------------ */ - const NAME = 'tab' - const VERSION = '4.0.0' - const DATA_KEY = 'bs.tab' - const EVENT_KEY = `.${DATA_KEY}` - const DATA_API_KEY = '.data-api' - const JQUERY_NO_CONFLICT = $.fn[NAME] - const TRANSITION_DURATION = 150 + const NAME = 'tab' + const VERSION = '4.0.0' + const DATA_KEY = 'bs.tab' + const EVENT_KEY = `.${DATA_KEY}` + const DATA_API_KEY = '.data-api' + const JQUERY_NO_CONFLICT = $.fn[NAME] const Event = { HIDE : `hide${EVENT_KEY}`, @@ -162,9 +161,11 @@ const Tab = (($) => { ) if (active && isTransitioning) { + const transitionDuration = Util.getTransitionDurationFromElement(active) + $(active) .one(Util.TRANSITION_END, complete) - .emulateTransitionEnd(TRANSITION_DURATION) + .emulateTransitionEnd(transitionDuration) } else { complete() } diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 861f45a86a..19a020d43d 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -16,13 +16,12 @@ const Tooltip = (($) => { * ------------------------------------------------------------------------ */ - const NAME = 'tooltip' - const VERSION = '4.0.0' - const DATA_KEY = 'bs.tooltip' - const EVENT_KEY = `.${DATA_KEY}` - const JQUERY_NO_CONFLICT = $.fn[NAME] - const TRANSITION_DURATION = 150 - const CLASS_PREFIX = 'bs-tooltip' + const NAME = 'tooltip' + const VERSION = '4.0.0' + const DATA_KEY = 'bs.tooltip' + const EVENT_KEY = `.${DATA_KEY}` + const JQUERY_NO_CONFLICT = $.fn[NAME] + const CLASS_PREFIX = 'bs-tooltip' const BSCLS_PREFIX_REGEX = new RegExp(`(^|\\s)${CLASS_PREFIX}\\S+`, 'g') const DefaultType = { @@ -335,9 +334,11 @@ const Tooltip = (($) => { } if (Util.supportsTransitionEnd() && $(this.tip).hasClass(ClassName.FADE)) { + const transitionDuration = Util.getTransitionDurationFromElement(this.tip) + $(this.tip) .one(Util.TRANSITION_END, complete) - .emulateTransitionEnd(Tooltip._TRANSITION_DURATION) + .emulateTransitionEnd(transitionDuration) } else { complete() } @@ -384,9 +385,11 @@ const Tooltip = (($) => { if (Util.supportsTransitionEnd() && $(this.tip).hasClass(ClassName.FADE)) { + const transitionDuration = Util.getTransitionDurationFromElement(tip) + $(tip) .one(Util.TRANSITION_END, complete) - .emulateTransitionEnd(TRANSITION_DURATION) + .emulateTransitionEnd(transitionDuration) } else { complete() } diff --git a/js/src/util.js b/js/src/util.js index 7e5d5de5c6..05a69977c2 100644 --- a/js/src/util.js +++ b/js/src/util.js @@ -17,6 +17,7 @@ const Util = (($) => { let transition = false const MAX_UID = 1000000 + const MILLISECONDS_MULTIPLIER = 1000 // Shoutout AngusCroll (https://goo.gl/pxwQGp) function toType(obj) { @@ -104,6 +105,23 @@ const Util = (($) => { } }, + getTransitionDurationFromElement(element) { + // Get transition-duration of the element + let transitionDuration = $(element).css('transition-duration') + + // Return 0 if element or transition duration is not found + if (!transitionDuration) { + return 0 + } + + // If multiple durations are defined, take the first + transitionDuration = transitionDuration.split(',')[0] + + // jQuery always converts transition durations into seconds, + // so multiply by 1000 + return parseFloat(transitionDuration) * MILLISECONDS_MULTIPLIER + }, + reflow(element) { return element.offsetHeight }, diff --git a/js/tests/unit/util.js b/js/tests/unit/util.js index 83eb601a51..46b1e35fec 100644 --- a/js/tests/unit/util.js +++ b/js/tests/unit/util.js @@ -42,6 +42,50 @@ $(function () { assert.strictEqual(typeof Util.isElement({}) === 'undefined', true) }) + QUnit.test('Util.getTransitionDurationFromElement should accept transition durations in milliseconds', function (assert) { + assert.expect(1) + var $div = $('
').appendTo($('#qunit-fixture')) + + assert.strictEqual(Util.getTransitionDurationFromElement($div), 300) + }) + + QUnit.test('Util.getTransitionDurationFromElement should accept transition durations in seconds', function (assert) { + assert.expect(1) + var $div = $('').appendTo($('#qunit-fixture')) + + assert.strictEqual(Util.getTransitionDurationFromElement($div), 400) + }) + + QUnit.test('Util.getTransitionDurationFromElement should get the first transition duration if multiple transition durations are defined', function (assert) { + assert.expect(1) + var $div = $('').appendTo($('#qunit-fixture')) + + assert.strictEqual(Util.getTransitionDurationFromElement($div), 300) + }) + + QUnit.test('Util.getTransitionDurationFromElement should return 0 if transition duration is not defined', function (assert) { + assert.expect(1) + var $div = $('').appendTo($('#qunit-fixture')) + + assert.strictEqual(Util.getTransitionDurationFromElement($div), 0) + }) + + QUnit.test('Util.getTransitionDurationFromElement should return 0 if element is not found in DOM', function (assert) { + assert.expect(1) + var $div = $('#fake-id') + + assert.strictEqual(Util.getTransitionDurationFromElement($div), 0) + }) + + QUnit.test('Util.getTransitionDurationFromElement should properly handle inherited transition durations', function (assert) { + assert.expect(1) + var $parent = $('') + var $child = $('') + $('#qunit-fixture').append($parent.append($child)) + + assert.strictEqual(Util.getTransitionDurationFromElement($child), 5000) + }) + QUnit.test('Util.getUID should generate a new id uniq', function (assert) { assert.expect(2) var id = Util.getUID('test') diff --git a/js/tests/visual/alert.html b/js/tests/visual/alert.html index 9baee6f506..d6b5cc350c 100644 --- a/js/tests/visual/alert.html +++ b/js/tests/visual/alert.html @@ -42,6 +42,13 @@ + +