Svelte中的动画
时间:2021-03-26 02:52:07
motion
Svelte
提供一个svelte/motion
模块,用于支持动画,此模块有两个函数:tweened
、spring
,通过这两个函数可以创建一个store
,然后通过set
设置和update
更新其值。
tweened
通过tweened
创建的值当发生改变时,不会立即更新而是随着时间动态的更新。tweened
主要接受两个参数:
value
:初始值option
:deplay
:延时执行的时间(毫秒)duration
:持续时间(毫秒)easing
:动画函数。可以使用svelte/easing
提供的函数interpolate
:自定义动画区间的值。必须是(a,b) => t => value
格式的函数,其中a
为起始值,b
为目标值,t
是 0~1 之前的数字,value
是结果。如:两个颜色间平滑过渡可以用d3-interpolate。
<script>
import { tweened } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';
const progressList = [0, 25, 50, 75, 100];
let progress = tweened(0, {
duration: 300,
easing: cubicOut
});
</script>
<input type="range" bind:value={$progress} />
<div>
{#each progressList as item (item)}
<button on:click={() => progress.set(item)}>{item}%</button>
{/each}
</div>
spring
spring
是对tweened
的一个补充用于弹性设置,适用于多个值间的变化。主要接受两个参数:
value
:初始值option
:stiffness
:0~1,值越高,弹性越大,默认为:0.15
damping
:0~1,值越高,弹性越小,默认为:0.8
precision
:精确稳定的值,值越低代表越精确,默认为:0.001
<script>
import { spring } from 'svelte/motion';
let coords = spring(
{ x: 50, y: 50 },
{
stiffness: 0.1,
damping: 0.25
}
);
let size = spring(10);
</script>
<div style="position: absolute; right: 1em;">
<label>
<h3>stiffness ({coords.stiffness})</h3>
<input bind:value={coords.stiffness} type="range" min="0" max="1" step="0.01" />
</label>
<label>
<h3>damping ({coords.damping})</h3>
<input bind:value={coords.damping} type="range" min="0" max="1" step="0.01" />
</label>
</div>
<svg
on:mousemove={(e) => coords.set({ x: e.clientX, y: e.clientY })}
on:mousedown={() => size.set(30)}
on:mouseup={() => size.set(10)}
>
<circle cx={$coords.x} cy={$coords.y} r={$size} />
</svg>
<style>
svg {
width: 100%;
height: 100%;
margin: -8px;
}
circle {
fill: #ff3e00;
}
</style>
Transitions
Svelte
提供了store/transition
模块用于过渡动画,其中有fade
、blur
、fly
、slide
、scale
、draw
、crossfade
。可通过transition:xxx
来设置in
、out
的动画,也可单独设置动画:in:xxx
、out:xxx
。
fade
对元素的不透明度进行动画处理。接受以下参数:
delay
:延迟的毫秒数,默认为0
。duration
:转换持续的毫秒数,默认为400
。easing
:过渡动画,可以使用svelte/easing
提供的函数,默认为linear
。
<script>
import { fade } from 'svelte/transition';
let status = true;
const onClick = () => (status = !status);
</script>
<button on:click={onClick}> 切换Hello显示状态 </button>
{#if status}
<h1 transition:fade>Hello!</h1>
<h1 out:fade={{ delay: 250, duration: 300 }}>out Hello!</h1>
<h1 in:fade={{ delay: 250, duration: 300 }}>in Hello!</h1>
<h1 transition:fade={{ delay: 250, duration: 300 }}>延迟Hello!</h1>
{/if}
blur
对元素的不透明度添加滤镜动画处理。接受以下参数:
delay
:延迟的毫秒数,默认为0
。duration
:转换持续的毫秒数,默认为400
。easing
:过渡动画,可以使用svelte/easing
提供的函数,默认为cubicOut
。opacity
:不透明度,默认为0
。amount
:模糊大小(单位为像素),默认为5
。
<script>
import { blur } from 'svelte/transition';
let status = true;
const onClick = () => (status = !status);
</script>
<button on:click={onClick}> 切换Hello显示状态 </button>
{#if status}
<h1 transition:blur>Hello!</h1>
{/if}
fly
对元素的x
、y
以及不透明度进行动画处理。接受以下参数:
delay
:延迟的毫秒数,默认为0
。duration
:转换持续的毫秒数,默认为400
。easing
:过渡动画,可以使用svelte/easing
提供的函数,默认为cubicOut
。opacity
:不透明度,默认为0
。x
:x
轴偏移量,默认为0
。y
:y
轴偏移量,默认为0
。
<script>
import { fly } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
let status = true;
const onClick = () => (status = !status);
</script>
<button on:click={onClick}> 切换Hello显示状态 </button>
{#if status}
<h1
transition:fly={{ delay: 250, duration: 300, x: 100, y: 100, opacity: 0.5, easing: quintOut }}
>
Hello
</h1>
{/if}
slide
对元素的滑入,滑出进行动画处理。接受以下参数:
delay
:延迟的毫秒数,默认为0
。duration
:转换持续的毫秒数,默认为400
。easing
:过渡动画,可以使用svelte/easing
提供的函数,默认为cubicOut
。
<script>
import { slide } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
let status = true;
const onClick = () => (status = !status);
</script>
<button on:click={onClick}> 切换Hello显示状态 </button>
{#if status}
<h1 transition:slide={{ delay: 250, duration: 300, easing: quintOut }}>Hello</h1>
{/if}
scale
对元素的不透明度进行拉远动画处理。接受以下参数:
delay
:延迟的毫秒数,默认为0
。duration
:转换持续的毫秒数,默认为400
。easing
:过渡动画,可以使用svelte/easing
提供的函数,默认为cubicOut
。start
:拉远比例0~1
,默认为0
。opacity
:不透明度,默认为0
。
<script>
import { scale } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
let status = true;
const onClick = () => (status = !status);
</script>
<button on:click={onClick}> 切换Hello显示状态 </button>
{#if status}
<h1 transition:scale={{ duration: 500, delay: 500, opacity: 0.5, start: 0.3, easing: quintOut }}>
Hello
</h1>
{/if}
draw
对 svg 元素的笔触进行动画处理。接受以下参数:
delay
:延迟的毫秒数,默认为0
。duration
:转换持续的毫秒数,默认为400
。easing
:过渡动画,可以使用svelte/easing
提供的函数,默认为cubicOut
。speed
:转换持续的毫秒数,默认为400
。
<script>
import { draw } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
let status = true;
const onClick = () => (status = !status);
</script>
<button on:click={onClick}> 切换显示状态 </button>
<svg viewBox="0 0 5 5" xmlns="http://www.w3.org/2000/svg">
{#if status}
<path
transition:draw={{ duration: 5000, easing: quintOut }}
d="M2 1 h1 v1 h1 v1 h-1 v1 h-1 v-1 h-1 v-1 h1 z"
fill="none"
stroke="cornflowerblue"
stroke-width="0.1px"
stroke-linejoin="round"
/>
{/if}
</svg>
flip
Svelte
还提供了svelte/animate
模块,目前只有flip
。flip
计算元素的开始位置和结束位置,并进行动画处理,接受以下参数:
delay
:延迟的毫秒数,默认为0
。duration
:转换持续的时间(类型:number | function),默认为d => Math.sqrt(d) * 120
。number
:以毫秒为单位。function
:distance: number => duration: number
接受以像素为单位的距离,返回以毫秒为单位的持续时间。
easing
:过渡动画,可以使用svelte/easing
提供的函数,默认为cubicOut
。
<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.key === 'Enter' && 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>
svelte/easing
Svelte
提供了以下过渡动画:
ease | in | out | inOut |
---|---|---|---|
back | backIn | backOut | backInOut |
bounce | bounceIn | bounceOut | bounceInOut |
circ | circIn | circOut | circInOut |
cubic | cubicIn | cubicOut | cubicInOut |
elastic elasticIn elasticOut elasticInOut | |||
expo | expoIn | expoOut | expoInOut |
quad | quadIn | quadOut | quadInOut |
quart | quartIn | quartOut | quartInOut |
quint | quintIn | quintOut | quintInOut |
sine | sineIn | sineOut | sineInOut |
由此可见,Svelte
中内置的动画还是很强大,组合使用可以使用户体验感非常好。