Tag: Content

Weekly Platform News: Impact of Third-Party Code, Passive Mixed Content, Countries with the Slowest Connections

In this week’s roundup, Lighthouse sheds light on third-party scripts, insecure resources will get blocked on secure sites, and many country connection speeds are still trying to catch up to others… literally.


Measure the impact of third-party code during page load

Lighthouse, Chrome’s built-in auditing tool, now shows a warning when the impact of third-party code on page load performance is too high. The pre-existing “Third-party usage” diagnostic audit will now fail if the total main-thread blocking time caused by third-parties is larger than 250ms during page load.

Note: This feature was added in Lighthouse version 5.3.0, which is currently available in Chrome Canary.

(via Patrick Hulce)

Passive mixed content is coming to an end

Currently, browsers still allow web pages loaded over a secure connection (HTTPS) to load images, videos, and audio over an insecure connection. Such insecurely-loaded resources on securely-loaded pages are known as “passive mixed content,” and they represent a security and privacy risk.

An insecurely-loaded image can allow an attacker to communicate incorrect information to the user (e.g., a fabricated stock chart), mutate client-side state (e.g., set a cookie), or induce the user to take an unintended action (e.g., changing the label on a button).

Starting next February, Chrome will auto-upgrade all passive mixed content to https:, and resources that fail to load over https: will be blocked. According to data from Chrome Beta, auto-upgrade currently fails for about 30% of image loads.

(via Emily Stark)

Fast connections are still not common in many countries

Data from Chrome UX Report shows that there are still many countries and territories in the world where most people access the Internet over a 3G or slower connection. (This includes a number of small island nations that are not visible on this map.)

(via Paul Calvano)

More news…

Read even more news in my weekly Sunday issue that can be delivered to you via email every Monday morning.

More News →

The post Weekly Platform News: Impact of Third-Party Code, Passive Mixed Content, Countries with the Slowest Connections appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , , , , , ,

Images Are Not Static Content

We constantly hear about the importance of keeping websites lean and fast. A fast-loading website makes users more satisfied, and satisfied users spend more time and money on your website. However, website optimization is a complex task, as there is not one silver bullet to fix all of the issues causing poor performance.

We also hear that addressing the performance of images is a low hanging fruit if you want to improve your site’s user experience However, anyone who has gotten their hands dirty trying to optimize images and cover major use cases and scenarios with responsive images knows that the complexity of this task escalates quickly. For most medium to large sites, image optimization is not a task suited to humans. This is why image content delivery networks (CDN) exist.

An image CDN is indeed a content delivery network built especially for images. Just like the name suggests. So, why would we need a special CDN to serve images? Why not use a regular CDN to serve static files? Short answer is that images are not static files…

Most image CDNs treat an image as dynamic content by optimizing the image in different ways based on context where the image is consumed.

Explained a bit differently; if you’re using responsive images on your website, an image cdn will automatically generate the derivatives of the image according to the sizes specified in the markup, usually based on some URL parameters. For example, the below code selects from 3 derivatives specified in the srcset attribute based on 3 breakpoints:

<img src="//i.foo.com/image.jpg" alt="cat"    srcset="//i.foo.com/image.jpg?width=320 320w, //i.foo.com/image.jpg?width=640 640w, //i.foo.com/image.jpg?width=1280 1280w"    sizes="(max-width: 480px) 100vw, (max-width: 900px) 33vw, 254px">

This way, the developer or designer doesn’t have to worry about creating all the image versions beforehand. Which is very good news, because the number of derivative images may quickly grow exponentially based on many break points, image formats, and screen resolutions. And that is before we’ve started talking about art direction.

Dynamic Image Optimization on Autopilot

Now that we’ve seen how an image CDN can create different sizes of an image on the fly, let’s examine how this improves web performance.

Before we go further to choose an image CDN for the examples later, it is important to point out the difference between an image CDN and a digital asset management tool (DAM). A DAM, such as Cloudinary, is mostly focused for file management aspect and often allows you to edit images and apply art direction like filters. Usually these DAMs need a general purpose CDN in front and there is little support for automation of image optimization tasks.

On the opposite end of the scale is ImageEngine. ImageEngine is the most effective image CDN on the market thanks to its built in device detection that enables superior image optimization for mobile traffic. Since mobile devices account for more than 50% of the traffic in many countries, ImageEngine truly has an advantage over other CDNs. While most other image CDNs only offer little or no automatic optimization, ImageEngine has more advanced approach thanks to its focus on mobile traffic. Hence, ImageEngine will be able to produce the best results with less implementation effort and maintenance.

How ImageEngine Improves Web Performance

With ImageEngine handling all image traffic, images are no longer static content. Images are now adapted and served exactly in the size, format, compression rate and resolution needed. Fine. But how do we measure the improvement?

These days, the “go to tool” for identifying performance issues and measuring performance is Google Lighthouse. Lighthouse is available as a standalone app and in your Chrome developer tools.

We’ll run a performance audit on an e-commerce demo page listing product images.

The page has a typical responsive grid layout with product images. The layout has a few breakpoints where the display size of the images change because number of items per row changes. Moreover, there is a mouse over feature displaying a different image of the product. The mouseover effect is handled by JavaScript and even the hidden image is always loaded in our example. So all in all, quite a few images and potential sizes.

Step One: Assess Current State

Running the Lighthouse audit on the demo-page we see a number of issues, summarized in a performance score of 98. The best score is 100, so 98 might not seem that bad. Which is true, but pay more attention to the metrics below the score. The performance score is calculated based on a few metrics with varied weighting. The images on our page have direct and indirect impact on these metrics.

In the details of the report, we see a few opportunities related to images listed:

  • Properly size images. The images does not have the right pixel size. This is quite common on pages with a responsive or fluid layout.
  • Serve images in next-gen formats. For Chrome this basically mean to convert images to webp. Usually webp is a more efficient format than most others when it comes to byte size and decode speed.
  • Efficiently encode images. There is more compression that can be applied to the images before impacting perceived visual quality.

The estimated savings (to the right in the report) are huge. This demonstrates why addressing images is considered a low hanging fruit for performance.

If you haven’t signed up already, create a free ImageEngine trial account. Once you’ve completed signup you can define the image origin (usually your website) and a domain from which you want to serve images from. The image may be something like images.mydomain.com. You point this domain name to ImageEngine with a CNAME record in your DNS, and you’re good to go.

The next step is changing the markup to make the most out of ImageEngine’s automatic features.

If our previous image tag looked like this:

<img class="pic-1" src="images/demo9/img-1.jpg">

Our new image tag will look like this when the ImageEngine domain name is serving the images:

<img class="pic-1" src="https://images.mydomain.com/images/demo9/img-1.jpg">

Because our grid layout is fluid with 4 breakpoints, we might also consider to use responsive images syntax:

<img   class="pic-1"    src="https://images.mydomain.com/images/demo9/img-1.jpg"    sizes="(max-width: 576px) 93vw,           (max-width: 768px) 238px, (max-width: 768px) 238px,           (max-width: 992px) 148px, 253px" >

Thanks to ImageEngine’s support for Client hints, ImageEngine will now generate the exact pixel size needed. Client hints are additional HTTP headers the browser can send to enable more accurate image resizing. Client hints are currently only supported by Chrome browsers

Step Three: Measure the Improvement

Running the Lighthouse audit again, we see that the score is now 100. But more importantly, look at the improvements in timings. “Time to interactive” for example. 0.7 seconds less waiting for the user in order to interact with the page. All because images are optimized properly.

What does really “optimized” mean in this case? Why is the page faster and user experience better with ImageEngine? Most of the positive impact is due to reduction in byte size of the images. The less bytes, the faster are the images transferred from the host (or ImageEngine’s edge servers) to the browser. Moreover, lighter images are usually faster to decode and render onto the users screen. This is very simplified, but let’s see how much ImageEngine reduces the image payload using WebPageTest.org to compare our demo site with-, and without ImageEngine:

ImageEngine reduces the image payload to only 25% of the original size.

Bonus: Fix Caching

In the continuous hunt for improved performance, you may have seen this alert from Lighthouse.

Lighthouse thinks the images have a too short Time To Live (TTL) -measured in seconds- in the browser cache. By default, ImageEngine passes on the cache directives given by the origin but luckily this can be changed in ImageEngine’s management interface.

Next Step: Automate Image Optimization

We’ve seen how images should no longer can be treated as static content if we want a high performing web site. Because images have such a high impact on website performance, images must be tailored according to the capabilities and context of the browser and user.

A purpose-built image CDN will relieve humans of the responsibility of trying to accommodate all possible combinations of image formats, sizes and compression levels. Managing image derivatives, is not a task for humans as it will quickly grow to become unmanageable.

Using tools like Lighthouse and WebPageTest.org document the positive impact image CDNs like ImageEngine has on important performance metrics.

The post Images Are Not Static Content appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

How Web Content Can Affect Power Usage

Because we know that all people with battery-powered devices are constantly concerned about their battery levels, and that websites are significant consumers of that battery power, we should probably think about this stuff a lot more than we do.

I’d expect the browser itself to be our main ally here, doing smart things to reduce power consumption without us developers needing to think too much about it. But we’ve learned over the years that it’s always a shared responsibility. We regularly need to help the browser do its job the best it can (think responsive images and will-change).

Some direct tips from Benjamin Poulain and Simon Fraser’s article:

  • Minimize the use of timers to avoid waking up the CPU. Try to coalesce timer-based work into a few, infrequent timers. Lots of uncoordinated timers which trigger frequent CPU wake-ups are much worse than gathering that work into fewer chunks.
  • Minimize continually animating content, like animated images and auto-playing video. Be particularly vigilant to avoid “loading” spinner GIFs or CSS animations that continually trigger painting, even if you can’t see them. IntersectionObserver can be used to run animations only when they are visible.
  • Use declarative animations (CSS Animations and Transitions) where possible. The browser can optimize these away when the animating content is not visible, and they are more efficient than script-driven animation.
  • Avoid network polling to obtain periodic updates from a server. Use WebSockets or Fetch with a persistent connection, instead of polling.

I’d like to see more developer tooling along the lines of how macOS makes it easy to see apps that are demanding significant power:

WebKit DevTools does has it:

We used to have a Battery Status API, but that’s been deprecated, so not a big part of the story right now.

I was just at the Web Unleashed conferencewhere Kyle Simpson talked about this rather directly in his keynote lecture. His main idea is that we should ask users a bit more directly and solicit their preferences. Hey user, are you in a situation where you want to use as little battery power as possible? Tell us and we’ll do what we can to make that happen (even on a site-by-site basis).

Direct Link to ArticlePermalink

The post How Web Content Can Affect Power Usage appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Grid, content re-ordering and accessibility

Take this:

<ol>   <li>Get hungry</li>   <li>Order pizza</li>   <li>Eat pizza</li> </ol>

That HTML ends up in the DOM that way (and thus how it is is exposed to assistive technology), and by default, those list items are also visually shown in that order. In most layout situations, the visual order will match that DOM order. Do nothing, and the list items will flow in the block direction of the document. Apply flexbox, and it will flow in the inline direction of the document.

But flexbox and grid also allow you to muck it up. Now take this:

ol {    display: flex;   flex-direction: row-reverse; }

In this case, the DOM order still makes sense, but the visual order is all wrong. It’s not just row-reverse. There are a number of flexbox and grid properties that can get involved and confuse things: the order property, flowing items into columns instead of rows, and positioning items specifically in unusual orders, among others. Even absolute positioning could cause the same trouble.

Manuel Matuzovic says:

If the visual order and the DOM order don’t match, it can irritate and confuse users up to a point where the experience is so bad that the site is unusable.

Rachel Andrew highlights this issue (including things we’ve published) as a big issue, and hopes we can get tools at the CSS level to help.

I think this is something we sorely need to address at a CSS level. We need to provide a way to allow the tab and reading order to follow the visual order. Source order is a good default, if you are taking advantage of normal flow, a lot of the time following the source is exactly what you want. However not always, not at every breakpoint. If we don’t give people a solution for this, we will end up with a mess. We’ve given people these great tools, and now I feel as if I’m having to tell people not to use them.

Direct Link to ArticlePermalink

The post Grid, content re-ordering and accessibility appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

The Browser Can Remember Edited Content

You can make the text inside any HTML element editable by adding the contenteditable attribute.

<div contenteditable>   Hey, I'm like a textarea kinda now! </div>

I wouldn’t say there are wheelbarrows full of use-cases for that, but it’s neat. One possible use might be an in-progress design in which editing the content from the design itself is useful either for you, or for someone else (a client?) who needs to change the text.

So, great, contenteditable. Now someone can click into the text and edit it.

There is nothing permanent about those changes. Refresh the page, look in another browser or whatever. Those edits are gone.

Say you wanted to do a little better job and make the changes persistent. You aren’t trying to build a CMS here, or save the data through an authenticated connection to a database or anything. You just wanna make the edits to the text are maintained if the page refreshes.

One way is to chuck the data from the text changes you make into localStorage.

  1. When text is edited (on blur of the element), save the data to localStorage using a namespace and the
    ID of the element as the key.
  2. When the page loads, look through localStorage and see if there are any keys that match elements on the page and, if so, replace the content.
const editables = document.querySelectorAll("[contenteditable]");  // save edits editables.forEach(el => {   el.addEventListener("blur", () => {     localStorage.setItem("dataStorage-" + el.id, el.innerHTML);   }) });  // once on load for (var key in localStorage) {   if (key.includes("dataStorage-")) {     const id = key.replace("dataStorage-","");     document.querySelector("#" + id).innerHTML = localStorage.getItem(key);   } }

See the Pen
localStorage + contenteditable
by Chris Coyier (@chriscoyier)
on CodePen.


This reminds me of a few other things…

  • document.designMode = "on" is like a shortcut for making every element on the page behave like it has contenteditable.
  • Mavo is a little bit like this concept only with authentication, true data storage options, and editing UI.
  • If performance is a concern, KV storage is supposed to be a speeder async version of localStorage.

The post The Browser Can Remember Edited Content appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Fixed Headers, On-Page Links, and Overlapping Content, Oh My!

Let’s take a basic on-page link:

<a href="#section-two">Section Two</a>

When clicked, the browser will scroll itself to the element with that ID: <section id="section-two"></section>. A browser feature as old as browsers themselves, just about.

But as soon as we position: fixed; came into play, it became a bit of an issue. The browser will still jump to bring the newly targeted element into view, but that element may be obscured by a fixed position element, which is pretty bad UX.

I called this “headbutting the browswer window” nearly 10 years ago, and went over some possible solutions. Nicolas Gallager documented five different techniques. I’m even using a fixed position header here in v17 of CSS-Tricks, and I don’t particularly love any of those techniques. I sort of punted on it and added top padding to all my <h3> elements, which is big enough for the header to fit there.

There is a new way though! Finally!

Šime Vidas documented this in Web Platform News. There are a bunch of CSS properties that go together as part of CSS scroll snapping, but it turns out that scroll-padding and scroll-margin can be used outside of a scroll snapping container.

body {   scroll-padding-top: 70px; /* height of sticky header */ }

This only works in Chromium browsers:

See the Pen
Scroll Padding on Fixed Postion Headers
by Chris Coyier (@chriscoyier)
on CodePen.

This is such a useful thing we shoot hoot and holler for WebKit and Firefox to do it.

The post Fixed Headers, On-Page Links, and Overlapping Content, Oh My! appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]

See No Evil: Hidden Content and Accessibility

There is no one true way to hide something on the web. Nor should there be, because hiding is too vague. Are you hiding visually or temporarily (like a user menu), but the content should still be accessible? Are you hiding it from assistive tech on purpose? Are you showing it to assistive tech only? Are you hiding it at certain screen sizes or other scenarios? Or are you just plain hiding it from everyone all the time?

Paul Hebert digs into these scenarios. We’ve done a video on this subject as well.

Feels like many CSS properties play some role in hiding or revealing content: display, position, overflow, opacity, visibility, clip-path

Direct Link to ArticlePermalink

The post See No Evil: Hidden Content and Accessibility appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Extracting Text from Content Using HTML Slot, HTML Template and Shadow DOM

Chapter names in books, quotes from a speech, keywords in an article, stats on a report — these are all types of content that could be helpful to isolate and turn into a high-level summary of what’s important.

For example, have you seen the way Business Insider provides an article’s key points before getting into the content?

That’s the sort of thing we’re going to do, but try to extract the high points directly from the article using HTML Slot, HTML Template and Shadow DOM.

These three titular specifications are typically used as part of Web Components — fully functioning custom element modules meant to be reused in webpages.

Now, what we aim to do, i.e. text extraction, doesn’t need custom elements, but it can make use of those three technologies.

There is a more rudimentary approach to do this. For example, we could extract text and show the extracted text on a page with some basic script without utilizing slot and template. So why use them if we can go with something more familiar?

The reason is that using these technologies permits us a preset markup code (also optionally, style or script) for our extracted text in HTML. We’ll see that as we proceed with this article.

Now, as a very watered-down definition of the technologies we’ll be using, I’d say:

  • A template is a set of markup that can be reused in a page.
  • A slot is a placeholder spot for a designated element from the page.
  • A shadow DOM is a DOM tree that doesn’t really exist on the page till we add it using script.

We’ll see them in a little more depth once we get into coding. For now, what we’re going to make is an article that follows with a list of key points from the text. And, you probably guessed it, those key points are extracted from the article text and compiled into the key points section.

See the Pen
Text Extraction with HTML Slot and HTML Template
by Preethi Sam (@rpsthecoder)
on CodePen.

The key points are displayed as a list with a design in between the points. So, let’s first create a template for that list and designate a place for the list to go.

<article><!-- Article content --></article>  <!-- Section where the extracted keypoints will be displayed --> <section id='keyPointsSection'>   <h2>Key Points:</h2>   <ul><!-- Extracted key points will go in here --></ul> </section>  <!-- Template for the key points list --> <template id='keyPointsTemplate'>   <li><slot name='keyPoints'></slot></li>   <li style="text-align: center;">&#x2919;&mdash;&#x291a;</li> </template>

What we’ve got is a semantic <section> with a <ul> where the list of key points will go. Then we have a <template> for the list items that has two <li> elements: one with a <slot> placeholder for the key points from the article and another with a centered design.

The layout is arbitrary. What’s important is placing a <slot> where the extracted key points will go. Whatever’s inside the <template> will not be rendered on the page until we add it to the page using script.

Further, the markup inside <template> can be styled using inline styles, or CSS enclosed by <style>:

<template id='keyPointsTemplate'>     <li><slot name='keyPoints'></slot></li>     <li style="text-align: center;">&#x2919;&mdash;&#x291a;</li>     <style>         li{/* Some style */}     </style> </template>

The fun part! Let’s pick the key points from the article. Notice the value of the name attribute for the <slot> inside the <template> (keyPoints) because we’ll need that.

<article>   <h1>Bears</h1>   <p>Bears are carnivoran mammals of the family Ursidae. <span><span slot='keyPoints'>They are classified as caniforms, or doglike carnivorans</span></span>. Although only eight species of bears <!-- more content --> and partially in the Southern Hemisphere. <span><span slot='keyPoints'>Bears are found on the continents of North America, South America, Europe, and Asia</span></span>.<!-- more content --></p>   <p>While the polar bear is mostly carnivorous, <!-- more content -->. Bears use shelters, such as caves and logs, as their dens; <span><span slot='keyPoints'>Most species occupy their dens during the winter for a long period of hibernation</span></span>, up to 100 days.</p>   <!-- More paragraphs -->  </article>

The key points are wrapped in a <span> carrying a slot attribute value (“keyPoints“) matching the name of the <slot> placeholder inside the <template>.

Notice, too, that I’ve added another outer <span> wrapping the key points.

The reason is that slot names are usually unique and are not repeated, because one <slot> matches one element using one slot name. If there’re more than one element with the same slot name, the <slot> placeholder will be replaced by all those elements consecutively, ending in the last element being the final content at the placeholder.

So, if we matched that one single <slot> inside the <template> against all of the <span> elements with the same slot attribute value (our key points) in a paragraph or the whole article, we’d end up with only the last key point present in the paragraph or the article in place of the <slot>.

That’s not what we need. We need to show all the key points. So, we’re wrapping the key points with an outer <span> to match each of those individual key points separately with the <slot>. This is much more obvious by looking at the script, so let’s do that.

const keyPointsTemplate = document.querySelector('#keyPointsTemplate').content; const keyPointsSection = document.querySelector('#keyPointsSection > ul'); /* Loop through elements with 'slot' attribute */ document.querySelectorAll('[slot]').forEach((slot)=>{   let span = slot.parentNode.cloneNode(true);   span.attachShadow({  mode: 'closed' }).appendChild(keyPointsTemplate.cloneNode(true));   keyPointsSection.appendChild(span); });

First, we loop through every <span> with a slot attribute and get a copy of its parent (the outer <span>). Note that we could also loop through the outer <span> directly if we’d like, by giving them a common class value.

The outer <span> copy is then attached with a shadow tree (span.attachShadow) made up of a clone of the template’s content (keyPointsTemplate.cloneNode(true)).

This “attachment” causes the <slot> inside the template’s list item in the shadow tree to absorb the inner <span> carrying its matching slot name, i.e. our key point.

The slotted key point is then added to the key points section at the end of the page (keyPointsSection.appendChild(span)).

This happens with all the key points in the course of the loop.

That’s really about it. We’ve snagged all of the key points in the article, made copies of them, then dropped the copies into the list template so that all of the key points are grouped together providing a nice little CliffsNotes-like summary of the article.

Here’s that demo once again:

See the Pen
Text Extraction with HTML Slot and HTML Template
by Preethi Sam (@rpsthecoder)
on CodePen.

What do you think of this technique? Is it something that would be useful in long-form content, like blog posts, news articles, or even Wikipedia entries? What other use cases can you think of?

The post Extracting Text from Content Using HTML Slot, HTML Template and Shadow DOM appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , , ,
[Top]

Preventing Content Reflow From Lazy-Loaded Images

You know the concept of lazy loading images. It prevents the browser from loading images until those images are in (or nearly in) the browser’s viewport.

There are a plethora of JavaScript-based lazy loading solutions. GitHub has over 3,400 different lazy load repos, and those are just the ones with “lazy load” in a searchable string! Most of them rely on the same trick: Instead of putting an image’s URL in the src attribute, you put it in data-src — which is the same pattern for responsive images:

  • JavaScript watches the user scroll down the page
  • When the use encounters an image, JavaScript moves the data-src value into src where it belongs
  • The browser requests the image and it loads into view

The result is the browser loading fewer images up front so that the page loads faster. Additionally, if the user never scrolls far enough to see an image, that image is never loaded. That equals faster page loads and less data the user needs to spend.

“This is amazing!” you may be thinking. And, you’re right… it is amazing!

That said, it does indeed introduce a noticeable problem: images not containing the src attribute (including when it’s empty or invalid) have no height. This means that they’re not the right size in the page layout until they’re lazy-loaded.

When a user scrolls and images are lazy-loaded, those img elements go from a height of 0 pixels to whatever they need to be. This causes reflow, where the content below or around the image gets pushed to make room for the freshly loaded image. Reflow is a problem because it’s a user-blocking operation. It slows down the browser by forcing it to recalculate the layout of any elements that are affected by that image’s shape. The CSS scroll-behavior property may help here at some point, but its support needs to improve before it’s a viable option.

Lazy loading doesn’t guarantee that the image will fully load before it enters the viewport. The result is a perceived janky experience, even if it’s a big performance win.

There are other issues with lazy loading images that are worth mentioning but are outside the scope of this post. For example, if JavaScript fails to run at all, then no images will load on the page. That’s a common concern for any JavaScript-based solution but this article only concerned with solving the problems introduced by reflow.

If we could force pre-loaded images to maintain their normal width and height (i.e. their aspect ratio), we could prevent reflow problems while still lazy loading them. This is something I recently had to solve building a progressive web app at DockYard where I work.

For future reference, there’s an HTML attribute called intrinsicsize that’s designed to preserve the aspect ratio, but right now, that’s just experimental in Chrome.

Here’s how we did it.

Maintaining aspect ratio

There are many ways to go about the way we can maintain aspect ratios. Chris once rounded up an exhaustive list of options, but here’s what we’re looking at for image-specific options.

The image itself

The image src provides a natural aspect ratio. Even when an image is resized responsively, its natural dimensions still apply. Here’s a pretty common bit of responsive image CSS:

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

That CSS is telling images not to exceed the width of the element that contains them, but to scale the height properly so that there’s no “stretching” or “squishing” as the image is resized. Even if the image has inline height and width attributes, this CSS will keep them behaving nicely on small viewports.

However, that “natural aspect ratio” behavior breaks down if there’s no src yet. Browsers don’t care about data-src and don’t do anything with it, so it’s not really a viable solution for lazy loading reflow; but it is important to help understand the “normal” way images are laid out once they’ve loaded.

A pseudo-element

Many developers — including myself — have been frustrated trying to use pseudo-elements (e.g. ::before and ::after) to add decorations to img elements. Browsers don’t render an image’s pseudo-elements because img is a replaced element, meaning its layout is controlled by an external resource.

However, there is an exception to that rule: If an image’s src attribute is invalid, browsers will render its pseudo-elements. So, if we store the src for an image in data-src and the src is empty, then we can use a pseudo-element to set an aspect ratio:

[data-src]::before {   content: '';   display: block;   padding-top: 56.25%; }

That’ll set a 16:9 aspect ratio on ::before for any element with a data-src attribute. As soon as the data-src becomes the src, the browser stops rendering ::before and the image’s natural aspect ratio takes over.

Here’s a demo:

See the Pen Image Aspect Ratio: ::before padding by James Steinbach (@jdsteinbach) on CodePen.

There are a couple drawbacks to this solution, however. First, it relies on CSS and HTML working together. Your stylesheet needs to have a declaration for each image aspect ratio you need to support. It would be much better if the template could insert an image without needing CSS edits.

Second, it doesn’t work in Safari 12 and below, or Edge, at the time of writing. That’s a pretty big traffic swatch to send poor layouts. To be fair, maintaining the aspect ratio is a bit of a progressive enhancement — there’s nothing “broken” about the final rendered page. Still, it’s much more ideal to solve the reflow problem and for images to render as expected.

Data URI (Base64) PNGs

Another way we attempted to preserve the aspect ratio was to inline data URI for the src. as PNG. Using png-pixel.com will help with the lift of all that base64-encoding with any dimensions and colors. This can go straight into the image’s src attribute in the HTML:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAQAAAA3fa6RAAAADklEQVR42mNkAANGCAUAACMAA2w/AMgAAAAASUVORK5CYII=" data-src="//picsum.photos/900/600" alt="Lazy loading test image" />

The inline PNG there has a 3:2 aspect ratio (the same aspect ratio as the final image). When src is replaced with the data-src value, the image will maintain its aspect ratio exactly like we want!

Here’s a demo:

See the Pen Image Aspect Ratio: inline base64 PNG by James Steinbach (@jdsteinbach) on CodePen.

And, yes, this approach also comes with some drawbacks. Although the browser support is much better, it’s complicated to maintain. We need to generate a base64 string for each new image size, then make that object of strings available to whatever templating tool that’s being used. It’s also not the most efficient way to represent this data.

I kept exploring and found a smaller way.

Combine SVG with base64

After exploring the inline PNG option, I wondered if SVG might be a smaller format for inline images and here’s what I found: An SVG with a viewBox declaration is a placeholder image with an easily editable native aspect ratio.

First, I tried base64-encoding an SVG. Here’s an example of what that looked like in my HTML:

<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAzIDInPjwvc3ZnPg==" data-src="//picsum.photos/900/600" alt="Lazy loading test image">

On small, simple aspect ratios, this is roughly equivalent in size to the base64 PNGs. A 1:1 ratio would be 114 bytes with base64 PNG and 106 bytes with base64 SVG. A 2:3 ratio is 118 bytes with base64 PNG and 106 bytes with base64 SVG.

However, using base64 SVG for larger, more complex ratios stay small, which is a real winner in file size. A 16:9 ratio is 122 bytes in base64 PNG and 110 bytes in base64 SVG. A 923:742 ratio is 3,100 bytes in base64 PNG but only 114b in base64 SVG! (That’s not a common aspect ratio, but I needed to test with custom dimensions with my client’s use case.)

Here’s a table to see those comparisons more clearly:

Aspect Ratio base64 PNG base64 SVG
1:1 114 bytes 106 bytes
2:3 118 bytes 106 bytes
16:9 122 bytes 110 bytes
923:742 3,100 bytes 114 bytes

The differences are negligible with simple ratios, but you can see how extremely well SVG scales as ratios become complex.

We’ve got much better browser support now. This technique is supported by all the big players, including Chrome, Firefox, Safari, Opera, IE11, and Edge, but also has great support in mobile browsers, including Safari iOS, Chrome for Android, and Samsung for Android (from 4.4 up).

Here’s a demo:

See the Pen Image Aspect Ratio: inline base64 SVG by James Steinbach (@jdsteinbach) on CodePen.

🏆 We have a winner!

Yes, we do, but stick with me as we improve this approach even more! I remembered Chris suggesting that we should not use base64 encoding with SVG inlined in CSS background-images and thought that advice might apply here, too.

In this case, instead of base64-encoding the SVGs, I used the “Optimized URL-encoded” technique from that post. Here’s the markup:

<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 3 2'%3E%3C/svg%3E" data-src="//picsum.photos/900/600" alt="Lazy loading test image" />

This is just a tad smaller than base64 SVG. The 1:1 is 106 bytes in base64 and 92 bytes when URL-encoding. 16:9 outputs 110 bytes in base64 and 97 bytes when URL-encoded.

If you’re interested in more data size by file and encoding format, this demo compares different byte sizes between all of these techniques.

However, the real benefits that make the URL-encoded SVG a clear winner are that its format is human-readable, easily template-able, and infinitely customizable!

You don’t need to create a CSS block or generate a base64 string to get a perfect placeholder for images where the dimensions are unknown! For example, here’s a little React component that uses this technique:

const placeholderSrc = (width, height) => `data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 $  {width} $  {height}"%3E%3C/svg%3E`  const lazyImage = ({url, width, height, alt}) => {   return (     <img       src={placeholderSrc(width, height)}       data-src=https://css-tricks.com/preventing-content-reflow-from-lazy-loaded-images/       alt={alt} />   ) }

See the Pen React LazyLoad Image with Stable Aspect Ratio by James Steinbach (@jdsteinbach) on CodePen.

Or, if you prefer Vue:

See the Pen Vue LazyLoad Image with Stable Aspect Ratio by James Steinbach (@jdsteinbach) on CodePen.

I’m happy to report that browser support hasn’t changed with this improvement — we’ve still got the full support as base64 SVG!

Conclusion

We’ve explored several techniques to prevent content reflow by preserving the aspect ratio of a lazy-loaded image before the swap happens. The best technique I was able to find is inlined and optimized URL-encoded SVG with image dimensions defined in the viewBox attribute. That can be scripted with a function like this:

const placeholderSrc = (width, height) => `data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 $  {width} $  {height}"%3E%3C/svg%3E`

There are several benefits to this technique:

  • Solid browser support across desktop and mobile
  • Smallest byte size
  • Human-readable format
  • Easily templated without run-time encoding calls
  • Infinitely extensible

What do you think of this approach? Have you used something similar or have a completely different way of handling reflow? Let me know!

The post Preventing Content Reflow From Lazy-Loaded Images appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]