feat(Sticky): support sticky to bottom (#7979)
This commit is contained in:
+55
-38
@@ -1,20 +1,16 @@
|
||||
import { ref, reactive, computed, CSSProperties } from 'vue';
|
||||
import { computed, CSSProperties, PropType, reactive, ref } from 'vue';
|
||||
|
||||
// Utils
|
||||
import {
|
||||
isHidden,
|
||||
unitToPx,
|
||||
getScrollTop,
|
||||
getElementTop,
|
||||
createNamespace,
|
||||
} from '../utils';
|
||||
import { createNamespace, getScrollTop, isHidden, unitToPx } from '../utils';
|
||||
|
||||
// Composition
|
||||
import { useScrollParent, useEventListener } from '@vant/use';
|
||||
import { useEventListener, useScrollParent } from '@vant/use';
|
||||
import { useVisibilityChange } from '../composables/use-visibility-change';
|
||||
|
||||
const [createComponent, bem] = createNamespace('sticky');
|
||||
|
||||
export type Position = 'top' | 'bottom';
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
zIndex: [Number, String],
|
||||
@@ -23,6 +19,14 @@ export default createComponent({
|
||||
type: [Number, String],
|
||||
default: 0,
|
||||
},
|
||||
offsetBottom: {
|
||||
type: [Number, String],
|
||||
default: 0,
|
||||
},
|
||||
position: {
|
||||
type: String as PropType<Position>,
|
||||
default: 'top',
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['scroll'],
|
||||
@@ -33,24 +37,31 @@ export default createComponent({
|
||||
|
||||
const state = reactive({
|
||||
fixed: false,
|
||||
height: 0,
|
||||
height: 0, // root 高度
|
||||
width: 0, // root 宽度
|
||||
clientHeight: 0, // documentElement clientHeight
|
||||
transform: 0,
|
||||
});
|
||||
|
||||
const offsetTop = computed(() => unitToPx(props.offsetTop));
|
||||
const offsetBottom = computed(() => unitToPx(props.offsetBottom));
|
||||
|
||||
const style = computed<CSSProperties | undefined>(() => {
|
||||
if (!state.fixed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const top = offsetTop.value ? `${offsetTop.value}px` : undefined;
|
||||
const top = offsetTop.value ? `${offsetTop.value}px` : 0;
|
||||
const bottom = offsetBottom.value ? `${offsetBottom.value}px` : 0;
|
||||
const transform = state.transform
|
||||
? `translate3d(0, ${state.transform}px, 0)`
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
top,
|
||||
height: `${state.height}px`,
|
||||
width: `${state.width}px`,
|
||||
top: props.position === 'top' ? top : undefined,
|
||||
bottom: props.position === 'bottom' ? bottom : undefined,
|
||||
zIndex: props.zIndex !== undefined ? +props.zIndex : undefined,
|
||||
transform,
|
||||
};
|
||||
@@ -68,38 +79,43 @@ export default createComponent({
|
||||
return;
|
||||
}
|
||||
|
||||
state.height = root.value.offsetHeight;
|
||||
|
||||
const { container } = props;
|
||||
const rootRect = root.value.getBoundingClientRect();
|
||||
const containerRect = container?.getBoundingClientRect();
|
||||
state.height = rootRect.height;
|
||||
state.width = rootRect.width;
|
||||
state.clientHeight = document.documentElement.clientHeight;
|
||||
|
||||
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 (props.position === 'top') {
|
||||
if (container) {
|
||||
const difference =
|
||||
containerRect.bottom - offsetTop.value - state.height;
|
||||
state.fixed =
|
||||
offsetTop.value > rootRect.top && containerRect.bottom > 0;
|
||||
state.transform = difference < 0 ? difference : 0;
|
||||
} else {
|
||||
state.fixed = offsetTop.value > rootRect.top;
|
||||
}
|
||||
} else if (props.position === 'bottom') {
|
||||
if (container) {
|
||||
const difference =
|
||||
state.clientHeight -
|
||||
containerRect.top -
|
||||
offsetBottom.value -
|
||||
state.height;
|
||||
state.fixed =
|
||||
state.clientHeight - offsetBottom.value < rootRect.bottom &&
|
||||
state.clientHeight > containerRect.top;
|
||||
state.transform = difference < 0 ? -difference : 0;
|
||||
} else {
|
||||
state.fixed =
|
||||
state.clientHeight - offsetBottom.value < rootRect.bottom;
|
||||
}
|
||||
}
|
||||
|
||||
if (scrollTop + offsetTop.value > topToPageTop) {
|
||||
state.fixed = true;
|
||||
state.transform = 0;
|
||||
} else {
|
||||
state.fixed = false;
|
||||
}
|
||||
|
||||
emitScrollEvent(scrollTop);
|
||||
};
|
||||
|
||||
@@ -107,9 +123,10 @@ export default createComponent({
|
||||
useVisibilityChange(root, onScroll);
|
||||
|
||||
return () => {
|
||||
const { fixed, height } = state;
|
||||
const { fixed, height, width } = state;
|
||||
const rootStyle: CSSProperties = {
|
||||
height: fixed ? `${height}px` : undefined,
|
||||
width: fixed ? `${width}px` : undefined,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user