Skip to content

Commit

Permalink
fix(popup): 优化 destroy 动画 & 添加 display: none & 优化全局 portal 逻辑 (#246)
Browse files Browse the repository at this point in the history
* fix(popup): 优化 destroy 动画 & 添加 display: none

fix #231

* feat: 优化细节
  • Loading branch information
honkinglin authored Jan 16, 2022
1 parent 21ce6b7 commit 391de56
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 102 deletions.
47 changes: 29 additions & 18 deletions src/common/Portal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ export interface PortalProps {
/**
* 指定挂载的 HTML 节点, false 为挂载在 body
*/
getContainer?: React.ReactElement | AttachNode | boolean;
attach?: React.ReactElement | AttachNode | boolean;
children: React.ReactNode;
}

export function getAttach(attach: PortalProps['getContainer']) {
export function getAttach(attach: PortalProps['attach']) {
let parent: AttachNodeReturnValue;
if (typeof attach === 'string') {
parent = document.querySelector(attach);
Expand All @@ -25,25 +25,36 @@ export function getAttach(attach: PortalProps['getContainer']) {
}

const Portal = forwardRef((props: PortalProps, ref) => {
const { getContainer, children } = props;
const [parentContainer, container] = useMemo(() => {
const parent = getAttach(getContainer);

if (parent) {
const div = document.createElement('div');
parent.appendChild(div);
return [parent, div];
const { attach, children } = props;

const container = useMemo(() => {
const el = document.createElement('div');
return el;
}, []);

useEffect(() => {
let parentElement = document.body;
let el = null;

// 处理 attach
if (typeof attach === 'function') {
el = attach();
} else if (typeof attach === 'string') {
el = document.querySelector(attach);
}

// fix el in iframe
if (el && el.nodeType === 1) {
parentElement = el;
}
}, [getContainer]);

useEffect(
() => () => {
container.remove();
}, // eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
parentElement.appendChild(container);
return () => {
parentElement.removeChild(container);
};
}, [container, attach]);

useImperativeHandle(ref, () => parentContainer);
useImperativeHandle(ref, () => container);

return createPortal(children, container);
});
Expand Down
4 changes: 2 additions & 2 deletions src/dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const Dialog: React.ForwardRefRenderFunction<DialogInstance, DialogProps> = (pro

const {
visible,
attach: getContainer = 'body',
attach = 'body',
closeBtn,
footer,
onCancel = noop,
Expand Down Expand Up @@ -156,7 +156,7 @@ const Dialog: React.ForwardRefRenderFunction<DialogInstance, DialogProps> = (pro
visible={visible}
prefixCls={prefixCls}
header={renderHeader}
getContainer={getContainer}
attach={attach}
closeBtn={renderCloseIcon()}
classPrefix={classPrefix}
onClose={onClose}
Expand Down
10 changes: 4 additions & 6 deletions src/dialog/RenderDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React, { useLayoutEffect, useRef, CSSProperties, useEffect } from 'react'
import { CSSTransition } from 'react-transition-group';
import classnames from 'classnames';
import Portal from '../common/Portal';
import { AttachNode } from '../common';
import noop from '../_util/noop';
import { DialogProps } from './Dialog';

Expand All @@ -13,7 +12,6 @@ enum KeyCode {
export interface RenderDialogProps extends DialogProps {
prefixCls?: string;
classPrefix: string;
getContainer?: React.ReactElement | AttachNode | boolean;
}

const transitionTime = 300;
Expand All @@ -33,7 +31,7 @@ if (typeof window !== 'undefined' && window.document && window.document.document
const RenderDialog: React.FC<RenderDialogProps> = (props) => {
const {
prefixCls,
getContainer,
attach,
visible,
mode,
zIndex,
Expand Down Expand Up @@ -79,7 +77,7 @@ const RenderDialog: React.FC<RenderDialogProps> = (props) => {
} else if (isModal) {
document.body.style.cssText = bodyCssTextRef.current;
}
}, [preventScrollThrough, getContainer, visible, mode, isModal]);
}, [preventScrollThrough, attach, visible, mode, isModal]);

useEffect(() => {
if (visible) {
Expand Down Expand Up @@ -271,10 +269,10 @@ const RenderDialog: React.FC<RenderDialogProps> = (props) => {
let dom = null;

if (visible || wrap.current) {
if (getContainer === false) {
if (!attach) {
dom = dialog;
} else {
dom = <Portal getContainer={getContainer}>{dialog}</Portal>;
dom = <Portal attach={attach}>{dialog}</Portal>;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/drawer/DrawerWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const DrawerWrapper = forwardRef((props: DrawerWrapperProps, ref) => {
portal = <div ref={portalRef}>{cloneElement(children)}</div>;
} else {
portal = (
<Portal getContainer={attach} ref={portalRef}>
<Portal attach={attach} ref={portalRef}>
{children}
</Portal>
);
Expand Down
2 changes: 1 addition & 1 deletion src/loading/loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ const Loading: FC<LoadingProps> = (props) => {
}
if (attach) {
return (
<Portal getContainer={attach}>
<Portal attach={attach}>
<div className={classnames(name, baseClasses, fullClass, { [overlayClass]: showOverlay })} style={calcStyles}>
{commonContent()}
</div>
Expand Down
43 changes: 23 additions & 20 deletions src/popup/Popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import useDefault from '../_util/useDefault';
import useConfig from '../_util/useConfig';
import composeRefs from '../_util/composeRefs';
import { TdPopupProps } from './type';
import Portal from './Portal';
import Portal from '../common/Portal';
import useTriggerProps from './hooks/useTriggerProps';
import usePopupCssTransition from './hooks/usePopupCssTransition';

Expand Down Expand Up @@ -78,6 +78,7 @@ const Popup = forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
const contentRef = useRef<HTMLDivElement>(null);
const popupRef = useRef(null);
const popperRef = useRef(null);
const portalRef = useRef(null);

// 展开时候动态判断上下左右翻转
const onPopperFirstUpdate = useCallback((state) => {
Expand Down Expand Up @@ -158,28 +159,30 @@ const Popup = forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
// 初次不渲染.
const portal =
visible || overlayRef ? (
<Portal attach={attach}>
<CSSTransition in={visible} appear={true} unmountOnExit={destroyOnClose} {...cssTransitionState.props}>
<div
ref={composeRefs(setOverlayRef, ref)}
style={visible ? styles.popper : { ...styles.popper, display: 'none' }}
className={`${classPrefix}-popup`}
{...attributes.popper}
{...popupProps}
>
<CSSTransition in={visible} nodeRef={portalRef} timeout={400} appear unmountOnExit={destroyOnClose}>
<Portal attach={attach} ref={portalRef}>
<CSSTransition in={visible} appear {...cssTransitionState.props}>
<div
className={classNames(`${classPrefix}-popup__content`, overlayClassName, {
[`${classPrefix}-popup__content--arrow`]: showArrow,
})}
style={overlayVisibleStyle}
ref={contentRef}
ref={composeRefs(setOverlayRef, ref)}
style={styles.popper}
className={`${classPrefix}-popup`}
{...attributes.popper}
{...popupProps}
>
{showArrow ? <div style={styles.arrow} className={`${classPrefix}-popup__arrow`} /> : null}
{content}
<div
className={classNames(`${classPrefix}-popup__content`, overlayClassName, {
[`${classPrefix}-popup__content--arrow`]: showArrow,
})}
style={overlayVisibleStyle}
ref={contentRef}
>
{showArrow ? <div style={styles.arrow} className={`${classPrefix}-popup__arrow`} /> : null}
{content}
</div>
</div>
</div>
</CSSTransition>
</Portal>
</CSSTransition>
</Portal>
</CSSTransition>
) : null;

return (
Expand Down
46 changes: 0 additions & 46 deletions src/popup/Portal.tsx

This file was deleted.

39 changes: 31 additions & 8 deletions src/popup/hooks/usePopupCssTransition.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useRef } from 'react';

interface UsePopupCssTransitionParams {
contentRef: React.MutableRefObject<HTMLDivElement>;
Expand All @@ -8,20 +8,33 @@ interface UsePopupCssTransitionParams {

const usePopupCssTransition = ({ contentRef, classPrefix, expandAnimation }: UsePopupCssTransitionParams) => {
const [presetMaxHeight, setPresetMaxHeight] = useState<number>(null);
const timerRef = useRef(null);

const contentEle = contentRef?.current;

const popupAnimationClassPrefix = `${classPrefix}-popup--animation`;

const handleEntering = () => {
const defaultEvents = {
onEnter: handleEnter,
onExited: handleExited,
};

function handleEnter() {
clearTimeout(timerRef.current);
if (contentEle && contentEle.style.display === 'none') {
contentEle.style.display = 'block';
}
}

function handleEntering() {
setPresetMaxHeight(parseInt(getComputedStyle(contentEle).maxHeight, 10) || Infinity);
if (contentEle) {
contentEle.style.overflow = 'hidden';
contentEle.style.maxHeight = '0';
}
};
}

const handleEntered = () => {
function handleEntered() {
if (contentEle) {
const { scrollHeight } = contentEle;
const minHeight = presetMaxHeight !== Infinity ? presetMaxHeight : scrollHeight;
Expand All @@ -30,23 +43,33 @@ const usePopupCssTransition = ({ contentRef, classPrefix, expandAnimation }: Use
contentEle.style.overflow = '';
}
}
};
}

const handleExiting = () => {
function handleExiting() {
if (contentEle) {
contentEle.style.maxHeight = '0';
contentEle.style.overflow = 'hidden';
}
};
}

function handleExited() {
// 动画结束后隐藏
if (contentEle) {
timerRef.current = setTimeout(() => {
contentEle.style.display = 'none';
}, 200);
}
}

// 不需要扩展动画时,不需要生命周期函数
const lifeCircleEvent = expandAnimation
? {
...defaultEvents,
onEntering: handleEntering,
onEntered: handleEntered,
onExiting: handleExiting,
}
: {};
: { ...defaultEvents };

return {
props: {
Expand Down

0 comments on commit 391de56

Please sign in to comment.