mirror of https://github.com/sveltejs/svelte
parent
291703d291
commit
e9f0ffbd29
@ -1,35 +1,184 @@
|
|||||||
<script>
|
<script>
|
||||||
import { spring } from 'svelte/motion';
|
import { spring } from "svelte/motion";
|
||||||
|
import { framerate } from "svelte/environment";
|
||||||
|
import { derived } from "svelte/store";
|
||||||
|
|
||||||
let coords = spring({ x: 50, y: 50 }, {
|
const s = spring(50);
|
||||||
stiffness: 0.1,
|
let prev_time = now();
|
||||||
damping: 0.25
|
let prev_value = 50;
|
||||||
});
|
const velocity = derived(
|
||||||
|
s,
|
||||||
|
(v) =>
|
||||||
|
(-prev_value + (prev_value = v)) / (-prev_time + (prev_time = now())),
|
||||||
|
0.0
|
||||||
|
);
|
||||||
|
let l_time;
|
||||||
|
$: canvas && Draw($velocity, $s);
|
||||||
|
let { mass, stiffness, damping, soft } = s;
|
||||||
|
$: s.soft = soft;
|
||||||
|
$: s.mass = mass;
|
||||||
|
$: s.stiffness = stiffness;
|
||||||
|
$: s.damping = damping;
|
||||||
|
let solver;
|
||||||
|
|
||||||
let size = spring(10);
|
function solve_spring(target, prev_velocity) {
|
||||||
|
const target_ = target;
|
||||||
|
const delta = target - $s;
|
||||||
|
if (soft || 1 <= damping / (2.0 * Math.sqrt(stiffness * mass))) {
|
||||||
|
const angular_frequency = -Math.sqrt(stiffness / mass);
|
||||||
|
solver = (t) =>
|
||||||
|
target_ -
|
||||||
|
(delta + t * (-angular_frequency * delta - prev_velocity)) *
|
||||||
|
Math.exp(t * angular_frequency);
|
||||||
|
} else {
|
||||||
|
const damping_frequency = Math.sqrt(
|
||||||
|
4.0 * mass * stiffness - damping ** 2
|
||||||
|
);
|
||||||
|
const leftover =
|
||||||
|
(damping * delta - 2.0 * mass * prev_velocity) / damping_frequency;
|
||||||
|
const dfm = (0.5 * damping_frequency) / mass;
|
||||||
|
const dm = -(0.5 * damping) / mass;
|
||||||
|
let f = 0.0;
|
||||||
|
solver = (t) =>
|
||||||
|
target_ -
|
||||||
|
(Math.cos((f = t * dfm)) * delta + Math.sin(f) * leftover) *
|
||||||
|
Math.exp(t * dm);
|
||||||
|
}
|
||||||
|
reset_time = now();
|
||||||
|
s.set((target__ = target));
|
||||||
|
}
|
||||||
|
let target__ = 50;
|
||||||
|
let canvas;
|
||||||
|
const start_time = now();
|
||||||
|
let reset_time = start_time;
|
||||||
|
const canvas_history = [];
|
||||||
|
let max_x, min_x, max_y, min_y, canvas_width, canvas_height;
|
||||||
|
let step, length;
|
||||||
|
let last_index;
|
||||||
|
let ctx;
|
||||||
|
const XC = (x) => ((x - min_x) / (max_x - min_x)) * canvas_width;
|
||||||
|
const YC = (y) =>
|
||||||
|
canvas_height - ((y - min_y) / (max_y - min_y)) * canvas_height;
|
||||||
|
const get_index = (i = 0) =>
|
||||||
|
(i + Math.floor((prev_time - start_time) / framerate)) % length;
|
||||||
|
function Draw() {
|
||||||
|
if (!step) {
|
||||||
|
max_y = canvas_height / 2;
|
||||||
|
min_y = -canvas_height / 2;
|
||||||
|
max_x = canvas_width / 1000;
|
||||||
|
min_x = -max_x;
|
||||||
|
step = framerate / 1000; //framerate / canvas_width;
|
||||||
|
length = Math.floor(max_x / step);
|
||||||
|
canvas_history.length = length;
|
||||||
|
canvas_history.fill(0);
|
||||||
|
ctx = canvas.getContext("2d");
|
||||||
|
}
|
||||||
|
ctx.lineWidth = 12;
|
||||||
|
let offset = (prev_time - reset_time) / 1000;
|
||||||
|
const start_index = get_index(0);
|
||||||
|
if (last_index === (last_index = start_index) || !solver) return;
|
||||||
|
ctx.clearRect(0, 0, canvas_width, canvas_height);
|
||||||
|
ctx.beginPath();
|
||||||
|
let x = min_x,
|
||||||
|
y = 0,
|
||||||
|
i = start_index + 1;
|
||||||
|
ctx.moveTo(XC(x), YC(y));
|
||||||
|
for (x += step; i <= length; i++)
|
||||||
|
ctx.lineTo(XC(x), YC(canvas_history[i])), (x += step);
|
||||||
|
for (i = 0; i < start_index; i++)
|
||||||
|
ctx.lineTo(XC(x), YC(canvas_history[i])), (x += step);
|
||||||
|
ctx.lineTo(
|
||||||
|
XC(x),
|
||||||
|
YC((canvas_history[start_index] = canvas_height / 2 - prev_value))
|
||||||
|
);
|
||||||
|
if (Math.abs(prev_value - solver(offset)) > 20) {
|
||||||
|
solve_spring(target__, $velocity);
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
x = 0;
|
||||||
|
while (x <= max_x)
|
||||||
|
ctx.lineTo(
|
||||||
|
XC((x += step)),
|
||||||
|
canvas_height / 2 - YC(solver(x + step + offset))
|
||||||
|
);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
svg { width: 100%; height: 100%; margin: -8px }
|
:global(body) {
|
||||||
circle { fill: #ff3e00 }
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
svg {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
circle {
|
||||||
|
fill: tomato;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<svelte:window
|
||||||
|
on:mousemove={(e) => solve_spring(e.clientY, $velocity)}
|
||||||
|
bind:innerWidth={canvas_width}
|
||||||
|
bind:innerHeight={canvas_height} />
|
||||||
<div style="position: absolute; right: 1em;">
|
<div style="position: absolute; right: 1em;">
|
||||||
<label>
|
<label>
|
||||||
<h3>stiffness ({coords.stiffness})</h3>
|
<h3>velocity</h3>
|
||||||
<input bind:value={coords.stiffness} type="range" min="0" max="1" step="0.01">
|
<progress
|
||||||
</label>
|
value={!Number.isNaN($velocity) && Number.isFinite($velocity) && $velocity + 10}
|
||||||
|
min={0}
|
||||||
|
max={20} />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<h3>speed</h3>
|
||||||
|
<progress
|
||||||
|
value={!Number.isNaN($velocity) && Number.isFinite($velocity) && Math.abs($velocity)}
|
||||||
|
min={0}
|
||||||
|
max={20} />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<h3>y {$s.toFixed(0)}</h3>
|
||||||
|
<progress value={!Number.isNaN($s) && $s} min={0} max={1000} />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<h3>target {target__}</h3>
|
||||||
|
<progress value={!Number.isNaN(target__) && target__} min={0} max={1000} />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<h3>stiffness ({stiffness})</h3>
|
||||||
|
<input bind:value={stiffness} type="range" min="10" max="200" step="0.01" />
|
||||||
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
<h3>damping ({coords.damping})</h3>
|
<h3>damping ({damping})</h3>
|
||||||
<input bind:value={coords.damping} type="range" min="0" max="1" step="0.01">
|
<input bind:value={damping} type="range" min="0.1" max="20" step="0.01" />
|
||||||
</label>
|
</label>
|
||||||
|
<label>
|
||||||
|
<h3>mass ({mass})</h3>
|
||||||
|
<input bind:value={mass} type="range" min="0.1" max="20" step="0.01" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<h3>
|
||||||
|
soft
|
||||||
|
<input bind:checked={soft} type="checkbox" />
|
||||||
|
</h3>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<svg>
|
||||||
<svg
|
<circle
|
||||||
on:mousemove="{e => coords.set({ x: e.clientX, y: e.clientY })}"
|
cx={-15+canvas_width / 2}
|
||||||
on:mousedown="{() => size.set(30)}"
|
cy={$s}
|
||||||
on:mouseup="{() => size.set(10)}"
|
r={30} />
|
||||||
>
|
</svg>
|
||||||
<circle cx={$coords.x} cy={$coords.y} r={$size}/>
|
<canvas bind:this={canvas} width={canvas_width} height={canvas_height} />
|
||||||
</svg>
|
|
||||||
|
@ -1,147 +1,159 @@
|
|||||||
<script>
|
<script>
|
||||||
import { quintOut } from 'svelte/easing';
|
import { quintOut } from "svelte/easing";
|
||||||
import { crossfade } from 'svelte/transition';
|
import { crossfade } from "svelte/transition";
|
||||||
import { flip } from 'svelte/animate';
|
import { flip } from "svelte/animate";
|
||||||
|
|
||||||
const [send, receive] = crossfade({
|
const [send, receive] = crossfade({
|
||||||
fallback(node, params) {
|
fallback(node, params) {
|
||||||
const style = getComputedStyle(node);
|
const style = getComputedStyle(node);
|
||||||
const transform = style.transform === 'none' ? '' : style.transform;
|
const transform = style.transform === "none" ? "" : style.transform;
|
||||||
|
return {
|
||||||
return {
|
duration: 600,
|
||||||
duration: 600,
|
easing: quintOut,
|
||||||
easing: quintOut,
|
css: (t) => `
|
||||||
css: t => `
|
|
||||||
transform: ${transform} scale(${t});
|
transform: ${transform} scale(${t});
|
||||||
opacity: ${t}
|
opacity: ${t}
|
||||||
`
|
`,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let todos = [
|
let todos = [
|
||||||
{ id: 1, done: false, description: 'write some docs' },
|
{ key: 1, done: false, description: "write some docs" },
|
||||||
{ id: 2, done: false, description: 'start writing JSConf talk' },
|
{ key: 2, done: false, description: "start writing JSConf talk" },
|
||||||
{ id: 3, done: true, description: 'buy some milk' },
|
{ key: 3, done: true, description: "buy some milk" },
|
||||||
{ id: 4, done: false, description: 'mow the lawn' },
|
{ key: 4, done: false, description: "mow the lawn" },
|
||||||
{ id: 5, done: false, description: 'feed the turtle' },
|
{ key: 5, done: false, description: "feed the turtle" },
|
||||||
{ id: 6, done: false, description: 'fix some bugs' },
|
{ key: 6, done: false, description: "fix some bugs" },
|
||||||
];
|
];
|
||||||
|
|
||||||
let uid = todos.length + 1;
|
let ukey = todos.length + 1;
|
||||||
|
|
||||||
function add(input) {
|
function add(input) {
|
||||||
const todo = {
|
todos = [{ key: ukey++, done: false, description: input.value }, ...todos];
|
||||||
id: uid++,
|
input.value = "";
|
||||||
done: false,
|
}
|
||||||
description: input.value
|
|
||||||
};
|
function remove(key1) {
|
||||||
|
todos = todos.filter(({ key }) => key !== key1);
|
||||||
todos = [todo, ...todos];
|
}
|
||||||
input.value = '';
|
$: list = todos.reduce((prev, td) => (prev[+td.done].push(td), prev), [
|
||||||
}
|
[],
|
||||||
|
[],
|
||||||
function remove(todo) {
|
]);
|
||||||
todos = todos.filter(t => t !== todo);
|
const animating = new Map();
|
||||||
}
|
let lastShuffle = Date.now();
|
||||||
|
let t = 0;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.new-todo {
|
.new-todo {
|
||||||
font-size: 1.4em;
|
font-size: 1.4em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 2em 0 1em 0;
|
margin: 2em 0 1em 0;
|
||||||
}
|
}
|
||||||
|
.board {
|
||||||
.board {
|
max-width: 36em;
|
||||||
max-width: 36em;
|
margin: 0 auto;
|
||||||
margin: 0 auto;
|
}
|
||||||
}
|
.left,
|
||||||
|
.right {
|
||||||
.left, .right {
|
float: left;
|
||||||
float: left;
|
width: 50%;
|
||||||
width: 50%;
|
padding: 0 1em 0 0;
|
||||||
padding: 0 1em 0 0;
|
box-sizing: border-box;
|
||||||
box-sizing: border-box;
|
}
|
||||||
}
|
h2 {
|
||||||
|
font-size: 2em;
|
||||||
h2 {
|
font-weight: 200;
|
||||||
font-size: 2em;
|
user-select: none;
|
||||||
font-weight: 200;
|
}
|
||||||
user-select: none;
|
label {
|
||||||
}
|
top: 0;
|
||||||
|
left: 0;
|
||||||
label {
|
display: block;
|
||||||
top: 0;
|
font-size: 1em;
|
||||||
left: 0;
|
line-height: 1;
|
||||||
display: block;
|
padding: 0.5em;
|
||||||
font-size: 1em;
|
margin: 0 auto 0.5em auto;
|
||||||
line-height: 1;
|
border-radius: 2px;
|
||||||
padding: 0.5em;
|
background-color: #eee;
|
||||||
margin: 0 auto 0.5em auto;
|
user-select: none;
|
||||||
border-radius: 2px;
|
}
|
||||||
background-color: #eee;
|
input {
|
||||||
user-select: none;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
.right label {
|
||||||
input { margin: 0 }
|
background-color: rgb(180, 240, 100);
|
||||||
|
}
|
||||||
.right label {
|
button {
|
||||||
background-color: rgb(180,240,100);
|
float: right;
|
||||||
}
|
height: 1em;
|
||||||
|
box-sizing: border-box;
|
||||||
button {
|
padding: 0 0.5em;
|
||||||
float: right;
|
line-height: 1;
|
||||||
height: 1em;
|
background-color: transparent;
|
||||||
box-sizing: border-box;
|
border: none;
|
||||||
padding: 0 0.5em;
|
color: rgb(170, 30, 30);
|
||||||
line-height: 1;
|
opacity: 0;
|
||||||
background-color: transparent;
|
transition: opacity 0.2s;
|
||||||
border: none;
|
}
|
||||||
color: rgb(170,30,30);
|
label:hover button {
|
||||||
opacity: 0;
|
opacity: 1;
|
||||||
transition: opacity 0.2s;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
label:hover button {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class='board'>
|
<svelte:window
|
||||||
<input
|
on:keydown={(e) => {
|
||||||
class="new-todo"
|
let [{ length: a }, { length: b }] = list;
|
||||||
placeholder="what needs to be done?"
|
if (e.keyCode == 32) {
|
||||||
on:keydown="{event => event.which === 13 && add(event.target)}"
|
if (lastShuffle > Date.now() - 100) {
|
||||||
>
|
return;
|
||||||
|
}
|
||||||
<div class='left'>
|
lastShuffle = Date.now();
|
||||||
<h2>todo</h2>
|
e = 0;
|
||||||
{#each todos.filter(t => !t.done) as todo (todo.id)}
|
if (!a) e += 1;
|
||||||
<label
|
if (!b) e -= 2;
|
||||||
in:receive="{{key: todo.id}}"
|
if (!e) e += t = 0.5 + (b - a) / (a + b);
|
||||||
out:send="{{key: todo.id}}"
|
s: if (~e) {
|
||||||
animate:flip
|
a += b;
|
||||||
>
|
let w = 1;
|
||||||
<input type=checkbox bind:checked={todo.done}>
|
while (animating.get((e = (e = list[(e = +(Math.random() + e > 0.5))])[Math.floor(Math.random() * e.length)]).key)) if (w++ > a) break s;
|
||||||
{todo.description}
|
e.done = !e.done;
|
||||||
<button on:click="{() => remove(todo)}">x</button>
|
list = list;
|
||||||
</label>
|
}
|
||||||
{/each}
|
}
|
||||||
</div>
|
}} />
|
||||||
|
<div class="board">
|
||||||
<div class='right'>
|
<input
|
||||||
<h2>done</h2>
|
class="new-todo"
|
||||||
{#each todos.filter(t => t.done) as todo (todo.id)}
|
placeholder="what needs to be done?"
|
||||||
<label
|
on:keydown={(event) => void (event.key === 'Enter' && add(event.target))} />
|
||||||
in:receive="{{key: todo.id}}"
|
{#each list as todo, i (i)}
|
||||||
out:send="{{key: todo.id}}"
|
<div class={!i ? 'left' : 'right'}>
|
||||||
animate:flip
|
<h2>{!i ? 'todo' : 'done'}</h2>
|
||||||
>
|
{#each todo as { key, description, done: checked } (key)}
|
||||||
<input type=checkbox bind:checked={todo.done}>
|
<label
|
||||||
{todo.description}
|
in:receive={{ key }}
|
||||||
<button on:click="{() => remove(todo)}">x</button>
|
out:send={{ key }}
|
||||||
</label>
|
animate:flip
|
||||||
{/each}
|
on:outrostart={() => {
|
||||||
</div>
|
animating.set(key, true);
|
||||||
|
}}
|
||||||
|
on:outroend={() => {
|
||||||
|
animating.set(key, false);
|
||||||
|
}}
|
||||||
|
on:introstart={() => {
|
||||||
|
animating.set(key, true);
|
||||||
|
}}
|
||||||
|
on:introend={() => {
|
||||||
|
animating.set(key, false);
|
||||||
|
}}>
|
||||||
|
<input type="checkbox" bind:checked />
|
||||||
|
{description}
|
||||||
|
<button on:click={remove.bind(null, key)}>x</button>
|
||||||
|
</label>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,106 +1,107 @@
|
|||||||
<script>
|
<script>
|
||||||
import { interpolateString as interpolate } from 'd3-interpolate';
|
import { strings } from "svelte/interpolate";
|
||||||
import { tweened } from 'svelte/motion';
|
import { tweened } from "svelte/motion";
|
||||||
|
import { cubicBezier, easeOut } from "svelte/easing";
|
||||||
import Grid from './Grid.svelte';
|
import { onDestroy } from "svelte";
|
||||||
import Controls from './Controls.svelte';
|
|
||||||
|
import Grid from "./Grid.svelte";
|
||||||
import { eases, types } from './eases.js';
|
import Controls from "./Controls.svelte";
|
||||||
|
|
||||||
let current_type = 'In';
|
import { eases, types, generate } from "./eases.js";
|
||||||
let current_ease = 'sine';
|
|
||||||
let duration = 2000;
|
let current_type = "In";
|
||||||
let current = eases.get(current_ease)[current_type];
|
let current_ease = "sine";
|
||||||
let playing = false;
|
let duration = 2000;
|
||||||
let width;
|
let current = eases.get(current_ease)[current_type];
|
||||||
|
let playing = false;
|
||||||
const ease_path = tweened(current.shape, { interpolate });
|
let width;
|
||||||
const time = tweened(0);
|
|
||||||
const value = tweened(1000);
|
const ease_path = tweened(current.shape, {
|
||||||
|
interpolate: strings,
|
||||||
async function runAnimations() {
|
easing: easeOut,
|
||||||
playing = true;
|
});
|
||||||
|
const time = tweened(0);
|
||||||
value.set(1000, {duration: 0});
|
const value = tweened(1000);
|
||||||
time.set(0, {duration: 0});
|
|
||||||
|
async function runAnimations() {
|
||||||
await ease_path.set(current.shape);
|
playing = true;
|
||||||
await Promise.all([
|
value.setImmediate(1000);
|
||||||
time.set(1000, {duration, easing: x => x}),
|
time.setImmediate(0);
|
||||||
value.set(0, {duration, easing: current.fn})
|
ease_path.set(is_custom ? generate(current_bezier) : current.shape);
|
||||||
]);
|
time.set(1000, { duration });
|
||||||
|
value.set(0, { duration, easing: is_custom ? current_bezier : current.fn });
|
||||||
playing = false;
|
}
|
||||||
}
|
|
||||||
|
onDestroy(time.onRest(() => (playing = false)));
|
||||||
$: current = eases.get(current_ease)[current_type];
|
$: is_custom = current_ease.includes("Bezier");
|
||||||
$: current && runAnimations();
|
$: current = !is_custom && eases.get(current_ease)[current_type];
|
||||||
|
$: current_bezier, current, runAnimations();
|
||||||
|
let eq;
|
||||||
|
let x1, x2, y1, y2;
|
||||||
|
$: current_bezier = bezier && cubicBezier(...bezier);
|
||||||
|
let bezier;
|
||||||
|
let div;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.easing-vis {
|
.easing-vis {
|
||||||
display: flex;
|
display: flex;
|
||||||
max-height: 95%;
|
max-height: 95%;
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border: 1px solid #333;
|
border: 1px solid #333;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
.svg1 {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0 20px 0 0;
|
margin: 0 20px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.graph {
|
.graph {
|
||||||
transform: translate(200px,400px)
|
transform: translate(200px, 400px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width:600px) {
|
@media (max-width: 600px) {
|
||||||
.easing-vis {
|
.easing-vis {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
max-height: calc(100% - 3rem);
|
max-height: calc(100% - 3rem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
circle {
|
||||||
|
z-index: 10;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div bind:offsetWidth={width} class="easing-vis">
|
<div bind:offsetWidth={width} bind:this={div} class="easing-vis">
|
||||||
<svg viewBox="0 0 1400 1802">
|
{#if is_custom}
|
||||||
<g class="canvas">
|
<div
|
||||||
<Grid x={$time} y={$value}/>
|
style="position:absolute;z-index:10;left:120px;top:150px;font-size:24px;">
|
||||||
<g class="graph">
|
cubicBezier({bezier})
|
||||||
<path
|
</div>
|
||||||
d={$ease_path}
|
{/if}
|
||||||
stroke="#333"
|
<svg class="svg1" viewBox="0 0 1400 1802">
|
||||||
stroke-width="2"
|
<g class="canvas">
|
||||||
fill="none"
|
<Grid {is_custom} bind:bezier x={$time} y={$value}>
|
||||||
/>
|
<g class="graph">
|
||||||
|
<path d={$ease_path} stroke="tomato" stroke-width="10" fill="none" />
|
||||||
<path d="M0,23.647C0,22.41 27.014,0.407 28.496,0.025C29.978,-0.357 69.188,3.744 70.104,4.744C71.02,5.745 71.02,41.499 70.104,42.5C69.188,43.501 29.978,47.601 28.496,47.219C27.014,46.837 0,24.884 0,23.647Z"
|
<circle cx={$time} cy={$value} r="15" fill="#333" />
|
||||||
fill="#ff3e00"
|
</g>
|
||||||
style="transform: translate(1060px, {($value - 24)}px)"
|
</Grid>
|
||||||
/>
|
</g>
|
||||||
|
</svg>
|
||||||
<circle
|
<Controls
|
||||||
cx="{$time}"
|
{is_custom}
|
||||||
cy="{$value}"
|
{eases}
|
||||||
r="15"
|
{types}
|
||||||
fill="#ff3e00"
|
{playing}
|
||||||
/>
|
{width}
|
||||||
</g>
|
{bezier}
|
||||||
</g>
|
bind:duration
|
||||||
</svg>
|
bind:current_ease
|
||||||
|
bind:current_type
|
||||||
<Controls
|
on:play={runAnimations} />
|
||||||
{eases}
|
</div>
|
||||||
{types}
|
|
||||||
{playing}
|
|
||||||
{width}
|
|
||||||
bind:duration
|
|
||||||
bind:current_ease
|
|
||||||
bind:current_type
|
|
||||||
on:play={runAnimations}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
@ -1,186 +1,175 @@
|
|||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from "svelte";
|
||||||
|
|
||||||
export let current_ease;
|
export let current_ease;
|
||||||
export let current_type;
|
export let current_type;
|
||||||
export let eases;
|
export let eases;
|
||||||
export let types;
|
export let types;
|
||||||
export let duration;
|
export let duration;
|
||||||
export let playing;
|
export let playing;
|
||||||
export let width;
|
export let width;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
$: mobile = width && width < 600;
|
$: mobile = width && width < 600;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.easing-sidebar {
|
.easing-sidebar {
|
||||||
width: 11em;
|
width: 11em;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
background: #eee;
|
background: #eee;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
margin: 3px 0;
|
margin: 3px 0;
|
||||||
cursor:pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
li:hover {
|
li:hover {
|
||||||
background: #676778;
|
background: #676778;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
background: #ff3e00;
|
background: #ff3e00;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin: 0 10px 0 0;
|
margin: 0 10px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
display: inline;
|
display: inline;
|
||||||
padding: 0.2em;
|
padding: 0.2em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.duration {
|
.duration {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.duration span {
|
.duration span {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.duration input {
|
.duration input {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
margin: 10px 10px 10px 0 ;
|
margin: 10px 10px 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.duration button {
|
.duration button {
|
||||||
margin: 10px 5px;
|
margin: 10px 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.duration .number {
|
.duration .number {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.duration .play {
|
.duration .play {
|
||||||
margin: 0 5px 0 auto;
|
margin: 0 5px 0 auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width:600px) {
|
@media (max-width: 600px) {
|
||||||
.easing-types {
|
.easing-types {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.easing-sidebar {
|
.easing-sidebar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.duration .play {
|
.duration .play {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
width: unset;
|
width: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3:nth-of-type(2) {
|
h3:nth-of-type(2) {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul li {
|
ul li {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="easing-sidebar">
|
<div class="easing-sidebar">
|
||||||
<div class="easing-types">
|
<div class="easing-types">
|
||||||
<h3>Ease</h3>
|
<h3>Ease</h3>
|
||||||
{#if mobile}
|
{#if mobile}
|
||||||
<select bind:value={current_ease}>
|
<select bind:value={current_ease}>
|
||||||
{#each [...eases] as [name]}
|
{#each [...eases, [`cubicBezier`]] as [name]}
|
||||||
<option
|
<option value={name} class:selected={name === current_ease}>
|
||||||
value={name}
|
{name}
|
||||||
class:selected={name === current_ease}
|
</option>
|
||||||
>
|
{/each}
|
||||||
{name}
|
</select>
|
||||||
</option>
|
{:else}
|
||||||
{/each}
|
<ul>
|
||||||
</select>
|
{#each [...eases, [`cubicBezier`]] as [name]}
|
||||||
{:else}
|
<li
|
||||||
<ul>
|
class:selected={name === current_ease}
|
||||||
{#each [...eases] as [name]}
|
on:click={() => (current_ease = name)}>
|
||||||
<li
|
{name}
|
||||||
class:selected={name === current_ease}
|
</li>
|
||||||
on:click={() => current_ease = name}
|
{/each}
|
||||||
>
|
</ul>
|
||||||
{name}
|
{/if}
|
||||||
</li>
|
<h3>Type</h3>
|
||||||
{/each}
|
{#if mobile}
|
||||||
</ul>
|
<select bind:value={current_type}>
|
||||||
{/if}
|
{#each types as [name, type]}
|
||||||
<h3>Type</h3>
|
<option value={type}>{name}</option>
|
||||||
{#if mobile }
|
{/each}
|
||||||
<select bind:value={current_type}>
|
</select>
|
||||||
{#each types as [name, type]}
|
{:else}
|
||||||
<option
|
<ul>
|
||||||
value={type}
|
{#each types as [name, type]}
|
||||||
>
|
<li
|
||||||
{name}
|
class:selected={type === current_type}
|
||||||
</option>
|
on:click={() => (current_type = type)}>
|
||||||
{/each}
|
{name}
|
||||||
</select>
|
</li>
|
||||||
{:else}
|
{/each}
|
||||||
<ul>
|
</ul>
|
||||||
{#each types as [name, type]}
|
{/if}
|
||||||
<li
|
</div>
|
||||||
class:selected={type === current_type}
|
<h4>Duration</h4>
|
||||||
on:click={() => current_type = type}
|
<div class="duration">
|
||||||
>
|
<span>
|
||||||
{name}
|
<input type="number" bind:value={duration} min="0" step="100" />
|
||||||
</li>
|
<button class="number" on:click={() => (duration -= 100)}>-</button>
|
||||||
{/each}
|
<button class="number" on:click={() => (duration += 100)}>+</button>
|
||||||
</ul>
|
</span>
|
||||||
{/if}
|
<button class="play" on:click={() => dispatch('play')}>
|
||||||
</div>
|
{playing ? 'Restart' : 'Play'}
|
||||||
<h4>
|
</button>
|
||||||
Duration
|
</div>
|
||||||
</h4>
|
</div>
|
||||||
<div class="duration">
|
|
||||||
<span>
|
|
||||||
<input type="number" bind:value={duration} min="0" step="100"/>
|
|
||||||
<button class="number" on:click={() => duration -= 100}>-</button>
|
|
||||||
<button class="number" on:click={() => duration += 100}>+</button>
|
|
||||||
</span>
|
|
||||||
<button class="play" on:click={() => dispatch('play')}>
|
|
||||||
{playing ? 'Restart' : 'Play'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
@ -1,62 +1,98 @@
|
|||||||
<script>
|
<script>
|
||||||
export let x, y;
|
export let is_custom;
|
||||||
|
export let x, y;
|
||||||
|
let rect;
|
||||||
|
let rect2;
|
||||||
|
export let x1 = 200,
|
||||||
|
y1 = 1400,
|
||||||
|
x2 = 1200,
|
||||||
|
y2 = 400;
|
||||||
|
let selected1 = false,
|
||||||
|
selected2 = false;
|
||||||
|
export let bezier = [0, 0, 0, 0];
|
||||||
|
const radius = 30;
|
||||||
|
$: {
|
||||||
|
if (rect2) {
|
||||||
|
const { x, y, width, height } = rect2.getBoundingClientRect();
|
||||||
|
bezier = [
|
||||||
|
Math.max(0, Math.min(1, (x1 - 200) / 1000)),
|
||||||
|
-(y1 - 1400) / 1000,
|
||||||
|
Math.max(0, Math.min(1, 1 + (x2 - 1200) / 1000)),
|
||||||
|
1 - (y2 - 400) / 1000,
|
||||||
|
].map((v) => Math.round(v * 100) / 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.grid-line {
|
.grid-line {
|
||||||
stroke:#ccc;
|
stroke: #ccc;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
stroke-width: 2;
|
stroke-width: 2;
|
||||||
}
|
}
|
||||||
|
circle {
|
||||||
.grid-line-xy {
|
z-index: 10;
|
||||||
stroke: tomato;
|
position: absolute;
|
||||||
stroke-width: 2;
|
}
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<svelte:options namespace="svg" />
|
<svelte:options namespace="svg" />
|
||||||
|
|
||||||
<rect
|
<rect
|
||||||
x=0
|
bind:this={rect}
|
||||||
y=0
|
x="0"
|
||||||
width=1400
|
y="0"
|
||||||
height=1800
|
width="1400"
|
||||||
stroke=#ccc
|
height="1800"
|
||||||
style="opacity: 0.5"
|
stroke="#ccc"
|
||||||
fill=none
|
style="opacity: 0.5"
|
||||||
stroke-width=2
|
fill="none"
|
||||||
/>
|
stroke-width="2" />
|
||||||
|
|
||||||
{#each { length: 8 } as _, i}
|
{#each { length: 8 } as _, i}
|
||||||
{#if i < 6}
|
{#if i < 6}
|
||||||
<path
|
<path d="M{(i + 1) * 200} 0 L{(i + 1) * 200} 1802" class="grid-line" />
|
||||||
d="M{(i+1) * 200} 0 L{(i+1)*200} 1802"
|
{/if}
|
||||||
class="grid-line"
|
<path d="M0 {(i + 1) * 200} L1400 {(i + 1) * 200} " class="grid-line" />
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
<path
|
|
||||||
d="M0 {(i+1) * 200} L1400 {(i+1)*200} "
|
|
||||||
class="grid-line"
|
|
||||||
/>
|
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<path
|
|
||||||
style="transform: translateX({x+200}px)"
|
|
||||||
d="M0 0 L0 1800"
|
|
||||||
class="grid-line-xy"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
style="transform: translateY({y}px)"
|
|
||||||
d="M0 400 L1400 400"
|
|
||||||
class="grid-line-xy"
|
|
||||||
/>
|
|
||||||
<rect
|
<rect
|
||||||
x=200
|
bind:this={rect2}
|
||||||
y=400
|
x="200"
|
||||||
width=1000
|
y="400"
|
||||||
height=1000
|
width="1000"
|
||||||
stroke=#999
|
height="1000"
|
||||||
fill=none
|
stroke="#999"
|
||||||
stroke-width=2
|
fill="none"
|
||||||
/>
|
stroke-width="4" />
|
||||||
|
<svelte:window
|
||||||
|
on:mousemove={(e) => {
|
||||||
|
const { x, y, width, height } = rect.getBoundingClientRect();
|
||||||
|
const _x1 = Math.min(1200, Math.max(200, (e.clientX - x) * (1400 / width)));
|
||||||
|
const _y1 = Math.min(1800, Math.max(0, (e.clientY - y) * (1800 / height)));
|
||||||
|
if (selected1) (x1 = _x1), (y1 = _y1);
|
||||||
|
else if (selected2) (x2 = _x1), (y2 = _y1);
|
||||||
|
}}
|
||||||
|
on:mouseup={() => {
|
||||||
|
selected1 = selected2 = false;
|
||||||
|
}} />
|
||||||
|
<slot />
|
||||||
|
{#if is_custom}
|
||||||
|
<path d="M200 1400 L{x1} {y1} " stroke="#333333d9" stroke-width="10" />
|
||||||
|
<path d="M1200 400 L{x2} {y2} " stroke="#333333d9" stroke-width="10" />
|
||||||
|
<circle
|
||||||
|
cx={x1}
|
||||||
|
cy={y1}
|
||||||
|
r={radius}
|
||||||
|
fill="#333"
|
||||||
|
stroke="transparent"
|
||||||
|
stroke-width="100"
|
||||||
|
on:mousedown={() => (selected1 = true)} />
|
||||||
|
<circle
|
||||||
|
cx={x2}
|
||||||
|
cy={y2}
|
||||||
|
r={radius}
|
||||||
|
fill="#333"
|
||||||
|
stroke="transparent"
|
||||||
|
stroke-width="100"
|
||||||
|
on:mousedown={() => (selected2 = true)} />
|
||||||
|
{/if}
|
||||||
|
Loading…
Reference in new issue