除了属性,元素还可以有 指令,这些指令以某种方式控制元素的行为。
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: falsecapture— 使处理器在 捕获 阶段而不是 冒泡 阶段触发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><inputon:input={() => console.log('旧值:', value)}bind:valueon: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 绑定。
<videosrc={clip}bind:durationbind:bufferedbind:playedbind:seekablebind:seekingbind:endedbind:readyStatebind:currentTimebind:playbackRatebind:pausedbind:volumebind:mutedbind:videoWidthbind:videoHeight/>
图像元素绑定
图像元素 (<img>) 有两个只读绑定
:
naturalWidth(只读) —— 图片的原始宽度,在图片加载后可用naturalHeight(只读) —— 图片的原始高度,在图片加载后可用
<imgbind:naturalWidthbind:naturalHeight></img>
块级元素绑定
块级元素有 4 个只读绑定,使用类似于 这种方法 的技术进行测量:
clientWidthclientHeightoffsetWidthoffsetHeight
<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) {// 节点已挂载到 DOMreturn {destroy() {// 节点已从 DOM 中移除}};}</script><div use:foo />
App.svelte
<script lang="ts">import type { Action } from 'svelte/action';const foo: Action = (node) => {// 节点已挂载到 DOMreturn {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) {// 节点已挂载到 DOMreturn {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) => {// 节点已挂载到 DOMreturn {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 事件之外分派以下事件:
introstartintroendoutrostartoutroend
{#if visible}<ptransition: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}
