Files
WAI_Project_UNIX/uni_modules/cool-ui/components/cl-sticky/cl-sticky.uvue
2025-07-21 16:47:04 +08:00

161 lines
2.9 KiB
Plaintext
Raw 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
class="cl-sticky-wrapper"
:style="{
height: rect.height == 0 ? 'auto' : rect.height + 'px',
zIndex
}"
>
<view
class="cl-sticky"
:class="[
{
'is-active': isSticky
}
]"
:style="{
width: isSticky ? rect.width + 'px' : '100%',
left: isSticky ? rect.left + 'px' : 0,
top: stickyTop + 'px'
}"
>
<slot></slot>
<slot name="content" :is-sticky="isSticky"></slot>
</view>
</view>
</template>
<script lang="ts" setup>
import { isEmpty, router, usePage } from "@/cool";
import { computed, getCurrentInstance, nextTick, onMounted, reactive, ref, watch } from "vue";
defineOptions({
name: "cl-sticky"
});
defineSlots<{
default(): any;
content(props: { isSticky: boolean }): any;
}>();
const props = defineProps({
offsetTop: {
type: Number,
default: 0
},
zIndex: {
type: Number,
default: 100
},
scrollTop: {
type: Number,
default: 0
}
});
const page = usePage();
const { proxy } = getCurrentInstance()!;
// 定义Rect类型表示元素的位置信息
type Rect = {
height: number; // 高度
width: number; // 宽度
left: number; // 距离页面左侧的距离
top: number; // 距离页面顶部的距离
};
// rect为响应式对象存储当前sticky元素的位置信息
const rect = reactive<Rect>({
height: 0,
width: 0,
left: 0,
top: 0
});
// scrollTop为当前页面滚动的距离
const scrollTop = ref(0);
// 计算属性,判断当前是否处于吸顶状态
const isSticky = computed(() => {
if (rect.height == 0) return false;
return scrollTop.value >= rect.top;
});
// 计算属性返回sticky元素的top值吸顶时的偏移量
const stickyTop = computed(() => {
if (isSticky.value) {
let v = 0;
// #ifdef H5
// H5端默认导航栏高度为44
if (!router.isCustomNavbarPage()) {
v = 44;
}
// #endif
return v + props.offsetTop;
} else {
return 0;
}
});
// 获取sticky元素的位置信息并更新rect
function getRect() {
nextTick(() => {
uni.createSelectorQuery()
.in(proxy)
.select(".cl-sticky")
.boundingClientRect()
.exec((nodes) => {
if (isEmpty(nodes)) {
return;
}
const node = nodes[0] as NodeInfo;
// 赋值时做空值处理,保证类型安全
rect.height = node.height ?? 0;
rect.width = node.width ?? 0;
rect.left = node.left ?? 0;
// top需要减去offsetTop并加上当前滚动距离保证吸顶准确
rect.top = (node.top ?? 0) - props.offsetTop + scrollTop.value;
});
});
}
// 监听页面滚动事件
page.onPageScroll((top) => {
scrollTop.value = top;
});
onMounted(() => {
getRect();
watch(
computed(() => props.scrollTop),
(top: number) => {
scrollTop.value = top;
},
{
immediate: true
}
);
});
defineExpose({
getRect
});
</script>
<style lang="scss" scoped>
.cl-sticky {
@apply relative;
&.is-active {
@apply fixed;
}
}
</style>