Conserver les proportions de div mais remplir la largeur et la hauteur de l'écran en CSS

122

J'ai un site à créer qui a un rapport hauteur / largeur fixe d'environ 16:9paysage, comme une vidéo.

Je veux qu'il soit centré et élargi pour remplir la largeur disponible et la hauteur disponible, mais ne jamais grossir d'un côté ou de l'autre.

Par exemple:

  1. Une page haute et fine aurait le contenu s'étendant sur toute la largeur tout en conservant une hauteur proportionnelle.
  2. Une page courte et large aurait le contenu s'étendant sur toute la hauteur, avec une largeur proportionnelle.

J'ai étudié deux méthodes:

  1. Utilisez une image avec le bon rapport hauteur / largeur pour développer un conteneur div, mais je n'ai pas pu le faire se comporter de la même manière dans les principaux navigateurs.
  2. Définir un rembourrage inférieur proportionnel, mais qui ne fonctionne que relativement à la largeur et ignore la hauteur. Il ne cesse de s'agrandir avec la largeur et affiche des barres de défilement verticales.

Je sais que vous pourriez le faire avec JS assez facilement, mais j'aimerais une solution CSS pure.

Des idées?

Henry Gibson
la source
sitepoint.com/... Définir le conteneur div surposition: relative (default) height: 0 padding-<top/bottom>: H/W*100%
Ujjwal Singh

Réponses:

280

Utilisez les nouvelles unités de fenêtre CSS vw et vh(largeur de la fenêtre / hauteur de la fenêtre)

VIOLON

Redimensionnez verticalement et horizontalement et vous verrez que l'élément remplira toujours la taille maximale de la fenêtre sans casser le rapport et sans barres de défilement!

(PURE) 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 vous souhaitez utiliser un maximum de 90% de largeur et de hauteur de la fenêtre: FIDDLE

De plus, la prise en charge des navigateurs est également très bonne: IE9 +, FF, Chrome, Safari- caniuse

Danield
la source
1
C'est une très belle solution propre. Je pourrais idéalement faire avec le support du navigateur un peu plus en arrière. Je suppose que sur un navigateur plus ancien, vous auriez juste une largeur / hauteur minimale et la
Henry Gibson
8
Uhm .. merci de m'avoir épargné 2 jours de travail et une quantité infinie de frustrations pour les utilisateurs. 1000+ si je pouvais.
Schoening
2
Vous venez de me faire gagner beaucoup de temps. Merci infiniment.
Dylan Gattey
1
Malheureusement, les versions récentes iOS ne prend pas en charge , ou une mise en œuvre brisée d'unités de fenêtres ( vh, vw, vmin, vmax). Cependant, il existe une solution de contournement , qui utilise des requêtes multimédias pour cibler les appareils iOS.
Tim
1
je t'aime. excellente solution!
Guttemberg
20

Simplement reformuler Danieldla réponse dans un mixin MOINS, pour une utilisation ultérieure:

// 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
la source
11

Utilisez une requête multimédia CSS @media avec les unités de la fenêtre CSS vw et vhpour vous adapter au rapport hauteur / largeur de la fenêtre. Cela a l'avantage de mettre à jour d'autres propriétés, comme font-sizeaussi.

Ce violon montre le rapport hauteur / largeur fixe pour le div et le texte correspondant exactement à son échelle.

La taille initiale est basée sur la pleine largeur:

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

Ensuite, lorsque la fenêtre est plus large que le rapport hauteur / largeur souhaité, passez à la hauteur comme mesure de 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);
  }
}

Ceci est très similaire dans la veine à l' approchemax-width et de @Danield, juste plus flexible.max-height

Paul
la source
7

Ma question initiale était de savoir comment avoir à la fois un élément d'aspect fixe, mais l'adapter exactement à un conteneur spécifié, ce qui le rend un peu fastidieux. Si vous voulez simplement qu'un élément individuel conserve ses proportions, c'est beaucoup plus facile.

La meilleure méthode que j'ai rencontrée consiste à donner à un élément une hauteur nulle, puis à utiliser le pourcentage de remplissage en bas pour lui donner de la hauteur. Le pourcentage de remplissage est toujours proportionnel à la largeur d'un élément, et non à sa hauteur, même si son remplissage supérieur ou inférieur.

Propriétés de remplissage W3C

Ainsi, en utilisant cela, vous pouvez donner à un élément une largeur en pourcentage pour qu'il soit placé dans un conteneur, puis un remplissage pour spécifier le rapport hauteur / largeur, ou en d'autres termes, la relation entre sa largeur et sa hauteur.

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

Ainsi, dans l'exemple ci-dessus, l'objet prend 80% de la largeur du conteneur, puis sa hauteur est de 56,25% de cette valeur. Si sa largeur était de 160 pixels, le rembourrage inférieur, et donc la hauteur, serait de 90 pixels - un aspect 16: 9.

Le léger problème ici, qui n'est peut-être pas un problème pour vous, est qu'il n'y a pas d'espace naturel à l'intérieur de votre nouvel objet. Si vous avez besoin de mettre du texte par exemple et que ce texte doit prendre ses propres valeurs de remplissage, vous devez ajouter un conteneur à l'intérieur et spécifier les propriétés à l'intérieur.

De plus, les unités vw et vh ne sont pas prises en charge sur certains navigateurs plus anciens, donc la réponse acceptée à ma question pourrait ne pas vous être possible et vous devrez peut-être utiliser quelque chose de plus lo-fi.

Henry Gibson
la source
Pour éviter d'utiliser un balisage supplémentaire ici, vous pouvez ajouter un élément en utilisant :: before ou :: after et donner à cet élément une hauteur de 0 et un padding-bottom par rapport à la largeur des objets et 0 de sa propre largeur. Cela aura le même effet, mais avec moins de balisage, et rendra la zone d'objet complète utilisable pour insérer du contenu sans avoir besoin d'un conteneur.
Henry Gibson
6

Je crois comprendre que vous avez demandé que vous souhaitiez une CSSsolution spécifique. Pour conserver le rapport hauteur / largeur, vous devez diviser la hauteur par le rapport hauteur / largeur souhaité. 16: 9 = 1,777777777778.

Pour obtenir la hauteur correcte du conteneur, vous devez diviser la largeur actuelle par 1,777777777778. Puisque vous ne pouvez pas vérifier le widthcontenu du conteneur avec juste CSSou diviser par un pourcentage CSS, cela n'est pas possible sans JavaScript(à ma connaissance).

J'ai écrit un script de travail qui conservera le rapport hauteur / largeur souhaité.

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

Vous pouvez utiliser à functionnouveau si vous souhaitez afficher autre chose avec un rapport différent en utilisant

keepAspectRatio(id, width, height);
MCSharp
la source
Merci pour votre réponse. Faire cela dans un script est relativement simple, mais je voulais vraiment une solution basée sur CSS. Je veux une solution réactive fiable et vraiment rapide, et je trouve parfois que les événements de redimensionnement dans différents navigateurs sont un peu lents. Vous finissez par faire votre transformation un peu après le redimensionnement du navigateur, donc cela ne donne pas la meilleure expérience utilisateur. Vous voulez quelque chose qui rende le moment où le navigateur change de taille, donc du CSS pur.
Henry Gibson
1
@Henry Gibson Le script a été testé dans Chrome, FF, IE, Opera et Safari. Tous fonctionnent rapidement et sont fiables même dans les anciennes versions de navigateurs. Chaque navigateur déclenche ses propres événements. window.onresizeest déclenché au moment où le navigateur change de taille.
MCSharp
rappelez-vous que les utilisateurs finaux ne redimensionnent pas les navigateurs autant que les développeurs
Simon_Weaver
3

Il y a maintenant une nouvelle propriété CSS spécifié pour répondre ceci: object-fit.

http://docs.webplatform.org/wiki/css/properties/object-fit

La prise en charge des navigateurs fait encore un peu défaut ( http://caniuse.com/#feat=object-fit ) - fonctionne actuellement dans une certaine mesure dans la plupart des navigateurs sauf Microsoft - mais étant donné le temps, c'est exactement ce qui était requis pour cette question.

Henry Gibson
la source
1

La réponse de Danield est bonne, mais elle ne peut être utilisée que lorsque la div remplit toute la fenêtre, ou en utilisant un peu decalc , peut être utilisée si la largeur et la hauteur de l'autre contenu dans la fenêtre sont connues.

Cependant, en combinant l' margin-bottomastuce avec le procédé dans la réponse susmentionnée, le problème peut être réduit à simplement avoir à connaître la hauteur de l'autre contenu. Ceci est utile si vous avez un en-tête à hauteur fixe, mais que la largeur de la barre latérale, par exemple, n'est pas connue.

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>

Ici, c'est dans un jsfiddle utilisant scss , ce qui rend plus évident la provenance des valeurs.

Cameron Martin
la source
0

Sur la base de la réponse de Daniel , j'ai écrit ce mixin SASS avec interpolation ( #{}) pour l'iframe d'une vidéo youtube qui se trouve dans une boîte de dialogue modale 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}
  }
}

Usage:

.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>

Cela affichera toujours la vidéo sans les parties noires que YouTube ajoute à l'iframe lorsque la largeur ou la hauteur n'est pas proportionnelle à la vidéo. Remarques:

  • $sides-adjustment est un léger ajustement pour compenser une infime partie de ces parties noires apparaissant sur les côtés;
  • dans les appareils de très faible hauteur (<480px), le modal standard prend beaucoup de place, j'ai donc décidé de:
    1. cacher le .modal-footer;
    2. ajuster le positionnement du .modal-dialog;
    3. réduire la taille des fichiers .modal-header.

Après de nombreux tests, cela fonctionne parfaitement pour moi maintenant.

CPHPython
la source
-1

voici une solution basée sur la solution @Danield, qui fonctionne même au sein d'un div.

Dans cet exemple, j'utilise Angular mais il peut rapidement passer à vanilla JS ou à un autre framework.

L'idée principale est simplement de déplacer la solution @Danield dans un iframe vide et de copier les dimensions après le changement de la taille de la fenêtre iframe.

Cette iframe doit s'adapter aux dimensions de son élément père.

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

Voici quelques captures d'écran:

entrez la description de l'image ici

entrez la description de l'image ici

entrez la description de l'image ici

Angel Fraga Parodi
la source