pref: use hooks instead of directive (#108)

- remove v-resize
- add useContainerResize
- remove v-container
- add useContainerQueries
- add useContainerScroll
This commit is contained in:
Hayden
2025-02-01 11:56:17 +08:00
committed by GitHub
parent e5d9950429
commit 448ea4b1ba
10 changed files with 185 additions and 120 deletions

View File

@@ -1,46 +1,27 @@
import { defineResizeCallback } from 'hooks/resize'
import { computed, Directive, inject, InjectionKey, provide, ref } from 'vue'
const globalContainerSize = ref<Record<symbol, number>>({})
const containerNameKey = Symbol('containerName') as InjectionKey<symbol>
export const containerDirective: Directive<HTMLElement, symbol> = {
mounted: (el, binding) => {
const containerName = binding.value || Symbol('container')
const resizeCallback = defineResizeCallback((entries) => {
const entry = entries[0]
globalContainerSize.value[containerName] = entry.contentRect.width
})
const observer = new ResizeObserver(resizeCallback)
observer.observe(el)
el['_containerObserver'] = observer
},
unmounted: (el) => {
const observer = el['_containerObserver']
observer.disconnect()
},
}
import { useContainerResize } from 'hooks/resize'
import { type InjectionKey, type Ref, inject, provide, toRef } from 'vue'
const rem = parseFloat(getComputedStyle(document.documentElement).fontSize)
export const useContainerQueries = (containerName?: symbol) => {
const parentContainer = inject(containerNameKey, Symbol('unknown'))
const containerKey = Symbol('container') as InjectionKey<
Ref<HTMLElement | null>
>
const name = containerName ?? parentContainer
export const useContainerQueries = (
el?: HTMLElement | null | Ref<HTMLElement | null>,
) => {
const container = inject(containerKey, el ? toRef(el) : toRef(document.body))
provide(containerNameKey, name)
provide(containerKey, container)
const currentContainerSize = computed(() => {
return globalContainerSize.value[name] ?? 0
})
const { width } = useContainerResize(container)
/**
* @param size unit rem
*/
const generator = (size: number) => {
return (content: any, defaultContent: any = undefined) => {
return currentContainerSize.value > size * rem ? content : defaultContent
return width.value > size * rem ? content : defaultContent
}
}

View File

@@ -1,18 +1,5 @@
import { throttle } from 'lodash'
import { Directive } from 'vue'
export const resizeDirective: Directive<HTMLElement, ResizeObserverCallback> = {
mounted: (el, binding) => {
const callback = binding.value ?? (() => {})
const observer = new ResizeObserver(callback)
observer.observe(el)
el['observer'] = observer
},
unmounted: (el) => {
const observer = el['observer']
observer.disconnect()
},
}
import { type Ref, onUnmounted, ref, toRef, watch } from 'vue'
export const defineResizeCallback = (
callback: ResizeObserverCallback,
@@ -20,3 +7,45 @@ export const defineResizeCallback = (
) => {
return throttle(callback, wait ?? 100)
}
export const useContainerResize = (
el: HTMLElement | null | Ref<HTMLElement | null>,
) => {
const observer = ref<ResizeObserver | null>(null)
const width = ref(0)
const height = ref(0)
watch(
toRef(el),
(el) => {
if (el) {
const onResize = defineResizeCallback((entries) => {
const entry = entries[0]
width.value = entry.contentRect.width
height.value = entry.contentRect.height
})
observer.value = new ResizeObserver(onResize)
observer.value.observe(el)
}
},
{ immediate: true },
)
const stop = () => {
if (observer.value) {
observer.value.disconnect()
}
}
onUnmounted(() => {
stop()
})
return {
width,
height,
stop,
}
}

58
src/hooks/scroll.ts Normal file
View File

@@ -0,0 +1,58 @@
import { throttle } from 'lodash'
import { computed, onUnmounted, ref, toRef, watch, type Ref } from 'vue'
export interface UseScrollOption {
throttle?: number
onScroll?: (e: Event) => void
}
export const useContainerScroll = (
el: HTMLElement | null | Ref<HTMLElement | null>,
options?: UseScrollOption,
) => {
const scrollLeft = ref(0)
const scrollTop = ref(0)
const container = toRef(el)
const onScroll = throttle((e: Event) => {
options?.onScroll?.(e)
if (container.value) {
scrollLeft.value = container.value.scrollLeft
scrollTop.value = container.value.scrollTop
}
}, options?.throttle || 100)
watch(
container,
(el) => {
if (el) {
el.addEventListener('scroll', onScroll)
}
},
{ immediate: true },
)
const x = computed({
get: () => scrollLeft,
set: (val) => {
container.value?.scrollTo({ left: val.value })
},
})
const y = computed({
get: () => scrollTop,
set: (val) => {
container.value?.scrollTo({ top: val.value })
},
})
onUnmounted(() => {
if (container.value) {
container.value.removeEventListener('scroll', onScroll)
}
})
return { x, y }
}