1
0
Fork 0
muzika-gromche/Frontend/src/components/scrollsync/ScrollSync.vue

152 lines
3.2 KiB
Vue

<script lang="ts">
import type { Handler } from 'mitt'
import { useRafFn } from '@vueuse/core'
import mitt from 'mitt'
// eslint-disable-next-line import/no-duplicates
import { onBeforeUnmount, onMounted } from 'vue'
interface ScrollSyncEvent {
scrollTop: number
scrollHeight: number
clientHeight: number
scrollLeft: number
scrollWidth: number
clientWidth: number
barHeight: number
barWidth: number
emitter: string
group: string
}
type Events = {
'scroll-sync': ScrollSyncEvent
}
const emitter = mitt<Events>()
function useEvent<Key extends keyof Events>(
type: Key,
handler: Handler<Events[Key]>,
): void {
const { on, off } = emitter
onMounted(() => {
on(type, handler)
})
onBeforeUnmount(() => {
off(type, handler)
})
}
</script>
<script setup lang="ts">
// eslint-disable-next-line import/first, import/no-duplicates
import { useId, useTemplateRef } from 'vue'
const {
proportional,
vertical,
horizontal,
group,
} = defineProps<{
proportional?: boolean
vertical?: boolean
horizontal?: boolean
group: string
}>()
const uuid = useId()
const nodeRef = useTemplateRef('scroll-sync-container')
defineExpose({
scrollTo: (options: ScrollToOptions) => {
nodeRef.value?.scrollTo(options)
},
})
function handleScroll(event: Event) {
useRafFn(() => {
const {
scrollTop,
scrollHeight,
clientHeight,
scrollLeft,
scrollWidth,
clientWidth,
offsetHeight,
offsetWidth,
} = event.target as HTMLElement
emitter.emit('scroll-sync', {
scrollTop,
scrollHeight,
clientHeight,
scrollLeft,
scrollWidth,
clientWidth,
barHeight: offsetHeight - clientHeight,
barWidth: offsetWidth - clientWidth,
emitter: uuid,
group,
})
}, { once: true })
}
useEvent('scroll-sync', (event: ScrollSyncEvent) => {
const node = nodeRef.value
if (event.group !== group || event.emitter === uuid || node === null) {
return
}
const {
scrollTop,
scrollHeight,
clientHeight,
scrollLeft,
scrollWidth,
clientWidth,
barHeight,
barWidth,
} = event
// from https://github.com/okonet/react-scroll-sync
const scrollTopOffset = scrollHeight - clientHeight
const scrollLeftOffset = scrollWidth - clientWidth
/* Calculate the actual pane height */
const paneHeight = node.scrollHeight - clientHeight
const paneWidth = node.scrollWidth - clientWidth
/* Adjust the scrollTop position of it accordingly */
node.removeEventListener('scroll', handleScroll)
if (vertical && scrollTopOffset > barHeight) {
node.scrollTop = proportional ? (paneHeight * scrollTop) / scrollTopOffset : scrollTop
}
if (horizontal && scrollLeftOffset > barWidth) {
node.scrollLeft = proportional ? (paneWidth * scrollLeft) / scrollLeftOffset : scrollLeft
}
useRafFn(() => {
node.addEventListener('scroll', handleScroll)
}, { once: true })
})
onMounted(() => {
const node = nodeRef.value
node!.addEventListener('scroll', handleScroll)
})
</script>
<template>
<div ref="scroll-sync-container" class="scroll-sync-container">
<slot />
</div>
</template>
<style scoped>
.scroll-sync-container {
overflow: auto;
}
</style>