Skip to main content

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...

App.svelte
<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::

App.svelte
<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:

actions.js
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:

actions.js
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.

Tiếp theo: Thêm tham số

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
<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>
initialising