|
1 | 1 | <script setup lang="ts"> |
2 | | - import { useElementBounding, useWindowSize } from "@vueuse/core"; |
3 | | - import { computed, reactive, ref, watch } from "vue"; |
| 2 | + import { useElementBounding, useEventListener, useWindowSize } from "@vueuse/core"; |
| 3 | + import { computed, reactive, ref, watch, watchEffect } from "vue"; |
4 | 4 |
|
5 | | - defineProps<{ |
6 | | - stuck?: boolean, |
7 | | - }>(); |
| 5 | + defineProps<{}>(); |
8 | 6 |
|
9 | | - const emit = defineEmits(['update:stuck']); |
10 | 7 | const el = ref<HTMLElement>(); |
11 | | - // const content = computed<HTMLElement>(() => el.value?.querySelector('[data-sticky-content]')); |
12 | 8 | const containerRect = reactive(useElementBounding(() => el.value?.parentElement, { updateTiming: 'next-frame' })); |
13 | 9 | const selfRect = reactive(useElementBounding(el, { updateTiming: 'next-frame' })); |
14 | | - // const contentRect = reactive(useElementBounding(content, { updateTiming: 'next-frame' })); |
15 | 10 | const topbarSafeRect = reactive(useElementBounding(() => document.querySelector('[data-topbar-sticky-safe-area]') as HTMLElement, { updateTiming: 'next-frame' })); |
16 | 11 | const stuck = computed(() => { |
17 | 12 | const style = el.value ? window.getComputedStyle(el.value) : null; |
|
21 | 16 | && bottom >= 0 |
22 | 17 | && top <= parseFloat(style.top); |
23 | 18 | }); |
24 | | - const isOverflowing = ref(false); |
25 | 19 | const isOverflowingViewport = ref(false); |
26 | 20 | const { height: innerHeight } = useWindowSize(); |
27 | 21 | watch([containerRect, innerHeight], () => { |
28 | 22 | const style = window.getComputedStyle(el.value); |
29 | 23 | isOverflowingViewport.value = (parseFloat(style.top) + containerRect.height) > innerHeight.value; |
30 | 24 | }); |
31 | | - watch(stuck, () => { |
32 | | - emit('update:stuck', stuck.value); |
| 25 | +
|
| 26 | + // Stacked top |
| 27 | + const stackedTop = ref(0); |
| 28 | + const parentStickyTopEl = computed(() => el.value?.closest('[data-sticky-top-container]')?.querySelector('[data-sticky-top]') as HTMLElement); |
| 29 | + const parentStickyTopRect = reactive(useElementBounding(() => parentStickyTopEl.value, { updateTiming: 'next-frame' })); |
| 30 | + function updateStackedTop() { |
| 31 | + const parentStickyStyle = parentStickyTopEl.value ? window.getComputedStyle(parentStickyTopEl.value) : null; |
| 32 | + stackedTop.value = parentStickyTopEl.value && parentStickyStyle.position === 'sticky' |
| 33 | + ? parentStickyTopRect.height + parseFloat(parentStickyStyle.top || '0') |
| 34 | + : topbarSafeRect.height; |
| 35 | + } |
| 36 | + watchEffect(() => { |
| 37 | + updateStackedTop(); |
| 38 | + }); |
| 39 | + useEventListener(window, 'resize', () => { |
| 40 | + updateStackedTop(); |
33 | 41 | }); |
34 | | - // watch([stuck, contentRect], () => { |
35 | | - // if(content.value) { |
36 | | - // const topBarSafeArea = (document.querySelector('[data-topbar-sticky-safe-area]') as HTMLElement); |
37 | | - // topBarSafeArea.style.minWidth = stuck.value ? `${contentRect.width}px` : 'auto'; |
38 | | - // } |
39 | | - // }); |
40 | 42 | </script> |
41 | 43 |
|
42 | 44 | <template> |
43 | 45 | <div :style="{ |
44 | | - '--top-bar-height': `${topbarSafeRect.height}px`, |
45 | | - // '--sticky-safe-left-offset': stuck ? `${Math.max(topbarSafeRect.left - selfRect.left, 0)}px` : '0px', |
46 | | - // '--sticky-safe-right-offset': stuck ? `${Math.max(selfRect.right - topbarSafeRect.right - parseInt(window.getComputedStyle(el).paddingRight), 0)}px` : '0px', |
| 46 | + '--stacked-top': `${stackedTop}px` |
47 | 47 | }" |
48 | 48 | :data-stuck="stuck ? true : null" |
49 | 49 | :data-overflowing-viewport="isOverflowingViewport ? true : null" |
| 50 | + data-sticky-top |
50 | 51 | ref="el" |
51 | 52 | > |
52 | | - <slot v-bind="{ stuck, largerThanTopbar: selfRect.height > topbarSafeRect.height, isOverflowing }" /> |
| 53 | + <slot v-bind="{ stuck, largerThanTopbar: selfRect.height > topbarSafeRect.height }" /> |
53 | 54 | </div> |
54 | 55 | </template> |
0 commit comments