Posted on Leave a comment

Sass Techniques from the Trenches

Having been in the web development industry for more than 14 years, I’ve seen and written my fair share of good and bad CSS. When I began at Ramsey Solutions five years ago, I was introduced to Sass. It blew my mind how useful it was! I dove right in and wanted to learn everything I could about it. Over the past five years, I’ve utilized a number of different Sass techniques and patterns and fell in love with some that, to steal Apple’s phrase, just work.

In this article, I’ll explore a wide range of topics:

In my experience, finding the balance between simple and complex is the crucial component to making great software. Software should not only be easy for people to use, but for you and other developers to maintain in the future. I’d consider these techniques to be advanced, but not necessarily clever or complex, on purpose!

“Everyone knows that debugging is twice as hard as writing a program in the first place. So if you’re as clever as you can be when you write it, how will you ever debug it?”

—The Elements of Programming and Style (2nd Edition), Chapter 2

With that in mind, let’s first look at Sass’ ampersand.


The power of the ampersand

There are many different naming conventions you can use to organize your CSS. The one I enjoy using the most is SUIT, a variation of BEM (which is short for Block, Element, Modifier). If you’re unfamiliar with SUIT or BEM, I’d recommend taking a peek at one or both of them before moving on. I’ll be using the SUIT convention throughout the rest of this article.

Whatever naming convention you choose, the base idea is that every styled element gets its own class name, prepended with the component name. This idea is important for how some of the following organization works. Also, this article is descriptive, not prescriptive. Every project is different. You need to do what works best for your project and your team.

The ampersand is the main reason I like to use SUIT, BEM, and conventions like them. It allows me to use nesting and scoping without either biting back with specificity. Here’s an example. Without using the ampersand, I would need to create separate selectors to create -title and -content elements.

.MyComponent {   .MyComponent-title {} }  .MyComponent-content {}  // Compiles to .MyComponent .MyComponent-title {} // Not what we want. Unnecessary specificity! .MyComponent-content {} // Desired result

When using SUIT, I want the second result for -content to be how I write all my selectors. To do so, I would need to repeat the name of the component throughout. This increases my chance to mistype the name of the component as I write new styles. It’s also very noisy as it ends up ignoring the beginning of many selectors which can lead to glossing over obvious errors.

.MyComponent {} .MyComponent-title {} .MyComponent-content {} .MyComponent-author {} // Etc.

If this were normal CSS, we’d be stuck writing the above. Since we’re using Sass, there’s a much better approach using the ampersand. The ampersand is amazing because it contains a reference to the current selector along with any parents.

.A {   // & = '.A'   .B {     // & = '.A .B'     .C {       // & = '.A .B .C'     }   } }

You can see in the above example how the ampersand references each selector in the chain as it goes deeper into the nested code. By utilizing this feature, we can create new selectors without having to rewrite the name of the component each and every time.

.MyComponent {   &-title {}      &-content {} }  // Compiles to .MyComponent {} .MyComponent-title {} .MyComponent-content {}

This is great because we can take advantage of the ampersand to write the name of the component one time and simply reference the component name throughout. This decreases the chance that the component name is mistyped. Plus, the document as a whole becomes easier to read without .MyComponent repeated all over the code.


There are times when the component needs a variant or modifier, as they’re called in SUIT and BEM. Using the ampersand pattern makes it easier to create modifiers.

<div class="MyComponent MyComponent--xmasTheme"></div>
.MyComponent {   &--xmasTheme {} }  // Compiles to .MyComponent {} .MyComponent--xmasTheme {}

“But, what about modifying the child elements?” you might ask. “How are those selectors created? The modifier isn’t needed on every element, right?”

This is where variables can help!

Variables and scoping

In the past, I’ve created modifiers a few different ways. Most of the time, I’d rewrite the special theme name I want to apply when modifying the element.

.MyComponent {   &-title {     .MyComponent--xmasTheme & {     }   }      &-content {     .MyComponent--xmasTheme & {     }   } }  // Compiles to .MyComponent-title {} .MyComponent--xmasTheme .MyComponent-title {} .MyComponent-content {} .MyComponent--xmasTheme .MyComponent-content {}

This gets the job done, but I’m back to rewriting the component name in multiple places, not to mention the modifier name. There’s definitely a better way to do this. Enter Sass variables.

Before we explore Sass variables with selectors, we need to understand how they’re scoped. Sass variables have scope, just like they would in JavaScript, Ruby, or any other programming language. If declared outside of a selector, the variable is available to every selector in the document after its declaration.

$ fontSize: 1.4rem;  .a { font-size: $ fontSize; } .b { font-size: $ fontSize; }

Variables declared inside a selector are scoped only to that selector and its children.

$ fontSize: 1.4rem;  .MyComponent {    $ fontWeight: 600;   font-size: $ fontSize;       &-title {     font-weight: $ fontWeight; // Works!   } }  .MyComponent2 {    font-size: $ fontSize;       &-title {     font-weight: $ fontWeight; // produces an "undefined variable" error   } }

We know variables can store font names, integers, colors, etc. Did you know it can also store selectors? Using string interpolation, we can create new selectors with the variable.

// Sass string interpolation syntax is #{VARIABLE}  $ block: ".MyComponent";  #{$ block} {   &-title {     #{$ block}--xmasTheme & {     }   } }  // Compiles to .MyComponent {} .MyComponent-title {} .MyComponent--xmasTheme .MyComponent-title {}

That’s cool, but the variable is globally scoped. We can fix that by creating the $ block variable inside the component declaration, which would scope it to that component. Then we can re-use the $ block variable in other components. This helps DRY up the theme modifier.

.MyComponent {   $ block: '.MyComponent';      &-title {     #{$ block}--xmasTheme & {     }   }      &-content {     #{$ block}--xmasTheme & {     }   } }  // Compiles to .MyComponent {} .MyComponent-title {} .MyComponent--xmasTheme .MyComponent-title {} .MyComponent-content {} .MyComponent--xmasTheme .MyComponent-content {}

This is closer, but again, we have to write the theme name over and over. Let’s store that in a variable too!

.MyComponent {   $ block: '.MyComponent';   $ xmasTheme: '.MyComponent--xmasTheme';      &-title {     #{$ xmasTheme} & {     }   } }

This is much better! However, we can improve this even further. Variables can also store the value of the ampersand!

.MyComponent {   $ block: &;   $ xmasTheme: #{&}--xmasTheme;      &-title {     #{$ xmasTheme} & {     }   } }  // Still compiles to .MyComponent {} .MyComponent-title {} .MyComponent--xmasTheme .MyComponent-title {}

Now that’s what I’m talking about! “Caching” the selector with ampersand allows us to create our modifiers at the top and keep the theme modifications with the element it’s modifying.

“Sure, that works at the top level,” you say. “But what if you are nested really deep, like eight levels in?” You ask great questions.

No matter how deep the nest, this pattern always works because the main component name is never attached to any of the children, thanks to the SUIT naming convention and ampersand combo.

.MyComponent {    $ block: &;   $ xmasTheme: #{&}--xmasTheme;      &-content {     font-size: 1.5rem;     color: blue;          ul {       li {         strong {           span {             &::before {               background-color: blue;                              #{$ xmasTheme} & {                 background-color: red;               }             }           }         }       }     }   } }  // Compiles to  .MyComponent-content {   font-size: 1.5rem;   color: blue; }  .MyComponent-content ul li strong span::before {   background-color: blue; }  /* * The theme is still appended to the beginning of the selector! * Now, we never need to write deeply nested Sass that's hard to maintain and  * extremely brittle: https://css-tricks.com/sass-selector-combining/ */ .MyComponent--xmasTheme .MyComponent-content ul li strong span::before {   background-color: red; }

Code organization is the main reason I like to use this pattern.

  • It’s relatively DRY
  • It supports the “opt-in” approach, which keeps modifiers with the elements they modify
  • Naming stuff is hard but this enables us to reuse common element names like “title” and “content”
  • It’s low-lift to add a modifier to a component by placing the modifier class on the parent component

“Hhhmmmmm… doesn’t that get hard to read though after you create a bunch of different components? How do you know where you’re at when everything is named &-title and &-content?”

You continue to ask great questions. Who said the source Sass had to be in one file? We can import those components, so let’s turn to that topic!

The importance of imports

Credit: @Julien_He

One of Sass’ best features is @import. We can create separate Sass files (partials) and import them into other Sass files that compile together with the imported file located at the spot it’s imported. This makes it easy to package up related styles for components, utilities, etc. and pull them into a single file. Without @import, we’d need to link to separate CSS files (creating numerous network requests, which is badong) or write everything in a single stylesheet (which is tough to navigate and maintain).

.Component1 {   &-title {}   &-content {}   &-author {} }  .Component2 {   &-title {}   &-content {}   &-author {} }  .Component3 {   &-title {}   &-content {}   &-author {} }  .Component4 {   &-title {}   &-content {}   &-author {} }  .Component5 {   &-title {}   &-content {}   &-author {} }  // A couple hundred lines later...  .Component7384 {   &-title {}   &-content {}   &-author {} }  // WHERE AM I?

One of the more popular methodologies for organizing Sass files is the 7-1 Pattern. That’s seven distinct folders containing Sass files that are imported into a single Sass file.

Those folders are:

  • abstracts
  • base
  • components
  • layout
  • pages
  • themes
  • vendor

Use @import to pull each Sass file in those folder into a main Sass file. We want to import them in the following order to maintain good scope and avoid conflicts during compilation:

  1. abstracts
  2. vendor
  3. base
  4. layout
  5. components
  6. pages
  7. themes
@import 'abstracts/variables'; @import 'abstracts/functions'; @import 'abstracts/mixins';  @import 'vendors/some-third-party-component';  @import 'base/normalize';  @import 'layout/navigation'; @import 'layout/header'; @import 'layout/footer'; @import 'layout/sidebar'; @import 'layout/forms';  @import 'components/buttons'; @import 'components/hero'; @import 'components/pull-quote';  @import 'pages/home'; @import 'pages/contact';  @import 'themes/default'; @import 'themes/admin';

You may or may not want to use all of these folders (I personally don’t use the theme folder since I keep themes with their components), but the idea of separating all of styles into distinct files makes it easier to maintain and find code.

More of the benefits of using this approach:

  • Small components are easier to read and understand
  • Debugging becomes simpler
  • It’s clearer to determine when a new component should be created — like when a single component file gets to be too long, or the selector chain is too complex
  • This emphasizes re-usage — for example, it might make sense to generalize three component files that essentially do the same thing into one component

Speaking of re-usage, there are eventually patterns that get used often. That’s when we can reach for mixins.

Mixin’ it up

Mixins are a great way to reuse styles throughout a project. Let’s walk through creating a simple mixin and then give it a little bit of intelligence.

The designer I work with on a regular basis always sets font-size, font-weight, and line-height to specific values. I found myself typing all three out every time I needed to adjust the fonts for a component or element, so I created a mixin to quickly set those values. It’s like a little function I can use to define those properties without having to write them in full.

@mixin text($ size, $ lineHeight, $ weight) {   font-size: $ size;   line-height: $ lineHeight;   font-weight: $ weight; }

At this point, the mixin is pretty simple—it resembles something like a function in JavaScript. There’s the name of the mixin (text) and it takes in three arguments. Each argument is tied to a CSS property. When the mixin is called, Sass will copy the properties and the pass in the argument values.

.MyComponent {   @include text(18px, 27px, 500); }  // Compiles to .MyComponent {   font-size: 18px;   line-height: 27px;   font-weight: 500; }

While it’s a good demonstration, this particular mixin is a little limited. It assumes we always want to use the font-size, line-height, and font-weight properties when it’s called. So let’s use Sass’ if statement to help control the output.

@mixin text($ size, $ lineHeight, $ weight) {   // If the $ size argument is not empty, then output the argument   @if $ size != null {     font-size: $ size;   }      // If the $ lineHeight argument is not empty, then output the argument   @if $ lineHeight != null {     line-height: $ lineHeight;   }      // If the $ weight argument is not empty, then output the argument   @if $ weight != null {     font-weight: $ weight;   } }  .MyComponent {   @include text(12px, null, 300); }  // Compiles to .MyComponent {   font-size: 12px;   font-weight: 300; }

That’s better, but not quite there. If I try to use the mixin without using null as a parameter on the values I don’t want to use or provide, Sass will generate an error:

.MyComponent {   @include text(12px, null); // left off $ weight }  // Compiles to an error: // "Mixin text is missing argument $ weight."

To get around this, we can add default values to the parameters, allowing us to leave them off the function call. All optional parameters have to be declared after any required parameters.

// We define `null` as the default value for each argument @mixin text($ size: null, $ lineHeight: null, $ weight: null) {   @if $ size != null {     font-size: $ size;   }      @if $ lineHeight != null {     line-height: $ lineHeight;   }      @if $ weight != null {     font-weight: $ weight;   } }  .MyComponent {   &-title {     @include text(16px, 19px, 600);   }      &-author {     @include text($ weight: 800, $ size: 12px);   } }  // Compiles to .MyComponent-title {   font-size: 16px;   line-height: 19px;   font-weight: 600; }  .MyComponent-author {   font-size: 12px;   font-weight: 800; }

Not only do default argument values make the mixin easier to use, but we also gain the ability to name parameters and give them values that may be commonly used. On Line 21 above, the mixin is being called with the arguments out of order, but since the values are being called out as well, the mixin knows how to apply them.


There’s a particular mixin that I use on a daily basis: min-width. I prefer to create all my sites mobile first, or basically with the smallest viewport in mind. As the viewport grows wider, I define breakpoints to adjust the layout and the code for it. This is where I reach for the min-width mixin.

// Let's name this "min-width" and take a single argument we can // use to define the viewport width in a media query. @mixin min-width($ threshold) {   // We're calling another function (scut-rem) to convert pixels to rem units.   // We'll cover that in the next section.   @media screen and (min-width: scut-rem($ threshold)) {     @content;   } }  .MyComponent {   display: block;      // Call the min-width mixin and pass 768 as the argument.   // min-width passes 768 and scut-rem converts the unit.   @include min-width(768) {     display: flex;   } }  // Compiles to  .MyComponent {   display: block; }  @media screen and (min-width: 48rem) {   .MyComponent {     display: flex;   } }

There are a couple of new ideas here. The mixin has a nested function called @content. So, in the .MyComponent class, we’re no longer calling the mixin alone, but also a block of code that gets output inside the media query that’s generated. The resulting code will compile where @content is called. This allows the mixin to take care of the @media declaration and still accept custom code for that particular breakpoint.

I also am including the mixin within the .MyComponent declaration. Some people advocate keeping all responsive calls in a separate stylesheet to reduce the amount of times @media is written out in a stylesheet. Personally, I prefer to keep all variations and changes that a component can go through with that component’s declaration. It tends to make it easier to keep track of what’s going on and help debug the component if something doesn’t go right, rather than sifting through multiple files.

Did you notice the scut-rem function in there? That is a Sass function taken from a Sass library called Scut, created by David The Clark. Let’s take a look at how that works.

Getting functional

A function differs from a mixin in that mixins are meant to output common groups of properties, while a function modifies properties based on arguments that return a new result. In this case, scut-rem takes a pixel value and converts it to a rem value. This allows us to think in pixels, while working with rem units behind the scenes to avoid all that math.

I’ve simplified scut-rem in this example because it has a few extra features that utilize loops and lists, which are out of the scope of what we’re covering here. Let’s look at the function in its entirety, then break it down step-by-step.

// Simplified from the original source $ scut-rem-base: 16 !default;  @function scut-strip-unit ($ num) {   @return $ num / ($ num * 0 + 1); }  @function scut-rem ($ pixels) {   @return scut-strip-unit($ pixels) / $ scut-rem-base * 1rem; }  .MyComponent {   font-size: scut-rem(18px);   }  // Compiles to .MyComponent {   font-size: 1.125rem; }

The first thing to note is the declaration on Line 2. It’s using !default when declaring a variable, which tells Sass to set the value to 16 unless this variable is already defined. So if a variable is declared earlier in the stylesheet with a different value, it won’t be overridden here.

$ fontSize: 16px; $ fontSize: 12px !default;  .MyComponent {   font-size: $ fontSize; }  // Compiles to .MyComponent {   font-size: 16px; }

The next piece of the puzzle is scut-strip-unit. This function takes a px, rem, percent or other suffixed value and removes the unit label. Calling scut-strip-unit(12px) returns 12 instead of 12px. How does that work? In Sass, a unit divided by another unit of the same type will strip the unit and return the digit.

12px / 1px = 12

Now that we know that, let’s look at the scut-strip-unit function again.

@function scut-strip-unit ($ num) {   @return $ num / ($ num * 0 + 1); }

The function takes in a unit and divides it by 1 of the same unit. So if we pass in 12px, the function would look like: @return 12px / (12px * 0 + 1). Following the order of operations, Sass evaluates what’s in the parentheses first. Sass smartly ignores the px label, evaluates the expression, and tacks px back on once it’s done: 12 * 0 + 1 = 1px. The equation is now 12px / 1px which we know returns 12.

Why is this important to scut-rem? Looks look at it again.

$ scut-rem-base: 16 !default;  @function scut-rem ($ pixels) {   @return scut-strip-unit($ pixels) / $ scut-rem-base * 1rem; }  .MyComponent {   font-size: scut-rem(18px);   }

On Line 4, the scut-strip-unit function removes px from the argument and returns 18. The base variable is equal to 16 which turns the equation into: 18 / 16 * 1rem. Remember, Sass ignores any unit until the end of the equation, so 18 / 16 = 1.125. That result multiplied by 1rem gives us 1.125rem. Since Scut strips the unit off of the argument, we can call scut-rem with unit-less values, like scut-rem(18).

I don’t write that many functions because I try to keep the stuff I create as simple as possible. Being able to do some complex conversions using something like scut-rem is helpful though.

The selector order that placeholders mess up

End up where I think it did, that CSS?

I really don’t like to use placeholders and @extend in my code. I find it easy to get in trouble with them for a couple different reasons.

Be careful what is extended

I tried writing out some examples to demonstrate why using @extend can be problematic, but I have used them so little that I can’t create any decent examples. When I first learned Sass, I was surrounded by teammates who’ve already gone through the trials and tribulations. My friend Jon Bebee wrote an extremely excellent article on how @extend can get you into trouble. It’s a quick read and worth the time, so I’ll wait.

About those placeholders…

Jon proposes using placeholders as a solution to the problem he outlines: Placeholders don’t output any code until they’re used with @extend.

// % denotes an extended block %item {   display: block;   width: 50%;   margin: 0 auto; }  .MyComponent {   @extend %item;   color: blue; }  // Compiles to .MyComponent {   display: block;   width: 50%;   margin: 0 auto; }  .MyComponent {   color: blue; }

OK, wait. So it output .MyComponent twice? Why didn’t it simply combine the selectors?

These are the questions I had when I first started using placeholders (and then subsequently stopped). The clue is the name itself. Placeholders simply hold a reference to the place in the stylesheet they were declared. While a mixin copies the properties to the location it is used, placeholders copy the selector to the place where the placeholder was defined. As a result, it copies the .MyComponent selector and places it where %item is declared. Consider the following example:

%flexy {   display: flex; }  .A {   color: blue; }  .B {   @extend: %flexy;   color: green; }  .C {   @extend: %flexy;   color: red; }  // Compiles to .B, .C {   display: flex; }  .A {   color: blue; }  .B {   color: green; }  .C {   color: red; }

Even though B and C are declared further down in the stylesheet, the placeholder places the extended properties tall the way up to where it was originally declared. That’s not a big deal in this example because it’s really close to the source where it’s used. However, if we’re adhering to something like the 7-1 Pattern we covered earlier, then placeholders would be defined in a partial in the abstracts folder, which is one of the first imported files. That puts a lot of style between where the extend is intended and where it’s actually used. That can be hard to maintain as well as hard to debug.

Sass Guidelines (of course) does a great job covering placeholders and extend and I would recommend reading it. It not only explains the extend feature, but at the end, advocates against using it:

Opinions seem to be extremely divided regarding the benefits and problems from @extend to the point where many developers including myself have been advocating against it, […]


There are many other features of Sass I didn’t cover here, like loops and lists, but I’ve honestly haven’t relied on those features as much as the ones we did cover in this article. Take a look through the Sass documentation, if for nothing else, to see what things do. You may not find a use for everything right away, but a situation may come up and having that knowledge in your back pocket is priceless.

Let me know if I missed something or got something wrong! I’m always open to new ideas and would love to discuss it with you!

Further Reading

The post Sass Techniques from the Trenches appeared first on CSS-Tricks.

CSS-Tricks

Posted on Leave a comment

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

Posted on Leave a comment

Get References from HTML Built with Template Literals

One thing JavaScript template literals are great at is little blocks of HTML. Like:

// Probably from some API or whatever const data = {   title: "Title",   content: "Content" };  const some_html = `   <div class="module">     <h2>$  {data.title}</h2>     <p>$  {data.content}</p>   </div> `;

But that’s still just a string. It’s not ready to append to the DOM just yet. And what if we need references to those elements inside somehow?

We’ve written about a couple of libraries that are in this vein: lit-html and hyperHTML. Those are pretty small libs, but are also sorta more about re-rendering of templates in an efficient way (like super mini React).

What if you just need the nodes? That’s almost a one-liner:

const getNodes = str => {    return new DOMParser().parseFromString(str, 'text/html').body.childNodes; }

Now we could drop that template literal of HTML right into the DOM:

document.body.appendChild(getNodes(some_html)[0]);

Here’s that:

See the Pen pQyZOz by Chris Coyier (@chriscoyier) on CodePen.

But how do we get our hands on individual bits of that HTML? We don’t exactly have references to anything, even the whole chunk we put in.

I just saw this little lib called Facon that looks to do just this. It makes use of tagged template literals, which is super cool:

import f from 'facon';  const data = {   title: "Title",   content: "Content" };  let html = f`   <div class="module">     <h2>$  {data.title}</h2>     <p>$  {data.content}</p>   </div> `;  document.body.appendChild(html);

This skips the need for our little getNodes function, but more importantly, we can yank out those references!

let html = f`   <div class="module">     <h2 ref="title">$  {data.title}</h2>     <p ref="content">$  {data.content}</p>   </div> `;  let { title, content } = html.collect(); title.innerText = "Title Changed!";

Here’s that:

See the Pen Facon Template by Chris Coyier (@chriscoyier) on CodePen.

The post Get References from HTML Built with Template Literals appeared first on CSS-Tricks.

CSS-Tricks