模板语法基础
变量插值 {expression}
Svelte 使用单大括号 {} 在模板中插入 JavaScript 表达式。大括号内可以放任意有效的 JavaScript 表达式——变量、函数调用、三元运算符、模板字符串等。
<script lang="ts">
let name = $state('World');
let count = $state(42);
let user = $state({ name: 'Alice', age: 30 });
function greet(n: string) {
return `Hello, ${n}!`;
}
</script>
<!-- 基础变量插值 -->
<h1>Hello, {name}!</h1>
<!-- 表达式 -->
<p>计算结果:{count * 2 + 1}</p>
<!-- 函数调用 -->
<p>{greet(name)}</p>
<!-- 三元运算符 -->
<p>{count > 0 ? '正数' : '非正数'}</p>
<!-- 对象属性 -->
<p>{user.name} 今年 {user.age} 岁</p>
<!-- 属性中的插值 -->
<img src="/{name.toLowerCase()}.png" alt={name} />
<input placeholder="输入 {name} 的名字" />
HTML 插值 {@html}
默认情况下,Svelte 会对插值内容进行 HTML 转义以防止 XSS 攻击。如果你确定内容是安全的 HTML,可以使用 {@html} 直接渲染 HTML 字符串。
<script lang="ts">
// 安全的 HTML(来自服务器的 Markdown 渲染结果)
let htmlContent = $state('<strong>加粗文本</strong> 和 <em>斜体</em>');
</script>
<!-- 普通插值:会显示 HTML 标签字符串 -->
<p>{htmlContent}</p>
<!-- 输出:<strong>加粗文本... -->
<!-- {@html}:渲染为真实 HTML -->
<p>{@html htmlContent}</p>
<!-- 输出:加粗文本 和 斜体 -->
XSS 安全警告
永远不要对用户输入的内容使用 {@html},这会导致跨站脚本(XSS)攻击。只对来自可信来源(如服务端渲染的 Markdown)的 HTML 使用此功能。
CSS 作用域原理
编译后的 hash class
Svelte 最优雅的特性之一是自动 CSS 作用域。你在 <style> 块中写的样式,Svelte 编译器会自动给每条规则加上一个唯一的 hash 属性选择器,同时也给对应的 HTML 元素加上相同的 data 属性,从而实现样式隔离。
<!-- 源代码 -->
<h1>标题</h1>
<p>段落</p>
<style>
h1 { color: red; }
p { margin: 1rem; }
</style>
<!-- 编译输出 -->
<h1 class="svelte-abc123">标题</h1>
<p class="svelte-abc123">段落</p>
/* 编译后的 CSS */
h1.svelte-abc123 { color: red; }
p.svelte-abc123 { margin: 1rem; }
:global() — 突破作用域
有时你需要从父组件影响子组件内部的元素样式,可以使用 :global() 跳出作用域限制:
<style>
/* 全局样式:影响所有 h1,不限组件 */
:global(h1) {
font-family: system-ui;
}
/* 组合:父组件内(作用域内)的子组件的 .btn */
.wrapper :global(.btn) {
border-radius: 4px;
}
/* 全局关键帧动画 */
:global(@keyframes) spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
</style>
动态 class 与 style
class: 指令
Svelte 提供了简洁的 class: 指令来条件性地添加 CSS 类,比字符串拼接更清晰:
<script lang="ts">
let isActive = $state(false);
let isDisabled = $state(false);
let theme = $state('light');
</script>
<!-- class: 指令:当条件为 true 时添加 class -->
<button
class:active={isActive}
class:disabled={isDisabled}
onclick={() => (isActive = !isActive)}
>
点击切换状态
</button>
<!-- 多个 class 混合 -->
<div class="base-class {theme}" class:active={isActive}>
内容
</div>
<!-- 使用对象形式(Svelte 5)-->
<div class={{ active: isActive, disabled: isDisabled }}>
内容
</div>
style: 指令
<script lang="ts">
let color = $state('#FF3E00');
let size = $state(16);
</script>
<!-- style: 指令:动态设置单个 CSS 属性 -->
<p style:color style:font-size="{size}px">
动态样式文本
</p>
<!-- 也可以使用传统的 style 属性(字符串) -->
<p style="color: {color}; font-size: {size}px;">
传统方式
</p>
<!-- CSS 自定义属性(CSS Variables)-->
<div style="--accent: {color}">
<p style="color: var(--accent)">使用 CSS 变量</p>
</div>
过渡动画
内置过渡函数
Svelte 内置了多种过渡动画函数,通过 transition:、in:、out: 指令使用,当元素进入或离开 DOM 时自动触发:
<script lang="ts">
import { fade, fly, slide, scale, blur, draw } from 'svelte/transition';
import { flip } from 'svelte/animate';
import { elasticOut } from 'svelte/easing';
let visible = $state(true);
</script>
<button onclick={() => (visible = !visible)}>切换</button>
{#if visible}
<!-- fade:淡入淡出 -->
<div transition:fade={{ duration: 300 }}>淡入淡出</div>
<!-- fly:从指定位置飞入 -->
<p transition:fly={{ y: 20, duration: 400 }}>从下方飞入</p>
<!-- scale:缩放进入,带弹性缓动 -->
<div in:scale={{ duration: 300, easing: elasticOut }}>
弹性缩放
</div>
<!-- 不同的进入/离开动画 -->
<p in:fly={{ x: -100 }} out:fly={{ x: 100 }}>
左入右出
</p>
{/if}
fade
透明度从 0 到 1(进入)或从 1 到 0(离开)。参数:duration(毫秒),delay,easing。
fly
元素同时移动和淡入。参数:x(水平偏移),y(垂直偏移),duration,easing。
slide
元素高度/宽度渐变(折叠效果)。常用于手风琴菜单。
scale
元素从缩放到正常大小(进入)或反向(离开)。可配合弹性缓动函数。
blur
高斯模糊过渡,元素从模糊变清晰。适合焦点切换效果。
draw
SVG 路径绘制动画,让 SVG 线条看起来像正在被绘制。
自定义过渡函数
<script lang="ts">
import { cubicOut } from 'svelte/easing';
// 自定义过渡:从右侧旋转飞入
function spinIn(node: Element, { duration = 400 } = {}) {
return {
duration,
css: (t: number) => {
const eased = cubicOut(t);
return `
transform: rotate(${(1 - eased) * 360}deg) scale(${eased});
opacity: ${eased};
`;
}
};
}
</script>
{#if visible}
<div in:spinIn={{ duration: 600 }}>
旋转飞入!
</div>
{/if}
transition vs in/out 的区别
transition: 将相同的动画用于进入和离开。in: 只用于进入,out: 只用于离开。当你需要不同的进场和退场动画时,分开使用 in: 和 out:。
animate:列表重排动画
当 {#each} 列表中的元素顺序改变时,animate: 指令自动为每个元素从旧位置移动到新位置添加过渡动画(FLIP 动画)。
<script lang="ts">
import { flip } from 'svelte/animate';
import { fade } from 'svelte/transition';
let items = $state([
{ id: 1, text: '买菜', done: false },
{ id: 2, text: '写代码', done: false },
{ id: 3, text: '读书', done: false }
]);
function toggle(id: number) {
const item = items.find(i => i.id === id);
if (item) item.done = !item.done;
items = [...items.filter(i => !i.done), ...items.filter(i => i.done)];
}
</script>
<ul>
{#each items as item (item.id)}
<li
animate:flip={{ duration: 300 }}
out:fade={{ duration: 200 }}
>
<input type="checkbox" checked={item.done}
onchange={() => toggle(item.id)} />
<span class:done={item.done}>{item.text}</span>
</li>
{/each}
</ul>
<style>
.done { text-decoration: line-through; opacity: 0.5; }
</style>
Svelte 的特殊 HTML 标签
{@html content}
渲染原始 HTML 字符串,不转义。注意:存在 XSS 风险,只在信任的内容上使用(如 Markdown 渲染后的 HTML)。
{@const variable = expression}
在模板中声明局部常量。常用于在 {#each} 块内计算派生值,避免重复计算或在模板中写复杂表达式。
<svelte:head>
向 document.head 插入内容,如 <title>、<meta>、<link>。适合在页面组件中动态更新 SEO 相关标签。
<svelte:component this={ComponentClass}>
动态渲染组件:根据变量决定渲染哪个组件。当 ComponentClass 为 null 时不渲染。适合插件系统、动态表单字段或根据用户权限渲染不同视图。
<svelte:element this={tag}>
动态渲染 HTML 标签(Svelte 5 新增):
<svelte:element this="h1">标题</svelte:element>。tag 可以是任意有效的 HTML 标签名字符串。适合 CMS 内容渲染、无障碍标题层级动态调整。<script lang="ts">
let markdownHtml = $state('<p>来自 <strong>Markdown</strong> 的内容</p>');
let users = $state([
{ name: 'Alice', score: 88 },
{ name: 'Bob', score: 95 }
]);
// 动态组件:根据视图模式切换
import ChartView from './ChartView.svelte';
import TableView from './TableView.svelte';
let viewMode = $state<'chart' | 'table'>('table');
const ViewComponent = $derived(viewMode === 'chart' ? ChartView : TableView);
// 动态标签:内容层级自适应
let headingLevel = $state(1);
const headingTag = $derived(`h${headingLevel}`);
</script>
<!-- 渲染 Markdown 转换后的 HTML -->
<div class="prose">{@html markdownHtml}</div>
<!-- @const 声明局部变量:避免在模板中重复写相同表达式 -->
{#each users as user}
{@const grade = user.score >= 90 ? 'A' : user.score >= 80 ? 'B' : 'C'}
{@const isPassing = user.score >= 60}
<p class:pass={isPassing}>{user.name}:{user.score} 分 ({grade} 级)</p>
{/each}
<!-- 动态 head 内容 -->
<svelte:head>
<title>用户列表 — 我的应用</title>
</svelte:head>
<!-- 动态组件切换(Svelte 5 推荐用 {#if} 替代,svelte:component 仍支持)-->
<button onclick={() => viewMode = viewMode === 'chart' ? 'table' : 'chart'}>
切换视图
</button>
<svelte:component this={ViewComponent} data={users} />
<!-- 动态 HTML 标签 -->
<svelte:element this={headingTag}>
动态标题级别 (h{headingLevel})
</svelte:element>
本章小结
本章核心要点
- 模板语法简洁直观:
{变量}插值、{#if/else if/else}条件、{#each key=}(key 避免 DOM 复用错误)、{#await}处理异步状态。 - CSS 默认作用域隔离:<style> 内的样式自动限定到当前组件;使用
:global()突破隔离;CSS 自定义属性可以"穿透"组件边界实现主题传递;动态注入的 HTML 不在作用域内。 - class: 和 style: 指令更简洁:
class:active={isActive}替代三元拼接;style:color={color}自动处理 null/undefined(移除属性);两者都支持简写(变量名与属性名相同时)。 - 过渡动画零配置:内置 fade/slide/fly/scale/blur/draw;
transition:双向,in:/out:单向;自定义过渡函数返回{ duration, css(t) };animate:flip实现列表 FLIP 重排动画。 - {@const} 声明模板内局部变量:在 {#each} 块内避免重复计算,让模板更清晰;{@html} 渲染 HTML 字符串需防范 XSS。
- 动态组件与动态标签:
<svelte:component this={C}>动态渲染组件;<svelte:element this={tag}>动态渲染 HTML 元素;Svelte 5 中推荐用{#if}替代 svelte:component 以获得更好的类型推断。