upend/ui/src/components/Marquee.svelte

77 lines
1.5 KiB
Svelte

<script lang="ts">
import { onMount } from "svelte";
export let speed = 50;
let root: HTMLDivElement | undefined;
let inner: HTMLDivElement | undefined;
let overflowed = false;
let running = false;
let shiftWidth = "unset";
let animLength = "unset";
onMount(() => {
const resizeObserver = new ResizeObserver(() => {
if (!root) return;
overflowed = root.scrollWidth > root.clientWidth;
shiftWidth = `-${inner.clientWidth - root.clientWidth}px`;
animLength = `${inner.clientWidth / speed}s`;
});
resizeObserver.observe(inner);
});
</script>
<div
class="marquee"
class:overflowed
class:running
on:mouseenter={() => (running = true)}
on:mouseleave={() => (running = false)}
style={`--shift-width: ${shiftWidth}; --anim-length: ${animLength}`}
bind:this={root}
>
<div class="inner" bind:this={inner}>
<slot />
</div>
</div>
<style lang="scss">
.marquee {
height: 1.1em;
overflow: hidden;
flex-grow: 1;
}
.inner {
white-space: nowrap;
display: inline-block;
}
:global {
.overflowed .inner {
animation: marquee var(--anim-length) ease-in-out infinite;
animation-play-state: paused;
--padding: 1px;
}
.overflowed.running .inner {
animation-play-state: running;
}
@keyframes marquee {
0% {
transform: translateX(var(--padding));
}
50% {
transform: translateX(calc(var(--shift-width) - var(--padding)));
}
100% {
transform: translateX(var(--padding));
}
}
}
</style>