除了属性,元素还可以有 指令,这些指令以某种方式控制元素的行为。

on:eventname

  1. on:eventname={handler}
  1. on:eventname|modifiers={handler}

使用 on: 指令来监听 DOM 事件。

App.svelte

  1. <script>
  2. let count = 0;
  3. /** @param {MouseEvent} event */
  4. function handleClick(event) {
  5. count += 1;
  6. }
  7. </script>
  8. <button on:click={handleClick}>
  9. count: {count}
  10. </button>

App.svelte

  1. <script lang="ts">
  2. let count = 0;
  3. function handleClick(event: MouseEvent) {
  4. count += 1;
  5. }
  6. </script>
  7. <button on:click={handleClick}>
  8. count: {count}
  9. </button>

处理器可以内联声明,没有性能损失。与属性一样,指令值可以为了语法高亮器而引用。

  1. <button on:click={() => (count += 1)}>
  2. count: {count}
  3. </button>

使用 | 字符给 DOM 事件添加 修饰符

  1. <form on:submit|preventDefault={handleSubmit}>
  2. <!-- `submit` 事件的默认行为被阻止了,
  3. 所以页面不会重新加载 -->
  4. </form>

可用的修饰符有:

  • preventDefault — 在运行处理器之前调用 event.preventDefault()
  • stopPropagation — 调用 event.stopPropagation(),防止事件到达下一个元素
  • stopImmediatePropagation - 调用 event.stopImmediatePropagation(),阻止同一事件的其他侦听器被触发。
  • passive — 在触摸/滚轮事件上提高滚动性能(Svelte 会在安全的情况下自动添加它)
  • nonpassive — 显式设置 passive: false
  • capture — 使处理器在 捕获 阶段而不是 冒泡 阶段触发
  • once — 运行后移除处理器
  • self — 仅当 event.target 是元素本身时才触发处理器
  • trusted — 仅当 event.isTrustedtrue 时触发处理器。即如果事件是由用户操作触发的

修饰符可以串联在一起,例如 on:click|once|capture={...}

如果 on: 指令没有值,组件将 转发 事件,这意味着组件的使用者可以监听它。

  1. <button on:click> 组件本身将发出点击事件 </button>

可以为同一事件有多个事件监听器:

App.svelte

  1. <script>
  2. let counter = 0;
  3. function increment() {
  4. counter = counter + 1;
  5. }
  6. /** @param {MouseEvent} event */
  7. function track(event) {
  8. trackEvent(event);
  9. }
  10. </script>
  11. <button on:click={increment} on:click={track}>点击我!</button>

App.svelte

  1. <script lang="ts">
  2. let counter = 0;
  3. function increment() {
  4. counter = counter + 1;
  5. }
  6. function track(event: MouseEvent) {
  7. trackEvent(event);
  8. }
  9. </script>
  10. <button on:click={increment} on:click={track}>点击我!</button>

bind:property

  1. bind:property={variable}

数据通常从父到子流动。bind: 指令允许数据反向流动,从子到父。大多数绑定特定于特定元素。

最简单的绑定反映属性的值,如 input.value

  1. <input bind:value={name} />
  2. <textarea bind:value={text} />
  3. <input type="checkbox" bind:checked={yes} />

如果名称与值匹配,可以使用简写。

  1. <input bind:value />
  2. <!-- 等同于
  3. <input bind:value={value} />
  4. -->

数字输入值会被强制转换;即使 input.value 在 DOM 看来是字符串,Svelte 也会将其视为数字。如果输入为空或无效(在 type="number" 的情况下),则值为 null

  1. <input type="number" bind:value={num} />
  2. <input type="range" bind:value={num} />

<input type="file"> 元素上,您可以使用 bind:files 来获取选定文件的 FileList。它是只读的。

  1. <label for="avatar">上传图片:</label>
  2. <input accept="image/png, image/jpeg" bind:files id="avatar" name="avatar" type="file" />

如果您同时使用 bind: 指令和 on: 指令,它们定义的顺序会影响事件处理程序调用时绑定变量的值。

  1. <script>
  2. let value = 'Hello World';
  3. </script>
  4. <input
  5. on:input={() => console.log('旧值:', value)}
  6. bind:value
  7. on:input={() => console.log('新值:', value)}
  8. />

在这里,我们正在绑定一个文本输入的值,它使用 input 事件。其他元素上的绑定可能使用不同的事件,例如 change

绑定 <select>

<select> 值绑定对应于所选 <option> 上的 value 属性,它可以是任何值(不仅仅是字符串,就像通常在 DOM 中那样)。

  1. <select bind:value={selected}>
  2. <option value={a}>a</option>
  3. <option value={b}>b</option>
  4. <option value={c}>c</option>
  5. </select>

一个 <select multiple> 元素的行为类似于复选框组。绑定的变量是一个数组,其中包含对应于每个选定 <option>value 属性的条目。

  1. <select multiple bind:value={fillings}>
  2. <option value="Rice">Rice</option>
  3. <option value="Beans">Beans</option>
  4. <option value="Cheese">Cheese</option>
  5. <option value="Guac (extra)">Guac (extra)</option>
  6. </select>

<option> 的值与其文本内容匹配时,可以省略属性。

  1. <select multiple bind:value={fillings}>
  2. <option>Rice</option>
  3. <option>Beans</option>
  4. <option>Cheese</option>
  5. <option>Guac (extra)</option>
  6. </select>

具有 contenteditable 属性的元素支持以下绑定:

这些之间有细微的差别,更多信息请阅读 这里

  1. <div contenteditable="true" bind:innerHTML={html} />

<details> 元素支持绑定到 open 属性。

  1. <details bind:open={isOpen}>
  2. <summary>详情</summary>
  3. <p>小到足以逃脱偶然注意的东西。</p>
  4. </details>

媒体元素绑定

媒体元素 (<audio><video>) 有自己的一套绑定 —— 七个 只读 的…

  • duration (只读) —— 视频的总持续时间,以秒为单位
  • buffered (只读) —— 一个包含 {start, end} 对象的数组
  • played (只读) —— 同上
  • seekable (只读) —— 同上
  • seeking (只读) —— 布尔值
  • ended (只读) —— 布尔值
  • readyState (只读) —— 0 到 4(包括)之间的数字

…和五个 双向 绑定:

  • currentTime —— 视频中当前的播放时间,以秒为单位
  • playbackRate —— 播放视频的速度,1 是 ‘正常’
  • paused —— 这个应该不言自明
  • volume —— 0 到 1 之间的值
  • muted —— 布尔值,表示播放器是否静音

视频还有只读的 videoWidthvideoHeight 绑定。

  1. <video
  2. src={clip}
  3. bind:duration
  4. bind:buffered
  5. bind:played
  6. bind:seekable
  7. bind:seeking
  8. bind:ended
  9. bind:readyState
  10. bind:currentTime
  11. bind:playbackRate
  12. bind:paused
  13. bind:volume
  14. bind:muted
  15. bind:videoWidth
  16. bind:videoHeight
  17. />

图像元素绑定

图像元素 (<img>) 有两个只读绑定 :

  • naturalWidth (只读) —— 图片的原始宽度,在图片加载后可用
  • naturalHeight (只读) —— 图片的原始高度,在图片加载后可用
  1. <img
  2. bind:naturalWidth
  3. bind:naturalHeight
  4. ></img>

块级元素绑定

块级元素有 4 个只读绑定,使用类似于 这种方法 的技术进行测量:

  • clientWidth
  • clientHeight
  • offsetWidth
  • offsetHeight
  1. <div bind:offsetWidth={width} bind:offsetHeight={height}>
  2. <Chart {width} {height} />
  3. </div>

bind:group

  1. bind:group={variable}

一起工作的输入可以使用 bind:group

App.svelte

  1. <script>
  2. let tortilla = 'Plain';
  3. /** @type {Array<string>} */
  4. let fillings = [];
  5. </script>
  6. <!-- 分组的单选输入是相互排斥的 -->
  7. <input type="radio" bind:group={tortilla} value="Plain" />
  8. <input type="radio" bind:group={tortilla} value="Whole wheat" />
  9. <input type="radio" bind:group={tortilla} value="Spinach" />
  10. <!-- 分组的复选框输入填充一个数组 -->
  11. <input type="checkbox" bind:group={fillings} value="Rice" />
  12. <input type="checkbox" bind:group={fillings} value="Beans" />
  13. <input type="checkbox" bind:group={fillings} value="Cheese" />
  14. <input type="checkbox" bind:group={fillings} value="Guac (extra)" />

App.svelte

  1. <script lang="ts">
  2. let tortilla = 'Plain';
  3. let fillings: Array<string> = [];
  4. </script>
  5. <!-- 分组的单选输入是相互排斥的 -->
  6. <input type="radio" bind:group={tortilla} value="Plain" />
  7. <input type="radio" bind:group={tortilla} value="Whole wheat" />
  8. <input type="radio" bind:group={tortilla} value="Spinach" />
  9. <!-- 分组的复选框输入填充一个数组 -->
  10. <input type="checkbox" bind:group={fillings} value="Rice" />
  11. <input type="checkbox" bind:group={fillings} value="Beans" />
  12. <input type="checkbox" bind:group={fillings} value="Cheese" />
  13. <input type="checkbox" bind:group={fillings} value="Guac (extra)" />

bind:group 只有在输入在同一个 Svelte 组件中才有效。

bind:this

  1. bind:this={dom_node}

要获取对 DOM 节点的引用,请使用 bind:this

App.svelte

  1. <script>
  2. import { onMount } from 'svelte';
  3. /** @type {HTMLCanvasElement} */
  4. let canvasElement;
  5. onMount(() => {
  6. const ctx = canvasElement.getContext('2d');
  7. drawStuff(ctx);
  8. });
  9. </script>
  10. <canvas bind:this={canvasElement} />

App.svelte

  1. <script lang="ts">
  2. import { onMount } from 'svelte';
  3. let canvasElement: HTMLCanvasElement;
  4. onMount(() => {
  5. const ctx = canvasElement.getContext('2d');
  6. drawStuff(ctx);
  7. });
  8. </script>
  9. <canvas bind:this={canvasElement} />

class:name

  1. class:name={value}
  1. class:name

class: 指令提供了一种更短的方式来切换元素上的类。

  1. <!-- 这些是等效的 -->
  2. <div class={isActive ? 'active' : ''}>...</div>
  3. <div class:active={isActive}>...</div>
  4. <!-- 简写,当名称和值匹配时 -->
  5. <div class:active>...</div>
  6. <!-- 可以包括多个类切换 -->
  7. <div class:active class:inactive={!active} class:isAdmin>...</div>

style:property

  1. style:property={value}
  1. style:property="value"
  1. style:property

style: 指令提供了一种在元素上设置多个样式的简写。

  1. <!-- 这些是等效的 -->
  2. <div style:color="red">...</div>
  3. <div style="color: red;">...</div>
  4. <!-- 可以使用变量 -->
  5. <div style:color={myColor}>...</div>
  6. <!-- 简写,当属性和变量名称匹配时 -->
  7. <div style:color>...</div>
  8. <!-- 可以包括多个样式 -->
  9. <div style:color style:width="12rem" style:background-color={darkMode ? 'black' : 'white'}>...</div>
  10. <!-- 样式可以标记为重要 -->
  11. <div style:color|important="red">...</div>

style: 指令与 style 属性结合时,指令将优先:

  1. <div style="color: blue;" style:color="red">这将是红色</div>

use:action

  1. use:action
  1. use:action={parameters}

ts

`

action = (node: HTMLElement, parameters: any) => {

update?: (parameters: any) => void,

destroy?: () => void

}

`

动作是当元素被创建时调用的函数。它们可以返回一个带有 destroy 方法的对象,该方法在元素卸载后被调用:

App.svelte

  1. <script>
  2. /** @type {import('svelte/action').Action} */
  3. function foo(node) {
  4. // 节点已挂载到 DOM
  5. return {
  6. destroy() {
  7. // 节点已从 DOM 中移除
  8. }
  9. };
  10. }
  11. </script>
  12. <div use:foo />

App.svelte

  1. <script lang="ts">
  2. import type { Action } from 'svelte/action';
  3. const foo: Action = (node) => {
  4. // 节点已挂载到 DOM
  5. return {
  6. destroy() {
  7. // 节点已从 DOM 中移除
  8. },
  9. };
  10. };
  11. </script>
  12. <div use:foo />

一个动作可以有一个参数。如果返回的值有一个 update 方法,它将在参数更改时被调用,紧接着 Svelte 将更新应用于标记后。

不要担心我们为每个组件实例重新声明 foo 函数的事实 —— Svelte 会提升任何不依赖于本地状态的函数,使其脱离组件定义。

App.svelte

  1. <script>
  2. export let bar;
  3. /** @type {import('svelte/action').Action} */
  4. function foo(node, bar) {
  5. // 节点已挂载到 DOM
  6. return {
  7. update(bar) {
  8. // `bar` 的值已更改
  9. },
  10. destroy() {
  11. // 节点已从 DOM 中移除
  12. }
  13. };
  14. }
  15. </script>
  16. <div use:foo={bar} />

App.svelte

  1. <script lang="ts">
  2. import type { Action } from 'svelte/action';
  3. export let bar;
  4. const foo: Action = (node, bar) => {
  5. // 节点已挂载到 DOM
  6. return {
  7. update(bar) {
  8. // `bar` 的值已更改
  9. },
  10. destroy() {
  11. // 节点已从 DOM 中移除
  12. },
  13. };
  14. };
  15. </script>
  16. <div use:foo={bar} />

svelte/action 页面上阅读更多。

transition:fn

  1. transition:fn
  1. transition:fn={params}
  1. transition:fn|global
  1. transition:fn|global={params}
  1. transition:fn|local
  1. 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: 指令表示一个 双向 过渡,这意味着它可以在过渡进行中平稳地逆转。

  1. {#if visible}
  2. <div transition:fade>淡入淡出</div>
  3. {/if}

过渡默认是局部的(在 Svelte 3 中,它们默认是全局的)。局部 过渡只在它们所属的块被创建或销毁时播放,而不是在父块被创建或销毁时。

  1. {#if x}
  2. {#if y}
  3. <!-- Svelte 3: <p transition:fade|local> -->
  4. <p transition:fade>仅当 y 更改时淡入淡出</p>
  5. <!-- Svelte 3: <p transition:fade> -->
  6. <p transition:fade|global>当 x y 更改时淡入淡出</p>
  7. {/if}
  8. {/if}

默认情况下,介绍过渡在第一次渲染时不会播放。您可以通过在 创建组件 时设置 intro: true 并标记过渡为 global 来修改这种行为。

过渡参数

像动作一样,过渡也可以有参数。

(双大括号 {{curlies}} 不是特殊语法;这是一个表达式标签内的对象字面量。)

  1. {#if visible}
  2. <div transition:fade={{ duration: 2000 }}>在两秒内淡入淡出</div>
  3. {/if}

自定义过渡函数

过渡可以使用自定义函数。如果返回的对象有一个 css 函数,Svelte 将创建一个在元素上播放的 CSS 动画。

传递给 csst 参数是一个值,在应用了 easing 函数后在 01 之间。 进入 过渡从 0 运行到 1退出 过渡从 1 运行到 0 —— 换句话说,1 是元素的自然状态,就好像没有应用过渡一样。u 参数等于 1 - t

该函数在过渡开始前多次调用,使用不同的 tu 参数。

App.svelte

  1. <script>
  2. import { elasticOut } from 'svelte/easing';
  3. /** @type {boolean} */
  4. export let visible;
  5. /**
  6. * @param {HTMLElement} node
  7. * @param {{ delay?: number, duration?: number, easing?: (t: number) => number }} params
  8. */
  9. function whoosh(node, params) {
  10. const existingTransform = getComputedStyle(node).transform.replace('none', '');
  11. return {
  12. delay: params.delay || 0,
  13. duration: params.duration || 400,
  14. easing: params.easing || elasticOut,
  15. css: (t, u) => `transform: ${existingTransform} scale(${t})`
  16. };
  17. }
  18. </script>
  19. {#if visible}
  20. <div in:whoosh>冲进来</div>
  21. {/if}

App.svelte

  1. <script lang="ts">
  2. import { elasticOut } from 'svelte/easing';
  3. export let visible: boolean;
  4. function whoosh(
  5. node: HTMLElement,
  6. params: { delay?: number; duration?: number; easing?: (t: number) => number },
  7. ) {
  8. const existingTransform = getComputedStyle(node).transform.replace('none', '');
  9. return {
  10. delay: params.delay || 0,
  11. duration: params.duration || 400,
  12. easing: params.easing || elasticOut,
  13. css: (t, u) => `transform: ${existingTransform} scale(${t})`,
  14. };
  15. }
  16. </script>
  17. {#if visible}
  18. <div in:whoosh>冲进来</div>
  19. {/if}

自定义过渡函数也可以返回一个 tick 函数,在过渡期间使用相同的 tu 参数调用。

如果可以使用 css 而不是 tick,请这样做 —— CSS 动画可以脱离主线程运行,防止在较慢的设备上出现卡顿。

App.svelte

  1. <script>
  2. export let visible = false;
  3. /**
  4. * @param {HTMLElement} node
  5. * @param {{ speed?: number }} params
  6. */
  7. function typewriter(node, { speed = 1 }) {
  8. const valid = node.childNodes.length === 1 && node.childNodes[0].nodeType === Node.TEXT_NODE;
  9. if (!valid) {
  10. throw new Error(`This transition only works on elements with a single text node child`);
  11. }
  12. const text = node.textContent;
  13. const duration = text.length / (speed * 0.01);
  14. return {
  15. duration,
  16. tick: (t) => {
  17. const i = ~~(text.length * t);
  18. node.textContent = text.slice(0, i);
  19. }
  20. };
  21. }
  22. </script>
  23. {#if visible}
  24. <p in:typewriter={{ speed: 1 }}>The quick brown fox jumps over the lazy dog</p>
  25. {/if}

App.svelte

  1. <script lang="ts">
  2. export let visible = false;
  3. function typewriter(node: HTMLElement, { speed = 1 }: { speed?: number }) {
  4. const valid = node.childNodes.length === 1 && node.childNodes[0].nodeType === Node.TEXT_NODE;
  5. if (!valid) {
  6. throw new Error(`This transition only works on elements with a single text node child`);
  7. }
  8. const text = node.textContent;
  9. const duration = text.length / (speed * 0.01);
  10. return {
  11. duration,
  12. tick: (t) => {
  13. const i = ~~(text.length * t);
  14. node.textContent = text.slice(0, i);
  15. },
  16. };
  17. }
  18. </script>
  19. {#if visible}
  20. <p in:typewriter={{ speed: 1 }}>The quick brown fox jumps over the lazy dog</p>
  21. {/if}

如果过渡返回一个函数而不是过渡对象,该函数将在下一个微任务中被调用。这允许多个过渡相互协调,实现 交叉淡入效果

过渡函数还接收第三个参数,options,其中包含有关过渡的信息。

options 对象中的可用值有:

  • direction - 根据过渡类型,可以是 inoutboth

过渡事件

带有过渡的元素将在任何标准 DOM 事件之外分派以下事件:

  • introstart
  • introend
  • outrostart
  • outroend
  1. {#if visible}
  2. <p
  3. transition:fly={{ y: 200, duration: 2000 }}
  4. on:introstart={() => (status = 'intro started')}
  5. on:outrostart={() => (status = 'outro started')}
  6. on:introend={() => (status = 'intro ended')}
  7. on:outroend={() => (status = 'outro ended')}
  8. >
  9. 飞入飞出
  10. </p>
  11. {/if}

in:fn/out:fn

  1. in:fn
  1. in:fn={params}
  1. in:fn|global
  1. in:fn|global={params}
  1. in:fn|local
  1. in:fn|local={params}
  1. out:fn
  1. out:fn={params}
  1. out:fn|global
  1. out:fn|global={params}
  1. out:fn|local
  1. out:fn|local={params}

transition: 类似,但只适用于进入 (in:) 或离开 (out:) DOM 的元素。

transition: 不同,使用 in:out: 应用的过渡不是双向的 —— 进入过渡将继续与退出过渡一起 ‘播放’,而不是逆转,如果块在过渡进行中被退出。如果退出过渡被中止,过渡将从头开始。

  1. {#if visible}
  2. <div in:fly out:fade>飞入,淡出</div>
  3. {/if}

animate:fn

  1. animate:name
  1. 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 的 内置动画函数自定义动画函数 一起使用。

  1. <!-- 当 `list` 被重新排序时,动画将运行 -->
  2. {#each list as item, index (item)}
  3. <li animate:flip>{item}</li>
  4. {/each}

动画参数

与动作和过渡一样,动画也可以有参数。

(双大括号 {{curlies}} 不是特殊语法;这是一个表达式标签内的对象字面量。)

  1. {#each list as item, index (item)}
  2. <li animate:flip={{ delay: 500 }}>{item}</li>
  3. {/each}

自定义动画函数

动画可以使用自定义函数,该函数提供 nodeanimation 对象和任何 parameters 作为参数。animation 参数是一个对象,包含 fromto 属性,每个属性都包含一个 DOMRect,描述元素在其 startend 位置的几何形状。from 属性是元素起始位置的 DOMRect,to 属性是元素在列表重新排序和 DOM 更新后的最终位置的 DOMRect。

如果返回的对象有一个 css 方法,Svelte 将创建一个在元素上播放的 CSS 动画。

传递给 csst 参数是一个值,在应用了 easing 函数后从 0 变为 1u 参数等于 1 - t

该函数在动画开始前多次调用,使用不同的 tu 参数。

  1. <script>
  2. import { cubicOut } from 'svelte/easing';
  3. /**
  4. * @param {HTMLElement} node
  5. * @param {{ from: DOMRect; to: DOMRect }} states
  6. * @param {any} params
  7. */
  8. function whizz(node, { from, to }, params) {
  9. const dx = from.left - to.left;
  10. const dy = from.top - to.top;
  11. const d = Math.sqrt(dx * dx + dy * dy);
  12. return {
  13. delay: 0,
  14. duration: Math.sqrt(d) * 120,
  15. easing: cubicOut,
  16. css: (t, u) => `transform: translate(${u * dx}px, ${u * dy}px) rotate(${t * 360}deg);`
  17. };
  18. }
  19. </script>
  20. {#each list as item, index (item)}
  21. <div animate:whizz>{item}</div>
  22. {/each}

自定义动画函数也可以返回一个 tick 函数,在动画期间使用相同的 tu 参数调用。

如果可以使用 css 而不是 tick,请这样做 —— CSS 动画可以脱离主线程运行,防止在较慢的设备上出现卡顿。

  1. <script>
  2. import { cubicOut } from 'svelte/easing';
  3. /**
  4. * @param {HTMLElement} node
  5. * @param {{ from: DOMRect; to: DOMRect }} states
  6. * @param {any} params
  7. */
  8. function whizz(node, { from, to }, params) {
  9. const dx = from.left - to.left;
  10. const dy = from.top - to.top;
  11. const d = Math.sqrt(dx * dx + dy * dy);
  12. return {
  13. delay: 0,
  14. duration: Math.sqrt(d) * 120,
  15. easing: cubicOut,
  16. tick: (t, u) => Object.assign(node.style, { color: t > 0.5 ? 'Pink' : 'Blue' })
  17. };
  18. }
  19. </script>
  20. {#each list as item, index (item)}
  21. <div animate:whizz>{item}</div>
  22. {/each}