77 lines
1.5 KiB
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>
|