upend/ui/src/components/Marquee.vue

87 lines
1.7 KiB
Vue

<template>
<div
class="marquee"
:class="{ overflowed }"
:style="`--shift-width: ${shiftWidth}; --anim-length: ${animLength}`"
ref="root"
>
<div class="inner" ref="inner">
<slot></slot>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from "vue";
export default defineComponent({
name: "Marquee",
props: {
speed: {
default: 30,
type: Number,
},
},
setup(props) {
const root = ref<HTMLDivElement | null>(null);
const inner = ref<HTMLDivElement | null>(null);
const overflowed = ref(false);
const shiftWidth = ref("unset");
const animLength = ref("unset");
onMounted(() => {
const resizeObserver = new ResizeObserver(() => {
if (!root.value) return;
overflowed.value = root.value!.scrollWidth > root.value!.clientWidth;
shiftWidth.value = `-${
inner.value!.clientWidth - root.value!.clientWidth
}px`;
animLength.value = `${inner.value!.clientWidth / props.speed}s`;
});
resizeObserver.observe(inner.value!);
});
return {
root,
inner,
overflowed,
shiftWidth,
animLength,
};
},
});
</script>
<style scoped lang="scss">
.marquee {
height: 1.1em;
overflow: hidden;
}
.inner {
white-space: nowrap;
display: inline-block;
}
</style>
<style lang="scss">
.overflowed .inner {
animation: marquee var(--anim-length) ease-in-out infinite;
--padding: 0.5em;
}
@keyframes marquee {
0% {
transform: translateX(var(--padding));
}
50% {
transform: translateX(calc(var(--shift-width) - var(--padding)));
}
100% {
transform: translateX(var(--padding));
}
}
</style>