Create responsive image tags.
npm i -S @bicycle-codes/image
Create responsive img
tags, with a srcset
property that allows browsers to download the best size image. Optionally, create a small, blurry image as a placeholder for the image with the blur up technique.
See MDN docs on responsive images
We want to display identical image content, just larger or smaller depending on the device
You need to define two things -- a list of sizes of images that are available:
[300, 600, 900]
And the media condition for the sizes attribute:
<img sizes="(min-width: 50em) 50em, 100vw" />
This is designed to work easily with either Cloudinary or locally hosted image files. If you are hosting images locally, you may want to create multiple resolutions of the images. For this, see the section on resizing images.
See also
Tip
Use the dev tools to throttle the internet speed, and load in a mobile view. You should see that the requests are made for a smaller version of the image.
- build consumable files:
npm run build
- start a local demo:
npm start
. Also, edit theindex.html
file inexample
to test different implementations.
Run all tests:
npm test
Run the tests for HTML generation:
npm run test-html
In general this will create defaults for attributes. The only required attributes are filename
and alt
. Everything else has defaults.
interface Props {
class?:string,
className?:string,
filename:string,
alt:string,
loading?:'eager'|'lazy',
fetchpriority?:'high'|'low'|'auto',
decoding?:'sync'|'async',
sizes?:string[],
srcset?: number[]
}
The default srcset
attribute has these sizes:
const defaultSizes = [1024, 768, 480]
Create an <img>
element with a srcset
attribute with relevant image sources.
import { html } from 'htm/preact'
import { render } from 'preact'
import { CloudinaryImg } from '@bicycle-codes/image/cloudinary/preact'
import '@bicycle-codes/image/style.css'
import './my-style.css'
//
// create an image tag with a good default `srcset` attribute
//
const { Image } = CloudinaryImg({ cloudName: 'nichoth' })
const Example = function () {
return html`<div>
<p>image</p>
<${Image} filename=${'testing'} class=${'my-image-test'}
sizes=${['50vw']}
/>
</div>`
}
//
// or pass in a custom `srcset` as an array of image widths
//
const CustomSrc = function () {
return html`<${Image} filename=${'testing'} class=${'my-image-test'}
sizes=${['50vw']} srcset=${[300, 600, 1000]}`
}
render(html`<${Example} />`, document.getElementById('root'))
Create an image with a blur up placeholder.
import { html } from 'htm/preact'
import { render } from 'preact'
import { CloudinaryImage } from '@bicycle-codes/image/cloudinary/preact'
import '@bicycle-codes/image/css'
import './my-style.css'
const { BlurredImage } = CloudinaryImage({ cloudName: 'nichoth' })
const placeholderImg = 'data:image/jpeg;base64,/9j/4AAQSkZJ...'
//
// create using the default srcset
//
const Example = function () {
return html`<${BlurredImage} filename=${'testing'} class=${'blur-test'}
blurPlaceholder=${placeholderImg}
sizes=${['50vw']}
/>`
}
// or pass in a custom srcset:
// srcset=${[300, 600, ...]}
Create an img
tag that links to locally hosted files. See the CLI section for info on creating images of different sizes.
Note
This uses a naming convention for image files. If you are dealing with a file
my-file.jpg
, then alternate resolutions should be named like
my-file-400.jpg
, my-file-800.jpg
, etc, for versions that are
400
and 800
px wide.
import { html } from 'htm/preact'
import { render } from 'preact'
import { Image, BlurredImage } from '@bicycle-codes/image/preact'
import '@bicycle-codes/image/style.css'
import './my-style.css'
const placeholderImg = 'data:image/jpeg;base64,/9j/4AAQSkZJ...'
const Example = function () {
return html`<div>
<p>hello</p>
<p>non-blurry image</p>
<div class="non-blurry-wrapper">
<${Image} filename=${'/100.jpg'} class=${'non-blurry-image'}
sizes=${['50vw']}
/>
</div>
<p>blurry image</p>
<${BlurredImage} filename=${'/100.jpg'} class=${'blur-test'}
blurPlaceholder=${placeholderImg}
sizes=${['50vw']}
/>
</div>`
}
render(html`<${Example} />`, document.getElementById('root'))
Create a tonic component for an img
tag with a good defualt srcset
attribute.
import Tonic from '@bicycle-codes/tonic'
import { CloudinaryTonic } from '@bicycle-codes/image/cloudinary/tonic'
import '@bicycle-codes/image/style.css'
import './my-style.css'
const { ImageTag, BlurredImage } = CloudinaryTonic({ cloudName: 'nichoth' })
const placeholderImg = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/...'
const sizes = ['50vw']
// `maxWidth` below is used as the `src` attribute on the image tag
// so it is used if the browser doens't understnad the `srcset` attribute
class TheApp extends Tonic {
render () {
return this.html`<div class="the-app">
<image-tag
id="tag-test"
filename="testing"
sizes=${sizes.join(', ')}
></image-tag>
<blurred-image
id="test"
filename="testing"
blurplaceholder=${placeholderImg}
sizes=${sizes.join(', ')}
maxWidth=${[1024]}
></blurred-image>
</div>`
}
}
Tonic.add(ImageTag)
Tonic.add(BlurredImage)
Tonic.add(TheApp)
Create tonic components that link to locally hosted files.
note
This uses a naming convention for image files. If you are dealing with a file my-file.jpg
, then alternate resolutions should be named like my-file-400.jpg
, my-file-800.jpg
, etc, for versions that are 400
and 800
px wide.
import Tonic from '@bicycle-codes/tonic'
import { ImageTag, BlurredImage } from '@bicycle-codes/image/tonic'
import '@bicycle-codes/image/style.css'
import './my-style.css'
const placeholderImg = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/...'
const sizes = ['50vw']
// `maxWidth` below is used as the `src` attribute on the image tag
// so it is used if the browser doens't understnad the `srcset` attribute
class TheApp extends Tonic {
render () {
return this.html`<div class="the-app">
<image-tag
id="tag-test"
filename="/100.jpg"
sizes=${sizes.join(', ')}
></image-tag>
<blurred-image
id="test"
filename="/100.jpg"
blurplaceholder=${placeholderImg}
sizes=${sizes.join(', ')}
maxWidth=${[1024]}
></blurred-image>
</div>`
}
}
Tonic.add(ImageTag)
Tonic.add(BlurredImage)
Tonic.add(TheApp)
Generate HTML strings instead of components.
note
This uses a naming convention for image files. If you are dealing with a file my-file.jpg
, then alternate resolutions should be named like my-file-400.jpg
, my-file-800.jpg
, etc, for versions that are 400
and 800
px wide.
// node js
import { html } from '@bicycle-codes/image'
const markup = html({
filename: '/aaa.jpg',
alt: 'test picture',
})
console.log(markup)
// =>
// <div class="image">
// <img
// alt="test picture"
// srcset="/aaa-1024.jpg 1024w, /aaa-768.jpg 768w, /aaa-480.jpg 480w"
// sizes="100vw"
// src="/aaa.jpg"
// decoding="auto"
// loading="lazy"
// fetchpriority="low"
// >
// </div>
import { CloudinaryImage } from '@bicycle-codes/image/cloudinary'
// pass in your cloudinary name
const { Image } = CloudinaryImage('nichoth')
const html = Image({
filename: 'bbb.jpg',
alt: 'testing'
})
We need small base64 encoded strings to use as placeholder images for the blur up effect.
Use the CLI to generate a small base64 encoded image to use as a blurry placeholder while a better quality image downloads.
First install this locally
npm i -S @bicycle-codes/image
Then call the node binary file included, aliased locally as image.stringify
.
--size
option is optional. The default is 40px.
-s, --size The width of the base64 image [number]
Convert a local file to base64 (this will write to stdout
):
npx image.stringify ./my-local-file.jpg --size 40
Or use Cloudinary as an image source:
npx image.stringify my-cloud-name my-filename.jpg -s 20
Use the shell to redirect output to a file:
npx image.stringify my-cloud-name my-filename.jpg > ./my-filename.base64
Use the exported functions getImgFile
and getImgCloudinary
to create base64 encoded strings in node.
import { getImgFile, getImgCloudinary } from '@bicycle-codes/image/bin/to-string'
const base64FromLocalFile = getImgFile('./file.jpg')
// (cloudName, filename)
const base64FromCloudinary = getImgCloudinary('nichoth', 'my-file.jpg')
note
There's no CJS version of the base64 functions because I used top level await
.
Create multiple resolutions of a single source image. This is suitable for the default resolution options that are created for the srcset
attribute in the client side JS.
First install locally:
npm i -S @bicycle-codes/image
Then run via npx
npx image.resize ./file.jpg --output ./output-dir
This will create 4 files in the output directory -- file-480.jpg
, file-768.jpg
, file-1024.jpg
, and file.jpg
. It will copy the original file in addition to resizing it to new versions.
Or use this in node
import { resize, defaultSizes } from '@bicycle-codes/image/resize'
// (filename, outputDir, sizes) {
await resize('./my-file.jpg', './output-dir', defaultSizes)
// ./output-dir now contains the default resolutions of my-file.jpg
See this nice article for more information about images -- A Guide to the Responsive Images Syntax in HTML
bholmes.dev -- Picture perfect image optimization
This project also includes tools to help with the "Blur Up" technique, which means creating a nice blurred placeholder image while a high resolution image downloads, then switching them out, so the high res image is visible when it's ready
See the section on the CLI for info on creating base64 strings of images.
A guide to getting the dominant color
Responsive Images: If you’re just changing resolutions, use srcset.
It’s actually not that bad to just leave it off. In that case, it assumes sizes="100vw"
https://bholmes.dev/blog/picture-perfect-image-optimization/#link-the-sizes-attribute
In general, the sizes attribute is a way to tell the browser which image to use for a given screen size.
our image is 100vw (100% screen width) below 500px, and 50vw when we hit
@media (min-width: 500px)
. This perfectly translates to sizes 👉sizes="(min-width: 500px) 50vw, 100vw"
If your only goal is improved performance, then use an img
tag with srcset
and sizes
attributes.
Use the picture
element to choose different files based on a media query or browser support, or for art direction purposes.
For example, cropping an image differently depending on the size of the screen and differences in the layout.
srcset
is a comma separated list of values, telling the browser which image size to download. src
is a fallback if the browser does not understand srcset
.
srcset defines the set of images we will allow the browser to choose between
You can use a value like filename.jpg 2x
(with an x
descriptor) to tell the browser about different files based on pixel denxity. If it is on a high-res screen, it can download the 2x
version.
Use srcset
with sizes
to let the browser choose based on page layout.
The sizes attribute describes the width that the image will display within the layout of your specific site. The width that images render at is layout-dependent rather than just viewport dependent.
Contain two things:
- A media condition. This must be omitted for the last item in the list.
- A source size value.
sizes defines a set of media conditions (e.g. screen widths) and indicates what image size would be best to choose
Media Conditions describe properties of the viewport, not of the image. For example,
(max-height: 500px) 1000px
proposes to use a source of 1000px width, if the viewport is not higher than 500px.
So sizes
tells us which image to choose based on screen size.
srcset
tells us different images that are available to choose from. The browser can use a variety of criteria to choose an image, like bandwidth cost in addition to screen size.
If the srcset attribute uses width descriptors, the sizes attribute must also be present, or the srcset itself will be ignored.