staff/uni_modules/wot-design-uni/components/wd-sticky/wd-sticky.vue

191 lines
4.8 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view :style="`${rootStyle};display: inline-block;`">
<view :class="`wd-sticky ${customClass}`" :style="stickyStyle" :id="styckyId">
<view class="wd-sticky__container" :style="containerStyle">
<wd-resize @resize="handleResize" custom-style="display: inline-block;">
<slot />
</wd-resize>
</view>
</view>
</view>
</template>
<script lang="ts">
export default {
name: 'wd-sticky',
options: {
addGlobalClass: true,
virtualHost: true,
styleIsolation: 'shared'
}
}
</script>
<script lang="ts" setup>
import wdResize from '../wd-resize/wd-resize.vue'
import { computed, getCurrentInstance, reactive, ref, type CSSProperties } from 'vue'
import { addUnit, getRect, objToStyle, pause, uuid } from '../common/util'
import { stickyProps } from './types'
import { useParent } from '../composables/useParent'
import { STICKY_BOX_KEY } from '../wd-sticky-box/types'
const props = defineProps(stickyProps)
const styckyId = ref<string>(`wd-sticky${uuid()}`)
const observerList = ref<UniApp.IntersectionObserver[]>([])
const stickyState = reactive({
position: 'absolute',
boxLeaved: false,
top: 0,
height: 0,
width: 0,
state: ''
})
const { parent: stickyBox } = useParent(STICKY_BOX_KEY)
const { proxy } = getCurrentInstance() as any
const rootStyle = computed(() => {
const style: CSSProperties = {
'z-index': props.zIndex,
height: addUnit(stickyState.height),
width: addUnit(stickyState.width)
}
if (!stickyState.boxLeaved) {
style['position'] = 'relative'
}
return `${objToStyle(style)};${props.customStyle}`
})
const stickyStyle = computed(() => {
const style: CSSProperties = {
'z-index': props.zIndex,
height: addUnit(stickyState.height),
width: addUnit(stickyState.width)
}
if (!stickyState.boxLeaved) {
style['position'] = 'relative'
}
return `${objToStyle(style)};`
})
const containerStyle = computed(() => {
const style: CSSProperties = {
position: stickyState.position as 'static' | 'relative' | 'absolute' | 'sticky' | 'fixed',
top: addUnit(stickyState.top)
}
return objToStyle(style)
})
const innerOffsetTop = computed(() => {
let top: number = 0
// #ifdef H5
// H5端导航栏为普通元素需要将组件移动到导航栏的下边沿
// H5的导航栏高度为44px
top = 44
// #endif
return top + props.offsetTop
})
/**
* 清除对当前组件的监听
*/
function clearObserver() {
while (observerList.value.length !== 0) {
observerList.value.pop()!.disconnect()
}
}
/**
* 添加对当前组件的监听
*/
function createObserver() {
const observer = uni.createIntersectionObserver(proxy, { thresholds: [0, 0.5] })
observerList.value.push(observer)
return observer
}
/**
* 当前内容高度发生变化时重置监听
*/
async function handleResize(detail: any) {
stickyState.width = detail.width
stickyState.height = detail.height
await pause()
observerContentScroll()
if (!stickyBox || !stickyBox.observerForChild) return
stickyBox.observerForChild(proxy)
}
/**
* 监听吸顶元素滚动事件
*/
function observerContentScroll() {
if (stickyState.height === 0 && stickyState.width === 0) return
const offset = innerOffsetTop.value + stickyState.height
clearObserver()
createObserver()
.relativeToViewport({
top: -offset
})
.observe(`#${styckyId.value}`, (result) => {
handleRelativeTo(result)
})
getRect(`#${styckyId.value}`, false, proxy).then((res) => {
// #ifdef H5
// H5端查询节点信息未计算导航栏高度
res.bottom = Number(res.bottom) + 44
// #endif
if (Number(res.bottom) <= offset) handleRelativeTo({ boundingClientRect: res })
})
}
/**
* @description 根据位置进行吸顶
*/
function handleRelativeTo({ boundingClientRect }: any) {
// sticky 高度大于或等于 wd-sticky-box使用 wd-sticky-box 无任何意义
if (stickyBox && stickyState.height >= stickyBox.boxStyle.height) {
stickyState.position = 'absolute'
stickyState.top = 0
return
}
let isStycky = boundingClientRect.top <= innerOffsetTop.value
// #ifdef H5 || APP-PLUS
isStycky = boundingClientRect.top < innerOffsetTop.value
// #endif
if (isStycky) {
stickyState.state = 'sticky'
stickyState.boxLeaved = false
stickyState.position = 'fixed'
stickyState.top = innerOffsetTop.value
} else {
stickyState.state = 'normal'
stickyState.boxLeaved = false
stickyState.position = 'absolute'
stickyState.top = 0
}
}
/**
* 设置位置
* @param setboxLeaved
* @param setPosition
* @param setTop
*/
function setPosition(boxLeaved: boolean, position: string, top: number) {
stickyState.boxLeaved = boxLeaved
stickyState.position = position
stickyState.top = top
}
defineExpose({
setPosition,
stickyState,
offsetTop: props.offsetTop
})
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>