feat(use): add useScrollParent

This commit is contained in:
chenjiahan 2020-09-12 22:04:02 +08:00
parent 68817e3aba
commit ecbaddd5e1
8 changed files with 91 additions and 52 deletions

View File

@ -1,3 +1,4 @@
export { useToggle } from './useToggle';
export { useClickAway } from './useClickAway';
export { useScrollParent } from './useScrollParent';
export { useEventListener } from './useEventListener';

View File

@ -0,0 +1,51 @@
import { ref, Ref, onMounted } from 'vue';
type ScrollElement = HTMLElement | Window;
const overflowScrollReg = /scroll|auto/i;
function isElement(node: Element) {
const ELEMENT_NODE_TYPE = 1;
return node.tagName !== 'HTML' && node.nodeType === ELEMENT_NODE_TYPE;
}
// http://w3help.org/zh-cn/causes/SD9013
// http://stackoverflow.com/questions/17016740/onscroll-function-is-not-working-for-chrome
export function getScrollParent(el: Element, root: ScrollElement = window) {
let node = el;
while (node && node !== root && isElement(node)) {
const { overflowY } = window.getComputedStyle(node);
if (overflowScrollReg.test(overflowY)) {
if (node.tagName !== 'BODY') {
return node;
}
// see: https://github.com/youzan/vant/issues/3823
const { overflowY: htmlOverflowY } = window.getComputedStyle(
node.parentNode as Element
);
if (overflowScrollReg.test(htmlOverflowY)) {
return node;
}
}
node = node.parentNode as Element;
}
return root;
}
export function useScrollParent(el: Ref<Element>) {
const scrollParent = ref();
onMounted(() => {
if (el.value) {
scrollParent.value = getScrollParent(el.value);
}
});
return scrollParent;
}

View File

@ -1,14 +0,0 @@
import { Ref, ref, onMounted } from 'vue';
import { getScroller } from '../utils/dom/scroll';
export function useScroller(el: Ref<HTMLElement>) {
const scrollerRef = ref();
onMounted(() => {
if (el.value) {
scrollerRef.value = getScroller(el.value);
}
});
return scrollerRef;
}

View File

@ -4,9 +4,8 @@ import { ref, provide, reactive, computed } from 'vue';
import { createNamespace, isDef } from '../utils';
// Composition
import { useClickAway, useEventListener } from '@vant/use';
import { useClickAway, useScrollParent, useEventListener } from '@vant/use';
import { useRect } from '../composition/use-rect';
import { useScroller } from '../composition/use-scroller';
const [createComponent, bem] = createNamespace('dropdown-menu');
@ -40,7 +39,7 @@ export default createComponent({
const rootRef = ref();
const children = reactive([]);
const scroller = useScroller(rootRef);
const scrollParent = useScrollParent(rootRef);
const opened = computed(() =>
children.some((item) => item.state.showWrapper)
@ -123,7 +122,7 @@ export default createComponent({
useClickAway(rootRef, onClickAway);
useEventListener('scroll', onScroll, { target: scroller });
useEventListener('scroll', onScroll, { target: scrollParent });
return () => (
<div ref={rootRef} class={bem()}>

View File

@ -12,10 +12,9 @@ import {
} from '../utils/dom/scroll';
// Composition
import { useEventListener } from '@vant/use';
import { useScrollParent, useEventListener } from '@vant/use';
import { useRect } from '../composition/use-rect';
import { useTouch } from '../composition/use-touch';
import { useScroller } from '../composition/use-scroller';
export const INDEX_BAR_KEY = 'vanIndexBar';
@ -58,7 +57,7 @@ export default createComponent({
const children = reactive([]);
const touch = useTouch();
const scroller = useScroller(rootRef);
const scrollParent = useScrollParent(rootRef);
provide(INDEX_BAR_KEY, { props, children });
@ -79,8 +78,8 @@ export default createComponent({
});
const getScrollerRect = () => {
if (scroller.value.getBoundingClientRect) {
return useRect(scroller);
if (scrollParent.value.getBoundingClientRect) {
return useRect(scrollParent);
}
return {
top: 0,
@ -88,13 +87,16 @@ export default createComponent({
};
};
const getAnchorTop = (element, scrollerRect) => {
if (scroller.value === window || scroller.value === document.body) {
const getAnchorTop = (element, scrollParentRect) => {
if (
scrollParent.value === window ||
scrollParent.value === document.body
) {
return getElementTop(element);
}
const rect = useRect(element);
return rect.top - scrollerRect.top + getScrollTop(scroller);
return rect.top - scrollParentRect.top + getScrollTop(scrollParent);
};
const getActiveAnchor = (scrollTop, rects) => {
@ -116,12 +118,12 @@ export default createComponent({
}
const { sticky, indexList } = props;
const scrollTop = getScrollTop(scroller.value);
const scrollerRect = getScrollerRect();
const scrollTop = getScrollTop(scrollParent.value);
const scrollParentRect = getScrollerRect();
const rects = children.map((item) => ({
height: item.height,
top: getAnchorTop(item.rootRef, scrollerRect),
top: getAnchorTop(item.rootRef, scrollParentRect),
}));
const active = getActiveAnchor(scrollTop, rects);
@ -144,11 +146,11 @@ export default createComponent({
state.active = true;
state.top =
Math.max(props.stickyOffsetTop, rects[index].top - scrollTop) +
scrollerRect.top;
scrollParentRect.top;
} else if (index === active - 1) {
const activeItemTop = rects[active].top - scrollTop;
state.active = activeItemTop > 0;
state.top = activeItemTop + scrollerRect.top - height;
state.top = activeItemTop + scrollParentRect.top - height;
} else {
state.active = false;
}
@ -156,7 +158,7 @@ export default createComponent({
}
};
useEventListener('scroll', onScroll, { target: scroller });
useEventListener('scroll', onScroll, { target: scrollParent });
watch(
() => props.indexList,

View File

@ -5,9 +5,8 @@ import { createNamespace } from '../utils';
import { isHidden } from '../utils/dom/style';
// Composition
import { useEventListener } from '@vant/use';
import { useScrollParent, useEventListener } from '@vant/use';
import { useRect } from '../composition/use-rect';
import { useScroller } from '../composition/use-scroller';
import { usePublicApi } from '../composition/use-public-api';
// Components
@ -44,7 +43,7 @@ export default createComponent({
const loading = ref(false);
const rootRef = ref();
const placeholderRef = ref();
const scroller = useScroller(rootRef);
const scrollParent = useScrollParent(rootRef);
const check = () => {
nextTick(() => {
@ -53,21 +52,22 @@ export default createComponent({
}
const { offset, direction } = props;
let scrollerRect;
let scrollParentRect;
if (scroller.value.getBoundingClientRect) {
scrollerRect = scroller.value.getBoundingClientRect();
if (scrollParent.value.getBoundingClientRect) {
scrollParentRect = scrollParent.value.getBoundingClientRect();
} else {
scrollerRect = {
scrollParentRect = {
top: 0,
bottom: scroller.value.innerHeight,
bottom: scrollParent.value.innerHeight,
};
}
const scrollerHeight = scrollerRect.bottom - scrollerRect.top;
const scrollParentHeight =
scrollParentRect.bottom - scrollParentRect.top;
/* istanbul ignore next */
if (!scrollerHeight || isHidden(rootRef.value)) {
if (!scrollParentHeight || isHidden(rootRef.value)) {
return false;
}
@ -75,9 +75,10 @@ export default createComponent({
const placeholderRect = useRect(placeholderRef);
if (direction === 'up') {
isReachEdge = scrollerRect.top - placeholderRect.top <= offset;
isReachEdge = scrollParentRect.top - placeholderRect.top <= offset;
} else {
isReachEdge = placeholderRect.bottom - scrollerRect.bottom <= offset;
isReachEdge =
placeholderRect.bottom - scrollParentRect.bottom <= offset;
}
if (isReachEdge) {
@ -143,7 +144,7 @@ export default createComponent({
usePublicApi({ check });
useEventListener('scroll', check, { target: scroller });
useEventListener('scroll', check, { target: scrollParent });
return () => {
const Content = slots.default?.();

View File

@ -6,8 +6,8 @@ import { getScrollTop } from '../utils/dom/scroll';
import { preventDefault } from '../utils/dom/event';
// Composition
import { useScrollParent } from '@vant/use';
import { useTouch } from '../composition/use-touch';
import { useScroller } from '../composition/use-scroller';
// Components
import Loading from '../loading';
@ -48,7 +48,7 @@ export default createComponent({
let reachTop;
const rootRef = ref();
const scroller = useScroller(rootRef);
const scrollParent = useScrollParent(rootRef);
const state = reactive({
status: 'normal',
@ -130,7 +130,7 @@ export default createComponent({
};
const checkPosition = (event) => {
reachTop = getScrollTop(scroller.value) === 0;
reachTop = getScrollTop(scrollParent.value) === 0;
if (reachTop) {
state.duration = 0;

View File

@ -7,8 +7,7 @@ import { createNamespace } from '../utils';
import { getScrollTop, getElementTop } from '../utils/dom/scroll';
// Composition
import { useEventListener } from '@vant/use';
import { useScroller } from '../composition/use-scroller';
import { useScrollParent, useEventListener } from '@vant/use';
import { useVisibilityChange } from '../composition/use-visibility-change';
const [createComponent, bem] = createNamespace('sticky');
@ -27,7 +26,7 @@ export default createComponent({
setup(props, { emit, slots }) {
const rootRef = ref();
const scroller = useScroller(rootRef);
const scrollParent = useScrollParent(rootRef);
const state = reactive({
fixed: false,
@ -101,7 +100,7 @@ export default createComponent({
emitScrollEvent(scrollTop);
};
useEventListener('scroll', onScroll, { target: scroller });
useEventListener('scroll', onScroll, { target: scrollParent });
useVisibilityChange(rootRef, onScroll);