mysticsymbolic.github.io/lib/auto-sizing-svg.tsx

131 wiersze
3.4 KiB
TypeScript

import React, { useCallback, useEffect, useRef, useState } from "react";
type AutoSizingSvgProps = {
padding?: number;
bgColor?: string;
sizeToElement?: React.RefObject<HTMLElement>;
children: JSX.Element | JSX.Element[];
};
function useResizeHandler(onResize: () => void) {
useEffect(() => {
window.addEventListener("resize", onResize);
return () => {
window.removeEventListener("resize", onResize);
};
});
}
/**
* An SVG element with an optional background color that
* automatically sizes itself to either its contents, or
* if the `sizeToElement` prop is provided, to the given
* container.
*/
export const AutoSizingSvg = React.forwardRef(
(props: AutoSizingSvgProps, ref: React.ForwardedRef<SVGSVGElement>) => {
const { bgColor, sizeToElement } = props;
const [x, setX] = useState(0);
const [y, setY] = useState(0);
const [width, setWidth] = useState(1);
const [height, setHeight] = useState(1);
const gRef = useRef<SVGGElement>(null);
const resizeToElement = useCallback(() => {
if (sizeToElement?.current) {
const bbox = sizeToElement.current.getBoundingClientRect();
setX(-bbox.width / 2);
setY(-bbox.height / 2);
setWidth(bbox.width);
setHeight(bbox.height);
return true;
}
return false;
}, [sizeToElement]);
useResizeHandler(resizeToElement);
// Note that we're passing `props.children` in as a dependency; it's not
// used anywhere in the effect, but since any change to the
// children may result in a dimension change in the SVG element, we
// want it to trigger the effect.
useEffect(() => {
if (!resizeToElement()) {
const svgEl = gRef.current;
if (svgEl) {
const bbox = svgEl.getBBox();
const padding = props.padding || 0;
setX(bbox.x - padding);
setY(bbox.y - padding);
setWidth(bbox.width + padding * 2);
setHeight(bbox.height + padding * 2);
}
}
}, [props.padding, resizeToElement, props.children]);
return (
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
width={`${width}px`}
height={`${height}px`}
viewBox={`${x} ${y} ${width} ${height}`}
ref={ref}
>
{bgColor && (
<rect
x={x}
y={y}
width={width}
height={height}
fill={bgColor}
data-is-background
/>
)}
<g ref={gRef}>{props.children}</g>
</svg>
);
}
);
export function getSvgMetadata(svgEl: SVGSVGElement) {
let bgColor: string | undefined = undefined;
const backgroundEl = svgEl.querySelector("[data-is-background]");
if (backgroundEl) {
bgColor = backgroundEl.getAttribute("fill") ?? undefined;
}
const { x, y, width, height } = svgEl.viewBox.baseVal;
return { x, y, width, height, bgColor };
}
export type SvgMetadata = ReturnType<typeof getSvgMetadata>;
export const SvgWithBackground: React.FC<SvgMetadata & { children?: any }> = ({
x,
y,
width,
height,
bgColor,
children,
}) => (
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
width={`${width}px`}
height={`${height}px`}
viewBox={`${x} ${y} ${width} ${height}`}
>
{bgColor && (
<rect
x={x}
y={y}
width={width}
height={height}
fill={bgColor}
data-is-background
/>
)}
{children}
</svg>
);