除了属性,元素还可以有 指令,这些指令以某种方式控制元素的行为。
on:eventname
on:eventname={handler}
on:eventname|modifiers={handler}
使用 on:
指令来监听 DOM 事件。
App.svelte
<script>
let count = 0;
/** @param {MouseEvent} event */
function handleClick(event) {
count += 1;
}
</script>
<button on:click={handleClick}>
count: {count}
</button>
App.svelte
<script lang="ts">
let count = 0;
function handleClick(event: MouseEvent) {
count += 1;
}
</script>
<button on:click={handleClick}>
count: {count}
</button>
处理器可以内联声明,没有性能损失。与属性一样,指令值可以为了语法高亮器而引用。
<button on:click={() => (count += 1)}>
count: {count}
</button>
使用 |
字符给 DOM 事件添加 修饰符。
<form on:submit|preventDefault={handleSubmit}>
<!-- `submit` 事件的默认行为被阻止了,
所以页面不会重新加载 -->
</form>
可用的修饰符有:
preventDefault
— 在运行处理器之前调用event.preventDefault()
stopPropagation
— 调用event.stopPropagation()
,防止事件到达下一个元素stopImmediatePropagation
- 调用event.stopImmediatePropagation()
,阻止同一事件的其他侦听器被触发。passive
— 在触摸/滚轮事件上提高滚动性能(Svelte 会在安全的情况下自动添加它)nonpassive
— 显式设置passive: false
capture
— 使处理器在 捕获 阶段而不是 冒泡 阶段触发once
— 运行后移除处理器self
— 仅当event.target
是元素本身时才触发处理器trusted
— 仅当event.isTrusted
为true
时触发处理器。即如果事件是由用户操作触发的
修饰符可以串联在一起,例如 on:click|once|capture={...}
。
如果 on:
指令没有值,组件将 转发 事件,这意味着组件的使用者可以监听它。
<button on:click> 组件本身将发出点击事件 </button>
可以为同一事件有多个事件监听器:
App.svelte
<script>
let counter = 0;
function increment() {
counter = counter + 1;
}
/** @param {MouseEvent} event */
function track(event) {
trackEvent(event);
}
</script>
<button on:click={increment} on:click={track}>点击我!</button>
App.svelte
<script lang="ts">
let counter = 0;
function increment() {
counter = counter + 1;
}
function track(event: MouseEvent) {
trackEvent(event);
}
</script>
<button on:click={increment} on:click={track}>点击我!</button>
bind:property
bind:property={variable}
数据通常从父到子流动。bind:
指令允许数据反向流动,从子到父。大多数绑定特定于特定元素。
最简单的绑定反映属性的值,如 input.value
。
<input bind:value={name} />
<textarea bind:value={text} />
<input type="checkbox" bind:checked={yes} />
如果名称与值匹配,可以使用简写。
<input bind:value />
<!-- 等同于
<input bind:value={value} />
-->
数字输入值会被强制转换;即使 input.value
在 DOM 看来是字符串,Svelte 也会将其视为数字。如果输入为空或无效(在 type="number"
的情况下),则值为 null
。
<input type="number" bind:value={num} />
<input type="range" bind:value={num} />
在 <input type="file">
元素上,您可以使用 bind:files
来获取选定文件的 FileList
。它是只读的。
<label for="avatar">上传图片:</label>
<input accept="image/png, image/jpeg" bind:files id="avatar" name="avatar" type="file" />
如果您同时使用 bind:
指令和 on:
指令,它们定义的顺序会影响事件处理程序调用时绑定变量的值。
<script>
let value = 'Hello World';
</script>
<input
on:input={() => console.log('旧值:', value)}
bind:value
on:input={() => console.log('新值:', value)}
/>
在这里,我们正在绑定一个文本输入的值,它使用 input
事件。其他元素上的绑定可能使用不同的事件,例如 change
。
绑定 <select>
值
<select>
值绑定对应于所选 <option>
上的 value
属性,它可以是任何值(不仅仅是字符串,就像通常在 DOM 中那样)。
<select bind:value={selected}>
<option value={a}>a</option>
<option value={b}>b</option>
<option value={c}>c</option>
</select>
一个 <select multiple>
元素的行为类似于复选框组。绑定的变量是一个数组,其中包含对应于每个选定 <option>
的 value
属性的条目。
<select multiple bind:value={fillings}>
<option value="Rice">Rice</option>
<option value="Beans">Beans</option>
<option value="Cheese">Cheese</option>
<option value="Guac (extra)">Guac (extra)</option>
</select>
当 <option>
的值与其文本内容匹配时,可以省略属性。
<select multiple bind:value={fillings}>
<option>Rice</option>
<option>Beans</option>
<option>Cheese</option>
<option>Guac (extra)</option>
</select>
具有 contenteditable
属性的元素支持以下绑定:
这些之间有细微的差别,更多信息请阅读 这里。
<div contenteditable="true" bind:innerHTML={html} />
<details>
元素支持绑定到 open
属性。
<details bind:open={isOpen}>
<summary>详情</summary>
<p>小到足以逃脱偶然注意的东西。</p>
</details>
媒体元素绑定
媒体元素 (<audio>
和 <video>
) 有自己的一套绑定 —— 七个 只读 的…
duration
(只读) —— 视频的总持续时间,以秒为单位buffered
(只读) —— 一个包含{start, end}
对象的数组played
(只读) —— 同上seekable
(只读) —— 同上seeking
(只读) —— 布尔值ended
(只读) —— 布尔值readyState
(只读) —— 0 到 4(包括)之间的数字
…和五个 双向 绑定:
currentTime
—— 视频中当前的播放时间,以秒为单位playbackRate
—— 播放视频的速度,1 是 ‘正常’paused
—— 这个应该不言自明volume
—— 0 到 1 之间的值muted
—— 布尔值,表示播放器是否静音
视频还有只读的 videoWidth
和 videoHeight
绑定。
<video
src={clip}
bind:duration
bind:buffered
bind:played
bind:seekable
bind:seeking
bind:ended
bind:readyState
bind:currentTime
bind:playbackRate
bind:paused
bind:volume
bind:muted
bind:videoWidth
bind:videoHeight
/>
图像元素绑定
图像元素 (<img>
) 有两个只读绑定
:
naturalWidth
(只读) —— 图片的原始宽度,在图片加载后可用naturalHeight
(只读) —— 图片的原始高度,在图片加载后可用
<img
bind:naturalWidth
bind:naturalHeight
></img>
块级元素绑定
块级元素有 4 个只读绑定,使用类似于 这种方法 的技术进行测量:
clientWidth
clientHeight
offsetWidth
offsetHeight
<div bind:offsetWidth={width} bind:offsetHeight={height}>
<Chart {width} {height} />
</div>
bind:group
bind:group={variable}
一起工作的输入可以使用 bind:group
。
App.svelte
<script>
let tortilla = 'Plain';
/** @type {Array<string>} */
let fillings = [];
</script>
<!-- 分组的单选输入是相互排斥的 -->
<input type="radio" bind:group={tortilla} value="Plain" />
<input type="radio" bind:group={tortilla} value="Whole wheat" />
<input type="radio" bind:group={tortilla} value="Spinach" />
<!-- 分组的复选框输入填充一个数组 -->
<input type="checkbox" bind:group={fillings} value="Rice" />
<input type="checkbox" bind:group={fillings} value="Beans" />
<input type="checkbox" bind:group={fillings} value="Cheese" />
<input type="checkbox" bind:group={fillings} value="Guac (extra)" />
App.svelte
<script lang="ts">
let tortilla = 'Plain';
let fillings: Array<string> = [];
</script>
<!-- 分组的单选输入是相互排斥的 -->
<input type="radio" bind:group={tortilla} value="Plain" />
<input type="radio" bind:group={tortilla} value="Whole wheat" />
<input type="radio" bind:group={tortilla} value="Spinach" />
<!-- 分组的复选框输入填充一个数组 -->
<input type="checkbox" bind:group={fillings} value="Rice" />
<input type="checkbox" bind:group={fillings} value="Beans" />
<input type="checkbox" bind:group={fillings} value="Cheese" />
<input type="checkbox" bind:group={fillings} value="Guac (extra)" />
bind:group
只有在输入在同一个 Svelte 组件中才有效。
bind:this
bind:this={dom_node}
要获取对 DOM 节点的引用,请使用 bind:this
。
App.svelte
<script>
import { onMount } from 'svelte';
/** @type {HTMLCanvasElement} */
let canvasElement;
onMount(() => {
const ctx = canvasElement.getContext('2d');
drawStuff(ctx);
});
</script>
<canvas bind:this={canvasElement} />
App.svelte
<script lang="ts">
import { onMount } from 'svelte';
let canvasElement: HTMLCanvasElement;
onMount(() => {
const ctx = canvasElement.getContext('2d');
drawStuff(ctx);
});
</script>
<canvas bind:this={canvasElement} />
class:name
class:name={value}
class:name
class:
指令提供了一种更短的方式来切换元素上的类。
<!-- 这些是等效的 -->
<div class={isActive ? 'active' : ''}>...</div>
<div class:active={isActive}>...</div>
<!-- 简写,当名称和值匹配时 -->
<div class:active>...</div>
<!-- 可以包括多个类切换 -->
<div class:active class:inactive={!active} class:isAdmin>...</div>
style:property
style:property={value}
style:property="value"
style:property
style:
指令提供了一种在元素上设置多个样式的简写。
<!-- 这些是等效的 -->
<div style:color="red">...</div>
<div style="color: red;">...</div>
<!-- 可以使用变量 -->
<div style:color={myColor}>...</div>
<!-- 简写,当属性和变量名称匹配时 -->
<div style:color>...</div>
<!-- 可以包括多个样式 -->
<div style:color style:width="12rem" style:background-color={darkMode ? 'black' : 'white'}>...</div>
<!-- 样式可以标记为重要 -->
<div style:color|important="red">...</div>
当 style:
指令与 style
属性结合时,指令将优先:
<div style="color: blue;" style:color="red">这将是红色</div>
use:action
use:action
use:action={parameters}
ts
`
action = (node: HTMLElement, parameters: any) => {
update?: (parameters: any) => void,
destroy?: () => void
}
`
动作是当元素被创建时调用的函数。它们可以返回一个带有 destroy
方法的对象,该方法在元素卸载后被调用:
App.svelte
<script>
/** @type {import('svelte/action').Action} */
function foo(node) {
// 节点已挂载到 DOM
return {
destroy() {
// 节点已从 DOM 中移除
}
};
}
</script>
<div use:foo />
App.svelte
<script lang="ts">
import type { Action } from 'svelte/action';
const foo: Action = (node) => {
// 节点已挂载到 DOM
return {
destroy() {
// 节点已从 DOM 中移除
},
};
};
</script>
<div use:foo />
一个动作可以有一个参数。如果返回的值有一个 update
方法,它将在参数更改时被调用,紧接着 Svelte 将更新应用于标记后。
不要担心我们为每个组件实例重新声明
foo
函数的事实 —— Svelte 会提升任何不依赖于本地状态的函数,使其脱离组件定义。
App.svelte
<script>
export let bar;
/** @type {import('svelte/action').Action} */
function foo(node, bar) {
// 节点已挂载到 DOM
return {
update(bar) {
// `bar` 的值已更改
},
destroy() {
// 节点已从 DOM 中移除
}
};
}
</script>
<div use:foo={bar} />
App.svelte
<script lang="ts">
import type { Action } from 'svelte/action';
export let bar;
const foo: Action = (node, bar) => {
// 节点已挂载到 DOM
return {
update(bar) {
// `bar` 的值已更改
},
destroy() {
// 节点已从 DOM 中移除
},
};
};
</script>
<div use:foo={bar} />
在 svelte/action
页面上阅读更多。
transition:fn
transition:fn
transition:fn={params}
transition:fn|global
transition:fn|global={params}
transition:fn|local
transition:fn|local={params}
ts
`
transition = (node: HTMLElement, params: any, options: { direction: ‘in’ | ‘out’ | ‘both’ }) => {
delay?: number,
duration?: number,
easing?: (t: number) => number,
css?: (t: number, u: number) => string,
tick?: (t: number, u: number) => void
}
‘
一个过渡是由元素进入或离开 DOM 作为状态更改的结果触发的。
当一个块正在过渡时,块内的所有元素,包括那些没有自己的过渡的元素,在块中的所有过渡完成之前都会保留在 DOM 中。
transition:
指令表示一个 双向 过渡,这意味着它可以在过渡进行中平稳地逆转。
{#if visible}
<div transition:fade>淡入淡出</div>
{/if}
过渡默认是局部的(在 Svelte 3 中,它们默认是全局的)。局部 过渡只在它们所属的块被创建或销毁时播放,而不是在父块被创建或销毁时。
{#if x}
{#if y}
<!-- Svelte 3: <p transition:fade|local> -->
<p transition:fade>仅当 y 更改时淡入淡出</p>
<!-- Svelte 3: <p transition:fade> -->
<p transition:fade|global>当 x 或 y 更改时淡入淡出</p>
{/if}
{/if}
默认情况下,介绍过渡在第一次渲染时不会播放。您可以通过在 创建组件 时设置
intro: true
并标记过渡为global
来修改这种行为。
过渡参数
像动作一样,过渡也可以有参数。
(双大括号 {{curlies}}
不是特殊语法;这是一个表达式标签内的对象字面量。)
{#if visible}
<div transition:fade={{ duration: 2000 }}>在两秒内淡入淡出</div>
{/if}
自定义过渡函数
过渡可以使用自定义函数。如果返回的对象有一个 css
函数,Svelte 将创建一个在元素上播放的 CSS 动画。
传递给 css
的 t
参数是一个值,在应用了 easing
函数后在 0
和 1
之间。 进入 过渡从 0
运行到 1
,退出 过渡从 1
运行到 0
—— 换句话说,1
是元素的自然状态,就好像没有应用过渡一样。u
参数等于 1 - t
。
该函数在过渡开始前多次调用,使用不同的 t
和 u
参数。
App.svelte
<script>
import { elasticOut } from 'svelte/easing';
/** @type {boolean} */
export let visible;
/**
* @param {HTMLElement} node
* @param {{ delay?: number, duration?: number, easing?: (t: number) => number }} params
*/
function whoosh(node, params) {
const existingTransform = getComputedStyle(node).transform.replace('none', '');
return {
delay: params.delay || 0,
duration: params.duration || 400,
easing: params.easing || elasticOut,
css: (t, u) => `transform: ${existingTransform} scale(${t})`
};
}
</script>
{#if visible}
<div in:whoosh>冲进来</div>
{/if}
App.svelte
<script lang="ts">
import { elasticOut } from 'svelte/easing';
export let visible: boolean;
function whoosh(
node: HTMLElement,
params: { delay?: number; duration?: number; easing?: (t: number) => number },
) {
const existingTransform = getComputedStyle(node).transform.replace('none', '');
return {
delay: params.delay || 0,
duration: params.duration || 400,
easing: params.easing || elasticOut,
css: (t, u) => `transform: ${existingTransform} scale(${t})`,
};
}
</script>
{#if visible}
<div in:whoosh>冲进来</div>
{/if}
自定义过渡函数也可以返回一个 tick
函数,在过渡期间使用相同的 t
和 u
参数调用。
如果可以使用
css
而不是tick
,请这样做 —— CSS 动画可以脱离主线程运行,防止在较慢的设备上出现卡顿。
App.svelte
<script>
export let visible = false;
/**
* @param {HTMLElement} node
* @param {{ speed?: number }} params
*/
function typewriter(node, { speed = 1 }) {
const valid = node.childNodes.length === 1 && node.childNodes[0].nodeType === Node.TEXT_NODE;
if (!valid) {
throw new Error(`This transition only works on elements with a single text node child`);
}
const text = node.textContent;
const duration = text.length / (speed * 0.01);
return {
duration,
tick: (t) => {
const i = ~~(text.length * t);
node.textContent = text.slice(0, i);
}
};
}
</script>
{#if visible}
<p in:typewriter={{ speed: 1 }}>The quick brown fox jumps over the lazy dog</p>
{/if}
App.svelte
<script lang="ts">
export let visible = false;
function typewriter(node: HTMLElement, { speed = 1 }: { speed?: number }) {
const valid = node.childNodes.length === 1 && node.childNodes[0].nodeType === Node.TEXT_NODE;
if (!valid) {
throw new Error(`This transition only works on elements with a single text node child`);
}
const text = node.textContent;
const duration = text.length / (speed * 0.01);
return {
duration,
tick: (t) => {
const i = ~~(text.length * t);
node.textContent = text.slice(0, i);
},
};
}
</script>
{#if visible}
<p in:typewriter={{ speed: 1 }}>The quick brown fox jumps over the lazy dog</p>
{/if}
如果过渡返回一个函数而不是过渡对象,该函数将在下一个微任务中被调用。这允许多个过渡相互协调,实现 交叉淡入效果。
过渡函数还接收第三个参数,options
,其中包含有关过渡的信息。
options
对象中的可用值有:
direction
- 根据过渡类型,可以是in
、out
或both
过渡事件
带有过渡的元素将在任何标准 DOM 事件之外分派以下事件:
introstart
introend
outrostart
outroend
{#if visible}
<p
transition:fly={{ y: 200, duration: 2000 }}
on:introstart={() => (status = 'intro started')}
on:outrostart={() => (status = 'outro started')}
on:introend={() => (status = 'intro ended')}
on:outroend={() => (status = 'outro ended')}
>
飞入飞出
</p>
{/if}
in:fn/out:fn
in:fn
in:fn={params}
in:fn|global
in:fn|global={params}
in:fn|local
in:fn|local={params}
out:fn
out:fn={params}
out:fn|global
out:fn|global={params}
out:fn|local
out:fn|local={params}
与 transition:
类似,但只适用于进入 (in:
) 或离开 (out:
) DOM 的元素。
与 transition:
不同,使用 in:
和 out:
应用的过渡不是双向的 —— 进入过渡将继续与退出过渡一起 ‘播放’,而不是逆转,如果块在过渡进行中被退出。如果退出过渡被中止,过渡将从头开始。
{#if visible}
<div in:fly out:fade>飞入,淡出</div>
{/if}
animate:fn
animate:name
animate:name={params}
ts
`
animation = (node: HTMLElement, { from: DOMRect, to: DOMRect } , params: any) => {
delay?: number,
duration?: number,
easing?: (t: number) => number,
css?: (t: number, u: number) => string,
tick?: (t: number, u: number) => void
}
‘
ts
`
DOMRect {
bottom: number,
height: number,
left: number,
right: number,
top: number,
width: number,
x: number,
y: number
}
‘
当 带键的 each 块 的内容被重新排序时,将触发动画。当元素被添加或删除时,动画不会运行,只有在 each 块中的现有数据项的索引发生变化时才会运行。Animate 指令必须在带键的 each 块的 直接 子元素上。
动画可以与 Svelte 的 内置动画函数 或 自定义动画函数 一起使用。
<!-- 当 `list` 被重新排序时,动画将运行 -->
{#each list as item, index (item)}
<li animate:flip>{item}</li>
{/each}
动画参数
与动作和过渡一样,动画也可以有参数。
(双大括号 {{curlies}}
不是特殊语法;这是一个表达式标签内的对象字面量。)
{#each list as item, index (item)}
<li animate:flip={{ delay: 500 }}>{item}</li>
{/each}
自定义动画函数
动画可以使用自定义函数,该函数提供 node
、animation
对象和任何 parameters
作为参数。animation
参数是一个对象,包含 from
和 to
属性,每个属性都包含一个 DOMRect,描述元素在其 start
和 end
位置的几何形状。from
属性是元素起始位置的 DOMRect,to
属性是元素在列表重新排序和 DOM 更新后的最终位置的 DOMRect。
如果返回的对象有一个 css
方法,Svelte 将创建一个在元素上播放的 CSS 动画。
传递给 css
的 t
参数是一个值,在应用了 easing
函数后从 0
变为 1
。u
参数等于 1 - t
。
该函数在动画开始前多次调用,使用不同的 t
和 u
参数。
<script>
import { cubicOut } from 'svelte/easing';
/**
* @param {HTMLElement} node
* @param {{ from: DOMRect; to: DOMRect }} states
* @param {any} params
*/
function whizz(node, { from, to }, params) {
const dx = from.left - to.left;
const dy = from.top - to.top;
const d = Math.sqrt(dx * dx + dy * dy);
return {
delay: 0,
duration: Math.sqrt(d) * 120,
easing: cubicOut,
css: (t, u) => `transform: translate(${u * dx}px, ${u * dy}px) rotate(${t * 360}deg);`
};
}
</script>
{#each list as item, index (item)}
<div animate:whizz>{item}</div>
{/each}
自定义动画函数也可以返回一个 tick
函数,在动画期间使用相同的 t
和 u
参数调用。
如果可以使用
css
而不是tick
,请这样做 —— CSS 动画可以脱离主线程运行,防止在较慢的设备上出现卡顿。
<script>
import { cubicOut } from 'svelte/easing';
/**
* @param {HTMLElement} node
* @param {{ from: DOMRect; to: DOMRect }} states
* @param {any} params
*/
function whizz(node, { from, to }, params) {
const dx = from.left - to.left;
const dy = from.top - to.top;
const d = Math.sqrt(dx * dx + dy * dy);
return {
delay: 0,
duration: Math.sqrt(d) * 120,
easing: cubicOut,
tick: (t, u) => Object.assign(node.style, { color: t > 0.5 ? 'Pink' : 'Blue' })
};
}
</script>
{#each list as item, index (item)}
<div animate:whizz>{item}</div>
{/each}