How to animate the display of dynamically sized elements with CSS only?

Asked

Viewed 596 times

12

I’m building a site that has some state transitions using CSS only. In one of them the user clicks the header and the div below displays or hides. In the other there is a sequence of images and when clicking on the current image it passes to the next one. The technique for both is similar: a input hidden with one or more labels linked to it, and the CSS selectors of "brothers" (+ and ~). Examples:

  1. Sections (uses checkboxes to hide/show each section)

.escondido { display: none; }
.box { display: none; width: 200px; height: 100px; }
.escondido:checked + .box { display: block; }

h2 { width: 200px; background-color: lightgray; cursor: pointer; }
.a { background-color: red; }
.b { background-color: blue; }
<label for="aba1"><h2>Seção 1</h2></label>
<input id="aba1" class="escondido" type="checkbox">
<div class="box a"></div>

<label for="aba2"><h2>Seção 2</h2></label>
<input id="aba2" class="escondido" type="checkbox">
<div class="box b"></div>

  1. Images (usa radio Buttons to circulate between images)

.escondido { display: none; }
.imagem { display:none; width: 200px; height: 100px; cursor: pointer; }
.escondido:checked + label .imagem { display: block; }

.a { background-color: red; }
.b { background-color: blue; }
.c { background-color: green; }
.d { background-color: yellow; }

.mini { display: inline-block; width: 10px; height: 10px; border-radius: 5px; }

.escondido.a:checked ~ label .mini.a { border: 2px solid black; }
.escondido.b:checked ~ label .mini.b { border: 2px solid black; }
.escondido.c:checked ~ label .mini.c { border: 2px solid black; }
.escondido.d:checked ~ label .mini.d { border: 2px solid black; }
<!-- Imagens -->
<input id="img1" class="escondido a" type="radio" name="imgs" checked>
<label for="img2"><div class="imagem a"></div></label>

<input id="img2" class="escondido b" type="radio" name="imgs">
<label for="img3"><div class="imagem b"></div></label>

<input id="img3" class="escondido c" type="radio" name="imgs">
<label for="img4"><div class="imagem c"></div></label>

<input id="img4" class="escondido d" type="radio" name="imgs">
<label for="img1"><div class="imagem d"></div></label>

<!-- Miniaturas -->
<label for="img1"><div class="imagem mini a"></div></label>
<label for="img2"><div class="imagem mini b"></div></label>
<label for="img3"><div class="imagem mini c"></div></label>
<label for="img4"><div class="imagem mini d"></div></label>

I would now like to animate the transition between the "displayed" and "hidden" states. I know it is not possible to animate the property display directly: jQuery for example when showing first reduces the size of the element to zero, passes the display from "hidden" to "displayed", then animate the size of the element from zero to its actual size. I can’t do something like that with just CSS, because you can’t say, "First do this, then do that"...

I tried to avoid the display and simply place the full height of the div at zero. It worked well if the div has fixed size, but not if size is variable (she "jumped" directly between zero and maximum sizes):

.escondido { display: none; }
.box { height: 0; width: 200px; overflow: hidden; transition: height 2s; }

h2 { width: 200px; background-color: lightgray; cursor: pointer; }
.a { background-color: red; }
.b { background-color: lightgray; }

.escondido:checked + .a { display: block; height: 100px; }
.escondido:checked + .b { display: block; height: auto; }
<label for="aba1"><h2>funciona</h2></label>
<input id="aba1" class="escondido" type="checkbox">
<div class="box a"></div>

<label for="aba2"><h2>não funciona</h2></label>
<input id="aba2" class="escondido" type="checkbox">
<div class="box b">Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</div>

(I also tried with max-height, and also worked on div fixed size, but in this case the variable size was not displayed...)

Is it possible to do this only with CSS, without specifying an absolute size? (and without compromising responsiveness, i.e. if by adjusting the width of the window the height of the div change, keep running) What properties I could try to animate to create the illusion of "hidden/displayed" with smooth transition?

Note: in the case of images, the exchange would be horizontal and not vertical, but as it is easier to deal with images (because usually its size is known) this case can be left out of the question (but suggestions for this case would also be very welcome).

2 answers

8

Some time ago I created an article on this same subject in my Blog.
You can do this using the max-height as follows:

.box { max-height: 0; transition: max-height 2s;}
.escondido:checked + .b { max-height:100px; }

Here is an example below:

.escondido { display: none; }
.box { max-height: 0; width: 200px; overflow: hidden; transition: max-height 2s; }

h2 { width: 200px; background-color: lightgray; cursor: pointer; }
.b { background-color: lightgray; }

.escondido:checked + .b { display: block; max-height:100px; }
<label for="aba2"><h2>Seção 2</h2></label>
<input id="aba2" class="escondido" type="checkbox">
<div class="box b">Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</div>

  • 1

    But then you’ll cut the text if it’s bigger, no?

  • yes, but one can define a max-height major, the only problem is that in doing so the transition will be affected by now calculating to a higher height

  • 1

    As @Bacco commented, unless I put a max-height large enough to ensure that the content will never be greater than it, may occur overflow... Funny that I had already tried this, but it hadn’t worked, but your example works. The biggest problem is (this you just commented on... : P)

  • 2

    Anyway, +1, because using a easing adequate this solution can be good enough in the absence of other.

2

I did an experiment using Transform’s scaleY function together in the transition, the effect is not the same but "solves" the question of relative height. Suddenly it can serve as a starting point for a truly definitive solution.

.escondido { display: none;}
.box {  
  height:0px;
	transform: scaleY(0);
	transform-origin: top;
  
  width: 200px; overflow: auto; 	
  transition: transform 1s, height 1s;
 
  }

h2 { width: 200px; background-color: lightgray; cursor: pointer; }
.a { background-color: red; }
.b { background-color: lightgray; }


.escondido:checked + .a { display: block; transform: scaleY(1); height:100px; }
.escondido:checked + .b { display: block; transform: scaleY(1); height: 100%; }
<label for="aba1"><h2>funciona</h2></label>
<input id="aba1" class="escondido" type="checkbox">
<div class="box a"></div>

<label for="aba2"><h2>quase funciona</h2></label>
<input id="aba2" class="escondido" type="checkbox">
<div class="box b">Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</div>

  • Very good! For the effect to be less "ugly", just animate also the opacity of 0 (when the div is hidden) until 1 (when it is displayed), so in the range where the content is more "squeezed" it will also be almost invisible. I won’t mark as accepted because as you said yourself is not a definitive solution, but since you solved my biggest problem (relative height) the reward is yours! :)

  • Really @mgibsonbr, this is definitely not a solution that deserves to be marked as a response. I really appreciate the reward, I’ve been working with web development for over 15 years but I just made up my mind a little while ago. I saw many questions that I could have contributed to the solution, but I did not have the privilege of being able to comment asking for some clarification. Thank you very much.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.