Svelte and SvelteKit course Β· Module 10: Actions
Popular Action Patterns
In this lesson5
Lazy Loading images
export function lazyLoad(node, src) {
const observer = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
node.src = src;
observer.disconnect();
}
});
observer.observe(node);
return {
destroy() {
observer.disconnect();
}
};
}<img use:lazyLoad={'/images/viking.jpg'} alt="Viking" />Auto Focus
export function autoFocus(node) {
node.focus();
}<input use:autoFocus placeholder="Auto focused!" />Portal
export function portal(node, target = 'body') {
const targetEl = document.querySelector(target);
targetEl.appendChild(node);
return {
destroy() {
node.remove();
}
};
}<div use:portal={'#modals'}>
Modal content
</div>Longpress
export function longpress(node, duration = 500) {
let timer;
function handleMouseDown() {
timer = setTimeout(() => {
node.dispatchEvent(new CustomEvent('longpress'));
}, duration);
}
function handleMouseUp() {
clearTimeout(timer);
}
node.addEventListener('mousedown', handleMouseDown);
node.addEventListener('mouseup', handleMouseUp);
node.addEventListener('mouseleave', handleMouseUp);
return {
update(newDuration) {
duration = newDuration;
},
destroy() {
clearTimeout(timer);
node.removeEventListener('mousedown', handleMouseDown);
node.removeEventListener('mouseup', handleMouseUp);
node.removeEventListener('mouseleave', handleMouseUp);
}
};
}<button use:longpress={1000} onlongpress={() => alert('Long pressed!')}>
Hold me for 1 second
</button>In Svelte 5 you handle a custom event that an action sends with dispatchEvent with a regular attribute: on + the event name, so onlongpress.
TypeScript Actions
import type { Action } from 'svelte/action';
interface TooltipParams {
text: string;
position?: 'top' | 'bottom';
}
export const tooltip: Action<HTMLElement, TooltipParams> = (node, params) => {
// ... implementation
return {
update(newParams: TooltipParams) {
// ...
},
destroy() {
// ...
}
};
};
// The third type parameter describes the custom events of the action
export const longpress: Action<HTMLElement, number, {
onlongpress: (e: CustomEvent) => void;
}> = (node, duration = 500) => {
// ...
};Code for this lesson: App.svelte
<script>
// Practical actions - resize observer, intersection observer
let width = $state(0);
let height = $state(0);
let visible = $state(false);
// Resize observer action
function resizeObserver(node, callback) {
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
callback(entry.contentRect);
}
});
observer.observe(node);
return {
destroy() {
observer.disconnect();
}
};
}
// Intersection observer action
function inview(node, callback) {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
callback(entry.isIntersecting);
});
}, { threshold: 0.5 });
observer.observe(node);
return {
destroy() {
observer.disconnect();
}
};
}
function handleResize(rect) {
width = Math.round(rect.width);
height = Math.round(rect.height);
}
function handleInview(isVisible) {
visible = isVisible;
}
</script>
<main class="observer-demo">
<h1>Observer Actions</h1>
<section
class="resizable"
use:resizeObserver={handleResize}
>
<h2>ResizeObserver</h2>
<p>Width: <strong>{width}px</strong></p>
<p>Height: <strong>{height}px</strong></p>
<p class="hint">Resize the browser window</p>
</section>
<div class="spacer">
<p>β Scroll down β</p>
</div>
<section
class="observed"
class:visible
use:inview={handleInview}
>
<h2>IntersectionObserver</h2>
<p>Status: <strong>{visible ? 'Visible!' : 'Hidden'}</strong></p>
</section>
</main>
<style>
.observer-demo {
font-family: 'Georgia', serif;
max-width: 500px;
margin: 0 auto;
padding: 2rem;
background: #1a1a2e;
color: #e8e8f7;
}
h1 {
color: #7cb3f0;
text-align: center;
}
section {
background: rgba(124, 179, 240, 0.1);
border: 1px solid #7cb3f0;
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
}
h2 {
color: #7cb3f0;
font-size: 1rem;
margin-top: 0;
}
strong {
color: #ffd700;
}
.hint {
font-size: 0.85rem;
color: #6a8ab9;
}
.spacer {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
color: #6a8ab9;
}
.observed {
transition: all 0.3s;
opacity: 0.5;
transform: scale(0.95);
}
.observed.visible {
opacity: 1;
transform: scale(1);
border-color: #ffd700;
}
</style>Docs entries used in this lesson
Spotted a mistake in this lesson?
Hands-on tasks in the game
- Code editor
Complete the action that automatically focuses the element