Bases de Svelte
Introduction
Liaisons
Classes et styles
Svelte avancé
Réutiliser du contenu
Mouvements
Liaisons avancées
Transitions avancées
API de contexte
Éléments spéciaux
<script module>
Next steps
Bases de SvelteKit
Introduction
Routing
Chargement de données
En-têtes et cookies
Modules partagés
Formulaires
$app/state
Erreurs et redirections
SvelteKit avancé
Options de page
Options de lien
Routing avancé
Chargement avancé
Variables d’environnement
Conclusion
Les actions ne sont rien de plus que des fonctions de cycle de vie pour un élément. Elles sont utiles pour des choses comme :
- s’interfacer avec des librairies tierces
- charger des images en différé
- afficher des tooltips
- ajouter des gestionnaires d’évènements personnalisés
Dans cette application, vous pouvez gribouiller dans le <canvas>
, et changer les couleurs et la
taille du pinceau via le menu. Mais si vous ouvrez le menu et parcourez les options avec la touche
Tab, vous vous rendrez vite compte que le focus reste coincé dans la modale.
Nous pouvons corriger ça avec une action. Importez trapFocus
depuis actions.svelte.js
...
<script>
import Canvas from './Canvas.svelte';
import { trapFocus } from './actions.svelte.js';
const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet', 'white', 'black'];
let selected = $state(colors[0]);
let size = $state(10);
let showMenu = $state(true);
</script>
<script lang="ts">
import Canvas from './Canvas.svelte';
import { trapFocus } from './actions.svelte.js';
const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet', 'white', 'black'];
let selected = $state(colors[0]);
let size = $state(10);
let showMenu = $state(true);
</script>
...puis ajoutez-le au menu avec la directive use:
:
<div class="menu" use:trapFocus>
Étudions la fonction trapFocus
dans le fichier actions.svelte.js
. Une fonction d’action est
appelée avec un node
— la <div class="menu">
dans notre cas — lorsque le noeud est monté dans le
DOM. Dans l’action, nous avons un effet.
D’abord, nous avons besoin d’ajouter un gestionnaire d’évènement qui intercepte les pressions sur la touche Tab :
$effect(() => {
focusable()[0]?.focus();
node.addEventListener('keydown', handleKeydown);
});
Ensuire, nous devons nettoyer l’effet lorsque le noeud est démonté — c’est-à-dire supprimer le gestionnaire, et remettre le focus à l’endroit où il était avant le montage de l’élément :
$effect(() => {
focusable()[0]?.focus();
node.addEventListener('keydown', handleKeydown);
return () => {
node.removeEventListener('keydown', handleKeydown);
previous?.focus();
};
});
Désormais, lorsque vous ouvrez le menu, vous pouvez parcourir les options avec la touche Tab.
Modifier cette page sur Github
<script>
import Canvas from './Canvas.svelte';
const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet', 'white', 'black'];
let selected = $state(colors[0]);
let size = $state(10);
let showMenu = $state(true);
</script>
<div class="container">
<Canvas color={selected} size={size} />
{#if showMenu}
<div
role="presentation"
class="modal-background"
onclick={(event) => {
if (event.target === event.currentTarget) {
showMenu = false;
}
}}
onkeydown={(e) => {
if (e.key === 'Escape') {
showMenu = false;
}
}}
>
<div class="menu">
<div class="colors">
{#each colors as color}
<button
class="color"
aria-label={color}
aria-current={selected === color}
style="--color: {color}"
onclick={() => {
selected = color;
}}
></button>
{/each}
</div>
<label>
petit
<input type="range" bind:value={size} min="1" max="50" />
grand
</label>
</div>
</div>
{/if}
<div class="controls">
<button class="show-menu" onclick={() => showMenu = !showMenu}>
{showMenu ? 'fermer' : 'menu'}
</button>
</div>
</div>
<style>
.container {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.controls {
position: absolute;
left: 0;
top: 0;
padding: 1em;
}
.show-menu {
width: 5em;
}
.modal-background {
position: fixed;
display: flex;
justify-content: center;
align-items: center;
left: 0;
top: 0;
width: 100%;
height: 100%;
backdrop-filter: blur(20px);
}
.menu {
position: relative;
background: var(--bg-2);
width: calc(100% - 2em);
max-width: 28em;
padding: 1em 1em 0.5em 1em;
border-radius: 1em;
box-sizing: border-box;
user-select: none;
}
.colors {
display: grid;
align-items: center;
grid-template-columns: repeat(9, 1fr);
grid-gap: 0.5em;
}
.color {
aspect-ratio: 1;
border-radius: 50%;
background: var(--color, #fff);
transform: none;
filter: drop-shadow(2px 2px 3px rgba(0,0,0,0.2));
transition: all 0.1s;
}
.color[aria-current="true"] {
transform: translate(1px, 1px);
filter: none;
box-shadow: inset 3px 3px 4px rgba(0,0,0,0.2);
}
.menu label {
display: flex;
width: 100%;
margin: 1em 0 0 0;
}
.menu input {
flex: 1;
}
</style>