diff --git a/example/.dumirc.ts b/example/.dumirc.ts index 79ec100..fa4881c 100644 --- a/example/.dumirc.ts +++ b/example/.dumirc.ts @@ -67,7 +67,6 @@ export default defineConfig({ 'dumi-theme-antd-style': path.join(__dirname, '../src'), }, mfsu: false, - extraBabelPlugins: ['@emotion'], styles: [ `html, body { background: transparent; } diff --git a/package.json b/package.json index eff6cfe..0cd86bc 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,6 @@ "polished": "^4", "rc-footer": "^0.6", "react-layout-kit": "^1", - "react-lazy-load": "^4.0.1", "react-syntax-highlighter": "^15", "shiki-es": "^0.2", "use-merge-value": "^1", diff --git a/src/builtins/Previewer/index.tsx b/src/builtins/Previewer/index.tsx index 57e0e5e..784ac29 100644 --- a/src/builtins/Previewer/index.tsx +++ b/src/builtins/Previewer/index.tsx @@ -1,11 +1,10 @@ -import { Card } from 'antd'; import { createStyles } from 'antd-style'; import { IPreviewerProps } from 'dumi/dist/client/theme-api/types'; import Previewer from 'dumi/theme-default/builtins/Previewer'; import { rgba } from 'polished'; -import { useMemo, useState } from 'react'; -import LazyLoad from 'react-lazy-load'; +import { useMemo } from 'react'; import DemoProvider from '../../components/DemoProvider'; +import { IntersectionLoad } from '../../components/LazyLoad'; const useStyles = createStyles(({ css, token, prefixCls }) => { return { @@ -124,8 +123,6 @@ const useStyles = createStyles(({ css, token, prefixCls }) => { export default (props: IPreviewerProps) => { const { styles, cx, theme } = useStyles(); - const [loading, setLoading] = useState(true); - const height = useMemo(() => { if (typeof props.iframe === 'number') { return props.iframe; @@ -138,26 +135,11 @@ export default (props: IPreviewerProps) => { return (
- { - setLoading(false); - }} - > + - - {loading && ( - - )} +
); }; diff --git a/src/components/LazyLoad/index.tsx b/src/components/LazyLoad/index.tsx new file mode 100644 index 0000000..adcc944 --- /dev/null +++ b/src/components/LazyLoad/index.tsx @@ -0,0 +1,137 @@ +// copy form https://github.com/loktar00/react-lazy-load/blob/master/src/LazyLoad.tsx#L2 +import { Card } from 'antd'; +import React, { Children, Component, createElement, ReactNode, RefObject } from 'react'; +import scrollParent from './utils'; + +type Props = { + children: ReactNode; + className?: string; + elementType?: string; + height?: string | number; + offset?: string | number; + threshold?: number; + width?: number | string; + onContentVisible?: () => void; +}; + +type State = { + visible: boolean; +}; + +export class IntersectionLoad extends Component { + static defaultProps = { + elementType: 'div', + className: '', + offset: 0, + threshold: 0, + width: null, + onContentVisible: null, + height: null, + }; + + elementObserver: IntersectionObserver | null; + + wrapper: RefObject | null; + + constructor(props: Props) { + super(props); + this.elementObserver = null; + this.wrapper = React.createRef(); + + this.state = { visible: false }; + } + + componentDidMount() { + let eventNode = this.getEventNode(); + + if (eventNode === window) { + eventNode = document.body; + } + + setTimeout(() => { + const { offset, threshold } = this.props; + const options = { + rootMargin: typeof offset === 'number' ? `${offset}px` : offset || '0px', + threshold: threshold || 0, + + root: document.body, + }; + + this.elementObserver = new IntersectionObserver(this.lazyLoadHandler, options); + + const node = this.wrapper?.current; + + if (node instanceof HTMLElement) { + this.elementObserver.observe(node); + } + }, 200); + } + + shouldComponentUpdate(_: Props, nextState: State) { + return nextState.visible; + } + + componentWillUnmount() { + const node = this.wrapper?.current; + console.log(node); + if (node && node instanceof HTMLElement) { + this.elementObserver?.unobserve(node); + } + } + + getEventNode() { + if (!this.wrapper?.current) return document.body; + return scrollParent(this.wrapper?.current); + } + + lazyLoadHandler = (entries: IntersectionObserverEntry[]) => { + const { onContentVisible } = this.props; + const [entry] = entries; + const { isIntersecting } = entry; + + if (isIntersecting) { + this.setState({ visible: true }, () => { + if (onContentVisible) { + onContentVisible(); + } + }); + + // Stop observing + const node = this.wrapper?.current; + if (node && node instanceof HTMLElement) { + this.elementObserver?.unobserve(node); + } + } + }; + + render() { + const { children, className, height, width, elementType } = this.props; + const { visible } = this.state; + + const elStyles = { width }; + const elClasses = `LazyLoad${visible ? ' is-visible' : ''}${className ? ` ${className}` : ''}`; + + const componentElementType = elementType || 'div'; + + return createElement( + componentElementType, + { + className: elClasses, + style: elStyles, + ref: this.wrapper, + }, + visible ? ( + Children.only(children) + ) : ( + + ), + ); + } +} diff --git a/src/components/LazyLoad/utils.ts b/src/components/LazyLoad/utils.ts new file mode 100644 index 0000000..8ea300c --- /dev/null +++ b/src/components/LazyLoad/utils.ts @@ -0,0 +1,33 @@ +const style = (element: HTMLElement, prop: string) => + typeof getComputedStyle !== 'undefined' + ? getComputedStyle(element, null).getPropertyValue(prop) + : element.style.getPropertyValue(prop); + +const overflow = (element: HTMLElement) => + style(element, 'overflow') + style(element, 'overflow-y') + style(element, 'overflow-x'); + +export default (element: HTMLElement) => { + if (!(element instanceof HTMLElement)) { + return window; + } + + let parent = element; + + while (parent) { + if (parent === document.body || parent === document.documentElement) { + break; + } + + if (!parent.parentNode) { + break; + } + + if (/(scroll|auto)/.test(overflow(parent))) { + return parent; + } + + parent = parent.parentNode as HTMLDivElement; + } + + return window; +};