封装一个ScaleContainer缩放组件
import React, { useCallback, useEffect, useRef, useState } from 'react';
import styles from './index.less';
import { observer, unobserver } from './observerUtils';
export interface ScaleContainerProps {
width: number;
height: number;
children: React.ReactElement;
disabled?: boolean;
maxScale?: number;
minScale?: number;
}
const ScaleContainer: React.FC<ScaleContainerProps> = (props) => {
const { children, disabled } = props;
const wrapDomRef = useRef<HTMLDivElement>(null);
const [scale, setScale] = useState<number>(1);
const [translateArr, setTranslateArr] = useState([0, 0]);
const onInternalResize = useCallback((target: HTMLDivElement) => {
const { width, height } = target.getBoundingClientRect();
const fixedWidth = Math.floor(width);
const fixedHeight = Math.floor(height);
const w = fixedWidth / props.width;
const h = fixedHeight / props.height;
const s = Math.min(w, h);
const leftNum = (fixedWidth - props.width * h) / 2;
const topNum = (fixedHeight - props.height * w) / 2;
setTranslateArr([leftNum <= 0 ? 0 : leftNum, topNum <= 0 ? 0 : topNum]);
setScale(s);
}, []);
useEffect(() => {
const dom = wrapDomRef.current;
if (!disabled && dom) {
observer(dom, onInternalResize);
}
return () => {
unobserver(dom, onInternalResize);
};
}, [disabled, wrapDomRef.current]);
return (
<div className={styles.scaleContainer} ref={wrapDomRef}>
<div
style={{
width: props.width + 'px',
height: props.height + 'px',
transform: `scale(${scale})`,
transformOrigin: 'top left',
left: translateArr[0],
top: translateArr[1],
position: 'absolute',
}}
>
{children}
</div>
</div>
);
};
export default ScaleContainer;
ScaleContainer的样式
.scaleContainer {
position: relative;
width: 100%;
height: 100%;
}
核心逻辑
import ResizeObserver from 'resize-observer-polyfill';
export type ResizeListener = (element: HTMLDivElement) => void;
const elementListeners = new Map<HTMLDivElement, Set<ResizeListener>>();
const ro = new ResizeObserver((entries, _observer) => {
for (const entry of entries) {
const listeners = elementListeners.get(entry.target as HTMLDivElement);
if (listeners && listeners.size) {
listeners.forEach((listen) => listen(entry.target as HTMLDivElement));
}
}
});
export const observer = (element: HTMLDivElement | null, callback: ResizeListener) => {
if (!element) {
return;
}
if (!elementListeners.has(element)) {
elementListeners.set(element, new Set());
ro.observe(element);
}
elementListeners.get(element)?.add(callback);
};
export const unobserver = (element: HTMLDivElement | null, callback: ResizeListener) => {
if (!element) {
return;
}
if (elementListeners.has(element)) {
elementListeners.get(element)?.delete(callback);
if (!elementListeners.get(element)?.size) {
ro.unobserve(element);
elementListeners.delete(element);
}
}
};
具体的使用方式
<ScaleContainer width={1920} height={1080}>
<div ref={mapRef} className={styles.mapBox} />
</ScaleContainer>