¿Cómo puedo hacer la transición de altura: 0; a la altura: auto; usando CSS?

2416

Estoy tratando de hacer una <ul>diapositiva hacia abajo usando transiciones CSS.

El <ul>comienza en height: 0;. Al pasar el mouse, la altura se establece en height:auto;. Sin embargo, esto hace que simplemente aparezca, no transicione,

Si lo hago de height: 40px;a height: auto;, entonces se deslizará hacia arriba height: 0;y, de repente, saltará a la altura correcta.

¿De qué otra manera podría hacer esto sin usar JavaScript?

#child0 {
  height: 0;
  overflow: hidden;
  background-color: #dedede;
  -moz-transition: height 1s ease;
  -webkit-transition: height 1s ease;
  -o-transition: height 1s ease;
  transition: height 1s ease;
}
#parent0:hover #child0 {
  height: auto;
}
#child40 {
  height: 40px;
  overflow: hidden;
  background-color: #dedede;
  -moz-transition: height 1s ease;
  -webkit-transition: height 1s ease;
  -o-transition: height 1s ease;
  transition: height 1s ease;
}
#parent40:hover #child40 {
  height: auto;
}
h1 {
  font-weight: bold;
}
The only difference between the two snippets of CSS is one has height: 0, the other height: 40.
<hr>
<div id="parent0">
  <h1>Hover me (height: 0)</h1>
  <div id="child0">Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>
  </div>
</div>
<hr>
<div id="parent40">
  <h1>Hover me (height: 40)</h1>
  <div id="child40">Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>
  </div>
</div>
4
  • Creo que la height:auto/max-heightsolución solo funcionará si el área de expansión es mayor que la heightque desea restringir. Si tiene un menú desplegable max-heightde 300px, pero un cuadro combinado, que puede regresar 50px, luego max-heightno lo ayudará, 50pxes variable dependiendo de la cantidad de elementos, puede llegar a una situación imposible en la que no puedo solucionarlo porque heightno es arreglado, height:autofue la solución, pero no puedo usar transiciones con esto. Christopher Thomas 9 de mayo de 2012 a las 12:31
  • 64
    OP está intentando una solución css, no js, ​​de lo contrario, podrían usar overflow y animateToni Leigh 16/04/15 a las 16:39
  • 5
    @VIDesignz: Pero el div interno -100% margin-top recibe el ancho del div contenedor, no la altura. Entonces, esta solución tiene el mismo tipo de problema, que la solución de altura máxima. Además, cuando el ancho es menor que la altura del contenido, no todo el contenido está oculto por -100% margin-top. Entonces esta es una solución incorrecta. GregTom 27 de diciembre de 2015 a las 8:23
  • 2
    Consulte github.com/w3c/csswg-drafts/issues/626 para obtener información sobre las especificaciones y la implementación de una solución adecuadaBen Creasy 20 de julio de 2017 a las 2:10
3143

Úselo max-heighten la transición y no height. Y establezca un valor en max-heightalgo más grande de lo que su caja obtendrá.

Vea la demostración de JSFiddle proporcionada por Chris Jordan en otra respuesta aquí.

#menu #list {
    max-height: 0;
    transition: max-height 0.15s ease-out;
    overflow: hidden;
    background: #d5d5d5;
}

#menu:hover #list {
    max-height: 500px;
    transition: max-height 0.25s ease-in;
}
<div id="menu">
    <a>hover me</a>
    <ul id="list">
        <!-- Create a bunch, or not a bunch, of li's to see the timing. -->
        <li>item</li>
        <li>item</li>
        <li>item</li>
        <li>item</li>
        <li>item</li>
    </ul>
</div>
6
  • 391
    esto funciona muy bien! excepto que hay un retraso cuando comienza, porque comienza para la altura máxima que inicialmente es muy alta ... hmm, creo que esto es algo molestovsync 5/12/11 a las 16:03
  • 193
    +1 ¡Gran solución! La velocidad de la transición se calcula como el tiempo que especifique para la transición al valor de altura máxima ... pero dado que la altura será menor que la altura máxima, la transición a la altura real ocurrirá más rápido (a menudo significativamente) que el tiempo especificado. kingjeffrey 3 de marzo de 2012 a las 4:15
  • 69
    Tenga en cuenta que esto puede causar un final de transición desagradable cuando tenga que usar valores que son mucho más grandes que el valor calculado real. Me di cuenta de esto al intentar hacer que un div creciera desde la altura 0 hasta la altura del contenido, que varía mucho debido a los diferentes tamaños de pantalla (2 líneas en mi monitor de 2560x1440 frente a> 10 líneas en un teléfono inteligente). Para esto terminé yendo con js. jpeltoniemi 17 de mayo de 2013 a las 10:22
  • 61
    Solución muy fea ya que crea un retraso en una dirección pero no en la otra. Tim 20/11/2013 a las 14:57
  • 100
    Esta es una solución bastante perezosa. Supongo que OP quiere usar height: auto porque la altura expandida del contenedor es algo impredecible. Esta solución provocará un retraso antes de que la animación sea visible. Además, la duración visible de la animación será impredecible. Obtendrá resultados mucho más predecibles (y probablemente más suaves) calculando la altura combinada de cada uno de los nodos secundarios de los contenedores y luego reduciendo a un valor de altura exacto. Shawn Whinnery 16/04/14 a las 22:06
378

Deberías usar scaleY en su lugar.

ul {
  background-color: #eee;
  transform: scaleY(0);    
  transform-origin: top;
  transition: transform 0.26s ease;
}
p:hover ~ ul {
  transform: scaleY(1);
}
<p>Hover This</p>
<ul>
  <li>Coffee</li>
  <li>Tea</li>
  <li>Milk</li>
</ul>

Hice una versión con prefijo de proveedor del código anterior en jsfiddle , y cambié su jsfiddle para usar scaleY en lugar de height.

Editar A algunas personas no les gusta cómo sescaleYtransforma el contenido. Si eso es un problema, sugiero usar en sucliplugar.

ul {
  clip: rect(auto, auto, 0, auto);
  position: absolute;
  margin: -1rem 0;
  padding: .5rem;

  color: white;

  background-color: rgba(0, 0, 0, 0.8);

  transition-property: clip;
  transition-duration: 0.5s;
  transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
h3:hover ~ ul,
h3:active ~ ul,
ul:hover {
  clip: rect(auto, auto, 10rem, auto);
}
<h3>Hover here</h3>
<ul>
  <li>This list</li>
  <li>is clipped.</li>
  <li>A clip transition</li>
  <li>will show it</li>
</ul>
<p>
  Some text...
</p>
6
  • 259
    Este método solo logra parcialmente el efecto deseado, pero en realidad no elimina el espacio. La caja transformada actúa como un elemento relativamente posicionado: el espacio se ocupa sin importar cómo se escala. Echa un vistazo a este jsFiddle que toma el primero y solo agrega un texto falso en la parte inferior. Observe cómo el texto debajo de él no se mueve hacia arriba cuando la altura del cuadro se escala a cero. animuson 29/07/2013 a las 20:37
  • 9
    Ahora lo hace: jsfiddle.net/gNDX3/1 Básicamente, necesita diseñar sus elementos de acuerdo con lo que necesita. No hay una fórmula mágica o un comportamiento similar a un widget en CSS / HTML. dotnetCarpenter 30 de agosto de 2013 a las 16:07
  • 88
    Si bien aplaudo a alguien que intenta diferentes enfoques, el efecto y las complicaciones del mundo real que trae esta solución son mucho peores que el ya terrible truco de altura máxima. Por favor, no use. mystrdat 18/11/2013 a las 19:53
  • 2
    "Ahora sí", excepto que el contenido debajo del elemento que desaparece salta antes de que el elemento se deslice. Puede ser útil a veces, sin embargo, el objetivo de las transiciones es cambiar suavemente las imágenes, no cambiar un salto absoluto por otro. Patrick Szalapski 2 de mayo a las 1:51
  • Este método distorsiona el contenido de la lista, "aplastándolos" verticalmente, de una manera que probablemente no sea aceptable para cualquiera que busque una respuesta a esto. Jay 18 jul a las 17:40
237

Actualmente no puede animar en altura cuando una de las alturas involucradas es auto, debe establecer dos alturas explícitas.

0
149

La solución que siempre he usado fue primero desvanecer, luego reducir los valores font-size, paddingy margin. No tiene el mismo aspecto que un paño, pero funciona sin estática heighto max-height.

Ejemplo de trabajo:

/* final display */
#menu #list {
    margin: .5em 1em;
    padding: 1em;
}

/* hide */
#menu:not(:hover) #list {
    font-size: 0;
    margin: 0;
    opacity: 0;
    padding: 0;
    /* fade out, then shrink */
    transition: opacity .25s,
                font-size .5s .25s,
                margin .5s .25s,
                padding .5s .25s;
}

/* reveal */
#menu:hover #list {
    /* unshrink, then fade in */
    transition: font-size .25s,
                margin .25s,
                padding .25s,
                opacity .5s .25s;
}
<div id="menu">
    <b>hover me</b>
    <ul id="list">
        <li>item</li>
        <li>item</li>
        <li>item</li>
        <li>item</li>
        <li>item</li>
    </ul>
</div>

<p>Another paragraph...</p>
4
  • 5
    Si tiene botones o cualquier otro elemento que no sea de texto, terminará con espacios en blanco no deseados mientras no se desplaza. Dennis W 23 oct 2016 a las 22:30
  • 4
    Puede refinarlo aún más seleccionando todos los elementos secundarios *y aplicando otros cambios. Los botones deberían haberse visto afectados por mi código anterior, sin embargo, si tuvieran el estilo correcto con em. Steven Vachon 24 oct 2016 a las 21:02
  • ¡Ew! Animar el tamaño de la fuente es feo. Robo Robok 6 de julio a las 15:47
  • 1
    Revisé más de 50 respuestas en esta página antes de darme cuenta de que tenía que animar el margen y el relleno de mi elemento para arreglar el jank al final 🤦‍♂️ graciasmarsnebulasoup 20 de julio a las 2:59
114

Sé que esta es la respuesta de treinta y tantos a esta pregunta, pero creo que vale la pena, así que aquí va. Esta es una solución solo de CSS con las siguientes propiedades:

  • No hay demoras al principio y la transición no se detiene antes. En ambas direcciones (expandiéndose y colapsando), si especifica una duración de transición de 300 ms en su CSS, entonces la transición toma 300 ms, punto.
  • Está cambiando la altura real (a diferencia de transform: scaleY(0)), por lo que hace lo correcto si hay contenido después del elemento plegable.
  • Mientras que (al igual que en otras soluciones) no son números mágicos (como "escoger una longitud que es mayor que su caja es cada vez va a ser"), no es fatal si sus extremos asunción siendo mal. La transición puede no parecer sorprendente en ese caso, pero antes y después de la transición, esto no es un problema: en el estado expandido ( height: auto), todo el contenido siempre tiene la altura correcta (a diferencia de, por ejemplo, si elige un max-heightque resulta ser demasiado baja). Y en el estado colapsado, la altura es cero como debería.

Manifestación

Aquí hay una demostración con tres elementos plegables, todos de diferentes alturas, que usan el mismo CSS. Es posible que desee hacer clic en "página completa" después de hacer clic en "ejecutar fragmento". Tenga en cuenta que JavaScript solo alterna la collapsedclase CSS, no hay ninguna medición involucrada. (Puede hacer esta demostración exacta sin ningún JavaScript utilizando una casilla de verificación o :target). También tenga en cuenta que la parte del CSS responsable de la transición es bastante corta, y el HTML solo requiere un único elemento contenedor adicional.

$(function () {
  $(".toggler").click(function () {
    $(this).next().toggleClass("collapsed");
    $(this).toggleClass("toggled"); // this just rotates the expander arrow
  });
});
.collapsible-wrapper {
  display: flex;
  overflow: hidden;
}
.collapsible-wrapper:after {
  content: '';
  height: 50px;
  transition: height 0.3s linear, max-height 0s 0.3s linear;
  max-height: 0px;
}
.collapsible {
  transition: margin-bottom 0.3s cubic-bezier(0, 0, 0, 1);
  margin-bottom: 0;
  max-height: 1000000px;
}
.collapsible-wrapper.collapsed > .collapsible {
  margin-bottom: -2000px;
  transition: margin-bottom 0.3s cubic-bezier(1, 0, 1, 1),
              visibility 0s 0.3s, max-height 0s 0.3s;
  visibility: hidden;
  max-height: 0;
}
.collapsible-wrapper.collapsed:after
{
  height: 0;
  transition: height 0.3s linear;
  max-height: 50px;
}

/* END of the collapsible implementation; the stuff below
   is just styling for this demo */

#container {
  display: flex;
  align-items: flex-start;
  max-width: 1000px;
  margin: 0 auto;
}  


.menu {
  border: 1px solid #ccc;
  box-shadow: 0 1px 3px rgba(0,0,0,0.5);
  margin: 20px;

  
}

.menu-item {
  display: block;
  background: linear-gradient(to bottom, #fff 0%,#eee 100%);
  margin: 0;
  padding: 1em;
  line-height: 1.3;
}
.collapsible .menu-item {
  border-left: 2px solid #888;
  border-right: 2px solid #888;
  background: linear-gradient(to bottom, #eee 0%,#ddd 100%);
}
.menu-item.toggler {
  background: linear-gradient(to bottom, #aaa 0%,#888 100%);
  color: white;
  cursor: pointer;
}
.menu-item.toggler:before {
  content: '';
  display: block;
  border-left: 8px solid white;
  border-top: 8px solid transparent;
  border-bottom: 8px solid transparent;
  width: 0;
  height: 0;
  float: right;
  transition: transform 0.3s ease-out;
}
.menu-item.toggler.toggled:before {
  transform: rotate(90deg);
}

body { font-family: sans-serif; font-size: 14px; }

*, *:after {
  box-sizing: border-box;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="container">
  <div class="menu">
    <div class="menu-item">Something involving a holodeck</div>
    <div class="menu-item">Send an away team</div>
    <div class="menu-item toggler">Advanced solutions</div>
    <div class="collapsible-wrapper collapsed">
      <div class="collapsible">
        <div class="menu-item">Separate saucer</div>
        <div class="menu-item">Send an away team that includes the captain (despite Riker's protest)</div>
        <div class="menu-item">Ask Worf</div>
        <div class="menu-item">Something involving Wesley, the 19th century, and a holodeck</div>
        <div class="menu-item">Ask Q for help</div>
      </div>
    </div>
    <div class="menu-item">Sweet-talk the alien aggressor</div>
    <div class="menu-item">Re-route power from auxiliary systems</div>
  </div>

  <div class="menu">
    <div class="menu-item">Something involving a holodeck</div>
    <div class="menu-item">Send an away team</div>
    <div class="menu-item toggler">Advanced solutions</div>
    <div class="collapsible-wrapper collapsed">
      <div class="collapsible">
        <div class="menu-item">Separate saucer</div>
        <div class="menu-item">Send an away team that includes the captain (despite Riker's protest)</div>
      </div>
    </div>
    <div class="menu-item">Sweet-talk the alien aggressor</div>
    <div class="menu-item">Re-route power from auxiliary systems</div>
  </div>

  <div class="menu">
    <div class="menu-item">Something involving a holodeck</div>
    <div class="menu-item">Send an away team</div>
    <div class="menu-item toggler">Advanced solutions</div>
    <div class="collapsible-wrapper collapsed">
      <div class="collapsible">
        <div class="menu-item">Separate saucer</div>
        <div class="menu-item">Send an away team that includes the captain (despite Riker's protest)</div>
        <div class="menu-item">Ask Worf</div>
        <div class="menu-item">Something involving Wesley, the 19th century, and a holodeck</div>
        <div class="menu-item">Ask Q for help</div>
        <div class="menu-item">Separate saucer</div>
        <div class="menu-item">Send an away team that includes the captain (despite Riker's protest)</div>
        <div class="menu-item">Ask Worf</div>
        <div class="menu-item">Something involving Wesley, the 19th century, and a holodeck</div>
        <div class="menu-item">Ask Q for help</div>
      </div>
    </div>
    <div class="menu-item">Sweet-talk the alien aggressor</div>
    <div class="menu-item">Re-route power from auxiliary systems</div>
  </div>

</div>

¿Como funciona?

De hecho, hay dos transiciones involucradas para que esto suceda. Uno de ellos margin-bottomcambia de 0px (en el estado expandido) al -2000pxestado colapsado (similar a esta respuesta ). El 2000 aquí es el primer número mágico, se basa en la suposición de que su caja no será más alta que esto (2000 píxeles parece una opción razonable).

El uso de la margin-bottomtransición por sí solo tiene dos problemas:

  • Si realmente tiene un cuadro que tiene más de 2000 píxeles, entonces margin-bottom: -2000pxno ocultará todo; habrá cosas visibles incluso en el caso contraído. Esta es una solución menor que haremos más adelante.
  • Si el cuadro real tiene, digamos, 1000 píxeles de alto y su transición tiene una longitud de 300 ms , entonces la transición visible ya ha terminado después de aproximadamente 150 ms (o, en la dirección opuesta, comienza 150 ms tarde).

Arreglando este segundo problema es donde entra la segunda transición, y esta transición apunta conceptualmente a la altura mínima del contenedor ("conceptualmente" porque en realidad no estamos usando la min-heightpropiedad para esto; más sobre eso más adelante).

Aquí hay una animación que muestra cómo la combinación de la transición del margen inferior con la transición de altura mínima, ambas de igual duración, nos da una transición combinada de altura completa a altura cero que tiene la misma duración.

animación como se describe arriba

La barra izquierda muestra cómo el margen inferior negativo empuja la parte inferior hacia arriba, reduciendo la altura visible. La barra del medio muestra cómo la altura mínima garantiza que, en el caso de colapso, la transición no termine antes, y en el caso de expansión, la transición no comience tarde. La barra de la derecha muestra cómo la combinación de los dos hace que la caja pase de la altura completa a la altura cero en la cantidad de tiempo correcta.

Para mi demostración, me he decidido por 50px como el valor de altura mínima superior. Este es el segundo número mágico, y debería ser más bajo que la altura de la caja. 50px también parece razonable; parece poco probable que a menudo desee hacer un elemento plegable que no tenga ni siquiera 50 píxeles de alto en primer lugar.

Como puede ver en la animación, la transición resultante es continua, pero no diferenciable: en el momento en que la altura mínima es igual a la altura completa ajustada por el margen inferior, hay un cambio repentino en la velocidad. Esto es muy notable en la animación porque usa una función de tiempo lineal para ambas transiciones y porque toda la transición es muy lenta. En el caso real (mi demostración en la parte superior), la transición solo toma 300 ms y la transición del margen inferior no es lineal. He jugado con muchas funciones de sincronización diferentes para ambas transiciones, y las que terminé sintieron que funcionaban mejor para la más amplia variedad de casos.

Quedan dos problemas por solucionar:

  1. el punto desde arriba, donde los cuadros de más de 2000 píxeles de altura no están completamente ocultos en el estado contraído,
  2. y el problema inverso, donde en el caso no oculto, los cuadros de menos de 50 píxeles de altura son demasiado altos incluso cuando la transición no se está ejecutando, porque la altura mínima los mantiene en 50 píxeles.

Resolvemos el primer problema dando al elemento contenedor a max-height: 0en el caso colapsado, con una 0s 0.3stransición. Esto significa que no es realmente una transición, pero max-heightse aplica con un retraso; solo se aplica una vez finalizada la transición. Para que esto funcione correctamente, también necesitamos elegir un valor numérico max-heightpara el estado opuesto, no colapsado. Pero a diferencia del caso de 2000px, donde elegir un número demasiado grande afecta la calidad de la transición, en este caso, realmente no importa. Entonces, podemos elegir un número que sea tan alto que sepamos que ninguna altura se acercará a este. Elegí un millón de píxeles. Si cree que es posible que deba admitir contenido de una altura de más de un millón de píxeles, entonces 1) lo siento, y 2) simplemente agregue un par de ceros.

El segundo problema es la razón por la que en realidad no estamos usando min-heightpara la transición de altura mínima. En cambio, hay un ::afterpseudo-elemento en el contenedor con una heighttransición de 50px a cero. Esto tiene el mismo efecto que a min-height: No permitirá que el contenedor se encoja por debajo de la altura que tenga actualmente el pseudoelemento. Pero debido a que estamos usando height, no min-height, ahora podemos usar max-height(una vez más aplicado con un retraso) para establecer la altura real del pseudoelemento en cero una vez que finaliza la transición, asegurándonos de que al menos fuera de la transición, incluso los elementos pequeños tienen la altura correcta. Debido a que min-heightes más fuerte que max-height, esto no funcionaría si usáramos el contenedor en min-heightlugar del pseudoelementoheight. Al igual que max-heighten el párrafo anterior, esto max-heighttambién necesita un valor para el extremo opuesto de la transición. Pero en este caso solo podemos elegir el 50px.

Probado en Chrome (Win, Mac, Android, iOS), Firefox (Win, Mac, Android), Edge, IE11 (excepto por un problema de diseño de flexbox con mi demostración que no me molesté en depurar) y Safari (Mac, iOS ). Hablando de flexbox, debería ser posible hacer que esto funcione sin usar ninguna flexbox; de hecho, creo que podría hacer que casi todo funcione en IE7, excepto por el hecho de que no tendrá transiciones CSS, lo que lo convierte en un ejercicio bastante inútil.

3
  • 67
    Desearía que pudiera acortar (simplificar) su solución, o al menos el ejemplo de código; parece que el 90% del ejemplo de código no es relevante para su respuesta. Gil Epshtain 30/07/18 a las 15:16
  • No sé por qué esta no es LA respuesta aceptada, está limpia, bien explicada, solo css (excepto por el conmutador, pero puede hacerlo en css). Honestamente: ¡BIEN HECHO! Zerowiel 19 de junio a las 19:49
  • Los pequeños problemas de interfaz nunca deberían verse como una tesis doctoral. Simplemente solucione el problema real. Si bien no dudo de que sea correcto, esta es una respuesta excesiva. Hannes Schneidermayer 25 de julio a las 14:11
97

Puedes, con un poco de jiggery-pokery no semántico. Mi enfoque habitual es animar la altura de un DIV externo que tiene un solo hijo que es un DIV sin estilo que se usa solo para medir la altura del contenido.

function growDiv() {
  var growDiv = document.getElementById('grow');
  if (growDiv.clientHeight) {
    growDiv.style.height = 0;
  } else {
    var wrapper = document.querySelector('.measuringWrapper');
    growDiv.style.height = wrapper.clientHeight + "px";
  }
}
#grow {
  -moz-transition: height .5s;
  -ms-transition: height .5s;
  -o-transition: height .5s;
  -webkit-transition: height .5s;
  transition: height .5s;
  height: 0;
  overflow: hidden;
  outline: 1px solid red;
}
<input type="button" onclick="growDiv()" value="grow">
<div id='grow'>
  <div class='measuringWrapper'>
    <div>
      The contents of my div.
    </div>
    <div>
      The contents of my div.
    </div>
    <div>
      The contents of my div.
    </div>
    <div>
      The contents of my div.
    </div>
    <div>
      The contents of my div.
    </div>
    <div>
      The contents of my div.
    </div>
  </div>
</div>

A uno le gustaría poder prescindir del .measuringWrappery simplemente configurar la altura del DIV en automático y tener esa animación, pero eso no parece funcionar (la altura se establece, pero no se produce ninguna animación).

function growDiv() {
  var growDiv = document.getElementById('grow');
  if (growDiv.clientHeight) {
    growDiv.style.height = 0;
  } else {
    growDiv.style.height = 'auto';
  }
}
#grow {
  -moz-transition: height .5s;
  -ms-transition: height .5s;
  -o-transition: height .5s;
  -webkit-transition: height .5s;
  transition: height .5s;
  height: 0;
  overflow: hidden;
  outline: 1px solid red;
}
<input type="button" onclick="growDiv()" value="grow">
<div id='grow'>
  <div>
    The contents of my div.
  </div>
  <div>
    The contents of my div.
  </div>
  <div>
    The contents of my div.
  </div>
  <div>
    The contents of my div.
  </div>
  <div>
    The contents of my div.
  </div>
  <div>
    The contents of my div.
  </div>
</div>

Mi interpretación es que se necesita una altura explícita para que se ejecute la animación. No puede obtener una animación en la altura cuando la altura (la altura inicial o final) es auto.

2
  • 10
    Dado que esto se basa en javascript, ¡también puede agregar fácilmente el MeasuringWrapper usando javascript también! Quickredfox 29/04/13 a las 21:23
  • 23
    Puedes hacerlo sin envoltorio. Solo: function growDiv () {var growDiv = document.getElementById ('crecer'); if (growDiv.clientHeight) {growDiv.style.height = 0; } else {growDiv.style.height = growDiv.scrollHeight + 'px'; }}user1742529 17 de junio de 2015 a las 9:30
56

Una solución visual para animar la altura mediante transiciones CSS3 es animar el relleno.

No obtiene el efecto de borrado completo, pero jugar con la duración de la transición y los valores de relleno deberían acercarlo lo suficiente. Si no desea establecer explícitamente la altura / altura máxima, esto debería ser lo que está buscando.

div {
    height: 0;
    overflow: hidden;
    padding: 0 18px;
    -webkit-transition: all .5s ease;
       -moz-transition: all .5s ease;
            transition: all .5s ease;
}
div.animated {
    height: auto;
    padding: 24px 18px;
}

http://jsfiddle.net/catharsis/n5XfG/17/ (extraído de stephband por encima de jsFiddle)

1
  • 22
    Excepto que aquí no estás animando la altura en absoluto. Estás animando el relleno ... puede desaparecer muy bien porque puede animarse desde el estado actual hasta 0, pero si miras de cerca cuando se expande, se abre con el texto y luego el relleno solo se anima ... porque no lo hace no sé cómo animar de 0 a automático ... necesita un rango numérico ... así es como funciona la interpolación. Rob R 15/12/14 a las 16:17
52

La respuesta aceptada funciona en la mayoría de los casos, pero no funciona bien cuando la divaltura puede variar mucho: la velocidad de la animación no depende de la altura real del contenido y puede verse entrecortada.

Aún puede realizar la animación real con CSS, pero necesita usar JavaScript para calcular la altura de los elementos, en lugar de intentar usar auto. No se requiere jQuery, aunque es posible que deba modificarlo un poco si desea compatibilidad (funciona en la última versión de Chrome :)).

window.toggleExpand = function(element) {
    if (!element.style.height || element.style.height == '0px') { 
        element.style.height = Array.prototype.reduce.call(element.childNodes, function(p, c) {return p + (c.offsetHeight || 0);}, 0) + 'px';
    } else {
        element.style.height = '0px';
    }
}
#menu #list {
    height: 0px;
    transition: height 0.3s ease;
    background: #d5d5d5;
    overflow: hidden;
}
<div id="menu">
    <input value="Toggle list" type="button" onclick="toggleExpand(document.getElementById('list'));">
    <ul id="list">
        <!-- Works well with dynamically-sized content. -->
        <li>item</li>
        <li><div style="height: 100px; width: 100px; background: red;"></div></li>
        <li>item</li>
        <li>item</li>
        <li>item</li>
    </ul>
</div>
2
  • @Coderer podría usar clientHeightTom 30 abr.20 a las 7:06
  • Esto tiene un resultado muy bueno. Pero JavaScript parece un código de máquina en su mayor parte. ¿Cómo podría volver a escribir el código como si estuviera hablando inglés (código legible)? klewis 9 de marzo a las 13:37
48

Mi solución es hacer la transición de la altura máxima a la altura exacta del contenido para una animación suave y agradable, luego usar una devolución de llamada de transición para establecer la altura máxima en 9999px para que el contenido pueda cambiar de tamaño libremente.

var content = $('#content');
content.inner = $('#content .inner'); // inner div needed to get size of content when closed

// css transition callback
content.on('transitionEnd webkitTransitionEnd transitionend oTransitionEnd msTransitionEnd', function(e){
    if(content.hasClass('open')){
        content.css('max-height', 9999); // try setting this to 'none'... I dare you!
    }
});

$('#toggle').on('click', function(e){
    content.toggleClass('open closed');
    content.contentHeight = content.outerHeight();
    
    if(content.hasClass('closed')){
        
        // disable transitions & set max-height to content height
        content.removeClass('transitions').css('max-height', content.contentHeight);
        setTimeout(function(){
            
            // enable & start transition
            content.addClass('transitions').css({
                'max-height': 0,
                'opacity': 0
            });
            
        }, 10); // 10ms timeout is the secret ingredient for disabling/enabling transitions
        // chrome only needs 1ms but FF needs ~10ms or it chokes on the first animation for some reason
        
    }else if(content.hasClass('open')){  
        
        content.contentHeight += content.inner.outerHeight(); // if closed, add inner height to content height
        content.css({
            'max-height': content.contentHeight,
            'opacity': 1
        });
        
    }
});
.transitions {
    transition: all 0.5s ease-in-out;
    -webkit-transition: all 0.5s ease-in-out;
    -moz-transition: all 0.5s ease-in-out;
}

body {
    font-family:Arial;
    line-height: 3ex;
}
code {
    display: inline-block;
    background: #fafafa;
    padding: 0 1ex;
}
#toggle {
    display:block;
    padding:10px;
    margin:10px auto;
    text-align:center;
    width:30ex;
}
#content {
    overflow:hidden;
    margin:10px;
    border:1px solid #666;
    background:#efefef;
    opacity:1;
}
#content .inner {
    padding:10px;
    overflow:auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<div id="content" class="open">
    <div class="inner">
        <h3>Smooth CSS Transitions Between <code>height: 0</code> and <code>height: auto</code></h3>
        <p>A clever workaround is to use <code>max-height</code> instead of <code>height</code>, and set it to something bigger than your content. Problem is the browser uses this value to calculate transition duration. So if you set it to <code>max-height: 1000px</code> but the content is only 100px high, the animation will be 10x too fast.</p>
        <p>Another option is to measure the content height with JS and transition to that fixed value, but then you have to keep track of the content and manually resize it if it changes.</p>
        <p>This solution is a hybrid of the two - transition to the measured content height, then set it to <code>max-height: 9999px</code> after the transition for fluid content sizing.</p>
    </div>
</div>

<br />

<button id="toggle">Challenge Accepted!</button>
0
41

Hubo poca mención de la Element.prototype.scrollHeightpropiedad que puede ser útil aquí y aún puede usarse con una transición CSS pura, aunque obviamente se requeriría soporte para scripts . La propiedad siempre contiene la altura "completa" de un elemento, independientemente de si su contenido se desborda como resultado de la altura contraída (por ejemplo, height: 0).

Como tal, para un elemento height: 0(efectivamente completamente colapsado), su altura "normal" o "completa" todavía está disponible a través de su scrollHeightvalor (invariablemente una longitud de píxel ).

Para tal elemento, asumiendo que ya tiene la transición configurada como, por ejemplo, (usando ulsegún la pregunta original):

ul {
    height: 0;
    transition: height 1s; /* An example transition. */
}

Podemos activar la "expansión" animada de altura deseada, usando solo CSS, con algo como lo siguiente (aquí asumiendo que la ulvariable se refiere a la lista):

ul.style.height = ul.scrollHeight + "px";

Eso es todo. Si necesita contraer la lista, cualquiera de las dos siguientes declaraciones será suficiente:

ul.style.height = 0;
ul.style.removeProperty("height");

Mi caso de uso particular giraba en torno a la animación de listas de longitudes desconocidas y, a menudo, considerables, por lo que no me sentía cómodo decidiéndome por una especificación heighto "suficientemente grande" arbitraria max-heighty arriesgando contenido cortado o contenido que de repente necesita desplazarse (si overflow: auto, por ejemplo) . Además, la relajación y la sincronización se rompen con las max-heightsoluciones basadas en, porque la altura utilizada puede alcanzar su valor máximo mucho antes de lo que tardaría max-heighten alcanzar 9999px. Y a medida que aumentan las resoluciones de pantalla, la longitud de los píxeles 9999pxdeja un mal sabor de boca. Esta solución en particular resuelve el problema de una manera elegante, en mi opinión.

Por último, esperamos que las futuras revisiones de CSS aborden la necesidad de los autores de hacer este tipo de cosas de forma aún más elegante: revise la noción de valores "calculados" frente a "usados" y "resueltos", y considere si las transiciones deben aplicarse a los valores calculados valores, incluidas las transiciones con widthy height(que actualmente reciben un tratamiento especial).

1
  • 9
    No lo encontró en las otras respuestas aquí porque interactuar con el DOM usando la Element.scrollHeightpropiedad requiere JavaScript y, como dice claramente la pregunta, quieren hacer esto sin JavaScript. TylerH 5/07/2016 a las 21:12
35

Úselo max-heightcon diferentes retardos y facilidades de transición para cada estado.

HTML:

<a href="#" id="trigger">Hover</a>
<ul id="toggled">
    <li>One</li>
    <li>Two</li>
    <li>Three</li>
<ul>

CSS:

#toggled{
    max-height: 0px;
    transition: max-height .8s cubic-bezier(0, 1, 0, 1) -.1s;
}

#trigger:hover + #toggled{
    max-height: 9999px;
    transition-timing-function: cubic-bezier(0.5, 0, 1, 0); 
    transition-delay: 0s;
}

Ver ejemplo: http://jsfiddle.net/0hnjehjc/1/

1
  • 15
    El problema aquí es que, para asegurarse de tener suficiente espacio en un entorno dinámico, debe usar alturas máximas ridículas como 999999px. Esto, por otro lado, cambiará su animación, porque los fotogramas se calculan a partir de este valor. Por lo tanto, puede terminar con un "retraso" de animación en una dirección y un final realmente rápido en la otra dirección (corr: funciones de temporización)Julian F. Weinert 24/06/15 a las 9:34
35

Sin valores codificados de forma rígida.

Sin JavaScript.

Sin aproximaciones.

El truco consiste en usar un oculto y duplicado divpara que el navegador comprenda lo que significa 100%.

Este método es adecuado siempre que pueda duplicar el DOM del elemento que desea animar.

.outer {
  border: dashed red 1px;
  position: relative;
}

.dummy {
  visibility: hidden;
}

.real {
  position: absolute;
  background: yellow;
  height: 0;
  transition: height 0.5s;
  overflow: hidden;
}

.outer:hover>.real {
  height: 100%;
}
Hover over the box below:
<div class="outer">
  <!-- The actual element that you'd like to animate -->
  <div class="real">
unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable
content unpredictable content unpredictable content unpredictable content
  </div>
  <!-- An exact copy of the element you'd like to animate. -->
  <div class="dummy" aria-hidden="true">
unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable
content unpredictable content unpredictable content unpredictable content
  </div>
</div>
0
23

Mientras publico esto, ya hay más de 30 respuestas, pero siento que mi respuesta mejora la respuesta ya aceptada por Jake.

No estaba contento con el problema que surge de simplemente usar max-heighty transiciones CSS3, ya que, como señalaron muchos comentaristas, debe establecer su max-heightvalor muy cerca de la altura real o obtendrá un retraso. Vea este JSFiddle para ver un ejemplo de ese problema.

Para evitar esto (sin usar JavaScript), agregué otro elemento HTML que cambia el transform: translateYvalor de CSS.

Esto significa que ambos max-heighty translateYse usan: max-heightpermite que el elemento empuje hacia abajo los elementos debajo de él, mientras que translateYda el efecto "instantáneo" que queremos. El problema con max-heighttodavía existe, pero su efecto ha disminuido. Esto significa que puede establecer una altura mucho mayor para su max-heightvalor y preocuparse menos por ello.

El beneficio general es que en la transición de regreso (el colapso), el usuario ve la translateYanimación de inmediato, por lo que realmente no importa cuánto tiempo max-heighttome.

Solución como violín

body {
  font-family: sans-serif;
}

.toggle {
  position: relative;
  border: 2px solid #333;
  border-radius: 3px;
  margin: 5px;
  width: 200px;
}

.toggle-header {
  margin: 0;
  padding: 10px;
  background-color: #333;
  color: white;
  text-align: center;
  cursor: pointer;
}

.toggle-height {
  background-color: tomato;
  overflow: hidden;
  transition: max-height .6s ease;
  max-height: 0;
}

.toggle:hover .toggle-height {
  max-height: 1000px;
}

.toggle-transform {
  padding: 5px;
  color: white;
  transition: transform .4s ease;
  transform: translateY(-100%);
}

.toggle:hover .toggle-transform {
  transform: translateY(0);
}
<div class="toggle">
  <div class="toggle-header">
    Toggle!
  </div>
  <div class="toggle-height">
    <div class="toggle-transform">
      <p>Content!</p>
      <p>Content!</p>
      <p>Content!</p>
      <p>Content!</p>
    </div>
  </div>
</div>

<div class="toggle">
  <div class="toggle-header">
    Toggle!
  </div>
  <div class="toggle-height">
    <div class="toggle-transform">
      <p>Content!</p>
      <p>Content!</p>
      <p>Content!</p>
      <p>Content!</p>
    </div>
  </div>
</div>
0
19

Ok, creo que se me ocurrió una respuesta súper simple ... no max-height, usa relativeposicionamiento, funciona con lielementos y es CSS puro. No lo he probado en nada más que en Firefox, aunque a juzgar por el CSS, debería funcionar en todos los navegadores.

FIDDLE: http://jsfiddle.net/n5XfG/2596/

CSS

.wrap { overflow:hidden; }

.inner {
            margin-top:-100%;
    -webkit-transition:margin-top 500ms;
            transition:margin-top 500ms;
}

.inner.open { margin-top:0px; }

HTML

<div class="wrap">
    <div class="inner">Some Cool Content</div>
</div>
1
  • 13
    Esto funcionará hasta que la altura del elemento exceda su ancho. Es la base de cómo se calculan los márgenes usando porcentajes: se calculan en función del ancho del elemento. Entonces, si tiene un elemento de 1000 px de ancho, entonces un elemento de 1100 px será demasiado grande para que funcione esta solución, lo que significa que tendrá que aumentar ese margen superior negativo. Básicamente, es exactamente el mismo problema que usar la altura o la altura máxima. dudewad 19/08/15 a las 20:55
16

EDITAR: Desplácese hacia abajo para obtener una respuesta actualizada
Estaba haciendo una lista desplegable y vi esta publicación ... muchas respuestas diferentes, pero decido compartir mi lista desplegable también, ... no es perfecto, pero al menos usará solo css para ¡desplegable! He estado usando transform: translateY (y) para transformar la lista a la vista ...
Puedes ver más en la prueba
http://jsfiddle.net/BVEpc/4/
He colocado div detrás de cada li porque mi la lista desplegable viene de arriba y para mostrarles correctamente esto era necesario, mi código div es:

#menu div {
    transition: 0.5s 1s;
    z-index:-1;
    -webkit-transform:translateY(-100%);
    -webkit-transform-origin: top;
}

y hover es:

#menu > li:hover div {
    transition: 0.5s;
    -webkit-transform:translateY(0);
}

y debido a que la altura de ul se establece en el contenido que puede superar el contenido de su cuerpo, es por eso que hice esto para ul:

 #menu ul {
    transition: 0s 1.5s;
    visibility:hidden;
    overflow:hidden;
}

y flotar:

#menu > li:hover ul {
     transition:none;
     visibility:visible;
}

la segunda vez después de la transición es un retraso y se ocultará después de que mi lista desplegable se haya cerrado animadamente ...
Espero que más tarde alguien se beneficie de esta.

EDITAR: ¡No puedo creer que las personas estén usando este prototipo! ¡Este menú desplegable es solo para un submenú y eso es todo! He actualizado uno mejor que puede tener dos submenús para la dirección ltr y rtl con soporte para IE 8.
Fiddle para LTR
Fiddle para RTL, con
suerte, alguien encontrará esto útil en el futuro.

0
10

Puede realizar la transición de altura: 0 a altura: automática siempre que proporcione también la altura mínima y la altura máxima.

div.stretchy{
    transition: 1s linear;
}

div.stretchy.hidden{
    height: 0;
}

div.stretchy.visible{
    height: auto;
    min-height:40px;
    max-height:400px;
}
1
  • Esto no hará la transición height, solo max-height. heightsaltará instantáneamente de 0a auto, mientras que max-heighthará la transición que parece funcionar , pero crea el mismo problema que otras respuestas intentan abordar. Michael Johansen 18 de agosto a las 12:51
8

Ampliando la respuesta de @ jake, la transición llegará hasta el valor de altura máxima, lo que provocará una animación extremadamente rápida; si configura las transiciones para ambos: desplazamiento y apagado, puede controlar la velocidad loca un poco más.

Entonces, li: hover es cuando el mouse ingresa al estado y luego la transición en la propiedad non-hover será la salida del mouse.

Con suerte, esto será de alguna ayuda.

p.ej:

.sidemenu li ul {
   max-height: 0px;
   -webkit-transition: all .3s ease;
   -moz-transition: all .3s ease;
   -o-transition: all .3s ease;
   -ms-transition: all .3s ease;
   transition: all .3s ease;
}
.sidemenu li:hover ul {
    max-height: 500px;
    -webkit-transition: all 1s ease;
   -moz-transition: all 1s ease;
   -o-transition: all 1s ease;
   -ms-transition: all 1s ease;
   transition: all 1s ease;
}
/* Adjust speeds to the possible height of the list */

Aquí hay un violín: http://jsfiddle.net/BukwJ/

0
8

Solución Flexbox

Pros:

  • sencillo
  • no JS
  • transición suave

Contras:

  • El elemento debe colocarse en un contenedor flexible de altura fija.

La forma en que funciona es siempre teniendo una base flexible: auto en el elemento con contenido y, en su lugar, transiciones flex-grow y flex-shrink.

Editar: JS Fiddle mejorado inspirado en la interfaz de Xbox One.

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  transition: 0.25s;
  font-family: monospace;
}

body {
  margin: 10px 0 0 10px;
}

.box {
  width: 150px;
  height: 150px;
  margin: 0 2px 10px 0;
  background: #2d333b;
  border: solid 10px #20262e;
  overflow: hidden;
  display: inline-flex;
  flex-direction: column;
}

.space {
  flex-basis: 100%;
  flex-grow: 1;
  flex-shrink: 0;    
}

p {
  flex-basis: auto;
  flex-grow: 0;
  flex-shrink: 1;
  background: #20262e;
  padding: 10px;
  width: 100%;
  text-align: left;
  color: white;
}

.box:hover .space {
  flex-grow: 0;
  flex-shrink: 1;
}
  
.box:hover p {
  flex-grow: 1;
  flex-shrink: 0;    
}
<div class="box">
  <div class="space"></div>
  <p>
    Super Metroid Prime Fusion
  </p>
</div>
<div class="box">
  <div class="space"></div>
  <p>
    Resident Evil 2 Remake
  </p>
</div>
<div class="box">
  <div class="space"></div>
  <p>
    Yolo The Game
  </p>
</div>
<div class="box">
  <div class="space"></div>
  <p>
    Final Fantasy 7 Remake + All Additional DLC + Golden Tophat
  </p>
</div>
<div class="box">
  <div class="space"></div>
  <p>
    DerpVille
  </p>
</div>

JS violín

0
6

Aquí hay una forma de hacer la transición desde cualquier altura inicial, incluido 0, a automática (tamaño completo y flexible) sin requerir un código fijo por nodo o cualquier código de usuario para inicializar: https://github.com/csuwildcat / transición-auto . Este es básicamente el santo grial de lo que quieres, creo -> http://codepen.io/csuwldcat/pen/kwsdF . Simplemente coloque el siguiente archivo JS en su página, y todo lo que necesita hacer después de eso es agregar / eliminar un solo atributo booleano - reveal=""- de los nodos que desea expandir y contraer.

Esto es todo lo que necesita hacer como usuario, una vez que incluya el bloque de código que se encuentra debajo del código de ejemplo:

/*** Nothing out of the ordinary in your styles ***/
<style>
    div {
        height: 0;
        overflow: hidden;
        transition: height 1s;
    }
</style>

/*** Just add and remove one attribute and transition to/from auto! ***/

<div>
    I have tons of content and I am 0px in height you can't see me...
</div>

<div reveal>
     I have tons of content and I am 0px in height you can't see me...
     but now that you added the 'reveal' attribute, 
     I magically transitioned to full height!...
</div>

Aquí está el bloque de código para incluir en su página, después de eso, todo es salsa:

Suelte este archivo JS en su página: todo es Just Works ™

/ * Código de altura: auto; transición * /

(function(doc){

/* feature detection for browsers that report different values for scrollHeight when an element's overflow is hidden vs visible (Firefox, IE) */
var test = doc.documentElement.appendChild(doc.createElement('x-reveal-test'));
    test.innerHTML = '-';
    test.style.cssText = 'display: block !important; height: 0px !important; padding: 0px !important; font-size: 0px !important; border-width: 0px !important; line-height: 1px !important; overflow: hidden !important;';
var scroll = test.scrollHeight || 2;
doc.documentElement.removeChild(test);

var loading = true,
    numReg = /^([0-9]*\.?[0-9]*)(.*)/,
    skipFrame = function(fn){
      requestAnimationFrame(function(){
        requestAnimationFrame(fn);
      });
    },
    /* 2 out of 3 uses of this function are purely to work around Chrome's catastrophically busted implementation of auto value CSS transitioning */
    revealFrame = function(el, state, height){
        el.setAttribute('reveal-transition', 'frame');
        el.style.height = height;
        skipFrame(function(){
            el.setAttribute('reveal-transition', state);
            el.style.height = '';
        });
    },
    transitionend = function(e){
      var node = e.target;
      if (node.hasAttribute('reveal')) {
        if (node.getAttribute('reveal-transition') == 'running') revealFrame(node, 'complete', '');
      } 
      else {
        node.removeAttribute('reveal-transition');
        node.style.height = '';
      }
    },
    animationstart = function(e){
      var node = e.target,
          name = e.animationName;   
      if (name == 'reveal' || name == 'unreveal') {

        if (loading) return revealFrame(node, 'complete', 'auto');

        var style = getComputedStyle(node),
            offset = (Number(style.paddingTop.match(numReg)[1])) +
                     (Number(style.paddingBottom.match(numReg)[1])) +
                     (Number(style.borderTopWidth.match(numReg)[1])) +
                     (Number(style.borderBottomWidth.match(numReg)[1]));

        if (name == 'reveal'){
          node.setAttribute('reveal-transition', 'running');
          node.style.height = node.scrollHeight - (offset / scroll) + 'px';
        }
        else {
            if (node.getAttribute('reveal-transition') == 'running') node.style.height = '';
            else revealFrame(node, 'running', node.scrollHeight - offset + 'px');
        }
      }
    };

doc.addEventListener('animationstart', animationstart, false);
doc.addEventListener('MSAnimationStart', animationstart, false);
doc.addEventListener('webkitAnimationStart', animationstart, false);
doc.addEventListener('transitionend', transitionend, false);
doc.addEventListener('MSTransitionEnd', transitionend, false);
doc.addEventListener('webkitTransitionEnd', transitionend, false);

/*
    Batshit readyState/DOMContentLoaded code to dance around Webkit/Chrome animation auto-run weirdness on initial page load.
    If they fixed their code, you could just check for if(doc.readyState != 'complete') in animationstart's if(loading) check
*/
if (document.readyState == 'complete') {
    skipFrame(function(){
        loading = false;
    });
}
else document.addEventListener('DOMContentLoaded', function(e){
    skipFrame(function(){
        loading = false;
    });
}, false);

/* Styles that allow for 'reveal' attribute triggers */
var styles = doc.createElement('style'),
    t = 'transition: none; ',
    au = 'animation: reveal 0.001s; ',
    ar = 'animation: unreveal 0.001s; ',
    clip = ' { from { opacity: 0; } to { opacity: 1; } }',
    r = 'keyframes reveal' + clip,
    u = 'keyframes unreveal' + clip;

styles.textContent = '[reveal] { -ms-'+ au + '-webkit-'+ au +'-moz-'+ au + au +'}' +
    '[reveal-transition="frame"] { -ms-' + t + '-webkit-' + t + '-moz-' + t + t + 'height: auto; }' +
    '[reveal-transition="complete"] { height: auto; }' +
    '[reveal-transition]:not([reveal]) { -webkit-'+ ar +'-moz-'+ ar + ar +'}' +
    '@-ms-' + r + '@-webkit-' + r + '@-moz-' + r + r +
    '@-ms-' + u +'@-webkit-' + u + '@-moz-' + u + u;

doc.querySelector('head').appendChild(styles);

})(document);

/ * Código para DEMO * /

    document.addEventListener('click', function(e){
      if (e.target.nodeName == 'BUTTON') {
        var next = e.target.nextElementSibling;
        next.hasAttribute('reveal') ? next.removeAttribute('reveal') : next.setAttribute('reveal', '');
      }
    }, false);
1
  • 14
    Quiero votar esto, pero en lugar de responder a la pregunta de cómo hacer que funcione, compartiste un complemento que escribiste para que funcione. Nosotros, los curiosos, nos quedamos con la ingeniería inversa de su complemento, lo cual no es muy divertido. Deseo que actualice su respuesta para que contenga más explicaciones de lo que hace su complemento y por qué. Cambie el código para que sea más explicativo. Por ejemplo, tiene una sección completa de código que simplemente escribe CSS estático. Prefiero ver el CSS que el código que lo genera. Puede omitir las partes aburridas, como repetir para todos los prefijos del navegador. gilly3 9 de enero de 2015 a las 0:39
6

Creo que se me ocurrió una solución realmente sólida.

¡OK! Sé que este problema es tan antiguo como Internet, pero creo que tengo una solución que convertí en un complemento llamado transición mutante . Mi solución establece los style=""atributos para los elementos rastreados cada vez que hay un cambio en el DOM. el resultado final es que puede usar un buen CSS ole para sus transiciones y no usar correcciones hacky o javascript especial. Lo único que tiene que hacer es configurar lo que desea rastrear en el elemento en cuestión usando data-mutant-attributes="X".

<div data-mutant-attributes="height">                                                                      
        This is an example with mutant-transition                                                                                                          
    </div>

¡Eso es todo! Esta solución usa MutationObserver para seguir los cambios en el DOM. Debido a esto, realmente no tiene que configurar nada o usar javascript para animar cosas manualmente. Los cambios se controlan automáticamente. Sin embargo, debido a que usa MutationObserver, esto solo hará la transición en IE11 +.

¡Violines!

2
  • 13
    Por lo general, cuando alguien pregunta cómo hacer algo "sin JavaScript", significa que la solución debe funcionar en navegadores que tienen JavaScript desactivado. No significa "sin escribir mis propios guiones". Entonces, decir que su complemento se puede usar sin usar JavaScript es, en el mejor de los casos, engañoso, considerando que es un complemento de JavaScript. BoltClock 1 de abril de 2017 a las 6:19
  • Debo aclarar, cuando digo que no tienes que usar ningún javascript especial, me refiero a que no tienes que escribir ningún javascript. simplemente incluya la biblioteca JS y especifique qué atributos desea ver en el HTML. No tiene que usar CSS de altura fija ni averiguar nada. solo peina y listo. JonTroncoso 3/04/2017 a las 21:05
6

La respuesta de Jake para animar la altura máxima es excelente, pero encontré molesto el retraso causado por establecer una altura máxima grande.

Se podría mover el contenido plegable a un div interno y calcular la altura máxima obteniendo la altura del div interno (a través de JQuery sería el outerHeight ()).

$('button').bind('click', function(e) { 
  e.preventDefault();
  w = $('#outer');
  if (w.hasClass('collapsed')) {
    w.css({ "max-height": $('#inner').outerHeight() + 'px' });
  } else {
    w.css({ "max-height": "0px" });
  }
  w.toggleClass('collapsed');
});

Aquí hay un enlace jsfiddle: http://jsfiddle.net/pbatey/duZpT

Aquí hay un jsfiddle con la cantidad mínima absoluta de código requerida: http://jsfiddle.net/8ncjjxh8/

6

Solución de una oración: use la transición de relleno . Es suficiente para la mayoría de los casos, como el acordeón, e incluso mejor porque es rápido debido a que el valor de relleno a menudo no es grande.

Si desea que el proceso de animación sea mejor, simplemente aumente el valor de relleno.

.parent{ border-top: #999 1px solid;}
h1{ margin: .5rem; font-size: 1.3rem}
.children {
  height: 0;
  overflow: hidden;
  background-color: #dedede;
  transition: padding .2s ease-in-out, opacity .2s ease-in-out;
  padding: 0 .5rem;
  opacity: 0;
}
.children::before, .children::after{ content: "";display: block;}
.children::before{ margin-top: -2rem;}
.children::after{ margin-bottom: -2rem;}
.parent:hover .children {
  height: auto;
  opacity: 1;
  padding: 2.5rem .5rem;/* 0.5 + abs(-2), make sure it's less than expected min-height */
}
<div class="parent">
  <h1>Hover me</h1>
  <div class="children">Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>
  </div>
</div>
<div class="parent">
  <h1>Hover me(long content)</h1>
  <div class="children">Some content
    <br>Some content<br>Some content
    <br>Some content<br>Some content
    <br>Some content<br>Some content
    <br>Some content<br>Some content
    <br>Some content<br>Some content
    <br>
  </div>
</div>
<div class="parent">
  <h1>Hover me(short content)</h1>
  <div class="children">Some content
    <br>Some content
    <br>Some content
    <br>
  </div>
</div>
5

Me doy cuenta de que este hilo está envejeciendo, pero ocupa un lugar destacado en ciertas búsquedas de Google, así que creo que vale la pena actualizarlo.

También solo obtiene / establece la propia altura del elemento:

var load_height = document.getElementById('target_box').clientHeight;
document.getElementById('target_box').style.height = load_height + 'px';

Debe volcar este Javascript inmediatamente después de la etiqueta de cierre de target_box en una etiqueta de secuencia de comandos en línea.

0
5

Pude hacer esto. Tengo un .child& a .parentdiv. El div hijo encaja perfectamente dentro del ancho / alto del padre con el absoluteposicionamiento. Luego animo la translatepropiedad para Yreducir su valor 100%. Su animación muy suave, sin fallas ni lados negativos como cualquier otra solución aquí.

Algo como esto, pseudocódigo

.parent{ position:relative; overflow:hidden; } 
/** shown state */
.child {
  position:absolute;top:0;:left:0;right:0;bottom:0;
  height: 100%;
  transition: transform @overlay-animation-duration ease-in-out;
  .translate(0, 0);
}

/** Animate to hidden by sliding down: */
.child.slidedown {
  .translate(0, 100%); /** Translate the element "out" the bottom of it's .scene container "mask" so its hidden */
}

Se podría especificar un heightsobre .parent, en px, %o como licencia auto. Este div luego enmascara el .childdiv cuando se desliza hacia abajo.

1
  • 1
    El ejemplo práctico de esto será muy apreciado. Neurotransmitter 26/06/18 a las 14:01
5

Recientemente he realizado la transición max-heightde los lielementos en lugar de la envoltura ul.

El razonamiento es que la demora para pequeño max-heightses mucho menos notable (si es que lo hace) en comparación con grande max-heights, y también puedo establecer mi max-heightvalor relativo al font-sizede en lilugar de un número enorme arbitrario usando emso rems.

Si mi tamaño de fuente es 1rem, estableceré mi max-heighten algo como 3rem(para acomodar el texto envuelto). Puedes ver un ejemplo aquí:

http://codepen.io/mindfullsilence/pen/DtzjE

0
5

Publiqué una respuesta con algo de JavaScript y me votaron negativamente, así que me enojé y lo intenté nuevamente, ¡y lo he descifrado solo con CSS!

Esta solución utiliza algunas 'técnicas':

  • padding-bottom:100%'hack' donde los porcentajes se definen en términos del ancho actual del elemento. Más información sobre esta técnica.
  • envoltura retráctil flotante, (se necesita un div adicional para aplicar el truco de limpieza flotante)
  • uso no semántico de https://caniuse.com/#feat=css-writing-mode y algunas transformaciones para deshacerlo (esto permite el uso del truco de relleno anterior en un contexto vertical)

Sin embargo, el resultado es que obtenemos una transición eficaz utilizando solo CSS y una única función de transición para lograr la transición sin problemas; ¡el Santo Grial!

¡Por supuesto, hay una desventaja! No puedo averiguar cómo controlar el ancho en el que se corta el contenido ( overflow:hidden); Debido al truco del fondo acolchado, el ancho y la altura están íntimamente relacionados. Sin embargo, puede haber una forma, así que volveremos a ella.

https://jsfiddle.net/EoghanM/n1rp3zb4/28/

body {
  padding: 1em;
}

.trigger {
  font-weight: bold;
}

/* .expander is there for float clearing purposes only */
.expander::after {
  content: '';
  display: table;
  clear: both;
}

.outer {
  float: left; /* purpose: shrink to fit content */
  border: 1px solid green;
  overflow: hidden;
}

.inner {
  transition: padding-bottom 0.3s ease-in-out;  /* or whatever crazy transition function you can come up with! */
  padding-bottom: 0%;  /* percentage padding is defined in terms of width. The width at this level is equal to the height of the content */
  height: 0;

  /* unfortunately, change of writing mode has other bad effects like orientation of cursor */
  writing-mode: vertical-rl;
  cursor: default; /* don't want the vertical-text (sideways I-beam) */
  transform: rotate(-90deg) translateX(-100%);  /* undo writing mode */
  transform-origin: 0 0;
  margin: 0;  /* left/right margins here will add to height */
}

.inner > div { white-space: nowrap; }

.expander:hover .inner,  /* to keep open when expanded */
.trigger:hover+.expander .inner {
  padding-bottom: 100%;
}
<div class="trigger">HoverMe</div>
<div class="expander">
  <div class="outer">
    <div class="inner">
      <div>First Item</div>
      <div>Content</div>
      <div>Content</div>
      <div>Content</div>
      <div>Long Content can't be wider than outer height unfortunately</div>
      <div>Last Item</div>
    </div>
  </div>
</div>
<div>
  after content</div>
</div>
0
5

Puede hacer esto creando una animación inversa (colapso) con ruta de clip.

#child0 {
    display: none;
}
#parent0:hover #child0 {
    display: block;
    animation: height-animation;
    animation-duration: 200ms;
    animation-timing-function: linear;
    animation-fill-mode: backwards;
    animation-iteration-count: 1;
    animation-delay: 200ms;
}
@keyframes height-animation {
    0% {
        clip-path: polygon(0% 0%, 100% 0.00%, 100% 0%, 0% 0%);
    }
    100% {
        clip-path: polygon(0% 0%, 100% 0.00%, 100% 100%, 0% 100%);
    }
}
<div id="parent0">
    <h1>Hover me (height: 0)</h1>
    <div id="child0">Some content
        <br>Some content
        <br>Some content
        <br>Some content
        <br>Some content
        <br>Some content
        <br>
    </div>
</div>
1
  • Genial, pero ¿cómo deshacer la revelación con animación? ViRuSTriNiTy 15 de julio a las 15:48
4

Aquí hay una solución que acabo de usar en combinación con jQuery. Esto funciona para la siguiente estructura HTML:

<nav id="main-nav">
    <ul>
        <li>
            <a class="main-link" href="yourlink.html">Link</a>
            <ul>
                <li><a href="yourlink.html">Sub Link</a></li>
            </ul>
        </li>
    </ul>
</nav>

y la función:

    $('#main-nav li ul').each(function(){
        $me = $(this);

        //Count the number of li elements in this UL
        var liCount = $me.find('li').size(),
        //Multiply the liCount by the height + the margin on each li
            ulHeight = liCount * 28;

        //Store height in the data-height attribute in the UL
        $me.attr("data-height", ulHeight);
    });

Luego puede usar una función de clic para establecer y eliminar la altura usando css()

$('#main-nav li a.main-link').click(function(){
    //Collapse all submenus back to 0
    $('#main-nav li ul').removeAttr('style');

    $(this).parent().addClass('current');

    //Set height on current submenu to it's height
    var $currentUl = $('li.current ul'),
        currentUlHeight = $currentUl.attr('data-height');
})

CSS:

#main-nav li ul { 
    height: 0;
    position: relative;
    overflow: hidden;
    opacity: 0; 
    filter: alpha(opacity=0); 
    -ms-filter: "alpha(opacity=0)";
    -khtml-opacity: 0; 
    -moz-opacity: 0;
    -webkit-transition: all .6s ease-in-out;
    -moz-transition: all .6s ease-in-out;
    -o-transition: all .6s ease-in-out;
    -ms-transition: all .6s ease-in-out;
    transition: all .6s ease-in-out;
}

#main-nav li.current ul {
    opacity: 1.0; 
    filter: alpha(opacity=100); 
    -ms-filter: "alpha(opacity=100)";
    -khtml-opacity: 1.0; 
    -moz-opacity: 1.0;
}

.ie #main-nav li.current ul { height: auto !important }

#main-nav li { height: 25px; display: block; margin-bottom: 3px }
0
4

Entiendo que la pregunta pide una solución sin JavaScript. Pero para aquellos interesados, aquí está mi solución usando solo un poco de JS.

ok, entonces el CSS del elemento cuya altura cambiará de forma predeterminada está configurado en height: 0;y cuando está abierto height: auto;. También tiene transition: height .25s ease-out;. Pero, por supuesto, el problema es que no hará la transición hacia o desdeheight: auto;

Entonces, lo que he hecho es al abrir o cerrar, establecer la altura en la scrollHeightpropiedad del elemento. Este nuevo estilo en línea tendrá una mayor especificidad y anulará tanto height: auto;y height: 0;como las ejecuciones de transición.

Al abrir, agrego un transitionenddetector de eventos que se ejecutará solo una vez y luego elimino el estilo en línea configurándolo de nuevo, height: auto;lo que permitirá que el elemento cambie de tamaño si es necesario, como en este ejemplo más complejo con submenús https://codepen.io/ninjabonsai/ bolígrafo / GzYyVe

Al cerrar, elimino el estilo en línea justo después del siguiente ciclo de bucle de eventos utilizando setTimeout sin demora. Este medio height: auto;se anula temporalmente, lo que permite la transición de regreso aheight 0;

const showHideElement = (element, open) => {
  element.style.height = element.scrollHeight + 'px';
  element.classList.toggle('open', open);

  if (open) {
    element.addEventListener('transitionend', () => {
      element.style.removeProperty('height');
    }, {
      once: true
    });
  } else {
    window.setTimeout(() => {
      element.style.removeProperty('height');
    });
  }
}

const menu = document.body.querySelector('#menu');
const list = document.body.querySelector('#menu > ul')

menu.addEventListener('mouseenter', () => showHideElement(list, true));
menu.addEventListener('mouseleave', () => showHideElement(list, false));
#menu > ul {
  height: 0;
  overflow: hidden;
  background-color: #999;
  transition: height .25s ease-out;
}

#menu > ul.open {
  height: auto;
}
<div id="menu">
  <a>hover me</a>
  <ul>
    <li>item</li>
    <li>item</li>
    <li>item</li>
    <li>item</li>
    <li>item</li>
  </ul>
</div>
0
4

No he leído todo en detalle pero he tenido este problema recientemente e hice lo siguiente:

div.class{
   min-height:1%;
   max-height:200px;
   -webkit-transition: all 0.5s ease;
   -moz-transition: all 0.5s ease;
   -o-transition: all 0.5s ease;
   -webkit-transition: all 0.5s ease;
   transition: all 0.5s ease;
   overflow:hidden;
}

div.class:hover{
   min-height:100%;
   max-height:3000px;
}

Esto le permite tener un div que al principio muestra contenido de hasta 200px de altura y al pasar el mouse, su tamaño se vuelve al menos tan alto como todo el contenido del div. La Div no se convierte en 3000px pero 3000px es el límite que estoy imponiendo. Asegúrese de tener la transición en non: hover, de lo contrario, podría obtener una representación extraña. De esta manera: hover hereda de non: hover.

La transición no funciona de px a% ni a auto. Necesitas usar la misma unidad de medida.

0