| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- <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 } from "@/cool";
- import { computed, getCurrentInstance, nextTick, onMounted, reactive, ref, watch } from "vue";
- import { usePage } from "../../hooks";
- 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 { proxy } = getCurrentInstance()!;
- // cl-page 上下文
- const { onScroll } = usePage();
- // 表示元素的位置信息
- type Rect = {
- height: number; // 高度
- width: number; // 宽度
- left: number; // 距离页面左侧的距离
- top: number; // 距离页面顶部的距离
- };
- // 存储当前sticky元素的位置信息
- const rect = reactive<Rect>({
- height: 0,
- width: 0,
- left: 0,
- top: 0
- });
- // 当前页面滚动的距离
- 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;
- });
- });
- }
- onMounted(() => {
- // 获取元素位置信息
- getRect();
- // 监听页面滚动事件
- onScroll((top) => {
- scrollTop.value = top;
- });
- // 监听参数变化
- 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>
|