Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion dist/display-toggle-fx.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions example/height-auto.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>

<script src="../dist/display-toggle-fx.min.js" type="text/javascript"></script>

<style type="text/css">
body { width:100%; height:100%; }
#testObj { display:block; overflow:hidden; background:#00f; position:absolute; top:100px; left:100px; width:auto; height:0px; opacity:1; transition:width 0.8s ease-in-out, height 0.8s ease-in-out; }
#testObj.visible { display:block; height:auto; }
</style>
</head>

<body>
<div id="testObj">

<div style="width:320px; background:#830; color:#fff;">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.</div>

</div>
</body>


<script type="text/javascript">
DisplayToggleFx.in(document.getElementById('testObj'), ['visible']);

// v1.1, we can interrupt the transition out
setTimeout(function() {
DisplayToggleFx.in(document.getElementById('testObj'), ['visible']);
}, 3300);

setTimeout(function() {
DisplayToggleFx.out(document.getElementById('testObj'), ['visible']);
}, 3000);

</script>

</html>
35 changes: 35 additions & 0 deletions example/width-height-auto.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>

<script src="../dist/display-toggle-fx.min.js" type="text/javascript"></script>

<style type="text/css">
body { width:100%; height:100%; }
#testObj { display:block; overflow:hidden; background:#000; padding:20px; position:absolute; top:100px; left:100px; width:0px; height:0px; opacity:1; transition:width 0.8s ease-in-out, height 0.8s ease-in-out; }
#testObj.visible { display:block; width:auto; height:auto; }
</style>
</head>

<body>
<div id="testObj">

<div style="width:320px; background:#830; color:#fff;">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.</div>

</div>
</body>


<script type="text/javascript">
DisplayToggleFx.in(document.getElementById('testObj'), ['visible']);

setTimeout(function() {
DisplayToggleFx.out(document.getElementById('testObj'), ['visible']);
document.getElementById('testObj').innerHTML = `<div style="width:320px; background:#830; color:#fff;">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer varius efficitur dolor ac venenatis.`;
DisplayToggleFx.in(document.getElementById('testObj'), ['visible']);
}, 4000);


</script>

</html>
62 changes: 51 additions & 11 deletions src/DisplayToggleFx.js
Original file line number Diff line number Diff line change
@@ -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 = {

Expand All @@ -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++) {
Expand All @@ -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;
}
Expand All @@ -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();
}
Expand Down
33 changes: 32 additions & 1 deletion test/DisplayToggleFx.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`;
}
}
}
};
Expand Down Expand Up @@ -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(`<div id="telem" style="display:none; transition: opacity 0.3s ease-in, transform 0.9s linear;"></div>`);

Expand All @@ -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(`<div id="telem" style="display:none; transition: opacity 0.3s ease-in, transform 800ms linear;"></div>`);

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);
});