Manter a proporção da div, mas preencher a largura e a altura da tela em CSS?

122

Eu tenho um site para montar que tem uma proporção fixa de aproximadamente 16:9paisagem, como um vídeo.

Quero centralizá-lo e expandi-lo para preencher a largura e a altura disponíveis, mas nunca aumentar em ambos os lados.

Por exemplo:

  1. Uma página alta e fina teria o conteúdo esticando toda a largura, mantendo uma altura proporcional.
  2. Uma página curta e larga teria o conteúdo esticando a altura total, com uma largura proporcional.

Existem dois métodos que eu tenho estudado:

  1. Use uma imagem com a proporção correta para expandir um contêiner div, mas não consegui que ela se comportasse da mesma maneira nos principais navegadores.
  2. Definir um preenchimento inferior proporcional, mas que funcione apenas em relação à largura e ignore a altura. Ele fica cada vez maior com a largura e exibe barras de rolagem verticais.

Eu sei que você poderia fazer isso com JS facilmente, mas eu gostaria de uma solução CSS pura.

Alguma ideia?

Henry Gibson
fonte
sitepoint.com/… Defina container div comoposition: relative (default) height: 0 padding-<top/bottom>: H/W*100%
Ujjwal Singh

Respostas:

279

Use as novas unidades de janela CSS vw e vh(largura / altura da janela)

FIDDLE

Redimensione verticalmente e horizontalmente e você verá que o elemento sempre preencherá o tamanho máximo da viewport sem quebrar a proporção e sem as barras de rolagem!

CSS (PURO)

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 */
}

Se você deseja usar no máximo 90% de largura e altura da janela atual : FIDDLE

Além disso, o suporte ao navegador também é muito bom: IE9 +, FF, Chrome, Safaricaniuse

Danield
fonte
1
Esta é uma solução limpa realmente agradável. Idealmente, eu poderia fazer isso com o suporte ao navegador um pouco mais atrás. Eu acho que em um navegador mais antigo que você tinha acabado de ter um mínimo de largura / altura e configurá-lo para que - se tivesse barras de rolagem que assim seja
Henry Gibson
8
Uhm .. obrigado por me salvar 2 dias de trabalho e uma quantidade infinita de frustrações dos usuários. 1000+ se eu pudesse.
precisa
2
Você acabou de me poupar muito tempo. Obrigado imensamente.
Dylan Gattey
1
Infelizmente, recentes iOS versões não suportam , ou ter uma implementação quebrada de unidades visor ( vh, vw, vmin, vmax). No entanto, existe uma solução alternativa , que usa consultas de mídia para direcionar dispositivos iOS.
Tim
1
eu te amo. ótima solução!
Guttemberg
20

Apenas reformule Danielda resposta em um mix MENOS, para 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
fonte
11

Use uma consulta de mídia CSS @media junto com as unidades de viewport CSS vw e vhpara se adaptar à proporção da viewport. Isso tem o benefício de atualizar outras propriedades, como font-sizetambém.

Esse violino demonstra a proporção fixa da div e o texto que corresponde exatamente à sua escala.

O tamanho inicial é baseado na largura total:

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

Em seguida, quando a janela de exibição for maior que a proporção desejada, alterne para altura como medida básica:

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

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

Isso é muito semelhante à abordagemmax-width e max-height da @Danield, apenas mais flexível.

Paulo
fonte
7

Minha pergunta original para isso era como ter um elemento de aspecto fixo, mas ajustá-lo exatamente a um contêiner especificado, o que o torna um pouco complicado. Se você simplesmente deseja que um elemento individual mantenha sua proporção, é muito mais fácil.

O melhor método que encontrei é atribuir uma altura zero ao elemento e, em seguida, usar a porcentagem de preenchimento inferior para dar a ele altura. O preenchimento de porcentagem é sempre proporcional à largura de um elemento, e não à sua altura, mesmo se o preenchimento superior ou inferior.

Propriedades de preenchimento do W3C

Assim, você pode fornecer a um elemento uma porcentagem de largura para ficar dentro de um contêiner e, em seguida, preencher para especificar a proporção, ou em outros termos, a relação entre sua largura e altura.

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

Portanto, no exemplo acima, o objeto ocupa 80% da largura do contêiner e sua altura é 56,25% desse valor. Se a largura fosse de 160 px, o preenchimento inferior e, portanto, a altura seria de 90 px - um aspecto 16: 9.

O pequeno problema aqui, que pode não ser um problema para você, é que não há espaço natural dentro do seu novo objeto. Se você precisar inserir algum texto, por exemplo, e esse texto precisar assumir seus próprios valores de preenchimento, será necessário adicionar um contêiner dentro e especificar as propriedades nele.

Além disso, unidades vw e vh não são suportadas em alguns navegadores mais antigos; portanto, a resposta aceita à minha pergunta pode não ser possível para você e você pode precisar usar algo mais lo-fi.

Henry Gibson
fonte
Para evitar o uso de marcação extra aqui, você pode adicionar um elemento usando :: before ou :: after e atribuir a esse elemento uma altura de 0 e um fundo de preenchimento em relação à largura dos objetos e à largura 0 dos objetos. Isso terá o mesmo efeito, mas com menos marcação, e tornará a área completa do objeto utilizável para inserir conteúdo sem a necessidade de um contêiner.
Henry Gibson
6

Entendo que você pediu que gostaria de uma CSSsolução específica. Para manter a proporção, é necessário dividir a altura pela proporção desejada. 16: 9 = 1,77777777777878.

Para obter a altura correta para o contêiner, é necessário dividir a largura atual por 1,77777777777778. Como você não pode verificar o widthcontêiner com apenas CSSou dividir por uma porcentagem CSS, isso não é possível sem JavaScript(que eu saiba).

Eu escrevi um script de trabalho que manterá a proporção desejada.

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

Você pode usá-lo functionnovamente se desejar exibir outra coisa com uma proporção diferente usando

keepAspectRatio(id, width, height);
MCSharp
fonte
Obrigado pela sua resposta. Fazer isso no script é relativamente simples, mas eu realmente queria uma solução baseada em CSS. Eu quero uma solução responsiva confiável realmente ágil e às vezes acho que os eventos de redimensionamento em diferentes navegadores estão um pouco atrasados. Você acaba transformando um pouco depois que o redimensionamento do navegador ocorre, para que não proporcione a melhor experiência ao usuário. Quer algo que renderize no momento em que o navegador altere o tamanho, portanto, CSS puro.
Henry Gibson
1
@ Henry Gibson O script foi testado no Chrome, FF, IE, Opera e Safari. Todos funcionam rapidamente e são confiáveis, mesmo em versões mais antigas de navegadores. Todo navegador dispara seus próprios eventos. window.onresizeé acionado no momento em que o navegador altera o tamanho.
MCSharp
lembrar os usuários finais não redimensionar navegadores quase tanto como os desenvolvedores fazer
Simon_Weaver
1

A resposta de Danield é boa, mas só pode ser usada quando a div preenche toda a viewport, ou usando um pouco de calc, pode ser usada se a largura e altura do outro conteúdo na viewport for conhecida.

No entanto, combinando o margin-bottomtruque com o método na resposta mencionada acima, o problema pode ser reduzido a apenas ter que saber a altura do outro conteúdo. Isso é útil se você tiver um cabeçalho de altura fixa, mas a largura da barra lateral, por exemplo, não for conhecida.

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>

Aqui está um jsfiddle usando scss , o que torna mais óbvio a origem dos valores.

Cameron Martin
fonte
0

Com base na resposta de Daniel , escrevi este mix SASS com interpolation ( #{}) para o iframe de um vídeo do youtube que está dentro de uma caixa de diálogo modal do 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>

Isso sempre exibirá o vídeo sem as partes pretas que o youtube adiciona ao iframe quando a largura ou a altura não são proporcionais ao vídeo. Notas:

  • $sides-adjustment é um pequeno ajuste para compensar uma pequena parte dessas partes pretas que aparecem nas laterais;
  • em dispositivos de altura muito baixa (<480px), o modal padrão ocupa muito espaço, então decidi:
    1. esconder o .modal-footer;
    2. ajuste o posicionamento do .modal-dialog;
    3. reduza os tamanhos do .modal-header.

Depois de muitos testes, isso está funcionando perfeitamente para mim agora.

CPHPython
fonte
-1

aqui uma solução baseada na solução @Danield, que funciona mesmo dentro de uma div.

Nesse exemplo, estou usando o Angular, mas ele pode se mover rapidamente para o vanilla JS ou outra estrutura.

A idéia principal é apenas mover a solução @Danield para um iframe vazio e copiar as dimensões após a alteração do tamanho da janela do iframe.

Esse iframe deve ajustar as dimensões ao seu elemento pai.

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

Aqui estão algumas capturas de tela:

insira a descrição da imagem aqui

insira a descrição da imagem aqui

insira a descrição da imagem aqui

Angel Fraga Parodi
fonte