Dynamically centering an image via CSS

Well, imagine this: You have website with a big background image, and you want it to work well across all screen sizes. That is, it should fill the whole viewport, no matter the screen size or aspect ratio. You also want to do this using CSS only.

In this post, we will first cover how to do this for background-image, and at the end, I will extend the solution to <img> tags.

TLDR – I just want the solution: Thats great! You can find the solution here.

If you’re interested in the details, read on!

Using background-size: cover to auto-size the image

Our first attempt might be using just background-size: cover to make the image always fill its container:

<div class="container">
</div>
.container {
    background-image: url('/assets/img/posts/2024-02/pexels-pixabay-235990.jpg');
    background-size: cover;
    background-repeat: no-repeat;
}

The result will look something like below. Note the following:

  • Good: The image always fills the whole container (due to background-size: cover)
  • Bad: We basically zoom in on the top left corner of the image. This lets the main motif of the image disappear in some cases, which is arguably not ideal.
Show additional boilerplate code

The following code is used throughout all examples to to allow playing around with the size of the image container.

.container {
    width: 100%;
    height: 100%;
}

.resizeable {
    overflow: hidden;
    resize: both;
    border: 1px solid black;
}

.natural-size {
    --image-aspect-ratio: calc(3848 / 2565);
    --initial-width: 200px;
    --initial-height: calc(var(--initial-width) / var(--image-aspect-ratio));

    width: var(--initial-width);
    height: var(--initial-height);
}

.half-width {
    width: calc(0.5 * var(--initial-width));
}

.half-height {
    height: calc(0.5 * var(--initial-height));
}

/* Arrange the samples in a row instead of in a column */

body {
    display: flex;
    flex-direction: row;
}

body > div {
    margin-right: 1rem;
    flex-shrink: 0;
}

/* Add a marker for the point where we zoom in on */

.show-focal-point {
    position: relative;
}

.show-focal-point::after {
    --focal-point-size: 10px;

    position: absolute;
    top: calc(var(--focal-point-y) - (var(--focal-point-size) / 2));
    left: calc(var(--focal-point-x) - (var(--focal-point-size) / 2));
    width: calc(var(--focal-point-size) + var(--focal-point-x) - var(--focal-point-x)); /* Hack to set `width: unset`` when --focal-point-x is not defined */

    content: "";
    background-color: red;
    aspect-ratio: 1/1;
    border-radius: 100%;
    z-index: 1;
}
<div class="resizeable natural-size">
    <div class="container">
    </div>
</div>
<div class="natural-size">
    <div class="container show-focal-point">
    </div>
</div>
<div class="natural-size half-width">
    <div class="container">
    </div>
</div>
<div class="natural-size half-height">
    <div class="container">
    </div>
</div>

Centering with background-position

We can easily fix the latter problem by using background-position: 50% 50% to zoom in on the center of the image instead of the top left corner:

.container { /* ... */
    background-position: 50% 50%;
}

As a bonus, here is the same solution using CSS variables:

.container { /* ... */
    --focal-point-x: 50%;
    --focal-point-y: 50%;
    background-position: var(--focal-point-x) var(--focal-point-y);
}

Non-centered focal points

Nested heading

Nested heading

Okay, that works! But now you have an image where that focal point isn’t in the center? Well, just set background-position to the center, and call it a day:

.container {
    --focal-point-x: 75%;
    --focal-point-y: 23%;
    background-position: var(--focal-point-x) var(--focal-point-y);
}

Final solution…

…for background images using background-image

Here is the full solution for background-image so far:

.container {
    background-image: url('/assets/img/posts/2024-02/pexels-pixabay-235990.jpg');
    background-size: cover;
    background-repeat: no-repeat;

    --focal-point-x: 75%;
    --focal-point-y: 23%;
    background-position: var(--focal-point-x) var(--focal-point-y);
}
<div class="container">
</div>

Bonus: …for <img> tags

We can use basically the same solution for <img> tags, by using object-fit/object-position instead of background-size/background-position:

img.container {
    --focal-point-x: 75%;
    --focal-point-y: 23%;
    object-position: var(--focal-point-x) var(--focal-point-y); /* object-position is analogous to background-position */
    object-fit: cover; /* object-fit is analogous to background-size */
}
<img class="container" src="/assets/img/posts/2024-02/pexels-pixabay-235990.jpg">