svelte-lazy
A Svelte component to lazy load any content: images, videos, components or anything else. It shows a placeholder until the content scrolls into view, and fades it in.
Native loading="lazy" covers images and iframes. svelte-lazy also defers rendering any markup or component, keeps the layout stable while images load, and works in scroll containers.
Live examples (source) · [Try it in the Svelte Playground][playground]
Install
npm i svelte-lazy
It works with Svelte 3, 4 and 5, and comes with TypeScript types.
Usage
<script>
import Lazy from 'svelte-lazy';
</script>
<Lazy height={300} placeholder="Loading...">
<img alt="" src="https://picsum.photos/id/412/400/300" />
</Lazy>
[!IMPORTANT]
Setheightto about the height of the content. Before loading, the container has this height. With the default0, all containers are empty and in view at once, so everything loads right away, and the page jumps when the content appears.
By default, content is unloaded when it leaves the viewport and loaded again when it comes back. Set keep to load it only once.
Examples
Load only once, with a component as the placeholder:
<script>
import Lazy from 'svelte-lazy';
import Spinner from './Spinner.svelte';
</script>
<Lazy height={400} keep placeholder={Spinner} placeholderProps={{ size: 'large' }}>
<HeavyChart />
</Lazy>
Start loading earlier, 500px before the content scrolls into view, and know when it loads:
<Lazy height={300} offset={500} onload={(node) => console.log('loaded', node)}>
<video controls src="movie.mp4"></video>
</Lazy>
Link to content that isn't loaded yet. The container has the id before loading, so #comments scrolls to it and loads it:
<a href="#comments">Comments</a>
<Lazy id="comments" height={600}>
<Comments />
</Lazy>
Style the container, the placeholder and the content by their classes:
<Lazy class="card" height={200}>...</Lazy>
<style>
:global(.card .svelte-lazy-content) {
border-radius: 8px;
}
</style>
Props
| Prop | Type | Default | Description |
| ------------------ | --------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| height | number \| string | 0 | Height of the container before loading, in px or any CSS length like '50vh' |
| keep | boolean | false | Keep the content after it leaves the viewport, instead of unloading it |
| offset | number | 150 | Load when the top of the container is within this many px below the bottom of the viewport or the scroll container |
| placeholder | string \| Component | null | Shown before loading, and while images in the content are loading |
| placeholderProps | object | null | Props for a component placeholder |
| id | string | undefined | id of the container |
| class | string | '' | Class added to the container, after svelte-lazy |
| fadeOption | object \| null | { delay: 0, duration: 400 } | Options of the fade transition. null to disable it |
| onload | (node) => void | null | Called with the container element when the content is loaded |
| resetHeightDelay | number | 0 | Delay in ms before the container height becomes auto after loading |
How it works
- The content loads when the container scrolls within
offsetof the bottom of the viewport, or of its scroll container. Unlesskeepis set, it unloads after leaving, and the container gets itsheightback. - If the content has an image, the placeholder stays until the image loads, then the container height becomes
auto. If the image fails, the content shows with the fixedheight. Only the first image of the content is waited for: useresetHeightDelayto wait longer for others. - With server-side rendering, the server renders the placeholder; the content loads in the browser.
- Only vertical scrolling is checked: in a horizontal scroll container, content loads by its vertical position.
<div id="{id}" class="svelte-lazy {class}">
<div class="svelte-lazy-placeholder">...</div>
</div>
and after:
<div id="{id}" class="svelte-lazy {class}">
<div class="svelte-lazy-content">...</div>
</div>
Changelog
See CHANGELOG.md.
Contributing
See CONTRIBUTING.md for development, tests and releases.
[playground]: https://svelte.dev/playground/untitled#H4sIAAAAAAAAA41W3Y7bNhZ-lQOlheVGluQZd2aikRxkC_Qquxsgze5FnAtKOrLY0qSWpOw4hu_3eh9xn6QgqT-PjbYYzAzF8_H8fueQJ4-THXqJp_bINC4Y-Xb0Aq-iDJWXfD55-tgYsdnwgh78rmlCd8ALvJwovLVfCK6Ra-UlXqoKSRu93nAAumuE1PCefDtCJcUOZhPTs-cNN6Aogg-10EI5yCeuGkZUDfkRDuQIooL3QuIOPtBCtTtzohBcaWjcoQw-mz2AExjPEpj9izCGx1kAtExgGa_u4Ry8gHzUkhxylJKiGoBP8TXw51-FLAfE8sdrxId2O8jvbsh_QdLJ726J_02YbCc-rG44y8VhANw_XAP-hkR2gNV9H-2XMb2_1AiMaFQamCClAsJLaLldB8DxYCQVlUoHVlSLA-wIPwKRaE9gCVwcjDaGGnBvSg0ZfKc00eh__jJ_7mUO_Q9xGMXxKC1EyzVkED-PZWRiCxn4hnuBDWgO2RpOLkR34HUGy2e3Mdj-fAKeOHkA42E4BxCGoYN9CRWjBfpxAE_OifOYlNSwcg01UcBFlw0oCGM5KX5L4EB0UYOuETpuA0OyR7tTIZZGS9XyQlPBHfiTS6hvpPMxABOjyBXKPUrITLbh760m5tw_u23fl1gIWapp6ACVkOA7BU5uemFADrALIBcljrBQ4k7sTT1KvDwCQCvwDTosGFHqPVX6bWhCJZQrf9qniy4Bs_kLDWBq589c6maBtR3-p0V5_IgMCy2kP2tm87ehxq_6J6dk_jzVcB4_huWwmFLJ5PRS9TvG_Fl4082QId_qurN07k32RQi7hS1UACcoaspKk4AEtGwxANXmWiK6z1GBRN1KDicoUWkpjgn4tmCD4pKqQnCOhfbnlmo2mjQaJuKGpztCuZ2NqUJHHpv_bONZVnnQKkymfFp3zf4KSVH3Q48otwLf_gtpOR_yZokNNdJtrbPT8s3DGURVKdTZKT5Dw0iBtWAlymzjvRekpHwbhuHGA2ENZicXla1tV1lnxDbneT3WLKW7LRCms9MIOIOSRbbxaq0blURRY6d26PyOaBmdeo_P0SqOo7tVvPEgmmpt1lN9adQMwjSyTdtlJDIZsWGnUZfMtWvvlChaYodL6-V6wpM0qpe9pBmSX1OuN976YyEFY7bJmSVEl287HCiHPcVDMBmfcKiRG_jRjYfR2bSk-0G7HVMbb30aOH2GCFyUqqPreTJp06ik-15Ry4b4OxJ0Q5AotwLf_gv5fNJPKaOd-ZOTmhk5LZ7RRisYhZBlGbiKny_bPM3Xr9MoX0OnytU5LUSJa0eaNLIfF9oTZAqvNf3_v_-71uWy6abqoCCi1TSgiNExEWPtjaRLURr1dU-jrs82PFX6yJxrZq8fYSU1z4xjAhXDr12Dm-XiIEmTgPnb7W7N992qGVCC60VFdpQdE1BHpXG3aGkAinC1UChp1Xc-QGiaujd5oKWuE7h7igddrksTWN2Ne2KPsmLisDgmQFotuu2GlKZVE4hhOTqTC1miTGDZfAUlGC1BbnN_efcI_W8EcbiaX8AXkpS0VcmoaOJssmUiJ8yfztZh8O-I3FLuDna3-LXSeOLd14WqSSkOxm3zY_y85eH9fPQj-gHeKajptjYU769gQjlKyLES3aOE8m0ASgAXuqZ8C-amU_BD9Meh9NfEPPgT3GRQDuH35Vq-eRhiHOqyvLuMm36z2112cvGXMn3T7MDVraRlb9bgFlTjTiVQINcoe_40pKD6mEAcPowmzah-qS5novjt-YKcyzj-_gU3l6sJN_NfsdCLimrz9toPNl9Q4OmaVs0VhWJDoYFEtqsOncmHOB7P257uT5sWTYbX4I7yRd9VcTw1Wi9f2jPsu6S7mfm3UMuxuJNcPk5OumfpaeK5ot_QOHE5Jfp4HuM-zEIwIRN49Vj8WFTV89T4Ihdai92L7LXsysWrgdBtmBtrYaddAlxwHLY5LoZiTuNgNOG6Xtj3j8_hNawGyt0mUX5FIcqt-ltMwt0kY_ayzMdXscvCkjxW99PEdrfqtRMTkLlpXiq6L5_yapi7adTPfC_wzOPTS8xL7vwl8DSh7EB56SUVMdfT7yn2q8cUDwAA