diff --git a/README.md b/README.md index e51558c..6021a68 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,16 @@ ## Motivation -This is small library that addresses a few issues when working with CSS transitions & elements that need to transition to or from the `display:none` state. +This is small library that addresses a few issues that pop up when working with CSS transitions on elements that need to transition to or from the `display:none` state and/or elements that needs to transition to a width or height value of `auto`. -- When transitioning "in" and going from `display:none` to a display state where the element is part of the document flow (e.g. `display:block`), [Reflow](https://developer.mozilla.org/en-US/docs/Glossary/Reflow) much occur. If not, the element is displayed instanlty in the DOM, in its final state (as if all transitions have been instantly played to completion). +Issues tackled: + +- When transitioning "in" and going from `display:none` to a display state where the element is part of the document flow (e.g. `display:block`), [Reflow](https://developer.mozilla.org/en-US/docs/Glossary/Reflow) must occur. If not, the element is displayed instanlty in the DOM, in its final state (as if all transitions have been instantly played to completion). - When transitioning "out" and going to `display:none`, the `display:none` CSS rule must be applied after all transitions effects are played to completion. If not, the element will disappear instantly, before any transition effects are rendered. +- When transitioning "in" and going to a width or height value of `auto`, the element will instantly assume its final size and not transition smoothly to it. Reflow must occur, but `auto` also can't be used here and the width/height of the element needs to be computed as an actual value first. + ## Philosophy As much as possible this library tries to work with and respect the rules defined via CSS classes applied to DOM elements. diff --git a/dist/display-toggle-fx.min.js b/dist/display-toggle-fx.min.js index edffe9b..de6aa44 100644 --- a/dist/display-toggle-fx.min.js +++ b/dist/display-toggle-fx.min.js @@ -1 +1 @@ -"use strict";var DisplayToggleFx={elementOutTimeouts:new Map,in:function(e,t){var o=DisplayToggleFx.elementOutTimeouts.get(e)||null;null!==o&&clearTimeout(o);for(var l=0;lo&&(o=t)}else{var l=1e3*e.replace(/s/g,"");l>o&&(o=l)}}),o},out:function(e,t,o){for(var l=DisplayToggleFx.getMaxTransitionDuration(e),i=0;io&&(o=t)}else{const t=1e3*e.replace(/s/g,"");t>o&&(o=t)}}),o},out:function(e,t,o){const l=DisplayToggleFx.getMaxTransitionDuration(e);e.style.removeProperty("width"),e.style.removeProperty("height");for(let o=0;o + + + + + + + + + +
+ +
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer varius efficitur dolor ac venenatis. Donec eu risus a orci porta varius. Fusce volutpat nulla vestibulum mi tincidunt vestibulum. Suspendisse vel condimentum eros, quis hendrerit elit. Praesent suscipit et tortor a euismod. Nunc egestas leo est, id accumsan urna blandit nec. Aliquam ante eros, maximus eu eros eu, porta luctus neque. Quisque at pellentesque orci. Nullam varius libero sit amet purus eleifend aliquam vel auctor lorem. Suspendisse convallis in neque ut interdum. Nam at augue ex. Suspendisse potenti. Pellentesque a fermentum justo.
+ +
+ + + + + + diff --git a/example/width-height-auto.html b/example/width-height-auto.html new file mode 100644 index 0000000..acbfe21 --- /dev/null +++ b/example/width-height-auto.html @@ -0,0 +1,35 @@ + + + + + + + + + + +
+ +
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer varius efficitur dolor ac venenatis. Donec eu risus a orci porta varius. Fusce volutpat nulla vestibulum mi tincidunt vestibulum. Suspendisse vel condimentum eros, quis hendrerit elit. Praesent suscipit et tortor a euismod. Nunc egestas leo est, id accumsan urna blandit nec. Aliquam ante eros, maximus eu eros eu, porta luctus neque. Quisque at pellentesque orci. Nullam varius libero sit amet purus eleifend aliquam vel auctor lorem. Suspendisse convallis in neque ut interdum. Nam at augue ex. Suspendisse potenti. Pellentesque a fermentum justo.
+ +
+ + + + + + diff --git a/src/DisplayToggleFx.js b/src/DisplayToggleFx.js index 9e82c76..a0eda4b 100644 --- a/src/DisplayToggleFx.js +++ b/src/DisplayToggleFx.js @@ -1,6 +1,8 @@ /** * DisplayToggleFx helps with applying/unapplying CSS classes that trigger CSS transitions, * where the DOM element needs to change from a display:none state → displayed state, and vice-versa + * + * v4.1 adds support for cases where an element's width or height changes to 'auto' */ const DisplayToggleFx = { @@ -16,22 +18,56 @@ const DisplayToggleFx = { if(timeoutId !== null) { clearTimeout(timeoutId); } - - // apply classes and get computed display + + // Get which properties are setup to transition + // We'll get all of them, but we really only care about dimensional properties (width, height) + const transitionProps = (window.getComputedStyle(_elem).getPropertyValue('transition-property')); // command-delimited string + const isTransitioningProperty = function(_propName) { + if(transitionProps.indexOf(_propName) === -1) { + return false; + } + + return true; + }; + + const currentWidth = (window.getComputedStyle(_elem).getPropertyValue('width')); + const currentHeight = (window.getComputedStyle(_elem).getPropertyValue('height')); + + // apply fx classes, compute future state of display and properties being transitioned for(let i=0; i<_fxClasses.length; i++) { _elem.classList.add(_fxClasses[i]); } - const computedDisplay = (window.getComputedStyle(_elem).getPropertyValue('display')); + const futureDisplay = (window.getComputedStyle(_elem).getPropertyValue('display')); + const futureWidth = (window.getComputedStyle(_elem).getPropertyValue('width')); + const futureHeight = (window.getComputedStyle(_elem).getPropertyValue('height')); - // remove classes and trigger reflow to render initial state + // remove classes, reset width and height to initial values, and trigger reflow to render initial state for(let i=0; i<_fxClasses.length; i++) { _elem.classList.remove(_fxClasses[i]); } + + if(isTransitioningProperty('width')) { + _elem.style.width = currentWidth; + } + + if(isTransitioningProperty('height')) { + _elem.style.height = currentHeight; + } + DisplayToggleFx.forceReflow(_elem); - // apply computed display value and trigger reflow - _elem.style.display = computedDisplay; + // apply computed display value, apply computed width and height values (in cases where width, height is being transitioned), and trigger reflow + _elem.style.display = futureDisplay; + + if(isTransitioningProperty('width')) { + _elem.style.width = futureWidth; + } + + if(isTransitioningProperty('height')) { + _elem.style.height = futureHeight; + } + DisplayToggleFx.forceReflow(_elem); for(let i=0; i<_fxClasses.length; i++) { @@ -53,16 +89,17 @@ const DisplayToggleFx = { var maxDurationMs = 0; allTransitionDurations.forEach(function(_dur) { - if(_dur.indexOf('s') !== -1) { - const durationMs = (_dur.replace(/s/g, '')) * 1000.0; + + if(_dur.indexOf('ms') !== -1) { // note that this parser rule is more specific and has to come first + const durationMs = (_dur.replace(/ms/g, '')) * 1.0; if(durationMs > maxDurationMs) { maxDurationMs = durationMs; } return; } - if(_dur.indexOf('ms') !== -1) { - const durationMs = (_dur.replace(/ms/g, '')); + if(_dur.indexOf('s') !== -1) { + const durationMs = (_dur.replace(/s/g, '')) * 1000.0; if(durationMs > maxDurationMs) { maxDurationMs = durationMs; } @@ -82,13 +119,16 @@ const DisplayToggleFx = { const maxTransitionDuration = DisplayToggleFx.getMaxTransitionDuration(_elem); + _elem.style.removeProperty('width'); + _elem.style.removeProperty('height'); + for(let i=0; i<_fxClasses.length; i++) { _elem.classList.remove(_fxClasses[i]); } const timeoutId = setTimeout(function() { _elem.style.removeProperty('display'); - + if(_onOutComplete) { _onOutComplete(); } diff --git a/test/DisplayToggleFx.test.js b/test/DisplayToggleFx.test.js index c9c8d8c..32555ac 100644 --- a/test/DisplayToggleFx.test.js +++ b/test/DisplayToggleFx.test.js @@ -35,6 +35,18 @@ test('DisplayToggleFx.in changes element display state based on window.getComput if(_key === `display`) { return `block`; } + + if(_key === `transition-property`) { + return `opacity, transform`; + } + + if(_key === `width`) { + return `64px`; + } + + if(_key === `height`) { + return `64px`; + } } } }; @@ -100,7 +112,7 @@ test('DisplayToggleFx.out calls completion callback', (done) => { }, 901); }); -test('DisplayToggleFx.getMaxTransitionDuration returns max duration', () => { +test('DisplayToggleFx.getMaxTransitionDuration returns max duration for values in seconds', () => { $('body').html(``); @@ -118,3 +130,22 @@ test('DisplayToggleFx.getMaxTransitionDuration returns max duration', () => { expect(maxDurMs).toEqual(900); }); + +test('DisplayToggleFx.getMaxTransitionDuration returns max duration for values in milliseconds', () => { + + $('body').html(``); + + window.getComputedStyle = function(_elem) { + return { + getPropertyValue: function(_key) { + if(_key === `transition-duration`) { + return "0.3s, 800ms"; + } + } + } + }; + + const maxDurMs = DisplayToggleFx.getMaxTransitionDuration(document.getElementById('telem')); + + expect(maxDurMs).toEqual(800.0); +});