Actions chủ yếu là các hàm vòng đời cấp phần tử. Chúng hữu ích để thực hiện các công việc như:
- tương tác với các thư viện của bên thứ ba
- trì hoản tải ảnh
- gợi ý
- thêm các xử lý sự kiện tùy chỉnh
Trong ứng dụng này, bạn có thể vẽ trên <canvas>, và thay đổi màu sắc và kích thước cọ thông qua menu. Nhưng nếu bạn mở menu và di chuyển qua các tùy chọn với phím Tab, bạn sẽ nhanh chóng nhận ra rằng sự tập trung không được giữ trong modal.
Chúng ta có thể sửa điều đó bằng một action. Nhập trapFocus từ actions.js...
<script>
import Canvas from './Canvas.svelte';
import { trapFocus } from './actions.js';
const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet', 'white', 'black'];
let selected = colors[0];
let size = 10;
let showMenu = true;
</script>...sau đó thêm nó vào menu với chỉ thị use::
<div class="menu" use:trapFocus>Hãy xem hàm trapFocus trong actions.js. Một hàm action được gọi với một node — <div class="menu"> trong trường hợp của chúng ta — khi node được gắn vào DOM, và có thể trả về một đối tượng action với một phương thức destroy.
Thứ nhất, chúng ta cần thêm một trình lắng nghe sự kiện mà chặn phím Tab:
focusable()[0]?.focus();
node.addEventListener('keydown', handleKeydown);Thứ hai, chúng ta cần thực hiện một số công việc dọn dẹp khi node được gỡ ra — xóa bỏ trình lắng nghe sự kiện, và khôi phục tập trung về ban đầu trước khi phần tử được gắn vào:
focusable()[0]?.focus();
node.addEventListener('keydown', handleKeydown);
return {
destroy() {
node.removeEventListener('keydown', handleKeydown);
previous?.focus();
}
};Bây giờ, khi bạn mở menu, bạn có thể di chuyển qua các tùy chọn với phím Tab.
<script>
import Canvas from './Canvas.svelte';
const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet', 'white', 'black'];
let selected = colors[0];
let size = 10;
let showMenu = true;
</script>
<div class="container">
<Canvas color={selected} size={size} /> {#if showMenu}<div
class="modal-background"
on:click|self={() => showMenu = false} on:keydown={(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}" on:click={() => {selected = color;
}}
></button>
{/each}</div>
<label>
nhỏ
<input type="range" bind:value={size} min="1" max="50" />to
</label>
</div>
</div>
{/if}<div class="controls">
<button class="show-menu" on:click={() => showMenu = !showMenu}> {showMenu ? 'close' : '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>