mirror of https://github.com/sveltejs/svelte
parent
ee3ef5bca7
commit
26b3e5171b
@ -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,5 @@
|
|||||||
|
---
|
||||||
|
title: The animate directive
|
||||||
|
---
|
||||||
|
|
||||||
|
TODO fix https://github.com/sveltejs/svelte/issues/2159 before working on `animate`
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"title": "Animations"
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
<script>
|
||||||
|
import { spring } from 'svelte/motion';
|
||||||
|
|
||||||
|
const coords = spring({ x: 0, y: 0 }, {
|
||||||
|
stiffness: 0.2,
|
||||||
|
damping: 0.4
|
||||||
|
});
|
||||||
|
|
||||||
|
function handlePanStart() {
|
||||||
|
coords.stiffness = coords.damping = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePanMove(event) {
|
||||||
|
coords.update($coords => ({
|
||||||
|
x: $coords.x + event.detail.dx,
|
||||||
|
y: $coords.y + event.detail.dy
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePanEnd(event) {
|
||||||
|
coords.stiffness = 0.2;
|
||||||
|
coords.damping = 0.4;
|
||||||
|
coords.set({ x: 0, y: 0 });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.box {
|
||||||
|
--width: 100px;
|
||||||
|
--height: 100px;
|
||||||
|
position: absolute;
|
||||||
|
width: var(--width);
|
||||||
|
height: var(--height);
|
||||||
|
left: calc(50% - var(--width) / 2);
|
||||||
|
top: calc(50% - var(--height) / 2);
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #ff3e00;
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="box"
|
||||||
|
on:panstart={handlePanStart}
|
||||||
|
on:panmove={handlePanMove}
|
||||||
|
on:panend={handlePanEnd}
|
||||||
|
style="transform: translate({$coords.x}px,{$coords.y}px)"
|
||||||
|
></div>
|
@ -0,0 +1,9 @@
|
|||||||
|
export function pannable(node) {
|
||||||
|
// setup work goes here...
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
// ...cleanup goes here
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
<script>
|
||||||
|
import { spring } from 'svelte/motion';
|
||||||
|
import { pannable } from './pannable.js';
|
||||||
|
|
||||||
|
const coords = spring({ x: 0, y: 0 }, {
|
||||||
|
stiffness: 0.2,
|
||||||
|
damping: 0.4
|
||||||
|
});
|
||||||
|
|
||||||
|
function handlePanStart() {
|
||||||
|
coords.stiffness = coords.damping = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePanMove(event) {
|
||||||
|
coords.update($coords => ({
|
||||||
|
x: $coords.x + event.detail.dx,
|
||||||
|
y: $coords.y + event.detail.dy
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePanEnd(event) {
|
||||||
|
coords.stiffness = 0.2;
|
||||||
|
coords.damping = 0.4;
|
||||||
|
coords.set({ x: 0, y: 0 });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.box {
|
||||||
|
--width: 100px;
|
||||||
|
--height: 100px;
|
||||||
|
position: absolute;
|
||||||
|
width: var(--width);
|
||||||
|
height: var(--height);
|
||||||
|
left: calc(50% - var(--width) / 2);
|
||||||
|
top: calc(50% - var(--height) / 2);
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #ff3e00;
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="box"
|
||||||
|
use:pannable
|
||||||
|
on:panstart={handlePanStart}
|
||||||
|
on:panmove={handlePanMove}
|
||||||
|
on:panend={handlePanEnd}
|
||||||
|
style="transform: translate({$coords.x}px,{$coords.y}px)"
|
||||||
|
></div>
|
@ -0,0 +1,47 @@
|
|||||||
|
export function pannable(node) {
|
||||||
|
let x;
|
||||||
|
let y;
|
||||||
|
|
||||||
|
function handleMousedown(event) {
|
||||||
|
x = event.clientX;
|
||||||
|
y = event.clientY;
|
||||||
|
|
||||||
|
node.dispatchEvent(new CustomEvent('panstart', {
|
||||||
|
detail: { x, y }
|
||||||
|
}));
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', handleMousemove);
|
||||||
|
window.addEventListener('mouseup', handleMouseup);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMousemove(event) {
|
||||||
|
const dx = event.clientX - x;
|
||||||
|
const dy = event.clientY - y;
|
||||||
|
x = event.clientX;
|
||||||
|
y = event.clientY;
|
||||||
|
|
||||||
|
node.dispatchEvent(new CustomEvent('panmove', {
|
||||||
|
detail: { x, y, dx, dy }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseup(event) {
|
||||||
|
x = event.clientX;
|
||||||
|
y = event.clientY;
|
||||||
|
|
||||||
|
node.dispatchEvent(new CustomEvent('panend', {
|
||||||
|
detail: { x, y }
|
||||||
|
}));
|
||||||
|
|
||||||
|
window.removeEventListener('mousemove', handleMousemove);
|
||||||
|
window.removeEventListener('mouseup', handleMouseup);
|
||||||
|
}
|
||||||
|
|
||||||
|
node.addEventListener('mousedown', handleMousedown);
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
node.removeEventListener('mousedown', handleMousedown);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
---
|
||||||
|
title: The use directive
|
||||||
|
---
|
||||||
|
|
||||||
|
Actions are essentially element-level lifecycle functions. They're useful for things like:
|
||||||
|
|
||||||
|
* interfacing with third-party libraries
|
||||||
|
* lazy-loaded images
|
||||||
|
* tooltips
|
||||||
|
* adding custom event handlers
|
||||||
|
|
||||||
|
In this app, we want to make the orange box 'pannable'. It has event handlers for the `panstart`, `panmove` and `panend` events, but these aren't native DOM events. We have to dispatch them ourselves. First, import the `pannable` function...
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { pannable } from './pannable.js';
|
||||||
|
```
|
||||||
|
|
||||||
|
...then use it with the element:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="box"
|
||||||
|
use:pannable
|
||||||
|
on:panstart={handlePanStart}
|
||||||
|
on:panmove={handlePanMove}
|
||||||
|
on:panend={handlePanEnd}
|
||||||
|
style="transform: translate({$coords.x}px,{$coords.y}px)"
|
||||||
|
></div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Open the `pannable.js` file. Like transition functions, an action function receives a `node` and some optional parameters, and returns an action object. That object must have a `destroy` function, which is called when the element is unmounted.
|
||||||
|
|
||||||
|
We want to fire `panstart` event when the user mouses down on the element, `panmove` events (with `dx` and `dy` properties showing how far the mouse moved) when they drag it, and `panend` events when they mouse up. One possible implementation looks like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
export function pannable(node) {
|
||||||
|
let x;
|
||||||
|
let y;
|
||||||
|
|
||||||
|
function handleMousedown(event) {
|
||||||
|
x = event.clientX;
|
||||||
|
y = event.clientY;
|
||||||
|
|
||||||
|
node.dispatchEvent(new CustomEvent('panstart', {
|
||||||
|
detail: { x, y }
|
||||||
|
}));
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', handleMousemove);
|
||||||
|
window.addEventListener('mouseup', handleMouseup);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMousemove(event) {
|
||||||
|
const dx = event.clientX - x;
|
||||||
|
const dy = event.clientY - y;
|
||||||
|
x = event.clientX;
|
||||||
|
y = event.clientY;
|
||||||
|
|
||||||
|
node.dispatchEvent(new CustomEvent('panmove', {
|
||||||
|
detail: { x, y, dx, dy }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseup(event) {
|
||||||
|
x = event.clientX;
|
||||||
|
y = event.clientY;
|
||||||
|
|
||||||
|
node.dispatchEvent(new CustomEvent('panend', {
|
||||||
|
detail: { x, y }
|
||||||
|
}));
|
||||||
|
|
||||||
|
window.removeEventListener('mousemove', handleMousemove);
|
||||||
|
window.removeEventListener('mouseup', handleMouseup);
|
||||||
|
}
|
||||||
|
|
||||||
|
node.addEventListener('mousedown', handleMousedown);
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
node.removeEventListener('mousedown', handleMousedown);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Update the `pannable` function and try moving the box around.
|
||||||
|
|
||||||
|
> This implementation is for demonstration purposes — a more complete one would also consider touch events.
|
||||||
|
|
@ -0,0 +1 @@
|
|||||||
|
TODO
|
@ -0,0 +1 @@
|
|||||||
|
TODO
|
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: Adding parameters
|
||||||
|
---
|
||||||
|
|
||||||
|
TODO example with Prism highlighting
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"title": "Actions"
|
||||||
|
}
|
Loading…
Reference in new issue