¿Mantener la relación de aspecto de div pero llenar el ancho y la altura de la pantalla en CSS?

122

Tengo un sitio para armar que tiene una relación de aspecto fija de aproximadamente 16:9paisaje, como un video.

Quiero centrarlo y expandirlo para llenar el ancho disponible y la altura disponible, pero nunca crecer más en ninguno de los lados.

Por ejemplo:

  1. Una página alta y delgada haría que el contenido se extendiera por todo el ancho manteniendo una altura proporcional.
  2. Una página corta y ancha tendría el contenido extendiéndose por toda la altura, con un ancho proporcional.

Hay dos métodos que he estado buscando:

  1. Use una imagen con la relación de aspecto correcta para expandir un contenedor div, pero no pude lograr que se comporte de la misma manera en los principales navegadores.
  2. Establecer un relleno inferior proporcional, pero que solo funciona en relación con el ancho e ignora la altura. Sigue creciendo con el ancho y muestra barras de desplazamiento verticales.

Sé que podrías hacer esto con JS con bastante facilidad, pero me gustaría una solución CSS pura.

¿Algunas ideas?

Henry Gibson
fuente
sitepoint.com/… Establecer div del contenedor enposition: relative (default) height: 0 padding-<top/bottom>: H/W*100%
Ujjwal Singh

Respuestas:

280

Utilice las nuevas unidades de ventana gráfica CSS vw y vh(ancho / altura de la ventana gráfica)

VIOLÍN

Cambie el tamaño vertical y horizontalmente y verá que el elemento siempre llenará el tamaño máximo de la ventana gráfica sin romper la proporción y sin barras de desplazamiento.

(PURO) CSS

div
{
    width: 100vw; 
    height: 56.25vw; /* height:width ratio = 9/16 = .5625  */
    background: pink;
    max-height: 100vh;
    max-width: 177.78vh; /* 16/9 = 1.778 */
    margin: auto;
    position: absolute;
    top:0;bottom:0; /* vertical center */
    left:0;right:0; /* horizontal center */
}

Si desea utilizar un máximo de, por ejemplo, el 90% de ancho y alto de la ventana gráfica : FIDDLE

Además, la compatibilidad del navegador también es bastante buena: IE9 +, FF, Chrome, Safari .

Danield
fuente
1
Esta es una solución limpia realmente agradable. Sin embargo, lo ideal sería que lo hiciera con el soporte del navegador un poco más atrás. Supongo que en un navegador más antiguo solo tendría un ancho / alto mínimo y lo establecería en eso, si tuviera barras de desplazamiento, que así sea
Henry Gibson
8
Uhm ... gracias por ahorrarme 2 días de trabajo y una cantidad infinita de frustraciones para los usuarios. 1000+ si pudiera.
Schoening
2
Me acabas de ahorrar tanto tiempo. Muchísimas gracias.
Dylan Gattey
1
Por desgracia, las últimas versiones de iOS no son compatibles , o tener una implementación roto de unidades de ventana gráfica ( vh, vw, vmin, vmax). Sin embargo, existe una solución alternativa , que utiliza consultas de medios para apuntar a dispositivos iOS.
Tim
1
te quiero. gran solucion!
Guttemberg
20

Simplemente reformulando Danieldla respuesta en un mixin MENOS, para un uso posterior:

// Mixin for ratio dimensions    
.viewportRatio(@x, @y) {
  width: 100vw;
  height: @y * 100vw / @x;
  max-width: @x / @y * 100vh;
  max-height: 100vh;
}

div {

  // Force a ratio of 5:1 for all <div>
  .viewportRatio(5, 1);

  background-color: blue;
  margin: 0;
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
}
Christophe Marois
fuente
11

Utilice una consulta de medios CSS @media junto con las unidades de ventana gráfica CSS vw y vhpara adaptarse a la relación de aspecto de la ventana gráfica. Esto tiene la ventaja de actualizar otras propiedades, como font-sizetambién.

Este violín demuestra la relación de aspecto fija para el div y el texto coincide exactamente con su escala.

El tamaño inicial se basa en el ancho completo:

div {
  width: 100vw; 
  height: calc(100vw * 9 / 16);

  font-size: 10vw;

  /* align center */
  margin: auto;
  position: absolute;
  top: 0px; right: 0px; bottom: 0px; left: 0px;

  /* visual indicators */
  background:
    url(data:image/gif;base64,R0lGODlhAgAUAIABAB1ziv///yH5BAEAAAEALAAAAAACABQAAAIHhI+pa+EPCwA7) repeat-y center top,
    url(data:image/gif;base64,R0lGODlhFAACAIABAIodZP///yH5BAEAAAEALAAAAAAUAAIAAAIIhI8Zu+nIVgEAOw==) repeat-x left center,
    silver;
}

Luego, cuando la ventana gráfica sea más ancha que la relación de aspecto deseada, cambie a la altura como medida base:

/* 100 * 16/9 = 177.778 */
@media (min-width: 177.778vh) {
  div {
    height: 100vh;
    width: calc(100vh * 16 / 9);

    font-size: calc(10vh * 16 / 9);
  }
}

Esto es muy similar al enfoquemax-width y de @Danield, solo que más flexible.max-height

Pablo
fuente
7

Mi pregunta original para esto era cómo tener ambos un elemento de un aspecto fijo, pero encajarlo exactamente dentro de un contenedor específico, lo que lo hace un poco complicado. Si simplemente desea que un elemento individual mantenga su relación de aspecto, es mucho más fácil.

El mejor método con el que me he encontrado es dar una altura cero al elemento y luego usar el porcentaje de relleno en la parte inferior para darle altura. El relleno porcentual es siempre proporcional al ancho de un elemento, y no a su altura, incluso si es el relleno superior o inferior.

Propiedades de relleno W3C

Entonces, utilizando eso, puede darle a un elemento un porcentaje de ancho para que se siente dentro de un contenedor, y luego un relleno para especificar la relación de aspecto, o en otros términos, la relación entre su ancho y alto.

.object {
    width: 80%; /* whatever width here, can be fixed no of pixels etc. */
    height: 0px;
    padding-bottom: 56.25%;
}
.object .content {
    position: absolute;
    top: 0px;
    left: 0px;
    height: 100%;
    width: 100%;
    box-sizing: border-box;
    -moz-box-sizing: border-box;
    padding: 40px;
}

Entonces, en el ejemplo anterior, el objeto ocupa el 80% del ancho del contenedor y luego su altura es el 56,25% de ese valor. Si su ancho fuera de 160 px, entonces el relleno inferior y, por lo tanto, la altura sería de 90 px, un aspecto de 16: 9.

El pequeño problema aquí, que puede no ser un problema para usted, es que no hay espacio natural dentro de su nuevo objeto. Si necesita poner algo de texto, por ejemplo, y ese texto necesita tomar sus propios valores de relleno, debe agregar un contenedor dentro y especificar las propiedades allí.

Además, las unidades vw y vh no son compatibles con algunos navegadores más antiguos, por lo que es posible que la respuesta aceptada a mi pregunta no sea posible para usted y que tenga que usar algo más lo-fi.

Henry Gibson
fuente
Para evitar el uso de marcado adicional aquí, puede agregar un elemento usando :: before o :: after y darle a ese elemento una altura de 0 y un padding-bottom en relación con el ancho del objeto y el ancho 0 propio. Eso tendrá el mismo efecto pero con menos marcado y hará que el área completa del objeto sea utilizable para colocar contenido sin necesidad de un contenedor.
Henry Gibson
6

Entiendo que solicitó que le gustaría una CSSsolución específica. Para mantener la relación de aspecto, deberá dividir la altura por la relación de aspecto deseada. 16: 9 = 1,7777777777778.

Para obtener la altura correcta para el contenedor, necesitaría dividir el ancho actual por 1.777777777778. Dado que no puede verificar el valor widthdel contenedor con solo CSSo dividir por un porcentaje CSS, esto no es posible sin JavaScript(que yo sepa).

Escribí un guión funcional que mantendrá la relación de aspecto deseada.

HTML

<div id="aspectRatio"></div>

CSS

body { width: 100%; height: 100%; padding: 0; margin: 0; }
#aspectRatio { background: #ff6a00; }

JavaScript

window.onload = function () {
    //Let's create a function that will scale an element with the desired ratio
    //Specify the element id, desired width, and height
    function keepAspectRatio(id, width, height) {
        var aspectRatioDiv = document.getElementById(id);
        aspectRatioDiv.style.width = window.innerWidth;
        aspectRatioDiv.style.height = (window.innerWidth / (width / height)) + "px";
    }

    //run the function when the window loads
    keepAspectRatio("aspectRatio", 16, 9);

    //run the function every time the window is resized
    window.onresize = function (event) {
        keepAspectRatio("aspectRatio", 16, 9);
    }
}

Puede usar de functionnuevo si desea mostrar algo más con una proporción diferente usando

keepAspectRatio(id, width, height);
MCSharp
fuente
Gracias por tu respuesta. Hacer esto en un script es relativamente sencillo, pero realmente quería una solución basada en CSS. Quiero una solución de respuesta confiable realmente ágil, y encuentro que a veces los eventos de cambio de tamaño en diferentes navegadores son un poco retrasados. Terminas haciendo tu transformación un poco después de que se haya producido el cambio de tamaño del navegador, por lo que no brinda la mejor experiencia de usuario. Quiere algo que muestre el momento en que el navegador cambia de tamaño, por lo tanto, CSS puro.
Henry Gibson
1
@Henry Gibson El script se ha probado en Chrome, FF, IE, Opera y Safari. Todos funcionan de forma ágil y son fiables incluso en versiones anteriores de navegadores. Cada navegador activa sus propios eventos. window.onresizese activa en el momento en que el navegador cambia de tamaño.
MCSharp
recuerde que los usuarios finales no
cambian el
1

La respuesta de Danield es buena, pero solo se puede usar cuando el div llena toda la ventana gráfica, o usando un poco de calc, se puede usar si se conoce el ancho y la altura del otro contenido en la ventana gráfica.

Sin embargo, al combinar el margin-bottomtruco con el método en la respuesta antes mencionada, el problema se puede reducir a tener que conocer la altura del otro contenido. Esto es útil si tiene un encabezado de altura fija, pero el ancho de la barra lateral, por ejemplo, no se conoce.

body {
  margin: 0;
  margin-top: 100px; /* simulating a header */
}

main {
  margin: 0 auto;
  max-width: calc(200vh - 200px);
}

section {
  padding-bottom: 50%;
  position: relative;
}

div {
  position:absolute;
  background-color: red;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
}
<main>
  <section>
    <div></div>
  </section>
</main>

Aquí está en un jsfiddle usando scss , lo que hace que sea más obvio de dónde provienen los valores.

Cameron Martin
fuente
0

Basado en la respuesta de Daniel , escribí este mixin SASS con interpolación ( #{}) para el iframe de un video de YouTube que está dentro de un diálogo modal de Bootstrap:

@mixin responsive-modal-wiframe($height: 9, $width: 16.5,
                                $max-w-allowed: 90, $max-h-allowed: 60) {
  $sides-adjustment: 1.7;

  iframe {
    width: #{$width / $height * ($max-h-allowed - $sides-adjustment)}vh;
    height: #{$height / $width * $max-w-allowed}vw;
    max-width: #{$max-w-allowed - $sides-adjustment}vw;
    max-height: #{$max-h-allowed}vh;
  }

  @media only screen and (max-height: 480px) {
    margin-top: 5px;
    .modal-header {
      padding: 5px;
      .modal-title {font-size: 16px}
    }
    .modal-footer {display: none}
  }
}

Uso:

.modal-dialog {
  width: 95vw; // the modal should occupy most of the screen's available space
  text-align: center; // this will center the titles and the iframe

  @include responsive-modal-wiframe();
}

HTML:

<div class="modal">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
        <h4 class="modal-title">Video</h4>
      </div>
      <div class="modal-body">
        <iframe src="https://www.youtube-nocookie.com/embed/<?= $video_id ?>?rel=0" frameborder="0"
          allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
          allowfullscreen></iframe>
      </div>
      <div class="modal-footer">
        <button class="btn btn-danger waves-effect waves-light"
          data-dismiss="modal" type="button">Close</button>
      </div>
    </div>
  </div>
</div>

Esto siempre mostrará el video sin esas partes negras que youtube agrega al iframe cuando el ancho o alto no es proporcional al video. Notas:

  • $sides-adjustment es un ligero ajuste para compensar una pequeña parte de estas partes negras que aparecen a los lados;
  • en dispositivos de muy baja altura (<480px), el modal estándar ocupa mucho espacio, así que decidí:
    1. ocultar el .modal-footer;
    2. ajustar la posición del .modal-dialog;
    3. reducir los tamaños del .modal-header.

Después de muchas pruebas, esto me está funcionando perfectamente ahora.

CPHPython
fuente
-1

aquí una solución basada en la solución @Danield, que funciona incluso dentro de un div.

En ese ejemplo, estoy usando Angular pero puede pasar rápidamente a Vanilla JS u otro marco.

La idea principal es simplemente mover la solución @Danield dentro de un iframe vacío y copiar las dimensiones después de que cambie el tamaño de la ventana del iframe.

Ese iframe tiene que ajustarse a las dimensiones de su elemento padre.

https://stackblitz.com/edit/angular-aspect-ratio-by-iframe?file=src%2Findex.html

Aquí algunas capturas de pantalla:

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Ángel Fraga Parodi
fuente