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

View File

@ -1,6 +1,6 @@
import React, { useEffect, useRef } from 'react';
import useMutationObserver from './useMutationObserver';
import { getStyleStr, getPixelRatio, rotateWatermark } from './utils';
import MutateObserver from '@rc-component/mutate-observer';
import { getStyleStr, getPixelRatio, rotateWatermark, reRendering } from './utils';
/**
* 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 watermarkRef = useRef<HTMLDivElement>();
const { createObserver, destroyObserver, reRendering } = useMutationObserver();
const stopObservation = useRef(false);
const destroyWatermark = () => {
if (watermarkRef.current) {
@ -107,7 +107,7 @@ const Watermark: React.FC<WatermarkProps> = (props) => {
const appendWatermark = (base64Url: string, markWidth: number) => {
if (containerRef.current && watermarkRef.current) {
destroyObserver();
stopObservation.current = true;
watermarkRef.current.setAttribute(
'style',
getStyleStr({
@ -117,14 +117,9 @@ const Watermark: React.FC<WatermarkProps> = (props) => {
}),
);
containerRef.current?.append(watermarkRef.current);
createObserver(containerRef.current, (mutations) => {
mutations.forEach((mutation) => {
if (reRendering(mutation, watermarkRef.current)) {
destroyWatermark();
// eslint-disable-next-line @typescript-eslint/no-use-before-define
renderWatermark();
}
});
// Delayed execution
setTimeout(() => {
stopObservation.current = false;
});
}
};
@ -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, [
rotate,
zIndex,
@ -240,9 +247,11 @@ const Watermark: React.FC<WatermarkProps> = (props) => {
]);
return (
<div ref={containerRef} className={className} style={{ position: 'relative', ...style }}>
{children}
</div>
<MutateObserver onMutate={onMutate}>
<div ref={containerRef} className={className} style={{ position: 'relative', ...style }}>
{children}
</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.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",
"@babel/runtime": "^7.18.3",
"@ctrl/tinycolor": "^3.4.0",
"@rc-component/mutate-observer": "^1.0.0",
"@rc-component/tour": "~1.1.0",
"classnames": "^2.2.6",
"copy-to-clipboard": "^3.2.0",