From 11ffa8dfda7b259ae1be8715e2f1ae9f27720eb6 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Mon, 10 May 2021 22:51:40 +0800 Subject: [PATCH] refactor: resizeObserver --- components/_util/props-util/index.js | 2 +- components/vc-resize-observer/index.jsx | 90 --------------- components/vc-resize-observer/index.tsx | 139 ++++++++++++++++++++++++ v2-doc | 2 +- 4 files changed, 141 insertions(+), 92 deletions(-) delete mode 100644 components/vc-resize-observer/index.jsx create mode 100644 components/vc-resize-observer/index.tsx diff --git a/components/_util/props-util/index.js b/components/_util/props-util/index.js index 86eb35642..068f57d54 100644 --- a/components/_util/props-util/index.js +++ b/components/_util/props-util/index.js @@ -116,7 +116,7 @@ const getSlotOptions = () => { throw Error('使用 .type 直接取值'); }; const findDOMNode = instance => { - let node = instance && (instance.$el || instance); + let node = instance?.vnode?.el || (instance && (instance.$el || instance)); while (node && !node.tagName) { node = node.nextSibling; } diff --git a/components/vc-resize-observer/index.jsx b/components/vc-resize-observer/index.jsx deleted file mode 100644 index 7c171f617..000000000 --- a/components/vc-resize-observer/index.jsx +++ /dev/null @@ -1,90 +0,0 @@ -// based on rc-resize-observer 0.1.3 -import { defineComponent } from 'vue'; -import BaseMixin from '../_util/BaseMixin'; -import { findDOMNode } from '../_util/props-util'; - -// Still need to be compatible with React 15, we use class component here -const VueResizeObserver = defineComponent({ - name: 'ResizeObserver', - mixins: [BaseMixin], - props: { - disabled: Boolean, - onResize: Function, - }, - data() { - this.currentElement = null; - this.resizeObserver = null; - return { - width: 0, - height: 0, - }; - }, - - mounted() { - this.onComponentUpdated(); - }, - - updated() { - this.onComponentUpdated(); - }, - beforeUnmount() { - this.destroyObserver(); - }, - methods: { - onComponentUpdated() { - const { disabled } = this.$props; - - // Unregister if disabled - if (disabled) { - this.destroyObserver(); - return; - } - - // Unregister if element changed - const element = findDOMNode(this); - const elementChanged = element !== this.currentElement; - if (elementChanged) { - this.destroyObserver(); - this.currentElement = element; - } - - if (!this.resizeObserver && element) { - this.resizeObserver = new ResizeObserver(this.handleResize); - this.resizeObserver.observe(element); - } - }, - - handleResize(entries) { - const { target } = entries[0]; - const { width, height } = target.getBoundingClientRect(); - const { offsetWidth, offsetHeight } = target; - /** - * Resize observer trigger when content size changed. - * In most case we just care about element size, - * let's use `boundary` instead of `contentRect` here to avoid shaking. - */ - const fixedWidth = Math.floor(width); - const fixedHeight = Math.floor(height); - - if (this.width !== fixedWidth || this.height !== fixedHeight) { - const size = { width: fixedWidth, height: fixedHeight, offsetWidth, offsetHeight }; - this.width = fixedWidth; - this.height = fixedHeight; - this.__emit('resize', size); - } - }, - - destroyObserver() { - if (this.resizeObserver) { - this.resizeObserver.disconnect(); - this.resizeObserver = null; - } - }, - }, - - render() { - return this.$slots.default?.()[0]; - }, -}); - -export default VueResizeObserver; diff --git a/components/vc-resize-observer/index.tsx b/components/vc-resize-observer/index.tsx new file mode 100644 index 000000000..723f0af52 --- /dev/null +++ b/components/vc-resize-observer/index.tsx @@ -0,0 +1,139 @@ +// based on rc-resize-observer 1.0.0 +import { + defineComponent, + getCurrentInstance, + onMounted, + onUnmounted, + onUpdated, + PropType, + reactive, + watch, +} from 'vue'; +import { findDOMNode } from '../_util/props-util'; + +interface ResizeObserverState { + height: number; + width: number; + offsetHeight: number; + offsetWidth: number; +} + +const VueResizeObserver = defineComponent({ + name: 'ResizeObserver', + props: { + disabled: Boolean, + onResize: Function as PropType< + ( + size: { + width: number; + height: number; + offsetWidth: number; + offsetHeight: number; + }, + element: HTMLElement, + ) => void + >, + }, + emits: ['resize'], + setup(props, { slots }) { + const state = reactive({ + width: 0, + height: 0, + offsetHeight: 0, + offsetWidth: 0, + }); + let currentElement: Element | null = null; + let resizeObserver: ResizeObserver | null = null; + + const destroyObserver = () => { + if (resizeObserver) { + resizeObserver.disconnect(); + resizeObserver = null; + } + }; + + const onResize: ResizeObserverCallback = (entries: ResizeObserverEntry[]) => { + const { onResize } = props; + + const target = entries[0].target as HTMLElement; + + const { width, height } = target.getBoundingClientRect(); + const { offsetWidth, offsetHeight } = target; + + /** + * Resize observer trigger when content size changed. + * In most case we just care about element size, + * let's use `boundary` instead of `contentRect` here to avoid shaking. + */ + const fixedWidth = Math.floor(width); + const fixedHeight = Math.floor(height); + + if ( + state.width !== fixedWidth || + state.height !== fixedHeight || + state.offsetWidth !== offsetWidth || + state.offsetHeight !== offsetHeight + ) { + const size = { width: fixedWidth, height: fixedHeight, offsetWidth, offsetHeight }; + + Object.assign(state, size); + if (onResize) { + // defer the callback but not defer to next frame + Promise.resolve().then(() => { + onResize( + { + ...size, + offsetWidth, + offsetHeight, + }, + target, + ); + }); + } + } + }; + const instance = getCurrentInstance(); + const registerObserver = () => { + const { disabled } = props; + + // Unregister if disabled + if (disabled) { + destroyObserver(); + return; + } + // Unregister if element changed + const element = findDOMNode(instance) as Element; + const elementChanged = element !== currentElement; + if (elementChanged) { + destroyObserver(); + currentElement = element; + } + + if (!resizeObserver && element) { + resizeObserver = new ResizeObserver(onResize); + resizeObserver.observe(element); + } + }; + onMounted(() => { + registerObserver(); + }); + onUpdated(() => { + registerObserver(); + }); + onUnmounted(() => { + destroyObserver(); + }); + watch( + () => props.disabled, + () => { + registerObserver(); + }, + { flush: 'post' }, + ); + return () => { + return slots.default?.()[0]; + }; + }, +}); + +export default VueResizeObserver; diff --git a/v2-doc b/v2-doc index a7013ae87..4da56dac8 160000 --- a/v2-doc +++ b/v2-doc @@ -1 +1 @@ -Subproject commit a7013ae87f69dcbcf547f4b023255b8a7a775557 +Subproject commit 4da56dac85f43ad7a288951cbf09d860ecf93bb4