import { ref, reactive, computed, CSSProperties } from 'vue'; // Utils import { isHidden, unitToPx, getScrollTop, getElementTop, createNamespace, } from '../utils'; // Composition import { useScrollParent, useEventListener } from '@vant/use'; import { useVisibilityChange } from '../composables/use-visibility-change'; const [createComponent, bem] = createNamespace('sticky'); export default createComponent({ props: { zIndex: [Number, String], container: null, offsetTop: { type: [Number, String], default: 0, }, }, emits: ['scroll'], setup(props, { emit, slots }) { const root = ref(); const scrollParent = useScrollParent(root); const state = reactive({ fixed: false, height: 0, transform: 0, }); const offsetTop = computed(() => unitToPx(props.offsetTop)); const style = computed(() => { if (!state.fixed) { return; } const top = offsetTop.value ? `${offsetTop.value}px` : undefined; const transform = state.transform ? `translate3d(0, ${state.transform}px, 0)` : undefined; return { top, zIndex: props.zIndex !== undefined ? +props.zIndex : undefined, transform, }; }); const emitScrollEvent = (scrollTop: number) => { emit('scroll', { scrollTop, isFixed: state.fixed, }); }; const onScroll = () => { if (!root.value || isHidden(root)) { return; } state.height = root.value.offsetHeight; const { container } = props; const scrollTop = getScrollTop(window); const topToPageTop = getElementTop(root.value); // The sticky component should be kept inside the container element if (container) { const bottomToPageTop = topToPageTop + container.offsetHeight; if (scrollTop + offsetTop.value + state.height > bottomToPageTop) { const distanceToBottom = state.height + scrollTop - bottomToPageTop; if (distanceToBottom < state.height) { state.fixed = true; state.transform = -(distanceToBottom + offsetTop.value); } else { state.fixed = false; } emitScrollEvent(scrollTop); return; } } if (scrollTop + offsetTop.value > topToPageTop) { state.fixed = true; state.transform = 0; } else { state.fixed = false; } emitScrollEvent(scrollTop); }; useEventListener('scroll', onScroll, { target: scrollParent }); useVisibilityChange(root, onScroll); return () => { const { fixed, height } = state; const rootStyle: CSSProperties = { height: fixed ? `${height}px` : undefined, }; return (
{slots.default?.()}
); }; }, });