1

I want to fade between two differently sized elements within a container overlaying each other. The first element should be faded out, then the container resized and finally the other element faded in.

Here's the related snippet:

var layer1 = document.getElementById("layer1");
var layer2 = document.getElementById("layer2");

function switchLayers() {
  layer1.addEventListener("transitionend", function() {
    layer2.classList.add("fadein");
  });
  layer1.classList.add("fadeout");
}
#container {
  position: relative;
  background-color: yellow;
  padding: 10px;
  overflow: hidden;
}

.layer {
  position: relative;
  width: 400px;
}

#layer1 {
  height: 100px;
  float: left;
  background-color: blue;
}

#layer2 {
  height: 150px;
  background-color: red;
  display: none;
  opacity: 0;
}

#layer1.fadeout {
  opacity: 0;
  transition: opacity 1s ease-out;
}

#layer2.fadein {
  display: block;
  opacity: 1;
  transition: opacity 1s ease-out;
}
<button onclick="switchLayers()">Switch layers</button>
<div id="container">
  <div id="layer1" class="layer"></div>
  <div id="layer2" class="layer"></div>
</div>

When the second layer's display property is set to block it works as expected, i.e. the opacity is changed from 0 to 1 within a second. Though if it's set to none, the transition suddenly is discrete.

I've tried to set all within the transition value to transition all properties and also tried to include the display property in the transition like this:

transition: display 0s, opacity 1s ease-out;

Though without success. Note that because the container should resize to the size of the currently displayed layer, the visibility property can't be used (as it hides the element but still lets it occupy the space).

How to made this work?

Sebastian Zartner
  • 18,808
  • 10
  • 90
  • 132
  • 2
    Possible duplicate of [Transitions on the display: property](http://stackoverflow.com/questions/3331353/transitions-on-the-display-property) – CBroe Feb 27 '17 at 09:00
  • The difference to that article is that I want to avoid `visibility` to be able to resize the container. I've edited my question to outline the difference. – Sebastian Zartner Feb 27 '17 at 09:12
  • The duplicate explains why you _can not_ transition the display property; so you _have to_ chose a different method of making the element show/no show. – CBroe Feb 27 '17 at 09:27
  • I don't want to transition the `display` property (it's clear to me that its transition happens discretely from not displayed to displayed). Though setting it obviously breaks the transitions of all *other* properties, which seems to be an implementation bug, because I can't see anything in the [CSS Transitions specification](https://drafts.csswg.org/css-transitions/) explaining that behavior. – Sebastian Zartner Feb 27 '17 at 10:05
  • 1
    display:none means an element behaves as if it was not there at all; specifically it frees the browser from having to calculate _any_ of the other property’s actual values ("computed value"). And since those aren’t calculated, you do not have proper _starting_ values to transition those to anything either. – CBroe Feb 27 '17 at 10:17
  • Sounds logical for `display:none;`, though as it's set to `display:block;` on the rule applied when switching layers, the starting values of the previous rule should be taken into account again. And I still don't see anything in the spec. saying that the values of the other properties are disregarded on `display:none;`, so I'm [asking for clarification on the CSSWG issue tracker](https://github.com/w3c/csswg-drafts/issues/1064). – Sebastian Zartner Feb 27 '17 at 10:50

2 Answers2

1

Try using the visibility property instead of display.

For more information regarding the state changes in visibility and display, refer article.

For transitioning the parent height, you have to manually change the height property of the #container. Using display: block & display: none will never transition the parent.

Refer code:

var layer1 = document.getElementById("layer1");
var layer2 = document.getElementById("layer2");

function switchLayers() {
  layer1.addEventListener("transitionend", function() {
    layer2.classList.add("fadein");
    document.getElementById("container").style.height = "170px";
  });
  layer1.classList.add("fadeout");
}
#container {
  position: relative;
  background-color: yellow;
  padding: 10px;
  height: 100px;
  overflow: hidden;
  transition: all 0.5s;
}

.layer {
  position: relative;
  width: 400px;
}

#layer1 {
  height: 100px;
  float: left;
  background-color: blue;
}

#layer2 {
  height: 150px;
  background-color: red;
  visibility: none;
  opacity: 0;
}

#layer1.fadeout {
  visibility: none;
  opacity: 0;
  transition: all 1s ease-out;
}

#layer2.fadein {
  visibility: visible;
  opacity: 1;
  transition: all 1s ease-out;
}
<button onclick="switchLayers()">Switch layers</button>
<div id="container">
  <div id="layer1" class="layer"></div>
  <div id="layer2" class="layer"></div>
</div>
nashcheez
  • 5,067
  • 1
  • 27
  • 53
1

There is no straightforward way. Transitions do not work on display, nor do they work on auto height. So, visibility is a good bet.

Note that because the container should resize to the size of the currently displayed layer, the visibility property can't be used (as it hides the element but still lets it occupy the space).

Then, you will need to hack it out. You can make use of min-height. Give a faux min-height to your container, and then apply the height of your layer2 to it once the transition ends. Also, because display on layer2 will block the transition, you need to separate out the classes for display and opacity and space out their application using a zero timeout in between.

Here is a crude idea:

var layer1 = document.getElementById("layer1"),
    layer2 = document.getElementById("layer2"),
    container = document.getElementById("container"),
    h = window.getComputedStyle(layer2).getPropertyValue("height");

container.addEventListener("transitionend", function(e) {
  if (e.target.id === 'layer1') {
    // apply layer2 height to container min-height
    container.style.minHeight = h; 
  }
  if (e.target.id === 'container') { 
    // First show the layer2
    layer2.classList.add("show"); 
    // Then a dummy pause to fadein
    setTimeout(function(){
      layer2.classList.add("fadein");
    }, 0);
  }
}, false);

function switchLayers() {
  layer1.classList.add("fadeout");
}
#container {
  position: relative;
  background-color: yellow;
  padding: 10px; overflow: hidden;
  min-height: 1px; /* faux min-height */
  transition: min-height 1s linear;
}

.layer { position: relative; width: 400px; }

#layer1 {
  height: 100px; float: left;
  background-color: blue;
  transition: all 1s linear;
}

#layer2 {
  height: 150px; background-color: red;
  display: none; opacity: 0;
  transition: opacity 1s linear;
}

#layer1.fadeout { opacity: 0; }
#layer2.show { display: block; } /* Separate out display */
#layer2.fadein { opacity: 1; } /* Separate out opacity */
<button onclick="switchLayers()">Switch layers</button>
<div id="container">
  <div id="layer1" class="layer"></div>
  <div id="layer2" class="layer"></div>
</div>
Abhitalks
  • 27,721
  • 5
  • 58
  • 81