状态管理
状态管理主要使用svelte/store 这个目录writable 声明一个可以obsevable的对象
const count = writable(0); 初始化 值
通过update 更新 类似react中 setState
通过set 直接更新值
通过subscribe 监听值得变化 。会返回一个unsubscribe函数,配合onDestory 取消监听
$count 自动指向 监听的值 ,所以再svelte中尽量避免使用$声明变量
// store.js
import { writable } from 'svelte/store';
export const count = writable(0);
<script>
import { count } from './stores.js';
function decrement() {
count.update(n=>n-1)
}
</script>
<button on:click={decrement}>
-
</button>
// Incrementer.svelte
<script>
import { count } from './stores.js';
function increment() {
count.update(n=>n+1)
}
</script>
<button on:click={increment}>
+
</button>
// Resetter.svelte
<script>
import { count } from './stores.js';
function reset() {
count.set(0)
}
</script>
<button on:click={reset}>
reset
</button>
// App.svelte
<script>
import {onDestory} from 'svelte';
import { count } from './stores.js';
import Incrementer from './Incrementer.svelte';
import Decrementer from './Decrementer.svelte';
import Resetter from './Resetter.svelte';
let count_value;
const unsubscribe = count.subscribe(value => {
count_value = value;
});
onDestory(unsubscribe) // 组件销毁时,取消监听防止内存泄漏
</script>
<h1>The count is {count_value} {$count}</h1>// count指向监听的值,自动监听
<Incrementer/>
<Decrementer/>
<Resetter/>
只读 状态变量
readable 声明一个只读的变量, null为初始值, 第二个函数 set参数为 修改状态方法
返回一个 销毁函数 以便于 自动销毁一些监听事件。 例如setInterval
import { readable } from 'svelte/store';
export const time = readable(null, function start(set) {
const interval = setInterval(()=>{
set(new Date())
},1000)
return function stop() {
clearInterval(interval)
};
});
状态管理中的 计算属性
derived 用于声明计算属性
export const elapsed = derived(
time,
$time => Math.round(($time - start) / 1000)
);
自定义状态管理变量
通过返回一个包含subscribe, set, update的对象 封装一个自定义状态管理变量
// stores.js
import { writable } from 'svelte/store';
function createCount() {
const { subscribe, set, update } = writable(0);
return {
subscribe,
increment: () => {},
decrement: () => {},
reset: () => {}
};
}
export const count = createCount();
<script>
import { count } from './stores.js';
</script>
<h1>The count is {$count}</h1>
<button on:click={count.increment}>+</button>
<button on:click={count.decrement}>-</button>
<button on:click={count.reset}>reset</button>
状态绑定到组件中
通过bind: 绑定状态到组件 ,相当于 on:click="{()=>$name+=1}"
$name += '!' 相当于 name.set($name + '!')
// stores.js
import { writable, derived } from 'svelte/store';
export const name = writable('world');
export const greeting = derived(
name,
$name => `Hello ${$name}!`
);
<script>
import { name, greeting } from './stores.js';
</script>
<h1>{$greeting}</h1>
<input bind:value={$name}>
<button on:click={()=>name.set($name+= '!')}>
add excuamation mark!
</button>
Motion 动画
svelte/motion 动画类型
svelte/easing 动画变化曲线
- delay:动画延迟
- duration:补间的持续时间(以毫秒为单位),或者
(from, to) => milliseconds
允许您(例如)指定更长的补间以获取更大的值变化的函数 - easing: 一个
p => t
函数 interpolate
—(from, to) => t => value
用于在任意值之间插值的自定义函数。默认情况下,Svelte将在数字,日期以及形状相同的数组和对象之间进行插值(只要它们仅包含数字和日期或其他有效的数组和对象)。如果要插值(例如)颜色字符串或转换矩阵,请提供自定义插值器
通过 progress 的set 和update 修改状态
<script>
import { writable } from 'svelte/store';
import { tweened } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';
const progress = tweened(0,{
duration:400,
easing:cubicOut
});
</script>
<style>
progress {
display: block;
width: 100%;
}
</style>
<progress value={$progress}></progress>
<button on:click="{() => progress.set(0)}">
0%
</button>
<button on:click="{() => progress.set(0.25)}">
25%
</button>
<button on:click="{() => progress.set(0.5)}">
50%
</button>
<button on:click="{() => progress.set(0.75)}">
75%
</button>
<button on:click="{() => progress.set(1)}">
100%
</button>
spring 动画
<script>
import { spring } from 'svelte/motion';
let coords = spring({ x: 50, y: 50 });
let size = spring(10);
</script>
Transitions 过渡动画
通过 transition:fade 指令进行过渡动画绑定
<script>
import { fade } from 'svelte/transition';
let visible = true;
</script>
<label>
<input type="checkbox" bind:checked={visible}>
visible
</label>
{#if visible}
<p transition:fade>
Fades in and out
</p>
{/if}
绑定动画参数
transition:fly 进行动画参数绑定
<script>
import { fly } from 'svelte/transition';
let visible = true;
</script>
<label>
<input type="checkbox" bind:checked={visible}>
visible
</label>
{#if visible}
<p transition: fly ={{y:200,duration:2000}}>
Fades in and out
</p>
{/if}
动画进场与离场
控制动画进场和离场
<script>
import { fly,fade } from 'svelte/transition';
let visible = true;
</script>
<label>
<input type="checkbox" bind:checked={visible}>
visible
</label>
{#if visible}
<p in:fly="{{ y: 200, duration: 2000 }}" out:fade>
Flies in and out
</p>
{/if}
自定义css 过渡动画
通过css 返回样式 来自定义过渡动画
- delay :过渡开始前的毫秒数
- duration:过渡时间(以毫秒为单位)
- easing:
p => t
缓动功能 css:
(t, u) => css
函数,在哪里u === 1 - t
tick:
(t, u) => {...}
对节点有一定影响的功能
<script>
import { fade } from 'svelte/transition';
import { elasticOut } from 'svelte/easing';
let visible = true;
function spin(node, { duration }) {
return {
duration,
css: t => {
const eased = elasticOut(t);
return `
transform: scale(${eased}) rotate(${eased * 1080}deg);
color: hsl(
${~~(t * 360)},
${Math.min(100, 1000 - 1000 * t)}%,
${Math.min(50, 500 - 500 * t)}%
);`
}
};
}
</script>
<style>
.centered {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
}
span {
position: absolute;
transform: translate(-50%,-50%);
font-size: 4em;
}
</style>
<label>
<input type="checkbox" bind:checked={visible}>
visible
</label>
{#if visible}
<div class="centered" in:spin="{{duration: 8000}}" out:fade>
<span>transitions!</span>
</div>
{/if}
自定义js过渡动画
这是模仿打字的js动画 通过tick来实现帧动画
<script>
let visible = false;
function typewriter(node, { speed = 50 }) {
const valid = (
node.childNodes.length === 1 &&
node.childNodes[0].nodeType === 3
);
if(!valid){
throw new Error(`This transition only works on elements with a singel text node child`);
}
const text = node.textContent;
const duration = text.length * speed;
return {
duration,
tick:t=>{
const i = ~~(text.length* t);
node.textContent = text.slice(0,i)
}
};
}
</script>
<label>
<input type="checkbox" bind:checked={visible}>
visible
</label>
{#if visible}
<p in:typewriter>
The quick brown fox jumps over the lazy dog
</p>
{/if}
过渡动画事件
过渡动画通过 on:introstart、on:introend
<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'}"
>
Flies in and out
</p>
过渡动画本地化(过渡动画绑定绑定父容器)
通过transition:slide|local 父容器销毁或增加时候不具备动画效果
<script>
import { slide } from 'svelte/transition';
let showItems = true;
let i = 5;
let items = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
</script>
<style>
div {
padding: 0.5em 0;
border-top: 1px solid #eee;
}
</style>
<label>
<input type="checkbox" bind:checked={showItems}>
show list
</label>
<label>
<input type="range" bind:value={i} max=10>
</label>
{#if showItems}
{#each items.slice(0, i) as item}
<div transition:slide|local>
{item}
</div>
{/each}
{/if}
延迟过渡
crossfade 生成组合动画
<script>
import { quintOut } from 'svelte/easing';
import { crossfade } from 'svelte/transition';
const [send, receive] = crossfade({
duration: d => Math.sqrt(d * 200),
fallback(node, params) {
const style = getComputedStyle(node);
const transform = style.transform === 'none' ? '' : style.transform;
return {
duration: 600,
easing: quintOut,
css: t => `
transform: ${transform} scale(${t});
opacity: ${t}
`
};
}
});
let uid = 1;
let todos = [
{ id: uid++, done: false, description: 'write some docs' },
{ id: uid++, done: false, description: 'start writing blog post' },
{ id: uid++, done: true, description: 'buy some milk' },
{ id: uid++, done: false, description: 'mow the lawn' },
{ id: uid++, done: false, description: 'feed the turtle' },
{ id: uid++, done: false, description: 'fix some bugs' },
];
function add(input) {
const todo = {
id: uid++,
done: false,
description: input.value
};
todos = [todo, ...todos];
input.value = '';
}
function remove(todo) {
todos = todos.filter(t => t !== todo);
}
function mark(todo, done) {
todo.done = done;
remove(todo);
todos = todos.concat(todo);
}
</script>
<div class='board'>
<input
placeholder="what needs to be done?"
on:keydown={e => e.which === 13 && add(e.target)}
>
<div class='left'>
<h2>todo</h2>
{#each todos.filter(t => !t.done) as todo (todo.id)}
<label in:receive="{{key: todo.id}}"
out:send="{{key: todo.id}}">
<input type=checkbox on:change={() => mark(todo, true)}>
{todo.description}
<button on:click="{() => remove(todo)}">remove</button>
</label>
{/each}
</div>
<div class='right'>
<h2>done</h2>
{#each todos.filter(t => t.done) as todo (todo.id)}
<label class="done" in:receive="{{key: todo.id}}"
out:send="{{key: todo.id}}">
<input type=checkbox checked on:change={() => mark(todo, false)}>
{todo.description}
<button on:click="{() => remove(todo)}">remove</button>
</label>
{/each}
</div>
</div>
<style>
.board {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 1em;
max-width: 36em;
margin: 0 auto;
}
.board > input {
font-size: 1.4em;
grid-column: 1/3;
}
h2 {
font-size: 2em;
font-weight: 200;
user-select: none;
margin: 0 0 0.5em 0;
}
label {
position: relative;
line-height: 1.2;
padding: 0.5em 2.5em 0.5em 2em;
margin: 0 0 0.5em 0;
border-radius: 2px;
user-select: none;
border: 1px solid hsl(240, 8%, 70%);
background-color:hsl(240, 8%, 93%);
color: #333;
}
input[type="checkbox"] {
position: absolute;
left: 0.5em;
top: 0.6em;
margin: 0;
}
.done {
border: 1px solid hsl(240, 8%, 90%);
background-color:hsl(240, 8%, 98%);
}
button {
position: absolute;
top: 0;
right: 0.2em;
width: 2em;
height: 100%;
background: no-repeat 50% 50% url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23676778' d='M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M17,7H14.5L13.5,6H10.5L9.5,7H7V9H17V7M9,18H15A1,1 0 0,0 16,17V10H8V17A1,1 0 0,0 9,18Z'%3E%3C/path%3E%3C/svg%3E");
background-size: 1.4em 1.4em;
border: none;
opacity: 0;
transition: opacity 0.2s;
text-indent: -9999px;
cursor: pointer;
}
label:hover button {
opacity: 1;
}
</style>
动画
flip 为不具有过渡效果的元素,添加动画
<script>
import { quintOut } from 'svelte/easing';
import { crossfade } from 'svelte/transition';
import { flip } from 'svelte/animate';
const [send, receive] = crossfade({
duration: d => Math.sqrt(d * 200),
fallback(node, params) {
const style = getComputedStyle(node);
const transform = style.transform === 'none' ? '' : style.transform;
return {
duration: 600,
easing: quintOut,
css: t => `
transform: ${transform} scale(${t});
opacity: ${t}
`
};
}
});
let uid = 1;
let todos = [
{ id: uid++, done: false, description: 'write some docs' },
{ id: uid++, done: false, description: 'start writing blog post' },
{ id: uid++, done: true, description: 'buy some milk' },
{ id: uid++, done: false, description: 'mow the lawn' },
{ id: uid++, done: false, description: 'feed the turtle' },
{ id: uid++, done: false, description: 'fix some bugs' },
];
function add(input) {
const todo = {
id: uid++,
done: false,
description: input.value
};
todos = [todo, ...todos];
input.value = '';
}
function remove(todo) {
todos = todos.filter(t => t !== todo);
}
function mark(todo, done) {
todo.done = done;
remove(todo);
todos = todos.concat(todo);
}
</script>
<div class='board'>
<input
placeholder="what needs to be done?"
on:keydown={e => e.which === 13 && add(e.target)}
>
<div class='left'>
<h2>todo</h2>
{#each todos.filter(t => !t.done) as todo (todo.id)}
<label
in:receive="{{key: todo.id}}"
out:send="{{key: todo.id}}"
animate:flip
>
<input type=checkbox on:change={() => mark(todo, true)}>
{todo.description}
<button on:click="{() => remove(todo)}">remove</button>
</label>
{/each}
</div>
<div class='right'>
<h2>done</h2>
{#each todos.filter(t => t.done) as todo (todo.id)}
<label
class="done"
in:receive="{{key: todo.id}}"
out:send="{{key: todo.id}}"
animate:flip
>
<input type=checkbox checked on:change={() => mark(todo, false)}>
{todo.description}
<button on:click="{() => remove(todo)}">remove</button>
</label>
{/each}
</div>
</div>
<style>
.board {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 1em;
max-width: 36em;
margin: 0 auto;
}
.board > input {
font-size: 1.4em;
grid-column: 1/3;
}
h2 {
font-size: 2em;
font-weight: 200;
user-select: none;
margin: 0 0 0.5em 0;
}
label {
position: relative;
line-height: 1.2;
padding: 0.5em 2.5em 0.5em 2em;
margin: 0 0 0.5em 0;
border-radius: 2px;
user-select: none;
border: 1px solid hsl(240, 8%, 70%);
background-color:hsl(240, 8%, 93%);
color: #333;
}
input[type="checkbox"] {
position: absolute;
left: 0.5em;
top: 0.6em;
margin: 0;
}
.done {
border: 1px solid hsl(240, 8%, 90%);
background-color:hsl(240, 8%, 98%);
}
button {
position: absolute;
top: 0;
right: 0.2em;
width: 2em;
height: 100%;
background: no-repeat 50% 50% url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23676778' d='M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M17,7H14.5L13.5,6H10.5L9.5,7H7V9H17V7M9,18H15A1,1 0 0,0 16,17V10H8V17A1,1 0 0,0 9,18Z'%3E%3C/path%3E%3C/svg%3E");
background-size: 1.4em 1.4em;
border: none;
opacity: 0;
transition: opacity 0.2s;
text-indent: -9999px;
cursor: pointer;
}
label:hover button {
opacity: 1;
}
</style>