High-performance responsive/progressive images for SvelteKit with the option to use Vercel optimized images.
In addition to being able to generate all permutations locally, this fork makes it possible to opt into using Vercel image optimization for the transformed image.
The primary benefit of not having to rely on the <picture>
element is that it unlocks the ability to preload srcset
in head
.
See the vercel-img demo site for more information about the Vercel addition.
Automatically transform local images into multiple widths and next-gen formats, then render a minimally invasive LQIP-included HTML representation into your SvelteKit project.
Includes special effects:
Hope you like cats. Demo: https://zerodevx.github.io/vercel-img/
Install the package:
$ npm i -D @leoj3n/vercel-img
Add imagetools
plugin into your vite.config.js
:
import { defineConfig } from 'vite'
import { sveltekit } from '@sveltejs/kit/vite'
import { imagetools } from '@leoj3n/vercel-img/vite'
export default defineConfig({
plugins: [sveltekit(), imagetools()]
})
Optionally, to silence typescript
warnings on image imports, create a new
file at src/ambient.d.ts
:
// Squelch warnings of image imports from your assets dir
declare module '$lib/assets/*' {
var meta
export default meta
}
Local image transformations are delegated to the excellent
vite-imagetools with a custom run
directive.
This preset generates optimized images with sensible defaults, including a base64
low-quality
image placeholder.
Invoke the preset with the ?as=run
query param:
import imageMeta from 'path/to/asset?as=run'
Use anywhere in your Svelte app:
<script>
// Import original full-sized image with `?as=run` query param
import cat from '$lib/assets/cat.jpg?as=run'
import Img from '@leoj3n/vercel-img'
</script>
<Img class="cool kitty" src="{cat}" alt="Very meow" />
The image component renders into:
<picture>
<source
type="image/avif"
srcset="path/to/avif-480 480w, path/to/avif-1024 1024w, path/to/avif-1920 1920w"
/>
<source
type="image/webp"
srcset="path/to/webp-480 480w, path/to/webp-1024 1024w, path/to/webp-1920 1920w"
/>
<source
type="image/jpeg"
srcset="path/to/jpeg-480 480w, path/to/jpeg-1024 1024w, path/to/jpeg-1920 1920w"
/>
<img
class="cool kitty"
width="1920"
height="1080"
loading="lazy"
decoding="async"
style="background: url(data:image/webp;base64,XXX) no-repeat center/cover"
alt="Very meow"
src="path/to/jpeg-1920"
/>
</picture>
vercel-img
does not set any global default image types, or widths, to generate for a passed image. You change this globally, by editing your vite.config.js
:
import ...
// By default, `run` is set to 'w=480;1024;1920&format=avif;webp;jpg' (9 variants)
export default defineConfig({
plugins: [
sveltekit(),
imagetools({
profiles: {
// Now we change `run` to generate 4 variants: 640/1280w in webp/jpg
run: new URLSearchParams('w=640;1280&format=webp;jpg')
}
})
]
})
[!NOTE]
runDefaultDirectives
is deprecated and will be removed in the next major; useprofiles
instead. When a profile is not used, behaviour falls back to standardvite-imagetools
, which in turn take defaults fromdefaultDirectives
as usual, so both can co-exist.
Use profiles to manage multiple defaults. Define in your vite.config.js
:
export default defineConfig({
plugins: [
sveltekit(),
imagetools({
profiles: {
sm: new URLSearchParams('w=640&format=webp;jpg'),
lg: new URLSearchParams('w=640;1280;1920&format=webp;jpg')
}
})
]
})
Then invoke in your app:
import sm from '$lib/a/1.jpg?as=sm' // use `sm` profile
import lg from '$lib/a/2.jpg?as=lg' // use `lg` profile
import normal from '$lib/a/3.jpg?as=run'
Widths/formats can be applied to a particular image. From your .svelte
file:
<script>
// We override defaults to generate 4 variants: 720/1560w in webp/jpg
import src from '$lib/a/cat.jpg?w=720;1560&format=webp;jpg&as=run'
import Img from '@leoj3n/vercel-img'
</script>
<Img {src} alt="cat" />
[!NOTE]
Order offormat
matters - the last format is used as the fallback image.
If just one variant is generated, then only the <img>
tag renders, so:
<script>
// Generate only 1 variant: 640x640 in jpg
import src from '$lib/a/cat.jpg?w=640&h=640&format=jpg&as=run'
import Img from '@leoj3n/vercel-img'
</script>
<Img {src} alt="cat" />
Renders into:
<img
width="640"
height="640"
loading="lazy"
decoding="async"
style="background: url(data:image/webp;base64,XXX) no-repeat center/cover"
alt="cat"
src="path/to/jpg-640"
/>
The run
directive takes an optional parameter that sets the LQIP's width. Using ?as=run
defaults
to 16px
LQIP - functionally equivalent to ?as=run:16
. Increase for a higher quality LQIP (eg.
?as=run:32
for 32px
LQIP) at the expense of a larger inline base64
(larger HTML size).
To disable LQIP, set ?as=run:0
.
For a dominant single-colour background, set ?as=run:1
, so:
<script>
import src from '$lib/a/cat.jpg?as=run:1'
import Img from '@leoj3n/vercel-img'
</script>
<!-- Render img with dominant colour background -->
<Img {src} alt="cat" />
Renders into:
<picture>
<source ... />
<img ... style="background: #abcdef" />
</picture>
The full repertoire of transformation directives offered by vite-imagetools can be used.
<script>
// Generate all 9 variants at fixed 600px height
import src from '$lib/a/cat.jpg?h=600&fit=cover&normalize&as=run'
import Img from '@leoj3n/vercel-img'
</script>
<Img {src} alt="cat" />
Use the sizes
attribute to define media conditions that provide hints as to which image size to
select when those conditions are true. Read up more on the
art direction problem.
<script>
import src from '$lib/a/cat.jpg?w=480;800as=run
import Img from '@leoj3n/vercel-img'
</script>
<!-- If viewport is <=600px, use 480px as viewport width -->
<Img {src} alt="cat" sizes="(max-width: 600px) 480px, 800px" />
vercel-img
utilises the browser's native lazy loading capability by setting the loading="lazy"
attribute on the rendered <img>
tag by default. This is supported by
most modern browsers. To load an image eagerly instead:
<script>
import src from '$lib/a/cat.jpg?as=run'
import Img from '@leoj3n/vercel-img'
</script>
<Img {src} alt="cat" loading="eager" />
Use Vite's import.meta.glob
feature.
<script>
import Img from '@leoj3n/vercel-img'
const modules = import.meta.glob('$lib/a/cats/*.*', {
import: 'default',
eager: true,
query: { w: 640, h: 640, fit: 'cover', as: 'run' }
})
const images = Object.entries(modules).map((i) => i[1])
</script>
{#each images as src}
<Img {src} alt="cat" />
{/each}
Use the vercel-img
component on its own by passing a src
object, like so:
<script>
import Img from '@leoj3n/vercel-img'
const src = {
img: { src: 'path/to/img', w: 1920, h: 1080 },
sources: {
// Order is important; last format is fallback img
webp: [
{ src: 'path/to/img', w: 1920 },
{ src: 'path/to/img', w: 1024 }
],
jpeg: [
{ src: 'path/to/img', w: 1920 },
{ src: 'path/to/img', w: 1024 }
]
}
}
</script>
<Img {src} alt="cat" />
Natively, browsers do already apply some blur when displaying low resolution images. That's enough for me, but you can apply your own using CSS.
<script>
import Img from '@leoj3n/vercel-img'
import src from '$lib/a/cat.jpg?as=run'
import { onMount } from 'svelte'
let ref, loaded
onMount(() => {
if (ref.complete) loaded = true
})
</script>
<div class="wrap">
<Img {src} bind:ref on:load={() => (loaded = true)} />
<div class="blur" class:loaded />
</div>
<style>
.wrap {
position: relative;
overflow: hidden;
}
.blur {
position: absolute;
inset: 0;
backdrop-filter: blur(20px);
pointer-events: none;
}
.loaded {
display: none;
}
</style>
Reveal images with a fade-in effect (aka medium.com) when they are loaded and in the viewport.
<script>
import src from '$lib/a/cat.jpg?as=run'
import { FxReveal as Img } from '@leoj3n/vercel-img'
</script>
<Img class="my-img" {src} alt="cat" />
<style>
:global(.my-img) {
width: 640px;
height: 480px;
/* These CSS vars (with their default values) are exposed */
--reveal-transform: scale(1.02);
--reveal-transition: opacity 1s ease-in, transform 0.8s ease-out;
--reveal-filter: blur(20px);
}
</style>
Apply a vertical parallax scrolling effect to an image, where factor
is a decimal value between 0
and 1, that controls how much slower the element scrolls, relative to the scrolling speed:
The default factor is 0.75
.
<script>
import src from '$lib/a/cat.jpg?as=run'
import { FxParallax as Img } from '@leoj3n/vercel-img'
</script>
<Img class="my-img" factor="0.5" {src} alt="cat" />
<style>
:global(.my-img) {
width: 100%;
height: 28rem;
}
</style>
Library is packaged via SvelteKit. Standard Github contribution workflow applies.
End-to-end testing via Playwright. To run tests headlessly:
$ npm run test
Please refer to the releases page.
ISC