mirror of https://github.com/sveltejs/svelte
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> –
|
||||
<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
|
||||
}
|
||||
];
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 8.8 KiB |
Loading…
Reference in new issue