From 0d5b53d2a58930de0a1c8e70a8653220dff0a8d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=98=89=E6=B6=B5?= Date: Sat, 11 Jan 2020 21:13:54 +0800 Subject: [PATCH 1/5] feat: add use-handler hook --- src/hooks/use-handler.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/hooks/use-handler.ts diff --git a/src/hooks/use-handler.ts b/src/hooks/use-handler.ts new file mode 100644 index 000000000..8fb12d6cc --- /dev/null +++ b/src/hooks/use-handler.ts @@ -0,0 +1,30 @@ +import { on, off } from '../utils'; +import { onMounted, onActivated, onUnmounted, onDeactivated } from 'vue'; + +export function useHandler( + target: HTMLElement | Document | Window, + event: string, + handler: any, + passive = false +) { + let added: boolean; + + function add() { + if (!added) { + on(target, event, handler, passive); + added = true; + } + } + + function remove() { + if (added) { + off(target, event, handler); + added = false; + } + } + + onMounted(add); + onActivated(add); + onUnmounted(remove); + onDeactivated(remove); +} From b6549ed7213ec5bc2dfeb66c0833aed27d43b1c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=98=89=E6=B6=B5?= Date: Sun, 12 Jan 2020 08:11:15 +0800 Subject: [PATCH 2/5] feat: useHandler add ref param --- src/hooks/use-handler.ts | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/hooks/use-handler.ts b/src/hooks/use-handler.ts index 8fb12d6cc..cd0a920b9 100644 --- a/src/hooks/use-handler.ts +++ b/src/hooks/use-handler.ts @@ -1,28 +1,44 @@ -import { on, off } from '../utils'; -import { onMounted, onActivated, onUnmounted, onDeactivated } from 'vue'; +import { on, off } from '../utils/dom/event'; +import { + Ref, + watch, + onMounted, + onActivated, + onUnmounted, + onDeactivated +} from 'vue'; export function useHandler( target: HTMLElement | Document | Window, event: string, handler: any, - passive = false + passive = false, + ref?: Ref ) { - let added: boolean; + let binded: boolean; function add() { - if (!added) { - on(target, event, handler, passive); - added = true; + if (binded || (ref && !ref.value)) { + return; } + + on(target, event, handler, passive); + binded = true; } function remove() { - if (added) { + if (binded) { off(target, event, handler); - added = false; + binded = false; } } + if (ref) { + watch(() => { + ref.value ? add() : remove(); + }); + } + onMounted(add); onActivated(add); onUnmounted(remove); From f329fd9f6f1a6c42ccbd62877194fa9e412eb860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=98=89=E6=B6=B5?= Date: Sun, 12 Jan 2020 08:58:58 +0800 Subject: [PATCH 3/5] feat: add use-click-outside hook --- src/hooks/use-click-outside.ts | 23 +++++++++++++++++++++++ src/hooks/use-handler.ts | 12 ++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 src/hooks/use-click-outside.ts diff --git a/src/hooks/use-click-outside.ts b/src/hooks/use-click-outside.ts new file mode 100644 index 000000000..0e97c1b47 --- /dev/null +++ b/src/hooks/use-click-outside.ts @@ -0,0 +1,23 @@ +import { Ref, onMounted } from 'vue'; +import { useHandler } from './use-handler'; + +export type UseClickOutsideOpitons = { + event: string; + callback: EventListener; + element: Ref; + flag?: Ref; +}; + +export function useClickOutside(options: UseClickOutsideOpitons) { + const { event = 'click', callback, element, flag } = options; + + function onClick(event: Event) { + if (!element.value.contains(event.target as Node)) { + callback(event); + } + } + + onMounted(() => { + useHandler(document, event, onClick, false, flag); + }); +} diff --git a/src/hooks/use-handler.ts b/src/hooks/use-handler.ts index cd0a920b9..0ea54b909 100644 --- a/src/hooks/use-handler.ts +++ b/src/hooks/use-handler.ts @@ -9,16 +9,16 @@ import { } from 'vue'; export function useHandler( - target: HTMLElement | Document | Window, + target: EventTarget, event: string, - handler: any, + handler: EventListener, passive = false, - ref?: Ref + flag?: Ref ) { let binded: boolean; function add() { - if (binded || (ref && !ref.value)) { + if (binded || (flag && !flag.value)) { return; } @@ -33,9 +33,9 @@ export function useHandler( } } - if (ref) { + if (flag) { watch(() => { - ref.value ? add() : remove(); + flag.value ? add() : remove(); }); } From 0dfce17f6ca01d91db1e6e665670f69e619b3989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=98=89=E6=B6=B5?= Date: Mon, 13 Jan 2020 15:40:38 +0800 Subject: [PATCH 4/5] feat: add use-touch hook --- src/hooks/use-click-outside.ts | 8 +-- .../{use-handler.ts => use-global-event.ts} | 2 +- src/hooks/use-touch.ts | 63 +++++++++++++++++++ 3 files changed, 67 insertions(+), 6 deletions(-) rename src/hooks/{use-handler.ts => use-global-event.ts} (95%) create mode 100644 src/hooks/use-touch.ts diff --git a/src/hooks/use-click-outside.ts b/src/hooks/use-click-outside.ts index 0e97c1b47..db64eb2a0 100644 --- a/src/hooks/use-click-outside.ts +++ b/src/hooks/use-click-outside.ts @@ -1,5 +1,5 @@ -import { Ref, onMounted } from 'vue'; -import { useHandler } from './use-handler'; +import { Ref } from 'vue'; +import { useGlobalEvent } from './use-global-event'; export type UseClickOutsideOpitons = { event: string; @@ -17,7 +17,5 @@ export function useClickOutside(options: UseClickOutsideOpitons) { } } - onMounted(() => { - useHandler(document, event, onClick, false, flag); - }); + useGlobalEvent(document, event, onClick, false, flag); } diff --git a/src/hooks/use-handler.ts b/src/hooks/use-global-event.ts similarity index 95% rename from src/hooks/use-handler.ts rename to src/hooks/use-global-event.ts index 0ea54b909..319c043bf 100644 --- a/src/hooks/use-handler.ts +++ b/src/hooks/use-global-event.ts @@ -8,7 +8,7 @@ import { onDeactivated } from 'vue'; -export function useHandler( +export function useGlobalEvent( target: EventTarget, event: string, handler: EventListener, diff --git a/src/hooks/use-touch.ts b/src/hooks/use-touch.ts new file mode 100644 index 000000000..aaaa5fb5c --- /dev/null +++ b/src/hooks/use-touch.ts @@ -0,0 +1,63 @@ +import { ref } from 'vue'; + +const MIN_DISTANCE = 10; + +function getDirection(x: number, y: number) { + if (x > y && x > MIN_DISTANCE) { + return 'horizontal'; + } + + if (y > x && y > MIN_DISTANCE) { + return 'vertical'; + } + + return ''; +} + +export function useTouch() { + const startX = ref(0); + const startY = ref(0); + const deltaX = ref(0); + const deltaY = ref(0); + const offsetX = ref(0); + const offsetY = ref(0); + const direction = ref(''); + + function reset() { + direction.value = ''; + deltaX.value = 0; + deltaY.value = 0; + offsetX.value = 0; + offsetY.value = 0; + } + + function start(event: TouchEvent) { + reset(); + startX.value = event.touches[0].clientX; + startY.value = event.touches[0].clientY; + } + + function move(event: TouchEvent) { + const touch = event.touches[0]; + deltaX.value = touch.clientX - this.startX; + deltaY.value = touch.clientY - this.startY; + offsetX.value = Math.abs(this.deltaX); + offsetY.value = Math.abs(this.deltaY); + + if (!direction.value) { + direction.value = getDirection(offsetX.value, offsetY.value); + } + } + + return { + move, + start, + startX, + startY, + deltaX, + deltaY, + offsetX, + offsetY, + direction + }; +} From d9d63959f5e88d1a17071a66d859228c99338b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=98=89=E6=B6=B5?= Date: Mon, 13 Jan 2020 16:37:27 +0800 Subject: [PATCH 5/5] feat: add use-lock-scroll hook --- src/dropdown-menu/index.js | 4 +-- src/hooks/use-lock-scroll.ts | 55 ++++++++++++++++++++++++++++++++++++ src/index-bar/index.js | 4 +-- src/list/index.js | 4 +-- src/mixins/popup/index.js | 4 +-- src/pull-refresh/index.js | 4 +-- src/sticky/index.js | 4 +-- src/utils/dom/scroll.ts | 2 +- 8 files changed, 68 insertions(+), 13 deletions(-) create mode 100644 src/hooks/use-lock-scroll.ts diff --git a/src/dropdown-menu/index.js b/src/dropdown-menu/index.js index e8bdab20c..e1bf411cd 100644 --- a/src/dropdown-menu/index.js +++ b/src/dropdown-menu/index.js @@ -2,7 +2,7 @@ import { createNamespace } from '../utils'; import { BORDER_TOP_BOTTOM } from '../utils/constant'; import { ParentMixin } from '../mixins/relation'; import { ClickOutsideMixin } from '../mixins/click-outside'; -import { getScrollEventTarget } from '../utils/dom/scroll'; +import { getScroller } from '../utils/dom/scroll'; const [createComponent, bem] = createNamespace('dropdown-menu'); @@ -47,7 +47,7 @@ export default createComponent({ computed: { scroller() { - return getScrollEventTarget(this.$el); + return getScroller(this.$el); } }, diff --git a/src/hooks/use-lock-scroll.ts b/src/hooks/use-lock-scroll.ts new file mode 100644 index 000000000..9783b2090 --- /dev/null +++ b/src/hooks/use-lock-scroll.ts @@ -0,0 +1,55 @@ +import { useTouch } from './use-touch'; +import { getScroller } from '../utils/dom/scroll'; +import { on, off, preventDefault } from '../utils/dom/event'; + +let count = 0; +const CLASSNAME = 'van-overflow-hidden'; + +export function useLockScroll(element: HTMLElement) { + const { start, move, deltaY, direction } = useTouch(); + + function onTouchMove(event: TouchEvent) { + move(event); + + if (direction.value !== 'vertical') { + return; + } + + let prevent = false; + const up = deltaY.value < 0; + const scroller = getScroller(event.target as HTMLElement, element); + const { scrollTop, scrollHeight, offsetHeight } = scroller as HTMLElement; + + if (scrollTop === 0) { + prevent = up && offsetHeight < scrollHeight; + } else if (scrollTop + offsetHeight >= scrollHeight) { + prevent = !up; + } + + if (prevent) { + preventDefault(event, true); + } + } + + function lock() { + if (!count) { + document.body.classList.add(CLASSNAME); + } + + count++; + on(document, 'touchstart', start); + on(document, 'touchmove', onTouchMove); + } + + lock(); + + return function unlock() { + count--; + off(document, 'touchstart', start); + off(document, 'touchmove', onTouchMove); + + if (!count) { + document.body.classList.remove(CLASSNAME); + } + }; +} diff --git a/src/index-bar/index.js b/src/index-bar/index.js index 2085467d1..d804b147a 100644 --- a/src/index-bar/index.js +++ b/src/index-bar/index.js @@ -10,7 +10,7 @@ import { getElementTop, getRootScrollTop, setRootScrollTop, - getScrollEventTarget + getScroller } from '../utils/dom/scroll'; const [createComponent, bem] = createNamespace('index-bar'); @@ -21,7 +21,7 @@ export default createComponent({ ParentMixin('vanIndexBar'), BindEventMixin(function(bind) { if (!this.scroller) { - this.scroller = getScrollEventTarget(this.$el); + this.scroller = getScroller(this.$el); } bind(this.scroller, 'scroll', this.onScroll); diff --git a/src/list/index.js b/src/list/index.js index 4478a945c..e0ab4da67 100644 --- a/src/list/index.js +++ b/src/list/index.js @@ -1,7 +1,7 @@ import { createNamespace } from '../utils'; import { isHidden } from '../utils/dom/style'; import { BindEventMixin } from '../mixins/bind-event'; -import { getScrollEventTarget } from '../utils/dom/scroll'; +import { getScroller } from '../utils/dom/scroll'; import Loading from '../loading'; const [createComponent, bem, t] = createNamespace('list'); @@ -10,7 +10,7 @@ export default createComponent({ mixins: [ BindEventMixin(function(bind) { if (!this.scroller) { - this.scroller = getScrollEventTarget(this.$el); + this.scroller = getScroller(this.$el); } bind(this.scroller, 'scroll', this.check); diff --git a/src/mixins/popup/index.js b/src/mixins/popup/index.js index 1b64cecf7..b700bccc6 100644 --- a/src/mixins/popup/index.js +++ b/src/mixins/popup/index.js @@ -4,7 +4,7 @@ import { PortalMixin } from '../portal'; import { CloseOnPopstateMixin } from '../close-on-popstate'; import { on, off, preventDefault } from '../../utils/dom/event'; import { openOverlay, closeOverlay, updateOverlay } from './overlay'; -import { getScrollEventTarget } from '../../utils/dom/scroll'; +import { getScroller } from '../../utils/dom/scroll'; export const popupMixinProps = { // whether to show popup @@ -152,7 +152,7 @@ export function PopupMixin(options = {}) { onTouchMove(event) { this.touchMove(event); const direction = this.deltaY > 0 ? '10' : '01'; - const el = getScrollEventTarget(event.target, this.$el); + const el = getScroller(event.target, this.$el); const { scrollHeight, offsetHeight, scrollTop } = el; let status = '11'; diff --git a/src/pull-refresh/index.js b/src/pull-refresh/index.js index 7215870ea..c83f20e16 100644 --- a/src/pull-refresh/index.js +++ b/src/pull-refresh/index.js @@ -1,7 +1,7 @@ import { createNamespace } from '../utils'; import { preventDefault } from '../utils/dom/event'; import { TouchMixin } from '../mixins/touch'; -import { getScrollTop, getScrollEventTarget } from '../utils/dom/scroll'; +import { getScrollTop, getScroller } from '../utils/dom/scroll'; import Loading from '../loading'; const [createComponent, bem, t] = createNamespace('pull-refresh'); @@ -76,7 +76,7 @@ export default createComponent({ mounted() { this.bindTouchEvent(this.$refs.track); - this.scrollEl = getScrollEventTarget(this.$el); + this.scrollEl = getScroller(this.$el); }, methods: { diff --git a/src/sticky/index.js b/src/sticky/index.js index 47eefbca2..5f71fad58 100644 --- a/src/sticky/index.js +++ b/src/sticky/index.js @@ -1,6 +1,6 @@ import { createNamespace, isDef } from '../utils'; import { BindEventMixin } from '../mixins/bind-event'; -import { getScrollTop, getElementTop, getScrollEventTarget } from '../utils/dom/scroll'; +import { getScrollTop, getElementTop, getScroller } from '../utils/dom/scroll'; const [createComponent, bem] = createNamespace('sticky'); @@ -8,7 +8,7 @@ export default createComponent({ mixins: [ BindEventMixin(function(bind) { if (!this.scroller) { - this.scroller = getScrollEventTarget(this.$el); + this.scroller = getScroller(this.$el); } bind(this.scroller, 'scroll', this.onScroll, true); diff --git a/src/utils/dom/scroll.ts b/src/utils/dom/scroll.ts index 544dfae1e..44a651bc1 100644 --- a/src/utils/dom/scroll.ts +++ b/src/utils/dom/scroll.ts @@ -4,7 +4,7 @@ type ScrollElement = HTMLElement | Window; // http://w3help.org/zh-cn/causes/SD9013 // http://stackoverflow.com/questions/17016740/onscroll-function-is-not-working-for-chrome const overflowScrollReg = /scroll|auto/i; -export function getScrollEventTarget(element: HTMLElement, rootParent: ScrollElement = window) { +export function getScroller(element: HTMLElement, rootParent: ScrollElement = window) { let node = element; while ( node &&