| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- <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>
|