@kage0x3b/svelte-img
v3.1.2
Published
High-performance responsive/progressive images for SvelteKit
Maintainers
Readme
svelte-img
High-performance responsive/progressive images for SvelteKit.
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:
- [x] Fade-in on image reveal
- [x] Parallax vertical scroll effect
Hope you like cats. Demo: https://svelte-img.moritz.website
This is a fork of the original svelte-img which is rewritten for Svelte 5 and uses newer vite-imagetools/sharp dependencies.
Install
Install the package:
$ npm i -D @kage0x3b/svelte-imgor
$ pnpm add -D @kage0x3b/svelte-imgAdd imagetools plugin into your vite.config.ts:
import { defineConfig } from 'vite';
import { sveltekit } from '@sveltejs/kit/vite';
import { imagetools } from '@kage0x3b/svelte-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
}Under the hood
Local image transformations are delegated to the excellent
vite-imagetools with a custom run directive.
This preset generates optimised 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';Usage
Use anywhere in your Svelte app:
<script lang="ts">
// Import original full-sized image with `?as=run` query param
import cat from '$lib/assets/images/cat.jpg?as=run';
import Img from '@kage0x3b/svelte-img';
</script>
<Img class="cool kitty" src={cat} alt="Very meow" />(If you pass a normal image source string the src attribute, the component will fallback to a normal <img> tag)
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>Features
Change default widths/formats
By default, svelte-img generates 9 variants of an original full-sized image - at 480/1024/1920
widths in avif/webp/jpg formats; and a 16px webp/base64 low-quality image placeholder (LQIP).
To change this globally, edit 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 instead: 640/1280w in webp/jpg
run: new URLSearchParams('w=640;1280&format=webp;jpg')
}
})
]
});[!NOTE]
runDefaultDirectivesis deprecated and will be removed in the next major; useprofilesinstead. When a profile is not used, behaviour falls back to standardvite-imagetools, which in turn take defaults fromdefaultDirectivesas usual, so both can co-exist.
Profiles
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/assets/images/1.jpg?as=sm'; // use `sm` profile
import lg from '$lib/assets/images/2.jpg?as=lg'; // use `lg` profile
import normal from '$lib/assets/images/3.jpg?as=run';On a per-image basis
Widths/formats can be applied to a particular image. From your .svelte file:
<script lang="ts">
// We override defaults to generate 4 variants: 720/1560w in webp/jpg
import src from '$lib/assets/images/cat.jpg?as=run&w=720;1560&format=webp;jpg';
import Img from '@kage0x3b/svelte-img';
</script>
<Img {src} alt="cat" />[!NOTE]
Order offormatmatters - the last format is used as the fallback image.
If just one variant is generated, then only the <img> tag renders, so:
<script lang="ts">
// Generate only 1 variant: 640x640 in jpg
import src from '$lib/assets/images/cat.jpg?as=run&w=640&h=640&format=jpg';
import Img from '@kage0x3b/svelte-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"
/>Change LQIP width
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 lang="ts">
import src from '$lib/assets/images/cat.jpg?as=run:1';
import Img from '@kage0x3b/svelte-img';
</script>
<!-- Render img with dominant colour background -->
<Img {src} alt="cat" />Renders into:
<picture>
<source ... />
<img ... style="background: #abcdef" />
</picture>Other transformations
The full repertoire
of transformation directives offered by
vite-imagetools can be used.
<script lang="ts">
// Generate all 9 variants at fixed 600px height
import src from '$lib/assets/images/cat.jpg?as=run&h=600&fit=cover&normalize';
import Img from '@kage0x3b/svelte-img';
</script>
<Img {src} alt="cat" />Responsive Image Sizes
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
responsive images and the picture element.
<script lang="ts">
import src from '$lib/assets/images/cat.jpg?as=run&w=480;800';
import Img from '@kage0x3b/svelte-img';
</script>
<!--
When the viewport is <=600px, tell the browser's image preloader that once
the CSS for our design has been parsed and applied, we expect the width of
the image in our design to be 480px.
-->
<img {src} alt="cat" sizes="(max-width: 600px) 480px, 800px" />Renders into:
<picture>
<source
type="image/avif"
sizes="(max-width: 600px) 480px, 800px"
srcset="path/to/avif-480 480w, path/to/avif-800 800w"
/>
<source
type="image/webp"
sizes="(max-width: 600px) 480px, 800px"
srcset="path/to/webp-480 480w, path/to/webp-800 800w"
/>
<source
type="image/jpeg"
sizes="(max-width: 600px) 480px, 800px"
srcset="path/to/jpeg-480 480w, path/to/jpeg-800 800w"
/>
<img
alt="cat"
width="800"
height="600"
loading="lazy"
decoding="async"
src="path/to/jpeg-800"
style="background: url(data:image/webp;base64,XXX) center center / cover no-repeat;"
/>
</picture>Lazy loading
svelte-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 lang="ts">
import src from '$lib/assets/images/cat.jpg?as=run';
import Img from '@kage0x3b/svelte-img';
</script>
<Img {src} alt="cat" loading="eager" />Batch loading local images
Use Vite's import.meta.glob feature.
<script lang="ts">
import Img from '@kage0x3b/svelte-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}Remote images from an API
Use the svelte-img component on its own by passing a src object, like so:
<script lang="ts">
import Img from '@kage0x3b/svelte-img';
import type { ImageSourceObject } from '@kage0x3b/svelte-img';
const src = {
sources: {
// Order is important; last format is fallback img
webp: 'path/to/480.webp 480w, ...', //srcset
jpeg: '...'
},
img: { src: 'path/to/img', w: 1920, h: 1080 },
} satisfies ImageSourceObject;
</script>
<Img {src} alt="cat" />Blurred image placeholders
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 lang="ts">
import Img from '@kage0x3b/svelte-img';
import src from '$lib/assets/images/cat.jpg?as=run';
import { onMount } from 'svelte';
let ref = $state<HTMLImageElement>();
let loaded = $state(false);
onMount(() => {
if (ref.complete) {
loaded = true;
}
});
</script>
<div class="wrap">
<Img {src} bind:ref onload={() => (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>Special Effects
Fade-in on reveal
Reveal images with a fade-in effect (aka medium.com) when they are loaded and in the viewport.
<script lang="ts">
import src from '$lib/assets/images/cat.jpg?as=run';
import { FxReveal as Img } from '@kage0x3b/svelte-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>Parallax
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:
- A value closer to 0 is faster, while a value closer to 1 is slower.
- A value of 1 behaves normally.
- A value of 0 effectively makes the element fixed on the page.
The default factor is 0.75.
<script lang="ts">
import src from '$lib/assets/images/cat.jpg?as=run';
import { FxParallax as Img } from '@kage0x3b/svelte-img';
</script>
<Img class="my-img" factor="0.5" {src} alt="cat" />
<style>
:global(.my-img) {
width: 100%;
height: 28rem;
}
</style>Development
Library is packaged via SvelteKit. Standard Github contribution workflow applies.
Tests
End-to-end testing via Playwright. To run tests headlessly:
$ pnpm testChangelog
Please refer to the releases page.
License
ISC
