124 lines
2.8 KiB
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}
|
|
,
|
|
{/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>
|