refactor: resizeObserver
parent
7a2f28ac2d
commit
11ffa8dfda
|
@ -116,7 +116,7 @@ const getSlotOptions = () => {
|
||||||
throw Error('使用 .type 直接取值');
|
throw Error('使用 .type 直接取值');
|
||||||
};
|
};
|
||||||
const findDOMNode = instance => {
|
const findDOMNode = instance => {
|
||||||
let node = instance && (instance.$el || instance);
|
let node = instance?.vnode?.el || (instance && (instance.$el || instance));
|
||||||
while (node && !node.tagName) {
|
while (node && !node.tagName) {
|
||||||
node = node.nextSibling;
|
node = node.nextSibling;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
|
@ -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<ResizeObserverState>({
|
||||||
|
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;
|
2
v2-doc
2
v2-doc
|
@ -1 +1 @@
|
||||||
Subproject commit a7013ae87f69dcbcf547f4b023255b8a7a775557
|
Subproject commit 4da56dac85f43ad7a288951cbf09d860ecf93bb4
|
Loading…
Reference in New Issue