178 lines
4.9 KiB
Vue
178 lines
4.9 KiB
Vue
|
|
<template>
|
|||
|
|
<view>
|
|||
|
|
<view
|
|||
|
|
class="u-sticky-wrap"
|
|||
|
|
:class="[elClass, customClass]"
|
|||
|
|
:style="
|
|||
|
|
$u.toStyle(
|
|||
|
|
{
|
|||
|
|
height: fixed ? height + 'px' : 'auto',
|
|||
|
|
backgroundColor: bgColor
|
|||
|
|
},
|
|||
|
|
customStyle
|
|||
|
|
)
|
|||
|
|
"
|
|||
|
|
>
|
|||
|
|
<view
|
|||
|
|
class="u-sticky"
|
|||
|
|
:style="{
|
|||
|
|
position: fixed ? 'fixed' : 'static',
|
|||
|
|
top: stickyTop + 'px',
|
|||
|
|
left: left + 'px',
|
|||
|
|
width: width == 'auto' ? 'auto' : width + 'px',
|
|||
|
|
zIndex: uZIndex
|
|||
|
|
}"
|
|||
|
|
>
|
|||
|
|
<slot></slot>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script lang="ts">
|
|||
|
|
export default {
|
|||
|
|
name: 'u-sticky',
|
|||
|
|
options: {
|
|||
|
|
addGlobalClass: true,
|
|||
|
|
// #ifndef MP-TOUTIAO
|
|||
|
|
virtualHost: true,
|
|||
|
|
// #endif
|
|||
|
|
styleIsolation: 'shared'
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<script setup lang="ts">
|
|||
|
|
import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount, getCurrentInstance } from 'vue';
|
|||
|
|
import { $u } from '../..';
|
|||
|
|
import { StickyProps } from './types';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* sticky 吸顶
|
|||
|
|
* @description 该组件与CSS中position: sticky属性实现的效果一致,当组件达到预设的到顶部距离时, 就会固定在指定位置,组件位置大于预设的顶部距离时,会重新按照正常的布局排列。
|
|||
|
|
* @tutorial https://uviewpro.cn/zh/components/sticky.html
|
|||
|
|
* @property {String|Number} offsetTop 吸顶时与顶部的距离,单位rpx(默认0)
|
|||
|
|
* @property {String|Number} index 自定义标识,用于区分是哪一个组件
|
|||
|
|
* @property {Boolean} enable 是否开启吸顶功能(默认true)
|
|||
|
|
* @property {String} bgColor 组件背景颜色(默认var(--u-bg-white))
|
|||
|
|
* @property {String|Number} zIndex 吸顶时的z-index值(默认970)
|
|||
|
|
* @property {String|Number} h5NavHeight 导航栏高度,自定义导航栏时(无导航栏时需设置为0),需要传入此值,单位px(默认44)
|
|||
|
|
* @event fixed 组件吸顶时触发
|
|||
|
|
* @event unfixed 组件取消吸顶时触发
|
|||
|
|
* @example <u-sticky offset-top="200"><view>塞下秋来风景异,衡阳雁去无留意</view></u-sticky>
|
|||
|
|
*/
|
|||
|
|
const props = defineProps(StickyProps);
|
|||
|
|
|
|||
|
|
const emit = defineEmits(['fixed', 'unfixed']);
|
|||
|
|
|
|||
|
|
const instance = getCurrentInstance();
|
|||
|
|
const fixed = ref(false);
|
|||
|
|
const height = ref<'auto' | number>('auto');
|
|||
|
|
const stickyTop = ref(0);
|
|||
|
|
const elClass = ref('');
|
|||
|
|
const left = ref(0);
|
|||
|
|
const width = ref<'auto' | number>('auto');
|
|||
|
|
let contentObserver: any = null;
|
|||
|
|
|
|||
|
|
elClass.value = $u.guid();
|
|||
|
|
|
|||
|
|
const uZIndex = computed(() => (props.zIndex ? props.zIndex : ($u?.zIndex?.sticky ?? 970)));
|
|||
|
|
|
|||
|
|
watch(
|
|||
|
|
() => props.offsetTop,
|
|||
|
|
() => {
|
|||
|
|
initObserver();
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
watch(
|
|||
|
|
() => props.enable,
|
|||
|
|
val => {
|
|||
|
|
if (val == false) {
|
|||
|
|
fixed.value = false;
|
|||
|
|
disconnectObserver('contentObserver');
|
|||
|
|
} else {
|
|||
|
|
initObserver();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
onMounted(() => {
|
|||
|
|
initObserver();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
onBeforeUnmount(() => {
|
|||
|
|
disconnectObserver('contentObserver');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 初始化 IntersectionObserver 监听
|
|||
|
|
*/
|
|||
|
|
function initObserver() {
|
|||
|
|
if (!props.enable) return;
|
|||
|
|
// #ifdef H5
|
|||
|
|
stickyTop.value =
|
|||
|
|
Number(props.offsetTop) !== 0
|
|||
|
|
? uni.upx2px(Number(props.offsetTop)) + Number(props.h5NavHeight)
|
|||
|
|
: Number(props.h5NavHeight);
|
|||
|
|
// #endif
|
|||
|
|
// #ifndef H5
|
|||
|
|
stickyTop.value = Number(props.offsetTop) !== 0 ? uni.upx2px(Number(props.offsetTop)) : 0;
|
|||
|
|
// #endif
|
|||
|
|
disconnectObserver('contentObserver');
|
|||
|
|
$u.getRect('.' + elClass.value, instance).then((res: any) => {
|
|||
|
|
height.value = res.height;
|
|||
|
|
left.value = res.left;
|
|||
|
|
width.value = res.width;
|
|||
|
|
nextTick(() => {
|
|||
|
|
observeContent();
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建 IntersectionObserver 监听内容区域
|
|||
|
|
*/
|
|||
|
|
function observeContent() {
|
|||
|
|
disconnectObserver('contentObserver');
|
|||
|
|
|
|||
|
|
contentObserver = uni.createIntersectionObserver(instance?.proxy, {
|
|||
|
|
thresholds: [0.95, 0.98, 1]
|
|||
|
|
});
|
|||
|
|
contentObserver.relativeToViewport({
|
|||
|
|
top: -stickyTop.value
|
|||
|
|
});
|
|||
|
|
contentObserver.observe('.' + elClass.value, (res: any) => {
|
|||
|
|
if (!props.enable) return;
|
|||
|
|
setFixed(res.boundingClientRect.top);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 设置 fixed 状态
|
|||
|
|
*/
|
|||
|
|
function setFixed(top: number) {
|
|||
|
|
const isFixed = top < stickyTop.value;
|
|||
|
|
if (isFixed) emit('fixed', props.index);
|
|||
|
|
else if (fixed.value) emit('unfixed', props.index);
|
|||
|
|
fixed.value = isFixed;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 断开 observer
|
|||
|
|
*/
|
|||
|
|
function disconnectObserver(observerName: string) {
|
|||
|
|
if (observerName === 'contentObserver' && contentObserver) {
|
|||
|
|
contentObserver.disconnect();
|
|||
|
|
contentObserver = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped lang="scss">
|
|||
|
|
@import '../../libs/css/style.components.scss';
|
|||
|
|
.u-sticky {
|
|||
|
|
z-index: 9999999999;
|
|||
|
|
}
|
|||
|
|
</style>
|