Posts tagged code

Preloading Images in a Responsive, WebP World

Watch out for giant images

It’s time for another Google Core Web Vitals themed TIL. This one concerns image loading. Specifically, huge, above the fold image preloading.

The site I work on uses a lot of super wide to full width hero images. We’d done some previous optimizations around serving images in WebP format (Serve images in modern formats) and using picture and srcset for responsive images (Properly size images). I won’t go into details since those topics already have a ton of tutorials that explain things better than I ever could. In fact, Smashing Magazine alone has you covered with Using WebP Image Format Today and Responsive Images Done Right: A Guide To picture And srcset.

Preload critical assets to improve loading speed.

The one downside to our earlier work? It made things a little more confusing when we started looking into preloading images.

Preloading assets that aren’t actually used isn’t particularly helpful (Chrome will even warn you if you do it!). If you’re serving a WebP image but preloading the fallback or preloading a mobile image on a desktop browser, you aren’t getting any benefits out of it.

Let’s say you have a picture block that selects an image based on browser support, screen resolution, and breakpoint:

<picture>
  <source srcset="large-image.webp, large-image-2x.webp 2x" media="(min-width: 768px)" type="image/webp" />
  <source srcset="large-image.jpg, large-image-2x.jpg 2x" media="(min-width: 768px)" />
  <source srcset="small-image.webp, small-image-2x.webp 2x" type="image/webp" />
  <img src="fallback-image.jpg" srcset="small-image.jpg, small-image-2x.jpg 2x" />
</picture>

The first question is how to handle different image formats. Looking at the Can I use entries for Webp image formats and preload, it seems pretty unlikely for a browser to support preloading but not WebP images. And according to a quick aside in this Addy Osmani article on hero images, you can use the type="image/webp" attribute with a link tag to specify that you want to preload a WebP image.

Next, we need to deal with screen sizes and resolutions. In addition to type, the link tag supports media and imagesrcset attributes which should look very similar to the media and srcset attributes on our source and img tags. The main difference is that you have to be more explicit with your breakpoints since the browser won’t be choosing the best image itself.

Putting that all together, you would add this code to your head tag to preload the images from the example above:

<link rel="preload" as="image" href="large-image.webp" media="(min-width: 768px)" imagesrcset="large-image.webp, large-image-2x.webp 2x" type="image/webp" />
<link rel="preload" as="image" href="small-image.webp" media="(max-width: 767px)" imagesrcset="small-image.webp, small-image-2x.webp 2x" type="image/webp" />

If you prefer an example with real images, check out this Pen on Codepen. Just click on Settings and HTML to view the preloading the code.

Optimizing Lodash imports with jscodeshift

Since the beginning of the year, I’ve spent a lot of time at work getting ready for Google Core Web Vitals-ageddon. Most of the techniques we’ve tried are fairly well-documented and I don’t want to retread the great advice and tutorials that are already out there (although I should put together a roundup of links). A few have required a little more investigation and experimentation, though, and those seemed worth writing up.

Remove unused JavaScript! Avoid enormous network payloads!

One easy trick for creating huge JavaScript bundles and making Google angry is importing the entire Lodash library when you only use a few methods. A lot has been written on Lodash and bundle sizes and best practices for imports (I’m partial to The Correct Way to Import Lodash Libraries - A Benchmark on BlazeMeter) but what I found lacking were tips on how to update an older, monolithic Rails app with inconsistent import patterns and the continual risk of unmanageable merge conflicts.

Enter jscodeshift.

jscodeshift is a toolkit that allows you to run codemods over JavaScript files and it was a lifesaver in this situation. According to the article above, direct imports are the way to go and jscodeshift makes it possible to instantly transform any files:

  • Importing the full library (import _ from 'lodash')
  • Importing methods from Lodash with curly brackets (import { name } from 'lodash')
  • Calling methods starting with _.

To get started with jscodeshift, run npm install -g jscodeshift to install it globally and follow along below.

Continue Reading

React Inner Image Zoom v2.0.0

Well. Apparently way back in July when I published react-inner-image-zoom v1.1.1, I announced it with a long intro about trying to get motivated during the lockdown and my hope that it would kick off a glorious new age of productivity.

That didn’t actually happen, but I’m sure it will now that we’re a year into lockdown and I actually finished v2.0.0 after putting it off for months.

So what do you need to know about the new version? It:

  • Refactors the code using React hooks so you’ll need React v16.8.0 or above to upgrade.
  • Renames the startsActive prop to zoomPreload.
  • Adds hideCloseButton and hideHint props to hide those elements. If the close button is hidden, zoom out will be triggered by tap on mobile.
  • Adds width, height, and hasSpacer props to make Lighthouse happy. The width and height will be added as attributes on the original image. If hasSpacer is true, those values will be used to get the original image’s aspect ratio and add a loading spacer to prevent cumulative layout shift.

If you find any bugs, be sure to submit an issue on Github.

Getting to know Vue (with a new image zoom component you can use)

Apologies for the title. It’s groan worthy and, I assume, has been done many times before but once it was in my head I couldn’t get it out. As penance, please accept the Vue Inner Image Zoom open source component.

I spend most of my time writing React so I thought it might be fun to give Vue.js a try. As a starter project, I decided to rewrite the React Inner Image Zoom component and accompanying demo pages using Vue. All of the functionality is the same, zoom with drag to explore on mobile and dragging or hover panning on desktop. Optional fullscreen on mobile and responsive image support. Since Vue isn’t my first language, if you use it and notice any egregious best practices missteps please let me know.

Installation details are available at NPM or Github. Demos are here

And if you’re wondering what I thought of Vue, here are a few observations:

  • It’s pretty easy to learn the basics. The new syntax took a little getting used to but it didn’t take too long to port over a component.
  • I really like Vue CLI and how easy it is to customize things like linting and testing.
  • One thing I missed was built in portal support (but it sounds like that might be included in the next version!).
  • For some reason my biggest sticking point was figuring out how to render code snippets as strings. It turned out to be fairly simple but it took me forever to even Google the right terms.

React Inner Image Zoom v1.1.1

I don’t know about everybody else but my productivity technique during the coronavirus lockdown has been to start a ton of projects, get antsy in the middle, and then abandon them for something new. And also, despite a very low bar, to somehow post on here even less than before.

Now that we’re somewhere between five months and five years into this (time has no meaning anymore), it feels like maybe I should try following through on some of this stuff. As a small step in that direction, I just published an update to my react-inner-image-zoom package.

Version 1.1.1 includes a new drag option for moving zoomed images on non-touch devices (based on a user request!) plus minor styling and dependency updates. The demo and code example are here.

Hopefully checking one item off my list will get me moving on my other half-finished odds and ends.

New! A Webpack plugin for optimizing Google AMP CSS

In a previous post, I went over my process trying to develop a PostCSS plugin to make updating custom CSS for Google AMP easier. That plugin seems to be working okay (yay!) but since it’s not integrated into my Webpack build, there’s still a lot of manual work to be done and room for human error. 

So the next step was clearly to jump into learning Webpack plugins. I needed a plugin that could watch for CSS files created using the MiniCssExtractPlugin, filter out all the unnecessary styles, and create a new file just for AMP. The results are on GitHub

My build process is pretty simple so it’s very likely there are options or interactions I haven’t thought of. If you end up using it and notice any problems, let me know!