156 lines
4.4 KiB
Vue
156 lines
4.4 KiB
Vue
<template>
|
||
<view style="position: relative">
|
||
<view :class="`wd-sticky-box ${props.customClass}`" :style="customStyle" :id="styckyBoxId">
|
||
<wd-resize @resize="handleResize">
|
||
<slot />
|
||
</wd-resize>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
export default {
|
||
name: 'wd-sticky-box',
|
||
options: {
|
||
addGlobalClass: true,
|
||
// virtualHost: true,
|
||
styleIsolation: 'shared'
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<script lang="ts" setup>
|
||
import wdResize from '../wd-resize/wd-resize.vue'
|
||
import { getCurrentInstance, onBeforeMount, reactive, ref } from 'vue'
|
||
import { getRect, uuid } from '../common/util'
|
||
import { baseProps } from '../common/props'
|
||
import { STICKY_BOX_KEY } from './types'
|
||
import { useChildren } from '../composables/useChildren'
|
||
|
||
const props = defineProps(baseProps)
|
||
|
||
const styckyBoxId = ref<string>(`wd-sticky-box${uuid()}`)
|
||
|
||
const observerMap = ref<Map<any, any>>(new Map())
|
||
|
||
const boxStyle = reactive({
|
||
height: 0,
|
||
width: 0
|
||
})
|
||
|
||
const { proxy } = getCurrentInstance() as any
|
||
|
||
const { children: stickyList, linkChildren } = useChildren(STICKY_BOX_KEY)
|
||
linkChildren({
|
||
boxStyle: boxStyle,
|
||
observerForChild
|
||
})
|
||
|
||
onBeforeMount(() => {
|
||
observerMap.value = new Map()
|
||
})
|
||
|
||
/**
|
||
* 容器大小变化后重新监听sticky组件与box组件的交叉状态
|
||
* @param detail
|
||
*/
|
||
function handleResize(detail: any) {
|
||
// 相对的容器大小改变后,同步设置 wd-sticky-box 的大小
|
||
boxStyle.width = detail.width
|
||
boxStyle.height = detail.height
|
||
// wd-sticky-box 大小变化时,重新监听所有吸顶元素
|
||
const temp = observerMap.value
|
||
observerMap.value = new Map()
|
||
for (const [uid] of temp) {
|
||
const child = stickyList.find((sticky) => {
|
||
return sticky.$.uid === uid
|
||
})
|
||
observerForChild(child)
|
||
}
|
||
temp.forEach((observer) => {
|
||
observer.disconnect()
|
||
})
|
||
temp.clear()
|
||
}
|
||
/**
|
||
* 删除对指定sticky的监听
|
||
* @param child 指定的子组件
|
||
*/
|
||
function deleteObserver(child: any) {
|
||
const observer = observerMap.value.get(child.$.uid)
|
||
if (!observer) return
|
||
observer.disconnect()
|
||
observerMap.value.delete(child.$.uid)
|
||
}
|
||
/**
|
||
* 针对指定sticky添加监听
|
||
* @param child 指定的子组件
|
||
*/
|
||
function createObserver(child: any) {
|
||
const observer = uni.createIntersectionObserver(proxy, { thresholds: [0, 0.5] })
|
||
observerMap.value.set(child.$.uid, observer)
|
||
return observer
|
||
}
|
||
/**
|
||
* 监听子组件
|
||
* @param child 子组件
|
||
*/
|
||
function observerForChild(child: any) {
|
||
deleteObserver(child)
|
||
const observer = createObserver(child)
|
||
const exposed = child.$.exposed
|
||
let offset = exposed.stickyState.height + exposed.offsetTop
|
||
// #ifdef H5
|
||
// H5端,导航栏为普通元素,需要将组件移动到导航栏的下边沿
|
||
// H5的导航栏高度为44px
|
||
offset = offset + 44
|
||
// #endif
|
||
|
||
if (boxStyle.height <= exposed.stickyState.height) {
|
||
exposed.setPosition(false, 'absolute', 0)
|
||
}
|
||
observer.relativeToViewport({ top: -offset }).observe(`#${styckyBoxId.value}`, (result) => {
|
||
handleRelativeTo(exposed, result)
|
||
})
|
||
// 当子组件默认处于边界外且永远不会进入边界内时,需要手动调用一次
|
||
getRect(`#${styckyBoxId.value}`, false, proxy)
|
||
.then((res) => {
|
||
// #ifdef H5
|
||
// H5端,查询节点信息未计算导航栏高度
|
||
res.bottom = Number(res.bottom) + 44
|
||
// #endif
|
||
if (Number(res.bottom) <= offset) handleRelativeTo(exposed, { boundingClientRect: res })
|
||
})
|
||
.catch((res) => {
|
||
console.log(res)
|
||
})
|
||
}
|
||
/**
|
||
* 监听容器组件
|
||
* @param {Object} exposed wd-sticky实例暴露出的事件
|
||
* @param {Object} boundingClientRect 边界信息
|
||
*/
|
||
function handleRelativeTo(exposed: any, { boundingClientRect }: any) {
|
||
let childOffsetTop = exposed.offsetTop
|
||
// #ifdef H5
|
||
// H5端,导航栏为普通元素,需要将组件移动到导航栏的下边沿
|
||
// H5的导航栏高度为44px
|
||
childOffsetTop = childOffsetTop + 44
|
||
// #endif
|
||
const offset = exposed.stickyState.height + childOffsetTop
|
||
let isAbsolute = boundingClientRect.bottom <= offset
|
||
// #ifdef H5 || APP-PLUS
|
||
isAbsolute = boundingClientRect.bottom < offset
|
||
// #endif
|
||
if (isAbsolute) {
|
||
exposed.setPosition(true, 'absolute', boundingClientRect.height - exposed.stickyState.height)
|
||
} else if (boundingClientRect.top <= offset && !isAbsolute) {
|
||
if (exposed.stickyState.state === 'normal') return
|
||
exposed.setPosition(false, 'fixed', childOffsetTop)
|
||
}
|
||
}
|
||
</script>
|
||
<style lang="scss" scoped>
|
||
@import './index.scss';
|
||
</style>
|