Jank-free page loading with media aspect ratios
Tags: css, webperf
Published: 2019-12-03 13:56:29

---
title: Jank-free page loading with media aspect ratios
published: true
date: 2019-11-30 17:00:02 UTC
tags: css,webperf
canonical_url: https://blog.logrocket.com/jank-free-page-loading-with-media-aspect-ratios/
cover_image: https://thepracticaldev.s3.amazonaws.com/i/55dgr7wiz5ftd4mnso8q.png
---
**Written by [Craig Buckler](https://blog.logrocket.com/author/craigbuckler/)**✏️

Responsive webpages often rearrange content while loading. This can lead to considerable frustration for users when the text they are reading is suddenly moved off-screen. In this article, we’ll look at several new and existing ways to solve this performance problem.

## Pre-RWD layouts

Fluid layouts were possible from the moment Sir Tim Berners-Lee developed the first web browser (as well as HTML, a page editor, web server, HTTP and DNS). Web content was only constrained by the width of the browser viewport.

In the days of dial-up access, developers religiously added width and height attributes to every `img` tag. This ensured appropriate space was allocated on the page before the image started to download and reflows were avoided.

```jsx
<img src="myimage.jpg" width="400" height="300" alt="my image" />
```

The unitless dimensions refer to pixels. In this case, a 400×300 area would be reserved and the image would be stretched or squeezed into that space, even if the real dimensions did not match.

As the web evolved, designers and developers moved toward fixed-width layouts, which were easier to comprehend, design, and code. Image width and height attributes became more critical when they were used to determine layout dimensions.

Unfortunately, fixed layouts were only effective when the browser was wide enough to accommodate the design. A 960px-width layout design looked good on a 1024×768 monitor, but somewhat sparse on a larger screen, and required awkward horizontal scrolling on a smaller device.

The smartphone revolution — and the iPhone launch in particular — changed everything. Small screens rapidly became important and, while the devices were capable of rendering standard webpages, panning and zooming to read text was laborious. Web developers initially solved the problem by duplicating content into alternative mobile sites, often with an “m” subdomain.

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## Post-RWD layouts

Ethan Marcotte’s pivotal 2010 article titled “[Responsive Web Design](https://alistapart.com/article/responsive-web-design/)” explained how [CSS media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/@media) could be used to create a single site layout that adapted to the viewport dimensions. Various techniques emerged, and for the past decade developers have been encouraged to omit `img width` and `height` attributes and use CSS sizing instead. For example, you could size an image to the width of its container.

```jsx
img {
  width: 100%;
  height: auto;
}
```

You could also ensure an image was no wider than its actual pixel width.

```jsx
img {
  max-width: 100%;
  height: auto;
}
```

This technique had an unfortunate consequence: page space could only be allocated once the image started to download and the browser could determine its dimensions. Content had to be reflowed as each image started to appear. The text you were reading could suddenly move or disappear off-screen, especially on mobile devices.

![Sizing an Image to the Width of a Container](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2019/11/sizing-image-width-container.gif?resize=661%2C675&ssl=1)

The loading issue is also evident with videos, iframes, and advertising.

There is no easy way to solve this problem within web code. Determining whether an asset has started to load and unintentionally reflowed content is currently impossible. Browsers have implemented [scroll anchoring](https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-anchor/Guide_to_scroll_anchoring) in an attempt to prevent content jumping, but the annoyance still occurs.

## Aspect ratio resolution

An aspect ratio defines the relationship between an element’s width and height. Consider a typical 16:9 screen: for every 16 units across, the screen is nine units deep.

![Aspect Ratio Resolution](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2019/11/aspect-ratio-resolution.png?resize=410%2C229&ssl=1)  
If we know one dimension, we can calculate the other.

- Given a 800px width, the height is 800 x 9 / 16 = 450px
- Given a 450px height, the width is 450 x 16 / 9 = 800px

Note: televisions and monitors are normally advertised using the diagonal measurement. Using a little Pythagorous, an 85-inch, 16:9 screen would therefore be 74.1×41.7 inches. The [screen size calculator](http://screen-size.info/) does the hard work for you.

In the case of browser rendering, the width of an HTML element is often known almost immediately because it can be determined during the initial layout. If an aspect ratio can be provided, the height and corresponding area can be calculated and reserved.

## Firefox 71 aspect ratio determination

Firefox 71 and above calculates aspect ratios when the width and height attributes are defined in an `img` tag.

```jsx
<!-- set a 400:300 - or 4:3 - aspect ratio -->
<img src="myimage.jpg" width="400" height="300" alt="my image" />
```

CSS can be used to size the image to the width of its containing element.

```jsx
img {
  width: 100%;
  height: auto;
}
```

`height: auto` is essential to ensure the image height is not fixed at 300px.

Therefore, if a 4:3 image is shown in a 200px-width parent container, its corresponding height is calculated and a 200px-by-150px area can be reserved before the image starts to download. The result is a jank-free loading experience.

![Firefox 71 Aspect Ratio Determination](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/11/firefox-71-aspect-ratio-determination.gif?resize=661%2C675&ssl=1)  
Any appropriate `img` width and height can be used since the image is resized by CSS (e.g., `width="4" height="3"`. However, it is best to set a reasonable size to ensure the image remains visible in very old browsers, if CSS fails to load, or when a user style sheet is defined.

Firefox 71 is set to be released in December 2019, and Chrome will support aspect ratio calculations shortly after. However, you can add `img` width and height attributes now. Older browsers and those without support will not reserve space, but the image will be sized as before.

### Responsive images (srcset and sizes attributes)

[Responsive images](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images) use the `srcset` and `sizes` attributes to define a set of bandwidth-efficient alternatives. This is typically used on high native resolution screens (HiDPI or Retina) to ensure the best quality image is downloaded. For example, only use the highest-resolution `img-400.jpg` on 4x DPI devices.

```jsx
<img width="100" height="100"
      alt="responsive image"
        src="img-100.jpg"
     srcset="img-100.jpg 1x,
             img-200.jpg 2x,
             img-300.jpg 3x,
             img-400.jpg 4x" />
```

It can also be used to load appropriate images for the space available. For example, use `img-400.jpg` when the browser viewport approaches a 400px width.

```jsx
<img width="100" height="100"
       alt="responsive image"
       src="img-100.jpg"
    srcset="img-100.jpg 100w,
            img-200.jpg 200w,
            img-300.jpg 300w,
            img-400.jpg 400w" />
```

Each image should use the same aspect ratio so the `img` width and height can be set.

### Art direction (picture element)

The HTML `picture` element requests one of its child elements according to browser support and conditions. Different images can be used according to the dimensions and orientation of a device. For example, download `landscape.jpg` when the viewport width is greater than the height, or fall back to `portrait.jpg` otherwise.

```jsx
<picture>
  <source srcset="landscape.jpg"
           media="(min-aspect-ratio:1/1)" />
  <img src="portrait.jpg" alt="portrait image" />
</picture>
```

Each image could have a different aspect ratio. At the time of writing, browser vendors are considering the best solution. The most likely option is width and height attributes on all `source` and `img` child elements.

```jsx
<picture>
  <source srcset="landscape.jpg"
           media="(min-aspect-ratio:1/1)"
           width="800" height="450" />
  <img src="portrait.jpg" alt="portrait image"
           width="450" height="800" />
</picture>
```

## Aspect ratios on other elements

The aspect ratio is not calculated when a `width` and `height` attribute is defined for an `iframe`, `video`, or any other element. This would be useful, so two proposals have been put forward, but neither is implemented in a browser at the time of writing:

1. The [`CSS aspect-ratio property`](https://drafts.csswg.org/css-sizing-4/#ratios), e.g. `aspect-ratio: 16/9`
2. The [`HTML intrinsicsize attribute`](https://github.com/WICG/intrinsicsize-attribute) (this seems less likely to gain support given that the `width` and `height` attributes effectively do the same thing)

Let’s go over some alternative aspect ratio options that work in most browsers.

### Stretching and cropping

Our puffin image is 1,600×1,200, so it has a 4:3 ratio.

```jsx
<img src="puffin.jpg" width="1600" height="1200" alt="a puffin" />
```

If we attempt to place it in an area with a different aspect ratio, such as 200×300, the browser will stretch or squash the image to fit.

```jsx
img {
  width: 200px;
  height: 300px;
  border: 2px solid #000;
}
```

![Stretching and Cropping an Image With Aspect Ratios](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/11/stretching-cropping-aspect-ratio.png?resize=201%2C300&ssl=1)  

The CSS [`object-fit`](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) property provides further options. `object-fit: fill` is the default and is identical to the image shown above.

`object-fit: contain` ensures the whole image is scaled to fit the available space. The aspect ratio is maintained, so borders will appear on either the horizontal or vertical edges.

```jsx
img {
  width: 200px;
  height: 300px;
  object-fit: contain;
  border: 2px solid #000;
}
```

![Image Scaled to Fit the Available Space With Aspect Ratios](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/11/image-scaled-aspect-ratio.png?resize=200%2C300&ssl=1)  
`object-fit: cover` resizes the image to fit the area without showing borders. Its aspect ratio is maintained so cropping can occur.

![Image Resized to Fit the Area Without Showing Borders](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/11/image-resized-fit-area-no-borders.png?resize=202%2C302&ssl=1)  
This may not be ideal, so [`object-position`](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) can be used to specify the horizontal and vertical alignment, e.g., `object-position: 20% 50%`.

![Image Aligned With Object Position](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2019/11/image-aligned-with-object-position.png?resize=203%2C302&ssl=1)  
`object-fit: none` does not resize the image, so in this case, only a small portion will be shown.

![Portion of an Image Not Resized](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/11/portion-image-not-resized.png?resize=202%2C301&ssl=1)  
Finally, `object-fit: scale-down` resizes the image as if `none` or `contain` were specified and chooses whichever results in the smallest size. Since our image is larger than the containing space, `contain` is effectively chosen.

The [`CSS background-size`](https://developer.mozilla.org/en-US/docs/Web/CSS/background-size) property provides similar options for background images.

While these methods may be appropriate for some images, they force the content into a sized container. If that container does not match the aspect ratio, parts of that content could be hidden, e.g., the playback controls on a `video` element.

### The aspect ratio padding trick

Fortunately, there is an unusual way to define the aspect ratio of any element using the CSS [`padding-top`](https://developer.mozilla.org/en-US/docs/Web/CSS/padding-top) or [`padding-bottom`](https://developer.mozilla.org/en-US/docs/Web/CSS/padding-bottom) property. When either is expressed as a percentage, the resulting padding is proportional to the width of the element, not the height.

The following element will be a perfect 16:9 ratio since `padding-top` is set to 56.25 percent of the width (9 / 16 x 100 percent). The actual height is set to zero because it would normally be added to the padding.

```jsx
.container-16-9 {
  position: relative;
  width: 100%;
  height: 0;
  padding-top: calc(9 / 16 * 100%);
}
```

If the width is 400px, the vertical padding is 56.25 percent of 400px which is 225px. The height will change as the width increases or decreases.

We can therefore place any element inside that container, such as a 16:9 video.

```jsx
<div class="container-16-9">
  <video controls preload="none">
    <source src="video.mp4" type='video/mp4' />
  </video>
</div>
```

We can also absolutely position it to retain the aspect ratio.

```jsx
.container-16-9 {
  position: relative;
  width: 100%;
  height: 0;
  padding-top: calc(9 / 16 * 100%);
}

.container-16-9 > :first-child {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
}
```

![Video Placed Inside a Container](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/11/video-placed-inside-container.png?resize=643%2C361&ssl=1)

It would be great if we could extract the container’s `width` and `height` attributes as [`CSS attr()`](https://developer.mozilla.org/en-US/docs/Web/CSS/attr) values, then use them in a [`calc()`](https://developer.mozilla.org/en-US/docs/Web/CSS/calc), but that is not possible because the `attr()` function only returns a string for use in [`content`](https://developer.mozilla.org/en-US/docs/Web/CSS/content) properties. However, browser vendors are considering casting.

Therefore, we can define a series of known ratios.

```jsx
.aspect {
  position: relative;
  width: 100%;
  height: 0;
  padding-top: 100%; /* default 1:1 */
}

.aspect.ratio-16-9 {
  padding-top: calc(9 / 16 * 100%);
}

.aspect.ratio-9-16 {
  padding-top: calc(16 / 9 * 100%);
}

.aspect.ratio-4-3 {
  padding-top: calc(3 / 4 * 100%);
}

.aspect.ratio-3-4 {
  padding-top: calc(4 / 3 * 100%);
}

.aspect > :first-child {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
}
```

Then, set an `aspect` and appropriate `ratio` class on the container.

```jsx
<div class="aspect ratio-16-9">
  <video controls preload="none">
    <source src="video.mp4" type='video/mp4' />
  </video>
</div>
```

A CSS preprocessor such as Sass may be an easier option since all aspect ratios can be defined in a list and calculated at build time.

```jsx
// define all known ratios
$ratio:
  (w: 16, h:  9 ),
  (w:  9, h: 16 ),
  (w:  4, h:  3 ),
  (w:  3, h:  4)
;

.aspect {
  position: relative;
  width: 100%;
  height: 0;
  padding-top: 100%; /* default 1:1 */
}

.aspect > :first-child {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
}

@each $r in $ratio {
  $w: map-get($r, w);
  $h: map-get($r, h);
  .aspect.ratio-#{$w}-#{$h} {
    padding-top: $h / $w * 100%;
  }
}
```

The main drawback of predefining these classes is that you cannot set any aspect ratio in the HTML like you can with `width` and `height` attributes. However, custom properties (CSS variables) provide an option since an aspect ratio can be defined in a style attribute.

```jsx
<!-- define a 21:9 container -->
<div style="--aspect-ratio: 21/9;">
  <video...></video>
</div>
```

Its value is available in CSS and can be used accordingly:

```jsx
@supports (--custom:property) {

  [style*="--aspect-ratio"] {
    position: relative;
    width: 100%;
    height: 0;
    padding-top: calc( 100% / (var(--aspect-ratio)) );
  }

  [style*="--aspect-ratio"] > :first-child {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
  }

}
```

This is shorter and more flexible, but will fail in older browsers without custom property support (the [`@supports`](https://developer.mozilla.org/en-US/docs/Web/CSS/@supports) rule ensures those browsers don’t even try).

The primary flaw of the padding trick is that the sized element must be wrapped in a container where the aspect ratio is defined. JavaScript options such as [fitvids](https://github.com/rosszurowski/fitvids) can handle this automatically, but there can still be some page reflowing as aspect ratios are calculated and containers are added.

## Conclusion

Text content being moved by reflowing pages as they load has been a user frustration for several years, but a little light has appeared at the end of the tunnel.

For the immediate future, remember to add `width` and `height` attributes to all `img` tags. Consider using the padding percentage trick on `video` and `iframe` containers if they are likely to cause a reflow.

As browsers evolve, the CSS [`aspect-ratio`](https://drafts.csswg.org/css-sizing-4/#ratios) property may become an essential way to reserve page space, and the era of janky page loading will be over.

* * * 
**Editor's note:** Seeing something wrong with this post? You can find the correct version [here](https://blog.logrocket.com/jank-free-page-loading-with-media-aspect-ratios/).

## Plug: [LogRocket](https://logrocket.com/signup/), a DVR for web apps
 
![LogRocket Dashboard Free Trial Banner](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png?resize=1200%2C677&ssl=1)
 
[LogRocket](https://logrocket.com/signup/) is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
 
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
 
[Try it for free](https://logrocket.com/signup/).
* * *
The post [Jank-free page loading with media aspect ratios](https://blog.logrocket.com/jank-free-page-loading-with-media-aspect-ratios/) appeared first on [LogRocket Blog](https://blog.logrocket.com).