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

View File

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

View File

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

View File

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

View File

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