TirayaFrontend/src/lib/components/ui/Marquee.svelte

124 lines
2.8 KiB
Svelte

<!-- -->
<script lang="ts">
import type { Link } from "$lib/util/types";
import LinkText from "./LinkText.svelte";
export let items: Link[] = [];
export let suffix: string | undefined = undefined;
export let nodrag = false;
let container: HTMLElement;
let ribbon: HTMLElement;
let timer: number | null = null;
// Ribbon position in pixels
let position = 0;
// Direction in which the ribbon moves (true: positive, false: negative)
let moveDirection = true;
// Maximum ribbon position in pixels
let moveLimit = 0;
// Pause the motion of the ribbon
let brake = false;
const DELAY = 50;
// Reset marquee when items change
$: if (items) reset();
function onMouseEnter() {
// If the mouse re-enters when the marquee is running, pause it
if (timer !== null) {
brake = true;
return;
}
const ribbonWidth = ribbon.getBoundingClientRect().width;
const containerWidth = container.getBoundingClientRect().width;
if (ribbonWidth > containerWidth) {
moveLimit = ribbonWidth - containerWidth;
moveDirection = true;
timer = window.setTimeout(onTimer, DELAY);
}
}
function onTimer() {
if (!brake) {
if (moveDirection) {
if (position < moveLimit) position++;
else moveDirection = false;
} else {
if (position > 0) {
position--;
} else {
reset();
return;
}
}
}
timer = window.setTimeout(onTimer, DELAY);
}
function reset() {
position = 0;
if (timer !== null) {
window.clearTimeout(timer);
timer = null;
}
}
</script>
<!-- When the window size changes, the pixel counts dont match anymore,
so the marquee is reset -->
<svelte:window on:resize={reset} />
<div class="marquee" bind:this={container}>
<div class="overflow-hidden">
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="marquee-ribbon"
style={`--trans-x:${-position}px;`}
bind:this={ribbon}
on:mouseenter={onMouseEnter}
on:mouseleave={() => {
brake = false;
}}
>
{#each items as item, i}
{#if i !== 0}
,&nbsp;
{/if}
<LinkText {item} {nodrag} />
{/each}
{#if suffix}
{suffix}
{/if}
</div>
</div>
</div>
<style lang="postcss">
.marquee {
width: 100%;
margin-left: -6px;
margin-right: -6px;
mask-image: linear-gradient(
90deg,
transparent 0,
#000 6px,
#000 calc(100% - 12px),
transparent
);
overflow: hidden;
position: relative;
}
.marquee-ribbon {
--trans-x: 0px;
display: flex;
padding-inline-start: 6px;
padding-inline-end: 12px;
transform: translateX(var(--trans-x));
white-space: nowrap;
width: fit-content;
}
</style>