update deferred transitions example

pull/3402/head
Richard Harris 5 years ago
parent 8669c76921
commit 4f26363fe0

@ -1,140 +1,153 @@
<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}
`
};
}
import { crossfade, scale } from 'svelte/transition';
import images from './images.js';
const [send, receive] = crossfade({
duration: 200,
fallback: scale
});
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
};
let selected = null;
let loading = null;
todos = [todo, ...todos];
input.value = '';
}
const ASSETS = `https://svelte-assets.surge.sh/crossfade`;
function remove(todo) {
todos = todos.filter(t => t !== todo);
}
const load = image => {
const timeout = setTimeout(() => loading = image, 100);
const img = new Image();
img.onload = () => {
selected = image;
clearTimeout(timeout);
loading = null;
};
img.src = `${ASSETS}/${image.id}.jpg`;
};
</script>
<div class="container">
<div class="phone">
<h1>Photo gallery</h1>
<div class="grid">
{#each images as image}
<div class="square">
{#if selected !== image}
<button
style="background-color: {image.color};"
on:click="{() => load(image)}"
in:receive={{key:image.id}}
out:send={{key:image.id}}
>{loading === image ? '...' : image.id}</button>
{/if}
</div>
{/each}
</div>
{#if selected}
{#await selected then d}
<div class="photo" in:receive={{key:d.id}} out:send={{key:d.id}}>
<img
alt={d.alt}
src="{ASSETS}/{d.id}.jpg"
on:click="{() => selected = null}"
>
<p class='credit'>
<a target="_blank" href="https://www.flickr.com/photos/{d.path}">via Flickr</a> &ndash;
<a target="_blank" href={d.license.url}>{d.license.name}</a>
</p>
</div>
{/await}
{/if}
</div>
</div>
<style>
.new-todo {
font-size: 1.4em;
.container {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
margin: 2em 0 1em 0;
height: 100%;
top: 0;
left: 0;
}
.board {
max-width: 36em;
margin: 0 auto;
.phone {
position: relative;
display: flex;
flex-direction: column;
width: 52vmin;
height: 76vmin;
border: 2vmin solid #ccc;
border-bottom-width: 10vmin;
padding: 3vmin;
border-radius: 2vmin;
}
.left, .right {
float: left;
width: 50%;
padding: 0 1em 0 0;
box-sizing: border-box;
h1 {
font-weight: 300;
text-transform: uppercase;
font-size: 5vmin;
margin: 0.2em 0 0.5em 0;
}
h2 {
font-size: 2em;
font-weight: 200;
user-select: none;
.grid {
display: grid;
flex: 1;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(4, 1fr);
grid-gap: 2vmin;
}
label {
button {
width: 100%;
height: 100%;
color: white;
font-size: 5vmin;
border: none;
margin: 0;
will-change: transform;
}
.photo, img {
position: absolute;
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;
width: 100%;
height: 100%;
overflow: hidden;
}
input { margin: 0 }
.right label {
background-color: rgb(180,240,100);
.photo {
display: flex;
align-items: stretch;
justify-content: flex-end;
flex-direction: column;
will-change: transform;
}
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;
img {
object-fit: cover;
cursor: pointer;
}
label:hover button {
opacity: 1;
.credit {
text-align: right;
font-size: 2.5vmin;
padding: 1em;
margin: 0;
color: white;
font-weight: bold;
opacity: 0.6;
background: rgba(0,0,0,0.4);
}
</style>
<div class='board'>
<input class="new-todo" placeholder="what needs to be done?" on:keydown="{event => event.which === 13 && add(event.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 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>
.credit a, .credit a:visited {
color: white;
}
</style>

@ -1,65 +0,0 @@
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,102 @@
const BY = {
name: 'CC BY 2.0',
url: 'https://creativecommons.org/licenses/by/2.0/'
};
const BY_SA = {
name: 'CC BY-SA 2.0',
url: 'https://creativecommons.org/licenses/by-sa/2.0/'
};
const BY_ND = {
name: 'CC BY-ND 2.0',
url: 'https://creativecommons.org/licenses/by-nd/2.0/'
};
// via http://labs.tineye.com/multicolr
export default [
{
color: '#001f3f',
id: '1',
alt: 'Crepuscular rays',
path: '43428526@N03/7863279376',
license: BY
},
{
color: '#0074D9',
id: '2',
alt: 'Lapland winter scene',
path: '25507134@N00/6527537485',
license: BY
},
{
color: '#7FDBFF',
id: '3',
alt: 'Jellyfish',
path: '37707866@N00/3354331318',
license: BY
},
{
color: '#39CCCC',
id: '4',
alt: 'A man scuba diving',
path: '32751486@N00/4608886209',
license: BY_SA
},
{
color: '#3D9970',
id: '5',
alt: 'Underwater scene',
path: '25483059@N08/5548569010',
license: BY
},
{
color: '#2ECC40',
id: '6',
alt: 'Ferns',
path: '8404611@N06/2447470760',
license: BY
},
{
color: '#01FF70',
id: '7',
alt: 'Posters in a bar',
path: '33917831@N00/114428206',
license: BY_SA
},
{
color: '#FFDC00',
id: '8',
alt: 'Daffodil',
path: '46417125@N04/4818617089',
license: BY_ND
},
{
color: '#FF851B',
id: '9',
alt: 'Dust storm in Sydney',
path: '56068058@N00/3945496657',
license: BY
},
{
color: '#FF4136',
id: '10',
alt: 'Postbox',
path: '31883499@N05/4216820032',
license: BY
},
{
color: '#85144b',
id: '11',
alt: 'Fireworks',
path: '8484971@N07/2625506561',
license: BY_ND
},
{
color: '#B10DC9',
id: '12',
alt: 'The Stereophonics',
path: '58028312@N00/5385464371',
license: BY_ND
}
];

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Loading…
Cancel
Save