| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- <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 && stickyBox.boxStyle && 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>
|