mirror of https://github.com/sveltejs/svelte
parent
8a350d6cbd
commit
ee3ef5bca7
@ -0,0 +1,14 @@
|
|||||||
|
<script>
|
||||||
|
let visible = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" bind:checked={visible}>
|
||||||
|
visible
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{#if visible}
|
||||||
|
<p>
|
||||||
|
Fades in and out
|
||||||
|
</p>
|
||||||
|
{/if}
|
@ -0,0 +1,15 @@
|
|||||||
|
<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}
|
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
title: The transition directive
|
||||||
|
---
|
||||||
|
|
||||||
|
We can make more appealing user interfaces by gracefully transitioning elements into and out of the DOM. Svelte makes this very easy with the `transition` directive.
|
||||||
|
|
||||||
|
First, import the `fade` function from `svelte/transition`...
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script>
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
let visible = true;
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
...then add it to the `<p>` element:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p transition:fade>Fades in and out</p>
|
||||||
|
```
|
@ -0,0 +1,15 @@
|
|||||||
|
<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}
|
@ -0,0 +1,15 @@
|
|||||||
|
<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 }}">
|
||||||
|
Flies in and out
|
||||||
|
</p>
|
||||||
|
{/if}
|
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
title: Adding parameters
|
||||||
|
---
|
||||||
|
|
||||||
|
Transition functions can accept parameters. Replace the `fade` transition with `fly`...
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script>
|
||||||
|
import { fly } from 'svelte/transition';
|
||||||
|
let visible = true;
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
...and apply it to the `<p>` along with some options:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p transition:fly="{{ y: 200, duration: 2000 }}">
|
||||||
|
Flies in and out
|
||||||
|
</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the transition is *reversible* — if you toggle the checkbox while the transition is ongoing, it transitions from the current point, rather than the beginning or the end.
|
@ -0,0 +1,15 @@
|
|||||||
|
<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 }}">
|
||||||
|
Flies in and out
|
||||||
|
</p>
|
||||||
|
{/if}
|
@ -0,0 +1,15 @@
|
|||||||
|
<script>
|
||||||
|
import { fade, fly } 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, fades out
|
||||||
|
</p>
|
||||||
|
{/if}
|
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
title: In and out
|
||||||
|
---
|
||||||
|
|
||||||
|
Instead of the `transition` directive, an element can have an `in` or an `out` directive, or both together:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p in:fly="{{ y: 200, duration: 2000 }}" out:fade>
|
||||||
|
Flies in, fades out
|
||||||
|
</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case, the transitions are *not* reversed.
|
@ -0,0 +1,38 @@
|
|||||||
|
<script>
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
|
let visible = true;
|
||||||
|
|
||||||
|
function spin(node, { duration }) {
|
||||||
|
return {
|
||||||
|
duration,
|
||||||
|
css: 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}
|
@ -0,0 +1,49 @@
|
|||||||
|
<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}
|
@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
title: Custom CSS transitions
|
||||||
|
---
|
||||||
|
|
||||||
|
The `svelte/transition` module has a handful of built-in transitions, but it's very easy to create your own. By way of example, this is the source of the `fade` transition:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function fade(node, {
|
||||||
|
delay = 0,
|
||||||
|
duration = 400
|
||||||
|
}) {
|
||||||
|
const o = +getComputedStyle(node).opacity;
|
||||||
|
|
||||||
|
return {
|
||||||
|
delay,
|
||||||
|
duration,
|
||||||
|
css: t => `opacity: ${t * o}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The function takes two arguments — the node to which the transition is applied, and any parameters that were passed in — and returns a transition object which can have the following properties:
|
||||||
|
|
||||||
|
* `delay` — milliseconds before the transition begins
|
||||||
|
* `duration` — length of the transition in milliseconds
|
||||||
|
* `easing` — a `p => t` easing function (see the chapter on [tweening](tutorial/tweened))
|
||||||
|
* `css` — a `(t, u) => css` function, where `u === 1 - t`
|
||||||
|
* `tick` — a `(t, u) => {...}` function that has some effect on the node
|
||||||
|
|
||||||
|
The `t` value is `0` at the beginning of an intro or the end of an outro, and `1` at the end of an intro or beginning of an outro.
|
||||||
|
|
||||||
|
Most of the time you should return the `css` property and *not* the `tick` property, as CSS animations run off the main thread to prevent jank where possible. Svelte 'simulates' the transition and constructs a CSS animation, then lets it run.
|
||||||
|
|
||||||
|
For example the `fade` transition generates a CSS animation somewhat like this:
|
||||||
|
|
||||||
|
```css
|
||||||
|
0% { opacity: 0 }
|
||||||
|
10% { opacity: 0.1 }
|
||||||
|
20% { opacity: 0.2 }
|
||||||
|
/* ... */
|
||||||
|
100% { opacity: 1 }
|
||||||
|
```
|
||||||
|
|
||||||
|
We can get a lot more creative though. Let's make something truly gratuitous:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<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>
|
||||||
|
```
|
||||||
|
|
||||||
|
Remember: with great power comes great responsibility.
|
@ -0,0 +1,22 @@
|
|||||||
|
<script>
|
||||||
|
let visible = false;
|
||||||
|
|
||||||
|
function typewriter(node, { speed = 50 }) {
|
||||||
|
// implementation goes here
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</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}
|
@ -0,0 +1,36 @@
|
|||||||
|
<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 single 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}
|
@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
title: Custom JS transitions
|
||||||
|
---
|
||||||
|
|
||||||
|
While you should generally use CSS for transitions as much as possible, there are some effects that can't be achieved without JavaScript, such as a typewriter effect:
|
||||||
|
|
||||||
|
```js
|
||||||
|
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 single 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
@ -0,0 +1,19 @@
|
|||||||
|
<script>
|
||||||
|
import { fly } from 'svelte/transition';
|
||||||
|
|
||||||
|
let visible = true;
|
||||||
|
let status = 'waiting...';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>status: {status}</p>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" bind:checked={visible}>
|
||||||
|
visible
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{#if visible}
|
||||||
|
<p transition:fly="{{ y: 200, duration: 2000 }}">
|
||||||
|
Flies in and out
|
||||||
|
</p>
|
||||||
|
{/if}
|
@ -0,0 +1,25 @@
|
|||||||
|
<script>
|
||||||
|
import { fly } from 'svelte/transition';
|
||||||
|
|
||||||
|
let visible = true;
|
||||||
|
let status = 'waiting...';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>status: {status}</p>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" bind:checked={visible}>
|
||||||
|
visible
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{#if visible}
|
||||||
|
<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>
|
||||||
|
{/if}
|
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
title: Transition events
|
||||||
|
---
|
||||||
|
|
||||||
|
It can be useful to know when transitions are beginning and ending. Svelte dispatches events that you can listen to like any other DOM event:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<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>
|
||||||
|
```
|
@ -0,0 +1,146 @@
|
|||||||
|
<script>
|
||||||
|
import { quintOut } from 'svelte/easing';
|
||||||
|
import crossfade from './crossfade.js'; // TODO put this in svelte/transition!
|
||||||
|
|
||||||
|
const { send, receive } = crossfade({
|
||||||
|
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 todos = [
|
||||||
|
{ id: 1, done: false, description: 'write some docs' },
|
||||||
|
{ id: 2, done: false, description: 'start writing JSConf talk' },
|
||||||
|
{ id: 3, done: true, description: 'buy some milk' },
|
||||||
|
{ id: 4, done: false, description: 'mow the lawn' },
|
||||||
|
{ id: 5, done: false, description: 'feed the turtle' },
|
||||||
|
{ id: 6, done: false, description: 'fix some bugs' },
|
||||||
|
];
|
||||||
|
|
||||||
|
let uid = todos.length + 1;
|
||||||
|
|
||||||
|
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 handleKeydown(event) {
|
||||||
|
if (event.which === 13) {
|
||||||
|
addTodo(event.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.new-todo {
|
||||||
|
font-size: 1.4em;
|
||||||
|
width: 100%;
|
||||||
|
margin: 2em 0 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board {
|
||||||
|
max-width: 36em;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left, .right {
|
||||||
|
float: left;
|
||||||
|
width: 50%;
|
||||||
|
padding: 0 1em 0 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: 200;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
display: block;
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0.5em;
|
||||||
|
margin: 0 auto 0.5em auto;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: #eee;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input { margin: 0 }
|
||||||
|
|
||||||
|
.right label {
|
||||||
|
background-color: rgb(180,240,100);
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
float: right;
|
||||||
|
height: 1em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 0.5em;
|
||||||
|
line-height: 1;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
color: rgb(170,30,30);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
label:hover button {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class='board'>
|
||||||
|
<input class="new-todo" placeholder="what needs to be done?" on:enter={add}>
|
||||||
|
|
||||||
|
<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 bind:checked={todo.done}>
|
||||||
|
{todo.description}
|
||||||
|
<button on:click="{() => remove(todo)}">x</button>
|
||||||
|
</label>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='right'>
|
||||||
|
<h2>done</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 bind:checked={todo.done}>
|
||||||
|
{todo.description}
|
||||||
|
<button on:click="{() => remove(todo)}">x</button>
|
||||||
|
</label>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,65 @@
|
|||||||
|
import { quintOut } from 'svelte/easing';
|
||||||
|
|
||||||
|
export default function crossfade({ send, receive, fallback }) {
|
||||||
|
let requested = new Map();
|
||||||
|
let provided = new Map();
|
||||||
|
|
||||||
|
function crossfade(from, node) {
|
||||||
|
const to = node.getBoundingClientRect();
|
||||||
|
const dx = from.left - to.left;
|
||||||
|
const dy = from.top - to.top;
|
||||||
|
|
||||||
|
const style = getComputedStyle(node);
|
||||||
|
const transform = style.transform === 'none' ? '' : style.transform;
|
||||||
|
|
||||||
|
return {
|
||||||
|
duration: 400,
|
||||||
|
easing: quintOut,
|
||||||
|
css: (t, u) => `
|
||||||
|
opacity: ${t};
|
||||||
|
transform: ${transform} translate(${u * dx}px,${u * dy}px);
|
||||||
|
`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
send(node, params) {
|
||||||
|
provided.set(params.key, {
|
||||||
|
rect: node.getBoundingClientRect()
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (requested.has(params.key)) {
|
||||||
|
const { rect } = requested.get(params.key);
|
||||||
|
requested.delete(params.key);
|
||||||
|
|
||||||
|
return crossfade(rect, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the node is disappearing altogether
|
||||||
|
// (i.e. wasn't claimed by the other list)
|
||||||
|
// then we need to supply an outro
|
||||||
|
provided.delete(params.key);
|
||||||
|
return fallback(node, params);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
receive(node, params) {
|
||||||
|
requested.set(params.key, {
|
||||||
|
rect: node.getBoundingClientRect()
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (provided.has(params.key)) {
|
||||||
|
const { rect } = provided.get(params.key);
|
||||||
|
provided.delete(params.key);
|
||||||
|
|
||||||
|
return crossfade(rect, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
requested.delete(params.key);
|
||||||
|
return fallback(node, params);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,146 @@
|
|||||||
|
<script>
|
||||||
|
import { quintOut } from 'svelte/easing';
|
||||||
|
import crossfade from './crossfade.js'; // TODO put this in svelte/transition!
|
||||||
|
|
||||||
|
const { send, receive } = crossfade({
|
||||||
|
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 todos = [
|
||||||
|
{ id: 1, done: false, description: 'write some docs' },
|
||||||
|
{ id: 2, done: false, description: 'start writing JSConf talk' },
|
||||||
|
{ id: 3, done: true, description: 'buy some milk' },
|
||||||
|
{ id: 4, done: false, description: 'mow the lawn' },
|
||||||
|
{ id: 5, done: false, description: 'feed the turtle' },
|
||||||
|
{ id: 6, done: false, description: 'fix some bugs' },
|
||||||
|
];
|
||||||
|
|
||||||
|
let uid = todos.length + 1;
|
||||||
|
|
||||||
|
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 handleKeydown(event) {
|
||||||
|
if (event.which === 13) {
|
||||||
|
addTodo(event.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.new-todo {
|
||||||
|
font-size: 1.4em;
|
||||||
|
width: 100%;
|
||||||
|
margin: 2em 0 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board {
|
||||||
|
max-width: 36em;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left, .right {
|
||||||
|
float: left;
|
||||||
|
width: 50%;
|
||||||
|
padding: 0 1em 0 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: 200;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
display: block;
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0.5em;
|
||||||
|
margin: 0 auto 0.5em auto;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: #eee;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input { margin: 0 }
|
||||||
|
|
||||||
|
.right label {
|
||||||
|
background-color: rgb(180,240,100);
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
float: right;
|
||||||
|
height: 1em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 0.5em;
|
||||||
|
line-height: 1;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
color: rgb(170,30,30);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
label:hover button {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class='board'>
|
||||||
|
<input class="new-todo" placeholder="what needs to be done?" on:enter={add}>
|
||||||
|
|
||||||
|
<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 bind:checked={todo.done}>
|
||||||
|
{todo.description}
|
||||||
|
<button on:click="{() => remove(todo)}">x</button>
|
||||||
|
</label>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='right'>
|
||||||
|
<h2>done</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 bind:checked={todo.done}>
|
||||||
|
{todo.description}
|
||||||
|
<button on:click="{() => remove(todo)}">x</button>
|
||||||
|
</label>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,65 @@
|
|||||||
|
import { quintOut } from 'svelte/easing';
|
||||||
|
|
||||||
|
export default function crossfade({ send, receive, fallback }) {
|
||||||
|
let requested = new Map();
|
||||||
|
let provided = new Map();
|
||||||
|
|
||||||
|
function crossfade(from, node) {
|
||||||
|
const to = node.getBoundingClientRect();
|
||||||
|
const dx = from.left - to.left;
|
||||||
|
const dy = from.top - to.top;
|
||||||
|
|
||||||
|
const style = getComputedStyle(node);
|
||||||
|
const transform = style.transform === 'none' ? '' : style.transform;
|
||||||
|
|
||||||
|
return {
|
||||||
|
duration: 400,
|
||||||
|
easing: quintOut,
|
||||||
|
css: (t, u) => `
|
||||||
|
opacity: ${t};
|
||||||
|
transform: ${transform} translate(${u * dx}px,${u * dy}px);
|
||||||
|
`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
send(node, params) {
|
||||||
|
provided.set(params.key, {
|
||||||
|
rect: node.getBoundingClientRect()
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (requested.has(params.key)) {
|
||||||
|
const { rect } = requested.get(params.key);
|
||||||
|
requested.delete(params.key);
|
||||||
|
|
||||||
|
return crossfade(rect, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the node is disappearing altogether
|
||||||
|
// (i.e. wasn't claimed by the other list)
|
||||||
|
// then we need to supply an outro
|
||||||
|
provided.delete(params.key);
|
||||||
|
return fallback(node, params);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
receive(node, params) {
|
||||||
|
requested.set(params.key, {
|
||||||
|
rect: node.getBoundingClientRect()
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (provided.has(params.key)) {
|
||||||
|
const { rect } = provided.get(params.key);
|
||||||
|
provided.delete(params.key);
|
||||||
|
|
||||||
|
return crossfade(rect, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
requested.delete(params.key);
|
||||||
|
return fallback(node, params);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: Deferred transitions
|
||||||
|
---
|
||||||
|
|
||||||
|
A particularly powerful feature of Svelte's transition engine is the ability to *defer* transitions, so that they can be coordinated between multiple elements.
|
||||||
|
|
||||||
|
TODO https://github.com/sveltejs/svelte/issues/2159
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"title": "Transitions"
|
||||||
|
}
|
Loading…
Reference in new issue