perf: perf animation

pull/2992/head
tanjinzhou 2020-10-10 13:57:37 +08:00
parent 22b72855e5
commit 103a05a471
6 changed files with 88 additions and 37 deletions

View File

@ -2,10 +2,11 @@ import { nextTick } from 'vue';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import { alignElement, alignPoint } from 'dom-align'; import { alignElement, alignPoint } from 'dom-align';
import addEventListener from '../vc-util/Dom/addEventListener'; import addEventListener from '../vc-util/Dom/addEventListener';
import { isWindow, buffer, isSamePoint, isSimilarValue, restoreFocus } from './util'; import { isWindow, buffer, isSamePoint, restoreFocus, monitorResize } from './util';
import { cloneElement } from '../_util/vnode'; import { cloneElement } from '../_util/vnode';
import clonedeep from 'lodash-es/cloneDeep'; import clonedeep from 'lodash-es/cloneDeep';
import { getSlot, findDOMNode } from '../_util/props-util'; import { getSlot, findDOMNode } from '../_util/props-util';
import useBuffer from './hooks/useBuffer';
function getElement(func) { function getElement(func) {
if (typeof func !== 'function' || !func) return null; if (typeof func !== 'function' || !func) return null;
@ -28,8 +29,14 @@ export default {
}, },
data() { data() {
this.aligned = false; this.aligned = false;
this.sourceResizeMonitor = { cancel: () => {} };
this.resizeMonitor = { cancel: () => {} };
this.prevProps = { ...this.$props }; this.prevProps = { ...this.$props };
return {}; const [forceAlign, cancelForceAlign] = useBuffer(this.goAlign, 0);
return {
forceAlign,
cancelForceAlign,
};
}, },
mounted() { mounted() {
nextTick(() => { nextTick(() => {
@ -39,6 +46,7 @@ export default {
if (!props.disabled && props.monitorWindowResize) { if (!props.disabled && props.monitorWindowResize) {
this.startMonitorWindowResize(); this.startMonitorWindowResize();
} }
this.startMonitorElementResize();
}); });
}, },
updated() { updated() {
@ -47,9 +55,6 @@ export default {
const props = this.$props; const props = this.$props;
let reAlign = false; let reAlign = false;
if (!props.disabled) { if (!props.disabled) {
const source = findDOMNode(this);
const sourceRect = source ? source.getBoundingClientRect() : null;
if (prevProps.disabled) { if (prevProps.disabled) {
reAlign = true; reAlign = true;
} else { } else {
@ -61,30 +66,18 @@ export default {
// Skip if is window // Skip if is window
reAlign = false; reAlign = false;
} else if ( } else if (
lastElement !== currentElement || // Element change
(lastElement && !currentElement && currentPoint) || // Change from element to point (lastElement && !currentElement && currentPoint) || // Change from element to point
(lastPoint && currentPoint && currentElement) || // Change from point to element (lastPoint && currentPoint && currentElement) // Change from point to element
(currentPoint && !isSamePoint(lastPoint, currentPoint))
) {
reAlign = true;
}
// If source element size changed
const preRect = this.sourceRect || {};
if (
!reAlign &&
source &&
(!isSimilarValue(preRect.width, sourceRect.width) ||
!isSimilarValue(preRect.height, sourceRect.height))
) { ) {
reAlign = true; reAlign = true;
} }
} }
this.sourceRect = { width: sourceRect.width, height: sourceRect.height };
} }
if (reAlign) { if (reAlign) {
this.forceAlign(); this.forceAlign();
} else {
this.startMonitorElementResize();
} }
if (props.monitorWindowResize && !props.disabled) { if (props.monitorWindowResize && !props.disabled) {
@ -97,12 +90,34 @@ export default {
}, },
beforeUnmount() { beforeUnmount() {
this.stopMonitorWindowResize(); this.stopMonitorWindowResize();
this.resizeMonitor?.cancel();
this.sourceResizeMonitor?.cancel();
this.cancelForceAlign();
}, },
methods: { methods: {
// TODO
startMonitorElementResize() { startMonitorElementResize() {
const currentElement = getElement(this.$props.target); const prevProps = this.prevProps;
const element = findDOMNode(this); const props = this.$props;
const lastElement = getElement(prevProps.target);
const currentElement = getElement(props.target);
const lastPoint = getPoint(prevProps.target);
const currentPoint = getPoint(props.target);
const source = findDOMNode(this);
const { sourceResizeMonitor, resizeMonitor } = this;
if (source !== sourceResizeMonitor.element) {
sourceResizeMonitor?.cancel();
sourceResizeMonitor.element = source;
sourceResizeMonitor.cancel = monitorResize(source, this.forceAlign);
}
if (lastElement !== currentElement || !isSamePoint(lastPoint, currentPoint)) {
//this.forceAlign();
// Add resize observer
if (resizeMonitor.element !== currentElement) {
resizeMonitor?.cancel();
resizeMonitor.element = currentElement;
resizeMonitor.cancel = monitorResize(currentElement, this.forceAlign);
}
}
}, },
startMonitorWindowResize() { startMonitorWindowResize() {
if (!this.resizeHandler) { if (!this.resizeHandler) {
@ -118,8 +133,7 @@ export default {
this.resizeHandler = null; this.resizeHandler = null;
} }
}, },
goAlign() {
forceAlign() {
const { disabled, target, align } = this.$props; const { disabled, target, align } = this.$props;
if (!disabled && target) { if (!disabled && target) {
const source = findDOMNode(this); const source = findDOMNode(this);
@ -138,8 +152,10 @@ export default {
} }
restoreFocus(activeElement, source); restoreFocus(activeElement, source);
this.aligned = true; this.aligned = true;
this.$attrs.onAlign && this.$attrs.onAlign(source, result); this.$attrs.onAlign && result && this.$attrs.onAlign(source, result);
return true;
} }
return false;
}, },
}, },

View File

@ -0,0 +1,37 @@
export default (callback: () => boolean, buffer: number) => {
let called = false;
let timeout = null;
function cancelTrigger() {
window.clearTimeout(timeout);
}
function trigger(force?: boolean) {
if (!called || force === true) {
if (callback() === false) {
// Not delay since callback cancelled self
return;
}
called = true;
cancelTrigger();
timeout = window.setTimeout(() => {
called = false;
}, buffer);
} else {
cancelTrigger();
timeout = window.setTimeout(() => {
called = false;
trigger();
}, buffer);
}
}
return [
trigger,
() => {
called = false;
cancelTrigger();
},
];
};

View File

@ -90,7 +90,7 @@ const SelectTrigger = defineComponent<SelectTriggerProps>({
dropdownRender, dropdownRender,
animation, animation,
transitionName, transitionName,
} = props; } = props as SelectTriggerProps;
const dropdownPrefixCls = `${prefixCls}-dropdown`; const dropdownPrefixCls = `${prefixCls}-dropdown`;
let popupNode = popupElement; let popupNode = popupElement;

View File

@ -65,7 +65,7 @@ export interface SelectorProps {
maxTagCount?: number; maxTagCount?: number;
maxTagTextLength?: number; maxTagTextLength?: number;
maxTagPlaceholder?: VNodeChild; maxTagPlaceholder?: VNodeChild;
tagRender?: (props: CustomTagProps) => VNode; tagRender?: (props: CustomTagProps) => VNodeChild;
/** Check if `tokenSeparators` contains `\n` or `\r\n` */ /** Check if `tokenSeparators` contains `\n` or `\r\n` */
tokenWithEnter?: boolean; tokenWithEnter?: boolean;
@ -225,7 +225,7 @@ const Selector = defineComponent<SelectorProps>({
onInputChange, onInputChange,
onInputCompositionStart, onInputCompositionStart,
onInputCompositionEnd, onInputCompositionEnd,
} = this; } = this as any;
const sharedProps = { const sharedProps = {
inputRef, inputRef,
onInputKeyDown: onInternalInputKeyDown, onInputKeyDown: onInternalInputKeyDown,

View File

@ -219,7 +219,7 @@ export default function generateSelector<
key?: Key; key?: Key;
disabled?: boolean; disabled?: boolean;
}[] }[]
>(config: GenerateConfig<OptionsType>): DefineComponent { >(config: GenerateConfig<OptionsType>): DefineComponent<SelectProps<OptionsType, ValueType>> {
const { const {
prefixCls: defaultPrefixCls, prefixCls: defaultPrefixCls,
components: { optionList: OptionList }, components: { optionList: OptionList },
@ -600,11 +600,7 @@ export default function generateSelector<
} }
}; };
useSelectTriggerControl( useSelectTriggerControl([containerRef, triggerRef], triggerOpen, onToggleOpen);
[containerRef.value, triggerRef.value && triggerRef.value.getPopupElement()],
triggerOpen,
onToggleOpen,
);
// ============================= Search ============================= // ============================= Search =============================
const triggerSearch = (searchText: string, fromTyping: boolean, isCompositing: boolean) => { const triggerSearch = (searchText: string, fromTyping: boolean, isCompositing: boolean) => {
@ -923,6 +919,7 @@ export default function generateSelector<
onSearchSubmit, onSearchSubmit,
containerRef, containerRef,
listRef, listRef,
triggerRef,
}; };
}, },
methods: { methods: {
@ -964,7 +961,7 @@ export default function generateSelector<
displayValues, displayValues,
activeValue, activeValue,
onSearchSubmit, onSearchSubmit,
} = this; } = this as any;
const { const {
prefixCls = defaultPrefixCls, prefixCls = defaultPrefixCls,
class: className, class: className,

View File

@ -1,12 +1,13 @@
import { onBeforeUnmount, onMounted, Ref } from 'vue'; import { onBeforeUnmount, onMounted, Ref } from 'vue';
export default function useSelectTriggerControl( export default function useSelectTriggerControl(
elements: (HTMLElement | undefined)[], refs: Ref[],
open: Ref<boolean>, open: Ref<boolean>,
triggerOpen: (open: boolean) => void, triggerOpen: (open: boolean) => void,
) { ) {
function onGlobalMouseDown(event: MouseEvent) { function onGlobalMouseDown(event: MouseEvent) {
const target = event.target as HTMLElement; const target = event.target as HTMLElement;
const elements = [refs[0]?.value, refs[1]?.value?.getPopupElement()];
if ( if (
open.value && open.value &&
elements.every(element => element && !element.contains(target) && element !== target) elements.every(element => element && !element.contains(target) && element !== target)