chaos/src/Point.svelte

120 lines
3.1 KiB
Svelte

<!--
Represent a point on the screen that can optionally be moved by the user.
-->
<script>
import { onMount, onDestroy, createEventDispatcher } from 'svelte';
/** Horizontal position of the point. */
export let x = 0;
/** Vertical position of the point. */
export let y = 0;
/** Radius of the circle that represents the point. */
export let size = 15;
/** Whether the user is allowed to move the point. */
export let movable = true;
// Reference to the <div> element that represents the point
let element = null;
// Bounds of the parent node, computed only once when the user
// starts dragging
let bounds;
// Whether the user is currently dragging the point
let dragging = false;
let dispatch = createEventDispatcher();
const start = evt =>
{
if (!movable) return;
bounds = element.parentNode.getBoundingClientRect();
dragging = true;
window.addEventListener('mousemove', move);
window.addEventListener('touchmove', move);
window.addEventListener('mouseup', end);
window.addEventListener('touchend', end);
};
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
const move = evt =>
{
const [userX, userY] = evt.changedTouches
? [evt.changedTouches[0].clientX, evt.changedTouches[0].clientY]
: [evt.clientX, evt.clientY];
dispatch('move', {
x: clamp(userX - bounds.left, 0, bounds.width),
y: clamp(userY - bounds.top, 0, bounds.height),
shift: evt.shiftKey
});
};
const end = evt =>
{
dragging = false;
window.removeEventListener('mousemove', move);
window.removeEventListener('touchmove', move);
window.removeEventListener('mouseup', end);
window.removeEventListener('touchend', end);
};
onMount(() =>
{
element.addEventListener('mousedown', start);
element.addEventListener('touchstart', start);
});
onDestroy(() =>
{
element.removeEventListener('mousedown', start);
element.removeEventListener('touchstart', start);
window.removeEventListener('mousemove', move);
window.removeEventListener('touchmove', move);
window.removeEventListener('mouseup', end);
window.removeEventListener('touchend', end);
});
</script>
<style>
div
{
display: block;
position: absolute;
border-radius: 100%;
background: #333;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
transition:
box-shadow .15s var(--easing),
transform .15s var(--easing),
opacity .15s var(--easing);
}
div.movable
{
cursor: pointer;
}
div.movable.dragging
{
box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2);
transform: scale(1.5);
}
</style>
<div
style="width: {size}px; height: {size}px; left: {x - size / 2}px; top: {y - size / 2}px;"
class:dragging
class:movable
draggable="false"
bind:this={element}
/>