Tag: Inline

Inline Image Previews with Sharp, BlurHash, and Lambda Functions

Don’t you hate it when you load a website or web app, some content displays and then some images load — causing content to shift around? That’s called content reflow and can lead to an incredibly annoying user experience for visitors.

I’ve previously written about solving this with React’s Suspense, which prevents the UI from loading until the images come in. This solves the content reflow problem but at the expense of performance. The user is blocked from seeing any content until the images come in.

Wouldn’t it be nice if we could have the best of both worlds: prevent content reflow while also not making the user wait for the images? This post will walk through generating blurry image previews and displaying them immediately, with the real images rendering over the preview whenever they happen to come in.

So you mean progressive JPEGs?

You might be wondering if I’m about to talk about progressive JPEGs, which are an alternate encoding that causes images to initially render — full size and blurry — and then gradually refine as the data come in until everything renders correctly.

This seems like a great solution until you get into some of the details. Re-encoding your images as progressive JPEGs is reasonably straightforward; there are plugins for Sharp that will handle that for you. Unfortunately, you still need to wait for some of your images’ bytes to come over the wire until even a blurry preview of your image displays, at which point your content will reflow, adjusting to the size of the image’s preview.

You might look for some sort of event to indicate that an initial preview of the image has loaded, but none currently exists, and the workarounds are … not ideal.

Let’s look at two alternatives for this.

The libraries we’ll be using

Before we start, I’d like to call out the versions of the libraries I’ll be using for this post:

Making our own previews

Most of us are used to using <img /> tags by providing a src attribute that’s a URL to some place on the internet where our image exists. But we can also provide a Base64 encoding of an image and just set that inline. We wouldn’t usually want to do that since those Base64 strings can get huge for images and embedding them in our JavaScript bundles can cause some serious bloat.

But what if, when we’re processing our images (to resize, adjust the quality, etc.), we also make a low quality, blurry version of our image and take the Base64 encoding of that? The size of that Base64 image preview will be significantly smaller. We could save that preview string, put it in our JavaScript bundle, and display that inline until our real image is done loading. This will cause a blurry preview of our image to show immediately while the image loads. When the real image is done loading, we can hide the preview and show the real image.

Let’s see how.

Generating our preview

For now, let’s look at Jimp, which has no dependencies on things like node-gyp and can be installed and used in a Lambda.

Here’s a function (stripped of error handling and logging) that uses Jimp to process an image, resize it, and then creates a blurry preview of the image:

function resizeImage(src, maxWidth, quality) {   return new Promise<ResizeImageResult>(res => {     Jimp.read(src, async function (err, image) {       if (image.bitmap.width > maxWidth) {         image.resize(maxWidth, Jimp.AUTO);       }       image.quality(quality);        const previewImage = image.clone();       previewImage.quality(25).blur(8);       const preview = await previewImage.getBase64Async(previewImage.getMIME());        res({ STATUS: "success", image, preview });     });   }); }

For this post, I’ll be using this image provided by Flickr Commons:

Photo of the Big Boy statue holding a burger.

And here’s what the preview looks like:

Blurry version of the Big Boy statue.

If you’d like to take a closer look, here’s the same preview in a CodeSandbox.

Obviously, this preview encoding isn’t small, but then again, neither is our image; smaller images will produce smaller previews. Measure and profile for your own use case to see how viable this solution is.

Now we can send that image preview down from our data layer, along with the actual image URL, and any other related data. We can immediately display the image preview, and when the actual image loads, swap it out. Here’s some (simplified) React code to do that:

const Landmark = ({ url, preview = "" }) => {     const [loaded, setLoaded] = useState(false);     const imgRef = useRef<HTMLImageElement>(null);        useEffect(() => {       // make sure the image src is added after the onload handler       if (imgRef.current) {         imgRef.current.src = url;       }     }, [url, imgRef, preview]);        return (       <>         <Preview loaded={loaded} preview={preview} />         <img           ref={imgRef}           onLoad={() => setTimeout(() => setLoaded(true), 3000)}           style={{ display: loaded ? "block" : "none" }}         />       </>     );   };      const Preview: FunctionComponent<LandmarkPreviewProps> = ({ preview, loaded }) => {     if (loaded) {       return null;     } else if (typeof preview === "string") {       return <img key="landmark-preview" alt="Landmark preview" src={preview} style={{ display: "block" }} />;     } else {       return <PreviewCanvas preview={preview} loaded={loaded} />;     }   };

Don’t worry about the PreviewCanvas component yet. And don’t worry about the fact that things like a changing URL aren’t accounted for.

Note that we set the image component’s src after the onLoad handler to ensure it fires. We show the preview, and when the real image loads, we swap it in.

Improving things with BlurHash

The image preview we saw before might not be small enough to send down with our JavaScript bundle. And these Base64 strings will not gzip well. Depending on how many of these images you have, this may or may not be good enough. But if you’d like to compress things even smaller and you’re willing to do a bit more work, there’s a wonderful library called BlurHash.

BlurHash generates incredibly small previews using Base83 encoding. Base83 encoding allows it to squeeze more information into fewer bytes, which is part of how it keeps the previews so small. 83 might seem like an arbitrary number, but the README sheds some light on this:

First, 83 seems to be about how many low-ASCII characters you can find that are safe for use in all of JSON, HTML and shells.

Secondly, 83 * 83 is very close to, and a little more than, 19 * 19 * 19, making it ideal for encoding three AC components in two characters.

The README also states how Signal and Mastodon use BlurHash.

Let’s see it in action.

Generating blurhash previews

For this, we’ll need to use the Sharp library.


Note

To generate your blurhash previews, you’ll likely want to run some sort of serverless function to process your images and generate the previews. I’ll be using AWS Lambda, but any alternative should work.

Just be careful about maximum size limitations. The binaries Sharp installs add about 9 MB to the serverless function’s size.

To run this code in an AWS Lambda, you’ll need to install the library like this:

"install-deps": "npm i && SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm i --arch=x64 --platform=linux sharp"

And make sure you’re not doing any sort of bundling to ensure all of the binaries are sent to your Lambda. This will affect the size of the Lambda deploy. Sharp alone will wind up being about 9 MB, which won’t be great for cold start times. The code you’ll see below is in a Lambda that just runs periodically (without any UI waiting on it), generating blurhash previews.


This code will look at the size of the image and create a blurhash preview:

import { encode, isBlurhashValid } from "blurhash"; const sharp = require("sharp");  export async function getBlurhashPreview(src) {   const image = sharp(src);   const dimensions = await image.metadata();    return new Promise(res => {     const { width, height } = dimensions;      image       .raw()       .ensureAlpha()       .toBuffer((err, buffer) => {         const blurhash = encode(new Uint8ClampedArray(buffer), width, height, 4, 4);         if (isBlurhashValid(blurhash)) {           return res({ blurhash, w: width, h: height });         } else {           return res(null);         }       });   }); }

Again, I’ve removed all error handling and logging for clarity. Worth noting is the call to ensureAlpha. This ensures that each pixel has 4 bytes, one each for RGB and Alpha.

Jimp lacks this method, which is why we’re using Sharp; if anyone knows otherwise, please drop a comment.

Also, note that we’re saving not only the preview string but also the dimensions of the image, which will make sense in a bit.

The real work happens here:

const blurhash = encode(new Uint8ClampedArray(buffer), width, height, 4, 4);

We’re calling blurhash‘s encode method, passing it our image and the image’s dimensions. The last two arguments are componentX and componentY, which from my understanding of the documentation, seem to control how many passes blurhash does on our image, adding more and more detail. The acceptable values are 1 to 9 (inclusive). From my own testing, 4 is a sweet spot that produces the best results.

Let’s see what this produces for that same image:

{   "blurhash" : "UAA]{ox^0eRiO_bJjdn~9#M_=|oLIUnzxtNG",   "w" : 276,   "h" : 400 }

That’s incredibly small! The tradeoff is that using this preview is a bit more involved.

Basically, we need to call blurhash‘s decode method and render our image preview in a canvas tag. This is what the PreviewCanvas component was doing before and why we were rendering it if the type of our preview was not a string: our blurhash previews use an entire object — containing not only the preview string but also the image dimensions.

Let’s look at our PreviewCanvas component:

const PreviewCanvas: FunctionComponent<CanvasPreviewProps> = ({ preview }) => {     const canvasRef = useRef<HTMLCanvasElement>(null);        useLayoutEffect(() => {       const pixels = decode(preview.blurhash, preview.w, preview.h);       const ctx = canvasRef.current.getContext("2d");       const imageData = ctx.createImageData(preview.w, preview.h);       imageData.data.set(pixels);       ctx.putImageData(imageData, 0, 0);     }, [preview]);        return <canvas ref={canvasRef} width={preview.w} height={preview.h} />;   };

Not too terribly much going on here. We’re decoding our preview and then calling some fairly specific Canvas APIs.

Let’s see what the image previews look like:

In a sense, it’s less detailed than our previous previews. But I’ve also found them to be a bit smoother and less pixelated. And they take up a tiny fraction of the size.

Test and use what works best for you.

Wrapping up

There are many ways to prevent content reflow as your images load on the web. One approach is to prevent your UI from rendering until the images come in. The downside is that your user winds up waiting longer for content.

A good middle-ground is to immediately show a preview of the image and swap the real thing in when it’s loaded. This post walked you through two ways of accomplishing that: generating degraded, blurry versions of an image using a tool like Sharp and using BlurHash to generate an extremely small, Base83 encoded preview.

Happy coding!


Inline Image Previews with Sharp, BlurHash, and Lambda Functions originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , , , ,

The Search For a Fixed Background Effect With Inline Images

I was working on a client project a few days ago and wanted to create a certain effect on an <img>. See, background images can do the effect I was looking for somewhat easily with background-attachment: fixed;. With that in place, a background image stays in place—even when the page scrolls. It isn’t used all that often, so the effect can look unusual and striking, especially when used sparingly.

Table of Contents

It took me some time to figure out how to achieve the same effect only with an inline image, rather than a CSS background image. This is a video of the effect in action:

The exact code for the above demo is available in this Git repo. Just note that it’s a Next.js project. We’ll get to a CodePen example with raw HTML in a bit.

Why use <img> instead of background-image?

The are a number of reasons I wanted this for my project:

  • It’s easier to lazy load (e.g. <img loading="lazy"… >.
  • It provides better SEO (not to mention accessibility), thanks to alt text.
  • It’s possible to use srcset/sizes to improve the loading performance.
  • It’s possible to use the <picture> tag to pick the best image size and format for the user’s browser.
  • It allows users to download save the image (without resorting to DevTools).

Overall, it’s better to use the image tag where you can, particularly if the image could be considered content and not decoration. So, I wound up landing on a technique that uses CSS clip-path. We’ll get to that in a moment, right after we first look at the background-image method for a nice side-by-side comparison of both approaches.

1. Using CSS background-image

This is the “original” way to pull off a fixed scrolling effect. Here’s the CSS:

.hero-section {   background-image: url("nice_bg_image.jpg");   background-repeat: no-repeat;   background-size: cover;   background-position: center;    background-attachment: fixed; }

But as we just saw, this approach isn’t ideal for some situations because it relies on the CSS background-image property to call and load the image. That means the image is technically not considered content—and thus unrecognized by screen readers. If we’re working with an image that is part of the content, then we really ought to make it accessible so it is consumed like content rather than decoration.

Otherwise, this technique works well, but only if the image spans the whole width of the viewport and/or is centered. If you have an image on the right or left side of the page like the example, you’ll run into a whole number of positioning issues because background-position is relative to the center of the viewport.

Fixing it requires a few media queries to make sure it is positioned properly on all devices.

2. Using the clip-path trick on an inline image

Someone on StackOverflow shared this clip-path trick and it gets the job done well. You also get to keep using the<img> tag, which, as we covered above, might be advantageous in some circumstances, especially where an image is part of the content rather than pure decoration.

Here’s the trick:

.image-container {   position: relative;   height: 200px;   clip-path: inset(0); }  .image {   object-fit: cover;   position: fixed;   left: 0;   top: 0;   width: 100%;   height: 100%; }

Check it out in action:

Now, before we rush out and plaster this snippet everywhere, it has its own set of downsides. For example, the code feels a bit lengthy to me for such a simple effect. But, even more important is the fact that working with clip-path comes with some implications as well. For one, I can’t just slap a border-radius: 10px; in there like I did in the earlier example to round the image’s corners. That won’t work—it requires making rounded corners from the clipping path itself.

Another example: I don’t know how to position the image within the clip-path. Again, this might be a matter of knowing clip-path really well and drawing it where you need to, or cropping the image itself ahead of time as needed.

Is there something better?

Personally, I gave up on using the fixed scrolling effect on inline images and am back to using a CSS background image—which I know is kind of limiting.

Have you ever tried pulling this off, particularly with an inline image, and managed it well? I’d love to hear!


The Search For a Fixed Background Effect With Inline Images originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , , , ,
[Top]

The Search For a Fixed Background Effect With Inline Images

I was working on a client project a few days ago and wanted to create a certain effect on an <img>. See, background images can do the effect I was looking for somewhat easily with background-attachment: fixed;. With that in place, a background image stays in place—even when the page scrolls. It isn’t used all that often, so the effect can look unusual and striking, especially when used sparingly.

It took me some time to figure out how to achieve the same effect only with an inline image, rather than a CSS background image. This is a video of the effect in action:

The exact code for the above demo is available in this Git repo. Just note that it’s a Next.js project. We’ll get to a CodePen example with raw HTML in a bit.

Why use <img> instead of background-image?

The are a number of reasons I wanted this for my project:

  • It’s easier to lazy load (e.g. <img loading="lazy"… >.
  • It provides better SEO (not to mention accessibility), thanks to alt text.
  • It’s possible to use srcset/sizes to improve the loading performance.
  • It’s possible to use the <picture> tag to pick the best image size and format for the user’s browser.
  • It allows users to download save the image (without resorting to DevTools).

Overall, it’s better to use the image tag where you can, particularly if the image could be considered content and not decoration. So, I wound up landing on a technique that uses CSS clip-path. We’ll get to that in a moment, right after we first look at the background-image method for a nice side-by-side comparison of both approaches.

1. Using CSS background-image

This is the “original” way to pull off a fixed scrolling effect. Here’s the CSS:

.hero-section {   background-image: url("nice_bg_image.jpg");   background-repeat: no-repeat;   background-size: cover;   background-position: center;    background-attachment: fixed; }

But as we just saw, this approach isn’t ideal for some situations because it relies on the CSS background-image property to call and load the image. That means the image is technically not considered content—and thus unrecognized by screen readers. If we’re working with an image that is part of the content, then we really ought to make it accessible so it is consumed like content rather than decoration.

Otherwise, this technique works well, but only if the image spans the whole width of the viewport and/or is centered. If you have an image on the right or left side of the page like the example, you’ll run into a whole number of positioning issues because background-position is relative to the center of the viewport.

Fixing it requires a few media queries to make sure it is positioned properly on all devices.

2. Using the clip-path trick on an inline image

Someone on StackOverflow shared this clip-path trick and it gets the job done well. You also get to keep using the<img> tag, which, as we covered above, might be advantageous in some circumstances, especially where an image is part of the content rather than pure decoration.

Here’s the trick:

.image-container {   position: relative;   height: 200px;   clip-path: inset(0); }  .image {   object-fit: cover;   position: fixed;   left: 0;   top: 0;   width: 100%;   height: 100%; }

Check it out in action:

Now, before we rush out and plaster this snippet everywhere, it has its own set of downsides. For example, the code feels a bit lengthy to me for such a simple effect. But, even more important is the fact that working with clip-path comes with some implications as well. For one, I can’t just slap a border-radius: 10px; in there like I did in the earlier example to round the image’s corners. That won’t work—it requires making rounded corners from the clipping path itself.

Another example: I don’t know how to position the image within the clip-path. Again, this might be a matter of knowing clip-path really well and drawing it where you need to, or cropping the image itself ahead of time as needed.

Is there something better?

Personally, I gave up on using the fixed scrolling effect on inline images and am back to using a CSS background image—which I know is kind of limiting.

Have you ever tried pulling this off, particularly with an inline image, and managed it well? I’d love to hear!


The Search For a Fixed Background Effect With Inline Images originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , , , ,
[Top]

Efficient Infinite Utility Helpers Using Inline CSS Custom Properties and calc()

I recently wrote a very basic Sass loop that outputs several padding and margin utility classes. Nothing fancy, really, just a Sass map with 11 spacing values, looped over to create classes for both padding and margin on each side. As we’ll see, this works, but it ends up a pretty hefty amount of CSS. We’re going to refactor it to use CSS custom properties and make the system much more trim.

Here’s the original Sass implementation:

$  space-stops: (   '0': 0,   '1': 0.25rem,   '2': 0.5rem,   '3': 0.75rem,   '4': 1rem,   '5': 1.25rem,   '6': 1.5rem,   '7': 1.75rem,   '8': 2rem,   '9': 2.25rem,   '10': 2.5rem, );  @each $  key, $  val in $  space-stops {   .p-#{$  key} {     padding: #{$  val} !important;   }   .pt-#{$  key} {     padding-top: #{$  val} !important;   }   .pr-#{$  key} {     padding-right: #{$  val} !important;   }   .pb-#{$  key} {     padding-bottom: #{$  val} !important;   }   .pl-#{$  key} {     padding-left: #{$  val} !important;   }   .px-#{$  key} {     padding-right: #{$  val} !important;     padding-left: #{$  val} !important;   }   .py-#{$  key} {     padding-top: #{$  val} !important;     padding-bottom: #{$  val} !important;   }    .m-#{$  key} {     margin: #{$  val} !important;   }   .mt-#{$  key} {     margin-top: #{$  val} !important;   }   .mr-#{$  key} {     margin-right: #{$  val} !important;   }   .mb-#{$  key} {     margin-bottom: #{$  val} !important;   }   .ml-#{$  key} {     margin-left: #{$  val} !important;   }   .mx-#{$  key} {     margin-right: #{$  val} !important;     margin-left: #{$  val} !important;   }   .my-#{$  key} {     margin-top: #{$  val} !important;     margin-bottom: #{$  val} !important;   } } 

This very much works. It outputs all the utility classes we need. But, it can also get bloated quickly. In my case, they were about 8.6kb uncompressed and under 1kb compressed. (Brotli was 542 bytes, and gzip came in at 925 bytes.)

Since they are extremely repetitive, they compress well, but I still couldn’t shake the feeling that all these classes were overkill. Plus, I hadn’t even done any small/medium/large breakpoints which are fairly typical for these kinds of helper classes.

Here’s a contrived example of what the responsive version might look like with small/medium/large classes added. We’ll re-use the $ space-stops map defined previously and throw our repetitious code into a mixin

@mixin finite-spacing-utils($  bp: '') {     @each $  key, $  val in $  space-stops {         .p-#{$  key}#{$  bp} {             padding: #{$  val} !important;         }         .pt-#{$  key}#{$  bp} {             padding-top: #{$  val} !important;         }         .pr-#{$  key}#{$  bp} {             padding-right: #{$  val} !important;         }         .pb-#{$  key}#{$  bp} {             padding-bottom: #{$  val} !important;         }         .pl-#{$  key}#{$  bp} {             padding-left: #{$  val} !important;         }         .px-#{$  key}#{$  bp} {             padding-right: #{$  val} !important;             padding-left: #{$  val} !important;         }         .py-#{$  key}#{$  bp} {             padding-top: #{$  val} !important;             padding-bottom: #{$  val} !important;         }          .m-#{$  key}#{$  bp} {             margin: #{$  val} !important;         }         .mt-#{$  key}#{$  bp} {             margin-top: #{$  val} !important;         }         .mr-#{$  key}#{$  bp} {             margin-right: #{$  val} !important;         }         .mb-#{$  key}#{$  bp} {             margin-bottom: #{$  val} !important;         }         .ml-#{$  key}#{$  bp} {             margin-left: #{$  val} !important;         }         .mx-#{$  key}#{$  bp} {             margin-right: #{$  val} !important;             margin-left: #{$  val} !important;         }         .my-#{$  key}#{$  bp} {             margin-top: #{$  val} !important;             margin-bottom: #{$  val} !important;         }     } }  @include finite-spacing-utils;  @media (min-width: 544px) {     @include finite-spacing-utils($  bp: '_sm'); }  @media (min-width: 768px) {     @include finite-spacing-utils($  bp: '_md'); }  @media (min-width: 1024px) {     @include finite-spacing-utils($  bp: '_lg'); }  

That clocks in at about 41.7kb uncompressed (and about 1kb with Brotli, and 3kb with gzip). It still compresses well, but it’s a bit ridiculous.

I knew it was possible to reference data-* attributes from within CSS using the [attr() function, so I wondered if it was possible to use calc() and attr() together to create dynamically-calculated spacing utility helpers via data-* attributes — like data-m="1" or data-m="1@md" — then in the CSS to do something like margin: calc(attr(data-m) * 0.25rem) (assuming I’m using a spacing scale incrementing at 0.25rem intervals). That could be very powerful.

But the end of that story is: no, you (currently) can’t use attr() with any property except the content property. Bummer. But in searching for attr() and calc() information, I found this intriguing Stack Overflow comment by Simon Rigét that suggests setting a CSS variable directly within an inline style attribute. Aha!

So it’s possible to do something like <div style="--p: 4;"> then, in CSS:

:root {   --p: 0; }  [style*='--p:'] {   padding: calc(0.25rem * var(--p)) !important; } 

In the case of the style="--p: 4;" example, you’d effectively end up with padding: 1rem !important;.

… and now you have an infinitely scalable spacing utility class monstrosity helper.

Here’s what that might look like in CSS:

:root {   --p: 0;   --pt: 0;   --pr: 0;   --pb: 0;   --pl: 0;   --px: 0;   --py: 0;   --m: 0;   --mt: 0;   --mr: 0;   --mb: 0;   --ml: 0;   --mx: 0;   --my: 0; }  [style*='--p:'] {   padding: calc(0.25rem * var(--p)) !important; } [style*='--pt:'] {   padding-top: calc(0.25rem * var(--pt)) !important; } [style*='--pr:'] {   padding-right: calc(0.25rem * var(--pr)) !important; } [style*='--pb:'] {   padding-bottom: calc(0.25rem * var(--pb)) !important; } [style*='--pl:'] {   padding-left: calc(0.25rem * var(--pl)) !important; } [style*='--px:'] {   padding-right: calc(0.25rem * var(--px)) !important;   padding-left: calc(0.25rem * var(--px)) !important; } [style*='--py:'] {   padding-top: calc(0.25rem * var(--py)) !important;   padding-bottom: calc(0.25rem * var(--py)) !important; }  [style*='--m:'] {   margin: calc(0.25rem * var(--m)) !important; } [style*='--mt:'] {   margin-top: calc(0.25rem * var(--mt)) !important; } [style*='--mr:'] {   margin-right: calc(0.25rem * var(--mr)) !important; } [style*='--mb:'] {   margin-bottom: calc(0.25rem * var(--mb)) !important; } [style*='--ml:'] {   margin-left: calc(0.25rem * var(--ml)) !important; } [style*='--mx:'] {   margin-right: calc(0.25rem * var(--mx)) !important;   margin-left: calc(0.25rem * var(--mx)) !important; } [style*='--my:'] {   margin-top: calc(0.25rem * var(--my)) !important;   margin-bottom: calc(0.25rem * var(--my)) !important; }  

This is a lot like the first Sass loop above, but there’s no loop going 11 times — and yet it’s infinite. It’s about 1.4kb uncompressed, 226 bytes with Brotli, or 284 bytes gzipped.

If you wanted to extend this for breakpoints, the unfortunate news is that you can’t put the “@” character in CSS variable names (although emojis and other UTF-8 characters are strangely permitted). So you could probably set up variable names like p_sm or sm_p. You’d have to add some extra CSS variables and some media queries to handle all this, but it won’t blow up exponentially the way traditional CSS classnames created with a Sass for-loop do.

Here’s the equivalent responsive version. We’ll use a Sass mixin again to cut down the repetition:

:root {   --p: 0;   --pt: 0;   --pr: 0;   --pb: 0;   --pl: 0;   --px: 0;   --py: 0;   --m: 0;   --mt: 0;   --mr: 0;   --mb: 0;   --ml: 0;   --mx: 0;   --my: 0; }  @mixin infinite-spacing-utils($  bp: '') {     [style*='--p#{$  bp}:'] {         padding: calc(0.25rem * var(--p)) !important;     }     [style*='--pt#{$  bp}:'] {         padding-top: calc(0.25rem * var(--pt)) !important;     }     [style*='--pr#{$  bp}:'] {         padding-right: calc(0.25rem * var(--pr)) !important;     }     [style*='--pb#{$  bp}:'] {         padding-bottom: calc(0.25rem * var(--pb)) !important;     }     [style*='--pl#{$  bp}:'] {         padding-left: calc(0.25rem * var(--pl)) !important;     }     [style*='--px#{$  bp}:'] {         padding-right: calc(0.25rem * var(--px)) !important;         padding-left: calc(0.25rem * var(--px)) !important;     }     [style*='--py#{$  bp}:'] {         padding-top: calc(0.25rem * var(--py)) !important;         padding-bottom: calc(0.25rem * var(--py)) !important;     }     [style*='--m#{$  bp}:'] {         margin: calc(0.25rem * var(--m)) !important;     }     [style*='--mt#{$  bp}:'] {         margin-top: calc(0.25rem * var(--mt)) !important;     }     [style*='--mr#{$  bp}:'] {         margin-right: calc(0.25rem * var(--mr)) !important;     }     [style*='--mb#{$  bp}:'] {         margin-bottom: calc(0.25rem * var(--mb)) !important;     }     [style*='--ml#{$  bp}:'] {         margin-left: calc(0.25rem * var(--ml)) !important;     }     [style*='--mx#{$  bp}:'] {         margin-right: calc(0.25rem * var(--mx)) !important;         margin-left: calc(0.25rem * var(--mx)) !important;     }     [style*='--my#{$  bp}:'] {         margin-top: calc(0.25rem * var(--my)) !important;         margin-bottom: calc(0.25rem * var(--my)) !important;     } }  @include infinite-spacing-utils;  @media (min-width: 544px) {     @include infinite-spacing-utils($  bp: '_sm'); }  @media (min-width: 768px) {     @include infinite-spacing-utils($  bp: '_md'); }  @media (min-width: 1024px) {     @include infinite-spacing-utils($  bp: '_lg'); }  

That’s about 6.1kb uncompressed, 428 bytes with Brotli, and 563 with gzip.

Do I think that writing HTML like <div style="--px:2; --my:4;"> is pleasing to the eye, or good developer ergonomics… no, not particularly. But could this approach be viable in situations where you (for some reason) need extremely minimal CSS, or perhaps no external CSS file at all? Yes, I sure do.

It’s worth pointing out here that CSS variables assigned in inline styles do not leak out. They’re scoped only to the current element and don’t change the value of the variable globally. Thank goodness! The one oddity I have found so far is that DevTools (at least in Chrome, Firefox, and Safari) do not report the styles using this technique in the “Computed” styles tab.

Also worth mentioning is that I’ve used good old padding  and margin properties with -top, -right, -bottom, and -left, but you could use the equivalent logical properties like padding-block and padding-inline. It’s even possible to shave off just a few more bytes by selectively mixing and matching logical properties with traditional properties. I managed to get it down to 400 bytes with Brotli and 521 with gzip this way.

Other use cases

This seems most appropriate for things that are on a (linear) incremental scale (which is why padding and margin seems like a good use case) but I could see this potentially working for widths and heights in grid systems (column numbers and/or widths). Maybe for typographic scales (but maybe not).

I’ve focused a lot on file size, but there may be some other uses here I’m not thinking of. Perhaps you wouldn’t write your code in this way, but a critical CSS tool could potentially refactor the code to use this approach.

Digging deeper

As I dug deeper, I found that Ahmad Shadeed blogged in 2019 about mixing calc() with CSS variable assignments within inline styles particularly for avatar sizes. Miriam Suzanne’s article on Smashing Magazine in 2019 didn’t use calc() but shared some amazing things you can do with variable assignments in inline styles.


The post Efficient Infinite Utility Helpers Using Inline CSS Custom Properties and calc() appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , , , , ,
[Top]

Inline Styles as Classes (lol)

If you’re abhorred by using inline styles, just move that style to the class attribute! And then make sure you have CSS in place that, ya know, does what it says on the box.

OK lemme dig in and totally ruin the joke.

  • First off, it’s a joke, so don’t actually do this. I don’t even mind the occasional inline style for one-off stuff, but this is not that.
  • To me the weirdest part is that period (.) character. Escaping the more unusual characters with a backslash () feels normal, but what is that period about?
  • The little period trick there doesn’t work when the following character is a number (e.g. .padding:.1rem;).
  • You can avoid the escaping and trickery if you go with an attribute selector like [class*="display: flex;"].
  • This reminds me of Mathias Bynens’ research: CSS character escape sequences. But… that doesn’t seem to work anymore? I wonder if browsers changed or if the tool broke and doesn’t output what it should anymore (e.g. does .color3a #bada55; look right?).

Here’s all that playing around:


The post Inline Styles as Classes (lol) appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, ,
[Top]

Want to Write a Hover Effect With Inline CSS? Use CSS Variables.

The other day I was working on a blog where each post has a custom color attached to it for a little dose of personality. The author gets to pick that color in the CMS when they’re writing the post. Just a super-light layer of art direction.

To make that color show up on the front end, I wrote the value right into an inline style attribute on the <article> element. My templates happened to be in Liquid, but this would look similar in other templating languages:

{% for post in posts %} <article style="background: {{post.custom_color}}">   <h1>{{post.title}}</h1>   {{content}} </article> {% endfor %}

No problem there. But then I thought, “Wouldn’t it be nice if the custom color only showed up when when hovering over the article card?” But you can’t write hover styles in a style attribute, right?

My first idea was to leave the style attribute in place and write CSS like this:

article {   background: lightgray !important; } article:hover {   /* Doesn't work! */   background: inherit; }

I can override the inline style by using !important, but there’s no way to undo that on hover.

Eventually, I decided I could use a style attribute to get the color value from the CMS, but instead of applying it right away, store it as a CSS variable:

<article style="--custom_color: {{post.custom_color}}">   <h1>{{post.title}}</h1>   {{content}} </article>

Then, that variable can be used to define the hover style in regular CSS:

article {   background: lightgray; } article:hover {   /* Works! */   background: var(--custom_color); }

Now that the color value is saved as a CSS variable, there are all kinds of other things we can do with it. For instance, we could make all links in the post appear in the custom color:

article a {   color: var(--custom_color); }

And because the variable is scoped to the <article> element, it won’t affect anything else on the page. We can even display multiple posts on the same page where each one renders in its own custom color.

Browser support for CSS variables is pretty deep, with the exception of Internet Explorer. Anyway, just a neat little trick that might come in handy if you find yourself working with light art direction in a CMS, as well as a reminder of just how awesome CSS variables can be.


The post Want to Write a Hover Effect With Inline CSS? Use CSS Variables. appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]

Inline SVG… Cached

I wrote that using inline <svg> icons makes for the best icon system. I still think that’s true. It’s the easiest possible way to drop an icon onto a page. No network request, perfectly styleable.

But inlining code has some drawbacks, one of which is that it doesn’t take advantage of caching. You’re making the browser read and process the same code over and over as you browse around. Not that big of a deal. There are much bigger performance fish to fry, right? But it’s still fun to think about more efficient patterns.

Scott Jehl wrote that just because you inline something doesn’t mean you can’t cache it. Let’s see if Scott’s idea can extend to SVG icons.

Starting with inline SVG

Like this…

<!DOCTYPE html> <html lang="en">  <head>   <title>Inline SVG</title>   <link rel="stylesheet" href="/styles/style.css"> </head>  <body>    ...     <svg width="24" height="24" viewBox="0 0 24 24" class="icon icon-alarm" xmlns="http://www.w3.org/2000/svg">     <path id="icon-alarm" d="M11.5,22C11.64,22 11.77,22 11.9,21.96C12.55,21.82 13.09,21.38 13.34,20.78C13.44,20.54 13.5,20.27 13.5,20H9.5A2,2 0 0,0 11.5,22M18,10.5C18,7.43 15.86,4.86 13,4.18V3.5A1.5,1.5 0 0,0 11.5,2A1.5,1.5 0 0,0 10,3.5V4.18C7.13,4.86 5,7.43 5,10.5V16L3,18V19H20V18L18,16M19.97,10H21.97C21.82,6.79 20.24,3.97 17.85,2.15L16.42,3.58C18.46,5 19.82,7.35 19.97,10M6.58,3.58L5.15,2.15C2.76,3.97 1.18,6.79 1,10H3C3.18,7.35 4.54,5 6.58,3.58Z"></path>   </svg>

It’s weirdly easy to toss text into browser cache as a file

In the above HTML, the selector .icon-alarm will fetch us the entire chunk of <svg> for that icon.

const iconHTML = document.querySelector(".icon-alarm").outerHTML;

Then we can plunk it into the browser’s cache like this:

if ("caches" in window) {   caches.open('static').then(function(cache) {     cache.put("/icons/icon-wheelchair.svg", new Response(       iconHTML,       { headers: {'Content-Type': 'image/svg+xml'} }     ));   } }

See the file path /icons/icon-wheelchair.svg? That’s kinda just made up. But it really will be put in the cache at that location.

Let’s make sure the browser grabs that file out of the cache when it’s requested

We’ll register a Service Worker on our pages:

if (navigator.serviceWorker) {      navigator.serviceWorker.register('/sw.js', {     scope: '/'   }); }

The service worker itself will be quite small, just a cache matcher:

self.addEventListener("fetch", event => {   let request = event.request;    event.respondWith(     caches.match(request).then(response => {       return response || fetch(request);     })   ); });

But… we never request that file, because our icons are inline.

True. But what if other pages benefitted from that cache? For example, an SVG icon could be placed on the page like this:

<svg class="icon">   <use xlink:href="/icons/icon-alarm.svg#icon-alarm" />  </svg>

Since /icons/icon-alarm.svg is sitting there ready in cache, the browser will happily pluck it out of cache and display it.

(I was kind of amazed this works. Edge doesn’t like <use> elements that link to files, but that’ll be over soon enough.)

And even if the file isn’t in the cache, assuming we actually chuck this file on the file system likely the result of some kind of “include” (I used Nunjucks on the demo).

But… <use> and inline SVG aren’t quite the same

True. What I like about the above is that it’s making use of the cache and the icons should render close to immediately. And there are some things you can style this way — for example, setting the fill on the parent icon should go through the shadow DOM that the <use> creates and colorize the SVG elements within.

Still, it’s not the same. The shadow DOM is a big barrier compared to inline SVG.

So, enhance them! We could asynchronously load a script that finds each SVG icon, Ajaxs for the SVG it needs, and replaces the <use> stuff…

const icons = document.querySelectorAll("svg.icon");  icons.forEach(icon => {   const url = icon.querySelector("use").getAttribute("xlink:href"); // Might wanna look for href also   fetch(url)     .then(response => response.text())     .then(data => {       // This is probably a bit layout-thrashy. Someone smarter than me could probably fix that up.        // Replace the <svg><use></svg> with inline SVG       const newEl = document.createElement("span");       newEl.innerHTML = data;       icon.parentNode.replaceChild(newEl, icon);        // Remove the <span>s       const parent = newEl.parentNode;       while (newEl.firstChild) parent.insertBefore(newEl.firstChild, newEl);       parent.removeChild(newEl);     }); });

Now, assuming this JavaScript executes correctly, this page has inline SVG available just like the original page did.

Demo & Repo

, ,
[Top]

Text Wrapping & Inline Pseudo Elements

I love posts like this. It’s just about adding a little icon to the end of certain links, but it ends up touching on a million things along the way. I think this is an example of why some people find front-end fun and some people rather dislike it.

Things involved:

  1. Cool [attribute] selectors that identify off-site links
  2. Deliberating on whether it’s OK to use additional HTML within links or not
  3. Usage of white-space
  4. Combining margin-left and padding-right to place icons into placeholder space
  5. Using custom properties to keep things easier
  6. Usage of inline SVG versus background SVG
  7. Considering inline versus inline-block
  8. Using masks

Direct Link to ArticlePermalink

The post Text Wrapping & Inline Pseudo Elements appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Multi-Line Inline Gradient

Came across this thread:

My first thought process was:

But it turns out we need a litttttle extra trickery to make it happen.

If a solid color is fine, then some padding combined with box-decoration-break should get the basic framework:

See the Pen Multiline Padding with box-decoration-break by Chris Coyier (@chriscoyier) on CodePen.

But a gradient on there is gonna get weird on multiple lines:

See the Pen Multiline Padding with box-decoration-break by Chris Coyier (@chriscoyier) on CodePen.

I’m gonna credit Matthias Ott, from that thread, with what looks like the perfect answer to me:

See the Pen Multiline background gradient with mix-blend-mode by Matthias Ott (@matthiasott) on CodePen.

The trick there is to set up the padded multi-line background just how you want it with pure white text and a black background. Then, a pseudo-element is set over the whole area with the gradient in the black area. Throw in mix-blend-mode: lighten; to make the gradient only appear on the black area. Nice one.

The post Multi-Line Inline Gradient appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

Accessible SVG Icons With Inline Sprites

This is a great look at accessible SVG markup patterns by Marco Hengstenberg. Here’s the ideal example:

<button type="button">   Menu   <svg class="svg-icon"      role="img"      height="10"      width="10"      viewBox="0 0 10 10"      aria-hidden="true"      focusable="false">      <path d="m1 7h8v2h-8zm0-3h8v2h-8zm0-3h8v2h-8z"/>     </svg> </button>

Notes:

  • It’s not the <svg> itself that is interactive — it’s wrapped in a <button> for that.
  • The .svg-icon class has some nice trickery, like em-based sizing to match the size of the text it’s next to, and currentColor to match the color.
  • Since real text is next to it, the icon can be safely ignored via aria-hidden="true". If you need an icon-only button, you can wrap that text in an accessibily-friendly .visually-hidden class.
  • The focusable="false" attribute solves an IE 11 bug.

Plus a handy Pen to reference all the patterns.

Direct Link to ArticlePermalink

The post Accessible SVG Icons With Inline Sprites appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]