Search code examples
cssbackground-imagebackground-positionbackground-size

Background size / position - cover the width, and a minimum offset from the top


I have a container with a background image. I would like it to:

  1. fill the width of the container at all times
  2. if the height of the container gets bigger, the image should stick to the bottom and increase the gap at the top
  3. if the height of the container is shorter than the image, it should maintain a 20px gap at the top, hiding the bottom of the image (as it overflows)
  4. I do not know the height/width of the image in my CSS
  5. The image should not skew (aspect ratio should be maintained)

It seems like what I need is contain on the width, but not height, but then I'm not sure how to do the minimum offset from the top.

See my attempts here:

background-size: 100% auto;
background-position: left 20px;

/* works when the height is shorter than the image
    ------------------
    |                |
    |                |
    |................| 
    |.              .|
    |.              .|
    |.              .|
    |.     .bg      .|
    |.              .|
    |.              .|
    |.              .|
    ------------------
     .              . <- this is clipped off, that is fine.
     ................
*/
/* 
    does not work when the height is larger than the image
    ------------------
    |                |
    |                |
    |................|
    |.              .|
    |.              .|
    |.              .|
    |.     .bg      .|
    |.              .|
    |.              .|
    |.              .|
    |.              .|
    |.              .|
    |................| <- I want this to be stuck to the bottom
    |                |
    |                |
    ------------------
*/

background-size: 100% auto;
background-position: left bottom;

/* works when the height is taller than the image

    ------------------
    |                |
    |                |
    |                |
    |................| 
    |.              .|
    |.              .|
    |.              .|
    |.     .bg      .|
    |.              .|
    |.              .|
    |.              .|
    |.              .|
    |................| <- stuck to the bottom, good!
    ------------------
*/
/* 
    does not work when the height is shorter than the image
     ................
     .              .
     .              .
     .              .
     .     .bg      .
     .              . <- This is clipped off
    ------------------
    |.              .|
    |.              .|
    |.              .|
    |................| 
    ------------------
*/

background-size: cover;
background-position: left 20px;

/* works when the width is wider than the image (scales it up)
    ------------------
    |                |
    |                |
    |................| 
    |.              .|
    |.              .|
    |.              .|
    |.     .bg      .|
    |.              .|
    |.              .|
    |.              .|
    ------------------
     .              . <- this is clipped off, that is fine.
     ................
*/
/* 
    does not work when the width is narrower than the image, and the height is taller
    ------------------
    |                |
    |                |
    |................|.....
    |.               |    . <- I do not want the width to overflow
    |.               |    .
    |.               |    .
    |.       .bg     |    .
    |.               |    .
    |.               |    .
    |.               |    .
    |.               |    .
    |.               |    .
    |................|..... 
    ------------------
*/

What I want:

/* if the container is shorter than the image
    ------------------
    |                |
    |                |
    |................| <- 20px offset from the top, full width of the container
    |.              .|
    |.              .|
    |.              .|
    |.     .bg      .|
    |.              .|
    |.              .|
    |.              .|
    ------------------
     .              . <- this is clipped off, that is fine.
     ................
*/
/* 
    if the container is larger than the image
    ------------------
    |                |
    |                |
    |                |
    |                |
    |................| <- full width of the container
    |.              .|
    |.              .|
    |.              .|
    |.     .bg      .|
    |.              .|
    |.              .|
    |.              .|
    |.              .|
    |.              .|
    |................| <- stuck to the bottom
    ------------------
*/

Snippet for testing:

/* width/heights are for illustrative purposes - actual width-heights are unknown */
  
div {
  background-size: cover;
  background-position: left 20px;
  
  background-repeat: no-repeat;
  margin-bottom: 50px;
  border: 1px solid red;

  width: 200px;
  height: 300px;
}

.taller {
  height: 500px;
}

.shorter {
  height: 100px;
}

.wider {
  width: 400px;
}

.narrower {
  width: 200px;
}
<!-- this image size and the container size are variable depending on author input - these are included as test cases, but I do not know the sizes -->

<strong>Same Size</strong>
<div style="background-image: url('https://picsum.photos/200/300');"></div>

<strong>Taller</strong>
<div class="taller" style="background-image: url('https://picsum.photos/200/300');"></div>

<strong>Wider</strong>
<div class="wider" style="background-image: url('https://picsum.photos/200/300');"></div>

<strong>Narrower</strong>
<div class="narrower" style="background-image: url('https://picsum.photos/200/300');"></div>

<strong>Shorter</strong>
<div class="shorter" style="background-image: url('https://picsum.photos/200/300');"></div>

<strong>Taller &amp; Wider</strong>
<div class="taller wider" style="background-image: url('https://picsum.photos/200/300');"></div>

<strong>Taller &amp; Narrower</strong>
<div class="taller narrower" style="background-image: url('https://picsum.photos/200/300');"></div>

<strong>Shorter &amp; Wider</strong>
<div class="shorter wider" style="background-image: url('https://picsum.photos/200/300');"></div>

<strong>Shorter &amp; Narrower</strong>
<div class="shorter narrower" style="background-image: url('https://picsum.photos/200/300');"></div>


Solution

  • It is possible using just CSS, but you need to change a little bit your markup, by adding container div. I made an container resizable to simplify testing of this solution.

    .container {
      resize: both;
      overflow: auto;
    
      position: relative;
      width: 200px;
      height: 200px;
      padding-top: 20px; 
      display: flex;
      justify-content: flex-end;
    }
    
    .image {
      margin-top: auto;
      width: 100%;
      height: 0;
      padding-top: 150%;
      background-size: contain;
      background-repeat: no-repeat;
    }
    <div class="container">
      <div class="image" style="background-image: url('https://picsum.photos/200/300');"></div>
    </div>

    How it works:

    .container has:

    • display: flex, it is important to allow a "margin magic" to work,

    • justify-content: flex-end to push the div with image to bottom;

    • padding-top: 20px to always keep the empty space you wanted

    .image has:

    • width: 100% to fill space horizontaly,

    • height: 0 and padding-top: 150% to keep the image proportions ratio,

    • background-repeat: no-repeat so image is used just once, and with contain it fill div horizontally,

    • margin-top: auto with display: flex of parent allows vertical move of div, but constrained by parent padding,

    EDIT in response to OP comment

    It seems, that you still can use this method even if you don't know the image width and height, and therefore cannot calculate ratio - if you modify it a little bit. It actually make it even simpler

    .container {
      resize: both;
      overflow: auto;
    
      position: relative;
      width: 200px;
      height: 200px;
      padding-top: 20px; 
      display: flex;
      justify-content: flex-end;
    }
    
    .image {
      margin-top: auto;
      width: 100%;
      height: auto;
    }
    <div class="container">
      <img class="image" src='https://picsum.photos/200/300'></div>
    </div>