SVELTE的介绍
什么是SVELTE
Svelte 是一个构建 web 应用程序的工具。Svelte 与诸如 React 和 Vue 等 JavaScript 框架类似,但是有一个关键的区别:Svelte 在 构建/编译阶段 将应用程序转换为理想的 JavaScript 应用,而不是在运行阶段解释应用程序的代码。这意味着不需要为框架所消耗的性能付出成本,并且在应用程序首次加载时没有额外损失。可以使用 Svelte 构建整个应用程序,也可以逐步将其融合到现有的代码中。还可以将组件作为独立的包(package)交付到任何地方。
核心思想:通过静态编译减少框架运行时的代码量。也就是说,vue 和 react 这类传统的框架,都必须引入运行时 (runtime) 代码,用于虚拟dom、diff 算法。Svelte完全溶入JavaScript,应用所有需要的运行时代码都包含在bundle.js里面了,除了引入这个组件本身,不需要再额外引入一个运行代码。
特性
No Runtime
Svelte通常被描述成“no-runtime”,这是因为Svelte在构建的过程中将大多数的工作转移到了编译阶段,而不是在浏览器中通过运行时代码进行处理。而传统的React 和 Vue都是基于运行时runtime的框架,组件在浏览器中运行时会产生额外的运行时开销。这些框架将组件抽象为虚拟 DOM(Virtual DOM)节点,并在运行时处理组件的创建、更新和销毁。这会导致一些额外的性能损失,尤其是在大型复杂应用程序中。
与之相反,Svelte 采用了一种不同的方法。在构建过程中,Svelte 将组件编译为高效的、可理解的 JavaScript 代码,而不需要额外的框架抽象层。结果是具有更少运行时开销和更快性能的应用程序。
这里我们通过source-map-explorer可视化构建产物,通过它来分析打包之后的js文件以及source-map。
Svelte:(3.9kb)
其中,internal/index.mjs文件来自 Svelte 内部运行时代码,它包含 Svelte 运行时所需的核心功能,如组件创建、更新和销毁等。internal/index.mjs 通常在 Svelte 项目的构建产物中占用相当大的空间。这是正常的,因为它是 Svelte 应用程序运行所必需的基本运行时代码。这些代码包括组件的生命周期方法、数据更新和响应式逻辑实现等。
这里我们也能发现他是按需加载,没有使用的组件不会被打包到产物中。但在vue中即使具名插槽的组件没有使用也会存在在构建好的js中。
React: (141.65kb)
其中,react-dom-production.min.js负责管理 DOM 的操作。react提供了主要处理组件的创建、更新、销毁等虚拟 DOM 操作。scheduler 用于处理 React 中的任务调度。它负责优先级调度、任务分组和任务执行等。
通过使用 source-map-explorer 分析 React、Vue 和 Svelte 项目的构建产物,可以发现
- Svelte 项目的运行时代码通常比 React 和 Vue 更小。这是因为 Svelte 将大部分工作转移到编译阶段,从而减少了浏览器中运行时代码的体积。React 和 Vue 则依赖于更大的运行时库来处理组件生命周期、虚拟 DOM 等。
- 在 React 和 Vue 中可能会发现一些额外的库,如
react-dom(用于 React 的 DOM 操作)或@vue/runtime-dom(用于 Vue 的运行时环境)。Svelte 则无需这些额外的库,因为它在编译时生成高效的 JavaScript 代码。 - 在 React 和 Vue 的构建产物中,会看到与虚拟 DOM 相关的模块和抽象层,这些模块用于处理组件的创建、更新和销毁。而在 Svelte 项目中,会发现更少的框架抽象层。
Less Code
当我们使用svelte封装组件的时候,会发现svelte的代码量要远远小于Vue和React。这里我们以一个简单的例子来进行比较(在输入框中输入内容,通过按钮进弹框弹出)
React:
import React, { useState } from "react";
export default function App() {
const [animal, setAnimal] = useState("dog");
const showModal = () => {
alert(`My favorite animal is ${animal}`);
};
return (
<>
<input
type="text"
value={animal}
onChange={(e) => {
setAnimal(e.target.value);
}}
/>
<button onClick={showModal}>弹出</button>
</>
);
}
Vue:
<template>
<div>
<input type="text" v-model="animal" />
<button @click="showModal">弹出</button>
</div>
</template>
<script>
export default {
name: 'alertComponent',
data () {
return {
animal: 'cat'
}
},
methods: {
showModal () {
alert(`My favorite animal is ${this.animal}`);
}
}
}
</script>
<style></style>
Svelte
<script>
let animal = 'dog';
const showModal = () => {
alert(`My favorite animal is ${animal}`);
};
</script>
<input type="text" bind:value={animal} />
<button on:click={showModal}>弹出</button>
无虚拟DOM
Rich Harris 在设计 Svelte 的时候没有采用 Virtual DOM 是因为觉得Virtual DOM Diff 的过程是非常低效的。
虚拟 DOM 是一种有效的技术,用于最小化实际 DOM 操作,从而提高性能。然而,它也带来了一定的开销,因为它需要创建虚拟节点、比较差异并应用更新。之所以大家觉得虚拟DOM高效的理由是:在浏览器当中,JavaScript的运算在现代的引擎中非常快,但DOM本身是非常缓慢的东西。而虚拟DOM不会直接操作原生的DOM节点。
但针对这些优势是有它固定的业务场景的,针对一些大型的系统,使用虚拟DOM 确实可以大大减少开发难度节省性能,但针对小型系统本身现有的机能已经足够满足性能开销,直接操作 DOM 也未必会与使用虚拟DOM 有显著的差异,反而是虚拟DOM 需要的 diff/patch 算法会更显的冗余。
下面我们可以通过一个长表格的案例来体验以下VDOM Diff和SVELTE的性能。
SVELTE:
<script>
function generateData() {
let arr = [];
for (let iterator = 0; iterator < 100000; iterator++) {
arr.push({ title: "title" + iterator, id: iterator });
}
return arr;
}
let data = generateData();
function updateData() {
data = data.map((item, index) => {
if (index % 10 === 0) {
return { ...item, title: "Updated " + item.title };
}
return item;
});
}
</script>
<div>
<button on:click={updateData}>Update Data</button>
</div>
<ul>
{#each data as item}
<li>{item.title}</li>
{/each}
</ul>
React:
function App() {
const [data, setData] = useState(generateData());
function generateData() {
let arr = [];
for (let iterator = 0; iterator < 100000; iterator++) {
arr.push({ title: "title" + iterator, id: iterator });
}
return arr;
}
const updateData = () => {
const newData = data.map((item, index) => {
if (index % 10 === 0) {
return { ...item, title: "Updated " + item.title };
}
return item;
});
setData(newData);
};
return (
<div className="App">
<div>
<button onClick={updateData}>Update Data</button>
</div>
<ul>
{data.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>
</div>
);
}
通过对比我们可以发现相对来说svelte更加流畅。进一步证明在有些场景下React使用虚拟DOM技术,在运行时对组件进行更新和重新渲染,可能会导致更多的性能开销。
svelte的出现
2016年,Svelte发布,Svelte是一种新型的Javascript渐进式框架,通过静态编译减少框架运行时的代码量,可以在编译时让前端应用程序更快、更小和更可维护。一个 Svelte 组件编译之后,所有需要的运行时代码都包含在里面了,除了引入这个组件本身,你不需要再额外引入一个所谓的框架运行时!
解决的问题: - no virtual DOM - truly reactive
svelte和其他前端框架响应式原理的比较
-
响应式原理
-
React
-
React 开发者使用
JSX语法来编写代码,JSX会被编译成ReactElement,运行时生成抽象的 Virtual DOM。 -
然后在每次重新 render 时,React 会重新对比前后两次 Virtual DOM,如果不需要更新则不作任何处理;如果只是 HTML 属性变更,那反映到 DOM 节点上就是调用该节点的
setAttribute方法;如果是 DOM 类型变更、key 变了或者是在新的 Virtual DOM 中找不到,则会执行相应的删除/新增 DOM 操作。 -
我们看看一个简单的点击计数的 DEMO 背后 React 都做了哪些事情。
import React from "react"; const Counter = () => { const [count, setCount] = React.useState(0); return <button onClick={() => setCount((val) => val + 1)}>{count}</button>; }; function App() { return <Counter />; } export default App;大致可以将整个流程分为三个部分,首先是调度器,这里主要是为了处理优先级(用户点击事件属于高优先级)和合成事件。 第二个部分是 Render 阶段,这里主要是遍历节点,找到需要更新的 Fiber Node,执行 Diff 算法计算需要执行那种类型的操作,打上 effectTag,生成一条带有 effectTag 的 Fiber Node 链表。常说的异步可中断也是发生在这个阶段。
第三个阶段是 Commit,这一步要做的事情是遍历第二步生成的链表,依次执行对应的操作(是新增,还是删除,还是修改...)
前置操作完成,计算出原来是 nodeValue 需要更新,最终执行了
firstChild.nodeValue = text。
-
-
Vue
发布者订阅模式, 大致过程是编译过程中收集依赖,基于 Proxy(3.x) ,defineProperty(2.x) 的 getter,setter 实现在数据变更时通知 Watcher。
Vue的实现很酷,每次修改data上的数据都像在施魔法。
-
基于SVELTE特性设计响应式编程
脱离VirtuaDom实现响应式编程
需求描述:使用js生成一个button,button里面绑定了一个count属性,点击一次count+1,button的内容页发生相应变化
function reactiveButton() {
let count = 0;
const button = document.createElement('button');
function renderButton() {
button.textContent = `count is ${count}`;
}
function setCount() {
count ++;
renderButton();
}
button.onclick = setCount;
renderButton();
return button
}
const button = reactiveButton();
document.body.appendChild(button);
操作颗粒度更细分
上面代码的操作颗粒度是按钮,举一个极端例子,按钮的内容是前面一万多个字符,然后在跟着一个count的值,当变更count并且执行renderButton的时候,会对整个按钮的文案进行操作。但是如果我们把count改变操作对象从button改为textNode,那就轻松多了
function reactiveButton() {
let count = 0;
const button = document.createElement('button');
// 为count和之前的文本各自创建TextNode元素
const t1 = document.createTextNode('count is ');
const t2 = document.createTextNode(count);
button.appendChild(t1);
button.appendChild(t2);
// 颗粒度改为直接操作count直接影响的TextNode元素
function renderTextNode() {
t2.textContent = count;
}
function setCount() {
count ++;
renderTextNode();
}
button.onclick = setCount;
return button
}
分离dom操作和变量赋值
// 数据的定义的操作
function instance() {
let count = 0;
function setCount() {
count ++;
}
return [count, setCount]
}
// 创建组件实例,返回dom的操作封装
function create_fragment(target = document.body, ctx) {
let button
let t1;
let t2;
let mounted = false;
return {
// 元素的初始化
create() {
button = document.createElement('button');
t1 = document.createTextNode('count is ');
t2 = document.createTextNode(ctx[0]);
},
// 元素的插入操作
// target表示父元素,anchor表示锚点元素
// 如果anchor存在,则插入到anchor之前,如果不存在,则作为父元素的最后一个子元素
mount(target, anchor) {
button.appendChild(t1);
button.appendChild(t2);
target.insertBefore(button, anchor || null);
if (!mounted) {
mounted = true;
button.onclick = () => {
// 点击后触发上下文对象中暴露的函数
ctx[1].call();
this.update();
}
}
},
// 更新节点,因为只有t2的文本节点需要更新,且收到上下文对象中的count影响
update() {
t2.textContent = ctx[0]
}
}
}
// 分别初始化dom和定义影响dom的变量
const ctx = instance();
const b = create_fragment(document.body, ctx);
b.create();
b.mount(document.body, null);
通过上诉的方式,确实页面确实显示出了一个按钮,且按钮内容为预期中的count is 0,但是点击后内容没有发生变化,原因是返回的ctx[1]函数改的是函数内作用域的count,并不是暴露出去的ctx[0]。
维护一个上下文数组和改变内容的方法
function instance(
+ $$invalidate // 初始化的时候传入一个可以有效改变上下文数组的函数
) {
let count = 0;
const setCount = () => {
- count ++;
+ $$invalidate(0, count ++)
}
return [count, setCount]
}
// 维护一个上下文数组和改变上下文对象的方法
- const ctx = instance();
+ const $$ = {};
+ $$.ctx = instance((i, val) => {
+ $$.ctx[i] = val;
+})
const b = create_fragment(
- ctx
+ $$.ctx
);
这样处理后确实点击内容会发生变化,但是很奇怪,点击第一次的时候,没有发生变化.
-
原因?
count ++这类的语法,是会先返回count,然后再进行+1操作,例如:
let a = 1;
const b = a ++;
// 这时候打印的还是1
console.log(b)
-
解决方案
不可能去改count++的常用写法,添加传参让用户传入最新的count值
function instance($$invalidate) {
let count = 0;
const setCount = () => {
$$invalidate(
0,
count ++,
+ count
)
}
return [count, setCount]
}
const $$ = {};
$$.ctx = instance((
i,
val,
+ ...res
) => {
- $$.ctx[i] = val
+ $$.ctx[i] = res.length ? res[0] : val;
})
多个变量
修改需求:页面上添加一个文本内容,显示count是否超过3了
简单的想法:对外抛出一个isMoreThan3变量,setCount方法执行的时候同时去改count和isMoreThan3的值
function instance($$invalidate) {
let count = 0;
+ let isMoreThan3 = count > 3;
const setCount = () => {
$$invalidate(0, count ++, count)
+ $$invalidate(2, count > 3)
}
return [
count,
setCount,
+ isMoreThan3,
]
}
同时进行之前类似的元素封装处理:create方法添加两个文本节点,mount方法插入两个文本节点,update方法改变绑定isMoreThan3的文本节点
// 创建节点
function create() {
button = document.createElement('button');
t1 = document.createTextNode('count is ');
t2 = document.createTextNode(ctx[0]);
+ t3 = document.createTextNode(' is more than 3: ');
+ t4 = document.createTextNode(ctx[2])
}
// 插入节点
function mount(target, anchor) {
button.appendChild(t1);
button.appendChild(t2);
+ button.appendChild(t3);
+ button.appendChild(t4);
target.insertBefore(button, anchor || null);
if (!mounted) {
mounted = true;
button.onclick = () => {
ctx[1].call();
this.update();
}
}
}
// 更新节点
function update() {
t2.textContent = ctx[0]
+ t4.textContent = ctx[2]
}
更新渲染原理
模拟了svelte实现响应式的过程,Svelte本身 是如何更新渲染结果的呢?Svelte 整体的更新渲染流程是怎么样子的呢?我们来编译两个例子。
Demo1
我们先看看最简单的代码会被编译成什么样子。
<script>
let name = 'world';
</script>
<h1>Hello {name}!</h1>
/* App.svelte generated by Svelte v3.59.2 */
function create_fragment(ctx) {
let h1;
return {
c() {
h1 = element("h1");
h1.textContent = `Hello ${name}!`;
},
m(target, anchor) {
insert(target, h1, anchor);
},
p: noop,
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(h1);
}
};
}
let name = 'world';
class App extends SvelteComponent {
constructor(options) {
super();
init(this, options, null, create_fragment, safe_not_equal, {});
}
}
export default App;
这段代码经由编译器编译后产生如下代码,包括三部分:
-
create_fragment方法,这个函数会返回一个对象,包含组件对应的多个方法:
- c:代表create,用于根据模版内容,创建对应DOM Element。
- m:代表mount,用于将c创建的DOM Element插入页面,完成组件首次渲染。
- d:代表detach,用于将组件对应DOM Element从页面中移除,detach方法会调用parentNode.removeChild.
-
class App的声明语句,每个组件对应一个继承自SvelteComponent的class,实例化时会调用init方法完成组件初始化,create_fragment会在init中调用。
-
变量name的声明
仔细观察流程图,会发现App组件编译的产物没有图中fragment内的p方法,由于上面的代码中是个静态的字符串,所以p对应的值为noop即no operate没有操作。
Demo2
这是我们再来看一下,可以改变状态的Demo,例子如下:
<div>
{count}
<button on:click={onClick}>click</button>
</div>
<script>
let count= 1
function onClick() {
count= count+ 1
}
</script>
这个时候,我们会发现编译产物发生了变化。
/* App.svelte generated by Svelte v3.59.2 */
function create_fragment(ctx) {
let div;
let t0;
let t1;
let button;
let mounted;
let dispose;
return {
c() {
div = element("div");
t0 = text(/*count*/ ctx[0]);
t1 = space();
button = element("button");
button.textContent = "click";
},
m(target, anchor) {
insert(target, div, anchor);
append(div, t0);
append(div, t1);
append(div, button);
if (!mounted) {
dispose = listen(button, "click", /*onClick*/ ctx[1]);
mounted = true;
}
},
p(ctx, [dirty]) {
if (dirty & /*count*/ 1) set_data(t0, /*count*/ ctx[0]);
},
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(div);
mounted = false;
dispose();
}
};
}
function instance($$self, $$props, $$invalidate) {
let count = 1;
function onClick() {
$$invalidate(0, count = count + 1);
}
return [count, onClick];
}
class App extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, {});
}
}
export default App;
-
顶层声明变量的方式变成了instance方法。
**
Svelte编译器会追踪<script>**内所有变量声明,一旦存在改变该变量的语句如count++,赋值等操作,就会将变量提取到instance中,并返回组件对应的ctx; -
同时如果改变的变量操作语句可以通过模板被引用,则该语句就会被
$$invalidate包裹。用于后续的DOM更新。 -
create_fragment生成
p方法来更新对应的 DOM 元素。Svelte 是在编译时候,就记录了数据 和 DOM 节点之间的对应关系,并且保存在 p 函数中。p函数set_data就是封装了 DOM 的原生方法(比如说innerHtml),把 DOM 节点更新。
上面我们了解了编译产物的变化,那如果此时我们点击按钮,以上这些变化时如何实现DOM更新的呢?它的详细流程是怎样的?
我们知道Svelte 回归到原生的JS,每个组件对应一个JS类,即组件实例instance。当组件的状态发生变化的时候,生成一个新的组件实例,使用差异算法来比较新旧组件的实例DOM的结构,然后更新需要更新的部分。具体流程如下:
-
首先,我们在浏览器执行如上所示的Svelte 编译修改之后的代码。因此当click事件发生之后,增加的instance方法将onClick的方法改写,会对count进行重新赋值(count = count+1),随后调用
?invalidate方法 -
随后,
?invalidate方法又调用了make_dirty方法,make_dirty是记住已经脏的数据。Svelte使用 位掩码(bitMask) 的技术来跟踪哪些值是脏的。位掩码是一种将多个布尔值存储在单个整数中的技术,一个比特位存放一个数据是否变化,一般**1表示脏数据,0表示是干净数据。简单点说,就是二进制 0000 0001代表第一个值是脏数据,0000 0011代表第一第二位都是脏数据。Svelte采用数组的方式来存放。数组中一项是二进制31位的比特位。假如超出31个数据了,超出的部分放到数组中的下一项 。这个记录的数组就是component.?.dirty**数组。 -
有了脏数据之后,会调用
schedule_update()函数,schedule_update的作用是,把一个 flush 的方法放到一个Promise.then里面,让flush方法在这一帧的微任务被执行的时候执行。就会在一帧16ms的微任务调用更新DOM节点的方法。
flush方法里面的逻辑是:遍历所有的diry_components中的组件,调用update方法,update方法里面,最后调用了组件的p()方法。
和我们设计的响应式编程的区别
- 封装了dom的创建函数:
- document.createElement('button')
+ element('button)
- 封装了容器代码
-const $$ = {};
- $$.update = () => {};
- $$.dirty = 0;
- $$.ctx = instance($$, (i, ret, ...res) => {
- const oldVal = $$.ctx[i];
- $$.ctx[i] = res.length ? res[0] : ret;
- if (oldVal !== $$.ctx[i]) {
- console.log($$.dirty, i, 'before')
- $$.dirty |= 1 << (i % 31)
- console.log($$.dirty, 'after')
- }
- update($$);
- })
- $$.update();
- const c = create_fragment($$.ctx);
- $$.fragment = c;
- c.create();
- c.mount(props.target, props.anchor);
- $$.dirty = 0;
+ init(this, options, instance, create_fragment, safe_not_equal, {});
- 提供了卸载fragment的方法
+ d(detaching) {
+ if (detaching) detach(button);
+ mounted = false; dispose();
+ }
基本使用
生命周期
svelte的声明周期函数相对比较简单,主要关注组件挂载、更新和卸载时的操作。
- onMount:组件挂载到 DOM 时调用,建议放异步请求,方便做服务端渲染
- beforeUpdate:组件状态更新之前调用。
- afterUpdate:组件状态更新完毕并重新渲染后调用。
- onDestroy:组件卸载时调用,比如说订阅的全局 store, 定时器等
import { onMount, beforeUpdate, afterUpdate, onDestroy } from 'svelte';
onMount(() => {
console.log('组件挂载到 DOM');
});
beforeUpdate(() => {
console.log('组件数据更新之前');
});
afterUpdate(() => {
console.log('组件数据更新之后');
});
onDestroy(() => {
console.log('组件卸载');
});
react和vue的生命周期更复杂,这里我们就不详细讲解了。
Slot
在 Svelte 中,插槽使用名为 <slot> 的特殊元素表示。如果子组件中有一个 <slot> 元素,那么父组件可以传递内容到该位置。
<DefaultSlot>
<!-- put content here -->
<span>111</span>
<span>222</span>
</DefaultSlot>
<NameSlot>
<span slot="name">
名字
</span>
<span slot="address">
地址CCCC
</span>
</NameSlot>
<slot name="name">
<span>Unknown name</span>
</slot>
<slot name="address">
<span >Unknown address</span>
</slot>
在 React 中,插槽通常使用 props.children 来实现。子组件可以访问 props.children 以获得父组件传递的内容。
function Card(props) {
return <div className="card">{props.children}</div>;
}
在vue中,slot是一种在父组件中向子组件传递内容的方式。插槽允许开发人员自定义子组件内部的结构。插槽也分为两种类型:默认插槽和具名插槽。
<template>
<div class="card">
<slot></slot>
<slot name="name"></slot>
<slot name="address"></slot>
</div>
</template>
<NameSlot>
<template v-slot:name>
<h2>这是名字</h2>
</template>
<template v-slot:address>
<p>这是地址</p>
</template>
</NameSlot>
Store
在 Svelte 中,Store(存储)是一种用于在组件之间共享响应式状态的机制。Svelte 提供了一个名为 writable 的方法来创建可读写的存储,以及一个名为 readable 的方法来创建只读存储,还提供了一个方法用于创建一个基于其他存储的派生存储。
import { writable } from 'svelte/store';
export const count = writable(0);
// 获取
import { count } from './stores.js';
let count_value;
const unsubscribe = count.subscribe(value => {
count_value = value;
});
<h1>The count is {count_value}</h1>
<Incrementer/>
<Decrementer/>
// 更改
<script>
import { count } from './stores.js';
function decrement() {
count.update(n => n - 1);
}
</script>
<button on:click={decrement}>
-
</button>
<script>
import { count } from './stores.js';
function increment() {
count.update(n => n + 1);
}
</script>
<button on:click={increment}>
+
</button>
在Vue中我们一般使用Vuex来实现store,通过state获取数据,dispatch改变数据,这里我们就不详细介绍了,同样的,react中我们可以使用redux来实现store
<!-- src/components/Counter.vue -->
<template>
<div>
<button @click="decrement">-</button>
<span>{{ count }}</span>
<button @click="increment">+</button>
</div>
</template>
<script>
export default {
computed: {
count() {
return this.$store.state.count;
}
},
methods: {
increment() {
this.$store.dispatch('increment');
},
decrement() {
this.$store.dispatch('decrement');
}
}
};
</script>
动画
Svelte 提供了内置的动画库,可以帮助轻松地为组件创建丰富的动画。例如我们实现一个进度条的组件
<script>
import { writable } from 'svelte/store';
const progress = writable(0);
</script>
<style>
progress { display: block; width: 100%; }
</style>
<progress value={$progress}></progress>
{#each [0, 0.25, 0.5, 0.75, 1] as p}
<button on:click={() => $progress = p}>
{p*100}%
</button>
{/each}
tweened(补间动画)
这个时候我们会感觉它比较生硬,缺乏动画效果。让我们修改progressstore 为tweened
import { tweened } from 'svelte/motion';
const progress = tweened(0, {
duration: 400
});
transition(过渡效果)
将进入元素或离开元素的过程如能优雅地使用过渡效果,可以大大增加用户界面的吸引力。Svelte 使用transition指令让这种操作轻而易举。下面我们已fade为例,此外还有fly,in,等其他方法。
<script>
import { fade } from 'svelte/transition';
let visible = true;
</script>
<label><input type="checkbox" bind:checked={visible}>显示</label>
{#if visible}
<p transition:fade>淡入淡出效果展示</p>
{/if}