mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-05-14 19:08:56 +08:00
125 lines
3.0 KiB
TypeScript
125 lines
3.0 KiB
TypeScript
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<HTMLElement>();
|
|
const scrollParent = useScrollParent(root);
|
|
|
|
const state = reactive({
|
|
fixed: false,
|
|
height: 0,
|
|
transform: 0,
|
|
});
|
|
|
|
const offsetTop = computed(() => unitToPx(props.offsetTop));
|
|
|
|
const style = computed<CSSProperties | undefined>(() => {
|
|
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 (
|
|
<div ref={root} style={rootStyle}>
|
|
<div class={bem({ fixed })} style={style.value}>
|
|
{slots.default?.()}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
},
|
|
});
|