chore: Watermark uses MutateObserver (#40081)

* chore: Watermark uses MutateObserver

* chore: upgrade @rc-component/mutate-observer
This commit is contained in:
JarvisArt 2023-01-10 10:56:54 +08:00 committed by GitHub
parent 52478c78d3
commit 26c3c326ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 42 additions and 58 deletions

View File

@ -2,7 +2,7 @@ import React from 'react';
import Watermark from '..'; import Watermark from '..';
import mountTest from '../../../tests/shared/mountTest'; import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest'; import rtlTest from '../../../tests/shared/rtlTest';
import { render, waitFor } from '../../../tests/utils'; import { render, waitFor, waitFakeTimer } from '../../../tests/utils';
describe('Watermark', () => { describe('Watermark', () => {
mountTest(Watermark); mountTest(Watermark);
@ -67,6 +67,7 @@ describe('Watermark', () => {
it('MutationObserver should work properly', async () => { it('MutationObserver should work properly', async () => {
const { container } = render(<Watermark className="watermark" content="MutationObserver" />); const { container } = render(<Watermark className="watermark" content="MutationObserver" />);
const target = container.querySelector<HTMLDivElement>('.watermark div'); const target = container.querySelector<HTMLDivElement>('.watermark div');
await waitFakeTimer();
target?.remove(); target?.remove();
await waitFor(() => expect(target).toBeTruthy()); await waitFor(() => expect(target).toBeTruthy());
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
@ -77,6 +78,7 @@ describe('Watermark', () => {
<Watermark offset={[-200, -200]} className="watermark" content="MutationObserver" />, <Watermark offset={[-200, -200]} className="watermark" content="MutationObserver" />,
); );
const target = container.querySelector<HTMLDivElement>('.watermark div'); const target = container.querySelector<HTMLDivElement>('.watermark div');
await waitFakeTimer();
target?.setAttribute('style', ''); target?.setAttribute('style', '');
await waitFor(() => expect(target).toBeTruthy()); await waitFor(() => expect(target).toBeTruthy());
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();

View File

@ -1,6 +1,6 @@
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
import useMutationObserver from './useMutationObserver'; import MutateObserver from '@rc-component/mutate-observer';
import { getStyleStr, getPixelRatio, rotateWatermark } from './utils'; import { getStyleStr, getPixelRatio, rotateWatermark, reRendering } from './utils';
/** /**
* Base size of the canvas, 1 for parallel layout and 2 for alternate layout * Base size of the canvas, 1 for parallel layout and 2 for alternate layout
@ -96,7 +96,7 @@ const Watermark: React.FC<WatermarkProps> = (props) => {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const watermarkRef = useRef<HTMLDivElement>(); const watermarkRef = useRef<HTMLDivElement>();
const { createObserver, destroyObserver, reRendering } = useMutationObserver(); const stopObservation = useRef(false);
const destroyWatermark = () => { const destroyWatermark = () => {
if (watermarkRef.current) { if (watermarkRef.current) {
@ -107,7 +107,7 @@ const Watermark: React.FC<WatermarkProps> = (props) => {
const appendWatermark = (base64Url: string, markWidth: number) => { const appendWatermark = (base64Url: string, markWidth: number) => {
if (containerRef.current && watermarkRef.current) { if (containerRef.current && watermarkRef.current) {
destroyObserver(); stopObservation.current = true;
watermarkRef.current.setAttribute( watermarkRef.current.setAttribute(
'style', 'style',
getStyleStr({ getStyleStr({
@ -117,14 +117,9 @@ const Watermark: React.FC<WatermarkProps> = (props) => {
}), }),
); );
containerRef.current?.append(watermarkRef.current); containerRef.current?.append(watermarkRef.current);
createObserver(containerRef.current, (mutations) => { // Delayed execution
mutations.forEach((mutation) => { setTimeout(() => {
if (reRendering(mutation, watermarkRef.current)) { stopObservation.current = false;
destroyWatermark();
// eslint-disable-next-line @typescript-eslint/no-use-before-define
renderWatermark();
}
});
}); });
} }
}; };
@ -221,6 +216,18 @@ const Watermark: React.FC<WatermarkProps> = (props) => {
} }
}; };
const onMutate = (mutations: MutationRecord[]) => {
if (stopObservation.current) {
return;
}
mutations.forEach((mutation) => {
if (reRendering(mutation, watermarkRef.current)) {
destroyWatermark();
renderWatermark();
}
});
};
useEffect(renderWatermark, [ useEffect(renderWatermark, [
rotate, rotate,
zIndex, zIndex,
@ -240,9 +247,11 @@ const Watermark: React.FC<WatermarkProps> = (props) => {
]); ]);
return ( return (
<MutateObserver onMutate={onMutate}>
<div ref={containerRef} className={className} style={{ position: 'relative', ...style }}> <div ref={containerRef} className={className} style={{ position: 'relative', ...style }}>
{children} {children}
</div> </div>
</MutateObserver>
); );
}; };

View File

@ -1,42 +0,0 @@
import { useEffect, useRef } from 'react';
export default function useMutationObserver() {
const instance = useRef<MutationObserver>();
const destroyObserver = () => {
if (instance.current) {
instance.current.takeRecords();
instance.current.disconnect();
instance.current = undefined;
}
};
const createObserver = (target: Node, callback: MutationCallback) => {
if (MutationObserver) {
destroyObserver();
instance.current = new MutationObserver(callback);
instance.current.observe(target, {
childList: true,
subtree: true,
attributeFilter: ['style', 'class'],
});
}
};
useEffect(() => destroyObserver, []);
const reRendering = (mutation: MutationRecord, watermarkElement?: HTMLElement) => {
let flag = false;
// Whether to delete the watermark node
if (mutation.removedNodes.length) {
flag = Array.from(mutation.removedNodes).some((node) => node === watermarkElement);
}
// Whether the watermark dom property value has been modified
if (mutation.type === 'attributes' && mutation.target === watermarkElement) {
flag = true;
}
return flag;
};
return { createObserver, destroyObserver, reRendering };
}

View File

@ -25,3 +25,17 @@ export function rotateWatermark(
ctx.rotate((Math.PI / 180) * Number(rotate)); ctx.rotate((Math.PI / 180) * Number(rotate));
ctx.translate(-rotateX, -rotateY); ctx.translate(-rotateX, -rotateY);
} }
/** Whether to re-render the watermark */
export const reRendering = (mutation: MutationRecord, watermarkElement?: HTMLElement) => {
let flag = false;
// Whether to delete the watermark node
if (mutation.removedNodes.length) {
flag = Array.from(mutation.removedNodes).some((node) => node === watermarkElement);
}
// Whether the watermark dom property value has been modified
if (mutation.type === 'attributes' && mutation.target === watermarkElement) {
flag = true;
}
return flag;
};

View File

@ -113,6 +113,7 @@
"@ant-design/react-slick": "~1.0.0", "@ant-design/react-slick": "~1.0.0",
"@babel/runtime": "^7.18.3", "@babel/runtime": "^7.18.3",
"@ctrl/tinycolor": "^3.4.0", "@ctrl/tinycolor": "^3.4.0",
"@rc-component/mutate-observer": "^1.0.0",
"@rc-component/tour": "~1.1.0", "@rc-component/tour": "~1.1.0",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"copy-to-clipboard": "^3.2.0", "copy-to-clipboard": "^3.2.0",