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