feat(Sticky): support sticky to bottom (#7979)

This commit is contained in:
kooriookami
2021-01-31 16:43:49 +08:00
committed by GitHub
parent 0f3610bafe
commit 6c5315f0b1
8 changed files with 314 additions and 67 deletions
+55 -38
View File
@@ -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 (