Search code examples
csscenteringaspect-ratio

CSS to create element with fixed ratio


I want to create an HTML page with:

  • A fixed-width, full-height Navigation pane on the left
  • A square element in the centre of the remaining area

I want this square to be as big as possible, expanding to fill the area not taken up by the navigation pane.

I have a JavaScript solution for this (see below and as a jsFiddle), but I'm hoping that it is possible to do this as a CSS only solution.


<!DOCTYPE html>
<html lang=en>
<head>
  <style>
    html, body {
      margin: 0;
      height: 100%;
      background-color: #fff;
    }
    nav {
      height: 100%;
      width: 96px;
      background-color: #666;
    }
    main {
      position: absolute;
      background-color: #000;
      color: #fff;
    }
  </style>  
</head>

<body>
<nav>
  Navigation
</nav>

<main>
  This should be square
</main>

<script>
;(function createSquareArea() {
  var main = document.querySelector("main")
  var nav = document.querySelector("nav")
  var navWidth = nav.getBoundingClientRect().width
  var debounceDelay = 100
  var timeout

  window.onresize = windowResized
  maintainRatio()

  function windowResized() {
    if (timeout) {
      window.clearTimeout(timeout)
    }
    timeout = window.setTimeout(maintainRatio, debounceDelay)
  }

  function maintainRatio() {
    timeout = 0

    var windowHeight = window.innerHeight
    var mainWidth = window.innerWidth - navWidth
    var minDimension = Math.min(windowHeight, mainWidth)

    var left = (mainWidth - minDimension) / 2 + navWidth
    var top = (windowHeight - minDimension) / 2

    main.style.left = left + "px"
    main.style.top = top + "px"
    main.style.width = minDimension + "px"
    main.style.height = minDimension + "px"
  }
})()
</script>
</body>
</html>

Solution

  • JSFiddle: https://jsfiddle.net/ymzq6zm0/7/

    HTML

    <nav>
      Navigation
    </nav>
    
    <main>
      <div class="sc">
        This should be square
      </div>
    </main>
    

    CSS

       html, body {
          margin: 0;
          height: 100%;
          background-color: #fff;
        }
        .wrapper {
          display: flex;
          align-items: center;
          height: 100%;
        }
        nav {
          float: left;
          height: 100%;
          width: 96px;
          background-color: #666;
        }
        main {
          float: left;
          width: calc(100% - 96px);
          height: 100vmin;
          max-height: calc(100vw - 96px);
        }
        .sc {
          margin: 0 auto;
          background-color: #000;
          color: #fff;
          height: 100vmin;
          width: 100vmin;
          max-width: 100%;
          max-height: calc(100vw - 96px);
        }
    

    How it works

    Our main objective here is to:

    • Center align .sc
    • Align .sc vertically center
    • Make sure .sc is always a sqaure
    • Make .sc responsive

    The square is highly responsive as it changes its height and width according to the window's or view port's height and width. We need to use vw (viewport's width) and vmin (lowest value between viewport's height and width). Read more about these units here: https://css-tricks.com/viewport-sized-typography/

    To make .sc a square, we need to make sure its width and height are always equal. Since the ratio of height and width of viewport is not always the same, we need to find out the lowest value between these two and assign them to .sc which can be done using the vmin unit mentioned above.

    The square should always remain centered in the remaining area after the navigation on the left, never cross the remaining area and resize accordingly.

    This can be accomplished the following codes:

        nav {
          float: left;
          height: 100%;
          width: 96px;
          background-color: #666;
        }
        main {
          float: left;
          width: calc(100% - 96px);
          height: 100vmin;
          max-height: calc(100vw - 96px);
        }
        .sc {
          margin: 0 auto;
          background-color: #000;
          color: #fff;
          height: 100vmin;
          width: 100vmin;
          max-width: 100%;
          max-height: calc(100vw - 96px);
        }
    

    main is the remaining area after nav. We make sure of this by using the calc property to subtract the nav width from 100%.

    The .sc is placed inside main and we have added the extra max-width and max-height properties to make sure .sc always resizes itself according to main.

    max-height: calc(100vw - 96px); property of .sc is always equal to width: calc(100% - 96px); property of main. They both calculate the same values.

    By adding max-width: 100%; to .sc we make sure it's maximum width is equal to that of main.

    Now since, both the max-height and max-width along with width and height of .sc remain the same, it will always be a square.

    At the end we put both nav and main inside .wrapper which is a flexbox and has align-items: center; property. This will ensure that the square is always vertically centered with respect to the nav.