fix: requestAnimationFrame error in ssr, close #4833

pull/4839/head^2
tangjinzhou 2021-11-02 16:07:35 +08:00
parent 7b03f8a739
commit a938bcb4de
15 changed files with 57 additions and 45 deletions

View File

@ -1,5 +1,6 @@
import cssAnimation from './css-animation'; import cssAnimation from './css-animation';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import { requestAnimationTimeout, cancelAnimationTimeout } from './requestAnimationTimeout';
function animate(node, show, done) { function animate(node, show, done) {
let height; let height;
@ -8,7 +9,7 @@ function animate(node, show, done) {
return cssAnimation(node, 'ant-motion-collapse-legacy', { return cssAnimation(node, 'ant-motion-collapse-legacy', {
start() { start() {
if (appearRequestAnimationFrameId) { if (appearRequestAnimationFrameId) {
cancelAnimationFrame(appearRequestAnimationFrameId); cancelAnimationTimeout(appearRequestAnimationFrameId);
} }
if (!show) { if (!show) {
node.style.height = `${node.offsetHeight}px`; node.style.height = `${node.offsetHeight}px`;
@ -18,7 +19,7 @@ function animate(node, show, done) {
// not get offsetHeight when appear // not get offsetHeight when appear
// set it into raf get correct offsetHeight // set it into raf get correct offsetHeight
if (height === 0) { if (height === 0) {
appearRequestAnimationFrameId = requestAnimationFrame(() => { appearRequestAnimationFrameId = requestAnimationTimeout(() => {
height = node.offsetHeight; height = node.offsetHeight;
node.style.height = '0px'; node.style.height = '0px';
node.style.opacity = '0'; node.style.opacity = '0';
@ -31,19 +32,19 @@ function animate(node, show, done) {
}, },
active() { active() {
if (requestAnimationFrameId) { if (requestAnimationFrameId) {
cancelAnimationFrame(requestAnimationFrameId); cancelAnimationTimeout(requestAnimationFrameId);
} }
requestAnimationFrameId = requestAnimationFrame(() => { requestAnimationFrameId = requestAnimationTimeout(() => {
node.style.height = `${show ? height : 0}px`; node.style.height = `${show ? height : 0}px`;
node.style.opacity = show ? '1' : '0'; node.style.opacity = show ? '1' : '0';
}); });
}, },
end() { end() {
if (appearRequestAnimationFrameId) { if (appearRequestAnimationFrameId) {
cancelAnimationFrame(appearRequestAnimationFrameId); cancelAnimationTimeout(appearRequestAnimationFrameId);
} }
if (requestAnimationFrameId) { if (requestAnimationFrameId) {
cancelAnimationFrame(requestAnimationFrameId); cancelAnimationTimeout(requestAnimationFrameId);
} }
node.style.height = ''; node.style.height = '';
node.style.opacity = ''; node.style.opacity = '';

View File

@ -1,13 +1,12 @@
interface RafMap { import getRequestAnimationFrame, { cancelRequestAnimationFrame } from './getRequestAnimationFrame';
[id: number]: number;
}
let id = 0; const oriRaf = getRequestAnimationFrame();
const ids: RafMap = {};
export type RafFrame = {
id: number;
};
// Support call raf with delay specified frame // Support call raf with delay specified frame
export default function raf(callback: () => void, delayFrames = 1): number { export default function raf(callback: () => void, delayFrames = 1): { id: number } {
const myId: number = id++;
let restFrames: number = delayFrames; let restFrames: number = delayFrames;
function internalCallback() { function internalCallback() {
@ -15,22 +14,20 @@ export default function raf(callback: () => void, delayFrames = 1): number {
if (restFrames <= 0) { if (restFrames <= 0) {
callback(); callback();
delete ids[myId];
} else { } else {
ids[myId] = requestAnimationFrame(internalCallback); frame.id = oriRaf(internalCallback);
} }
} }
ids[myId] = requestAnimationFrame(internalCallback); const frame = {
id: oriRaf(internalCallback),
};
return myId; return frame;
} }
raf.cancel = function cancel(pid?: number) { raf.cancel = function cancel(frame?: { id: number }) {
if (pid === undefined) return; if (!frame) return;
cancelAnimationFrame(ids[pid]); cancelRequestAnimationFrame(frame.id);
delete ids[pid];
}; };
raf.ids = ids; // export this for test usage

View File

@ -1,5 +1,8 @@
import type { RafFrame } from './raf';
import raf from './raf';
export default function throttleByAnimationFrame(fn: (...args: any[]) => void) { export default function throttleByAnimationFrame(fn: (...args: any[]) => void) {
let requestId: number | null; let requestId: RafFrame;
const later = (args: any[]) => () => { const later = (args: any[]) => () => {
requestId = null; requestId = null;
@ -8,11 +11,11 @@ export default function throttleByAnimationFrame(fn: (...args: any[]) => void) {
const throttled = (...args: any[]) => { const throttled = (...args: any[]) => {
if (requestId == null) { if (requestId == null) {
requestId = requestAnimationFrame(later(args)); requestId = raf(later(args));
} }
}; };
(throttled as any).cancel = () => cancelAnimationFrame(requestId!); (throttled as any).cancel = () => raf.cancel(requestId!);
return throttled; return throttled;
} }

View File

@ -4,6 +4,7 @@ import { computed, defineComponent, onBeforeUnmount, ref, watch } from 'vue';
import type { MenuMode } from './interface'; import type { MenuMode } from './interface';
import { useInjectMenu } from './hooks/useMenuContext'; import { useInjectMenu } from './hooks/useMenuContext';
import { placements, placementsRtl } from './placements'; import { placements, placementsRtl } from './placements';
import type { RafFrame } from '../../_util/raf';
import raf from '../../_util/raf'; import raf from '../../_util/raf';
import classNames from '../../_util/classNames'; import classNames from '../../_util/classNames';
@ -48,7 +49,7 @@ export default defineComponent({
const popupPlacement = computed(() => popupPlacementMap[props.mode]); const popupPlacement = computed(() => popupPlacementMap[props.mode]);
const visibleRef = ref<number>(); const visibleRef = ref<RafFrame>();
watch( watch(
() => props.visible, () => props.visible,
visible => { visible => {

View File

@ -1,5 +1,6 @@
import { onBeforeUnmount, watch, onActivated, defineComponent, ref } from 'vue'; import { onBeforeUnmount, watch, onActivated, defineComponent, ref } from 'vue';
import Tooltip, { tooltipProps } from '../tooltip'; import Tooltip, { tooltipProps } from '../tooltip';
import type { RafFrame } from '../_util/raf';
import raf from '../_util/raf'; import raf from '../_util/raf';
export default defineComponent({ export default defineComponent({
@ -9,7 +10,7 @@ export default defineComponent({
setup(props, { attrs, slots }) { setup(props, { attrs, slots }) {
const innerRef = ref<any>(null); const innerRef = ref<any>(null);
const rafRef = ref<number | null>(null); const rafRef = ref<RafFrame>(null);
function cancelKeepAlign() { function cancelKeepAlign() {
raf.cancel(rafRef.value!); raf.cancel(rafRef.value!);

View File

@ -1,3 +1,4 @@
import type { RafFrame } from '../../../_util/raf';
import raf from '../../../_util/raf'; import raf from '../../../_util/raf';
import { onMounted, reactive, ref } from 'vue'; import { onMounted, reactive, ref } from 'vue';
@ -5,7 +6,7 @@ type SetActionType<T> = Partial<T> | ((state: T) => Partial<T>);
export default function useFrameSetState<T extends object>( export default function useFrameSetState<T extends object>(
initial: T, initial: T,
): [Record<string, any>, (newState: SetActionType<T>) => void] { ): [Record<string, any>, (newState: SetActionType<T>) => void] {
const frame = ref(null); const frame = ref<RafFrame>(null);
const state = reactive({ ...initial }); const state = reactive({ ...initial });
const queue = ref<SetActionType<T>[]>([]); const queue = ref<SetActionType<T>[]>([]);

View File

@ -1,3 +1,5 @@
import type { RafFrame } from '../../_util/raf';
import raf from '../../_util/raf';
import type { ComputedRef, Ref, UnwrapRef } from 'vue'; import type { ComputedRef, Ref, UnwrapRef } from 'vue';
import { ref, onBeforeUnmount, watch } from 'vue'; import { ref, onBeforeUnmount, watch } from 'vue';
import type { ValueTextConfig } from './useValueTexts'; import type { ValueTextConfig } from './useValueTexts';
@ -8,15 +10,15 @@ export default function useHoverValue<DateType>(
{ formatList, generateConfig, locale }: ValueTextConfig<DateType>, { formatList, generateConfig, locale }: ValueTextConfig<DateType>,
): [ComputedRef<string>, (date: DateType) => void, (immediately?: boolean) => void] { ): [ComputedRef<string>, (date: DateType) => void, (immediately?: boolean) => void] {
const innerValue = ref<DateType>(null); const innerValue = ref<DateType>(null);
const raf = ref(null); let rafId: RafFrame;
function setValue(val: DateType, immediately = false) { function setValue(val: DateType, immediately = false) {
cancelAnimationFrame(raf.value); raf.cancel(rafId);
if (immediately) { if (immediately) {
innerValue.value = val as UnwrapRef<DateType>; innerValue.value = val as UnwrapRef<DateType>;
return; return;
} }
raf.value = requestAnimationFrame(() => { rafId = raf(() => {
innerValue.value = val as UnwrapRef<DateType>; innerValue.value = val as UnwrapRef<DateType>;
}); });
} }
@ -38,7 +40,7 @@ export default function useHoverValue<DateType>(
onLeave(true); onLeave(true);
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
cancelAnimationFrame(raf.value); raf.cancel(rafId);
}); });
return [firstText, onEnter, onLeave]; return [firstText, onEnter, onLeave];

View File

@ -3,6 +3,7 @@ import { onBeforeUnmount, watchEffect, watch, ref, computed } from 'vue';
import type { FocusEventHandler } from '../../_util/EventInterface'; import type { FocusEventHandler } from '../../_util/EventInterface';
import KeyCode from '../../_util/KeyCode'; import KeyCode from '../../_util/KeyCode';
import { addGlobalMousedownEvent, getTargetFromEvent } from '../utils/uiUtil'; import { addGlobalMousedownEvent, getTargetFromEvent } from '../utils/uiUtil';
import raf from '../../_util/raf';
export default function usePickerInput({ export default function usePickerInput({
open, open,
@ -161,7 +162,7 @@ export default function usePickerInput({
preventBlurRef.value = true; preventBlurRef.value = true;
// Always set back in case `onBlur` prevented by user // Always set back in case `onBlur` prevented by user
requestAnimationFrame(() => { raf(() => {
preventBlurRef.value = false; preventBlurRef.value = false;
}); });
} else if (!focused.value || clickedOutside) { } else if (!focused.value || clickedOutside) {

View File

@ -1,14 +1,15 @@
import isVisible from '../../vc-util/Dom/isVisible'; import isVisible from '../../vc-util/Dom/isVisible';
import KeyCode from '../../_util/KeyCode'; import KeyCode from '../../_util/KeyCode';
import type { RafFrame } from '../../_util/raf';
import raf from '../../_util/raf'; import raf from '../../_util/raf';
import type { GenerateConfig } from '../generate'; import type { GenerateConfig } from '../generate';
import type { CustomFormat, PanelMode, PickerMode } from '../interface'; import type { CustomFormat, PanelMode, PickerMode } from '../interface';
const scrollIds = new Map<HTMLElement, number>(); const scrollIds = new Map<HTMLElement, RafFrame>();
/** Trigger when element is visible in view */ /** Trigger when element is visible in view */
export function waitElementReady(element: HTMLElement, callback: () => void): () => void { export function waitElementReady(element: HTMLElement, callback: () => void): () => void {
let id: number; let id: RafFrame;
function tryOrNextFrame() { function tryOrNextFrame() {
if (isVisible(element)) { if (isVisible(element)) {
@ -30,14 +31,14 @@ export function waitElementReady(element: HTMLElement, callback: () => void): ()
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
export function scrollTo(element: HTMLElement, to: number, duration: number) { export function scrollTo(element: HTMLElement, to: number, duration: number) {
if (scrollIds.get(element)) { if (scrollIds.get(element)) {
cancelAnimationFrame(scrollIds.get(element)!); raf.cancel(scrollIds.get(element)!);
} }
// jump to target if duration zero // jump to target if duration zero
if (duration <= 0) { if (duration <= 0) {
scrollIds.set( scrollIds.set(
element, element,
requestAnimationFrame(() => { raf(() => {
element.scrollTop = to; element.scrollTop = to;
}), }),
); );
@ -49,7 +50,7 @@ export function scrollTo(element: HTMLElement, to: number, duration: number) {
scrollIds.set( scrollIds.set(
element, element,
requestAnimationFrame(() => { raf(() => {
element.scrollTop += perTick; element.scrollTop += perTick;
if (element.scrollTop !== to) { if (element.scrollTop !== to) {
scrollTo(element, to, duration - 10); scrollTo(element, to, duration - 10);

View File

@ -1,5 +1,6 @@
import addEventListenerWrap from '../../vc-util/Dom/addEventListener'; import addEventListenerWrap from '../../vc-util/Dom/addEventListener';
import type { EventHandler } from '../../_util/EventInterface'; import type { EventHandler } from '../../_util/EventInterface';
import type { RafFrame } from '../../_util/raf';
import raf from '../../_util/raf'; import raf from '../../_util/raf';
import { defineComponent, onUnmounted, computed, ref, watchEffect, getCurrentInstance } from 'vue'; import { defineComponent, onUnmounted, computed, ref, watchEffect, getCurrentInstance } from 'vue';
import type { PropType } from 'vue'; import type { PropType } from 'vue';
@ -73,7 +74,7 @@ export default defineComponent({
const instance = getCurrentInstance(); const instance = getCurrentInstance();
let baseWidth = 0; let baseWidth = 0;
const dragging = ref(false); const dragging = ref(false);
let rafId: number; let rafId: RafFrame;
const updateWidth = (e: HandleEvent) => { const updateWidth = (e: HandleEvent) => {
let pageX = 0; let pageX = 0;
if (e.touches) { if (e.touches) {

View File

@ -1,3 +1,4 @@
import type { RafFrame } from '../../_util/raf';
import raf from '../../_util/raf'; import raf from '../../_util/raf';
import type { Ref, UnwrapRef } from 'vue'; import type { Ref, UnwrapRef } from 'vue';
import { onBeforeUnmount, ref, shallowRef } from 'vue'; import { onBeforeUnmount, ref, shallowRef } from 'vue';
@ -8,7 +9,7 @@ export function useLayoutState<State>(
defaultState: State, defaultState: State,
): [Ref<State>, (updater: Updater<State>) => void] { ): [Ref<State>, (updater: Updater<State>) => void] {
const stateRef = shallowRef<State>(defaultState); const stateRef = shallowRef<State>(defaultState);
let rafId: number; let rafId: RafFrame;
const updateBatchRef = shallowRef<Updater<State>[]>([]); const updateBatchRef = shallowRef<Updater<State>[]>([]);
function setFrameState(updater: Updater<State>) { function setFrameState(updater: Updater<State>) {
updateBatchRef.value.push(updater); updateBatchRef.value.push(updater);

View File

@ -1,5 +1,6 @@
import type { Ref } from 'vue'; import type { Ref } from 'vue';
import { nextTick, onBeforeUnmount, ref, watch, onMounted } from 'vue'; import { nextTick, onBeforeUnmount, ref, watch, onMounted } from 'vue';
import type { RafFrame } from '../../_util/raf';
import raf from '../../_util/raf'; import raf from '../../_util/raf';
/** /**
@ -23,7 +24,7 @@ export default (
doMeasure: Func, doMeasure: Func,
): [Ref<PopupStatus>, (callback?: () => void) => void] => { ): [Ref<PopupStatus>, (callback?: () => void) => void] => {
const status = ref<PopupStatus>(null); const status = ref<PopupStatus>(null);
const rafRef = ref<number>(); const rafRef = ref<RafFrame>();
const destroyRef = ref(false); const destroyRef = ref(false);
function setStatus(nextStatus: PopupStatus) { function setStatus(nextStatus: PopupStatus) {
if (!destroyRef.value) { if (!destroyRef.value) {

View File

@ -1,4 +1,5 @@
import type { Ref } from 'vue'; import type { Ref } from 'vue';
import type { RafFrame } from '../../_util/raf';
import raf from '../../_util/raf'; import raf from '../../_util/raf';
import isFF from '../utils/isFirefox'; import isFF from '../utils/isFirefox';
import useOriginScroll from './useOriginScroll'; import useOriginScroll from './useOriginScroll';
@ -15,7 +16,7 @@ export default function useFrameWheel(
onWheelDelta: (offset: number) => void, onWheelDelta: (offset: number) => void,
): [(e: WheelEvent) => void, (e: FireFoxDOMMouseScrollEvent) => void] { ): [(e: WheelEvent) => void, (e: FireFoxDOMMouseScrollEvent) => void] {
let offsetRef = 0; let offsetRef = 0;
let nextFrame: number | null | undefined = null; let nextFrame: RafFrame = null;
// Firefox patch // Firefox patch
let wheelValue = null; let wheelValue = null;

View File

@ -1,5 +1,6 @@
import type { Data } from '../../_util/type'; import type { Data } from '../../_util/type';
import type { ComputedRef, Ref } from 'vue'; import type { ComputedRef, Ref } from 'vue';
import type { RafFrame } from '../../_util/raf';
import raf from '../../_util/raf'; import raf from '../../_util/raf';
import type { GetKey } from '../interface'; import type { GetKey } from '../interface';
@ -13,7 +14,7 @@ export default function useScrollTo(
syncScrollTop: (newTop: number) => void, syncScrollTop: (newTop: number) => void,
triggerFlash: () => void, triggerFlash: () => void,
) { ) {
let scroll: number | null = null; let scroll: RafFrame = null;
return (arg?: any) => { return (arg?: any) => {
// When not argument provided, we think dev may want to show the scrollbar // When not argument provided, we think dev may want to show the scrollbar

View File

@ -102,7 +102,6 @@
"@types/markdown-it": "^10.0.2", "@types/markdown-it": "^10.0.2",
"@types/node": "^14.0.0", "@types/node": "^14.0.0",
"@types/postcss-load-config": "^2.0.1", "@types/postcss-load-config": "^2.0.1",
"@types/raf": "^3.4.0",
"@typescript-eslint/eslint-plugin": "^4.1.0", "@typescript-eslint/eslint-plugin": "^4.1.0",
"@typescript-eslint/parser": "^4.1.0", "@typescript-eslint/parser": "^4.1.0",
"@vitejs/plugin-vue": "^1.2.4", "@vitejs/plugin-vue": "^1.2.4",