Skip to content

Instantly share code, notes, and snippets.

@giulioz
Last active September 3, 2019 09:11
Show Gist options
  • Save giulioz/ca4ec50390332f4c457b641bedb0a338 to your computer and use it in GitHub Desktop.
Save giulioz/ca4ec50390332f4c457b641bedb0a338 to your computer and use it in GitHub Desktop.
import React, { useEffect, useRef, PropsWithChildren, useState } from "react";
import { makeStyles } from "@material-ui/core/styles";
import useComponentSize from "@rehooks/component-size";
// USAGE:
// const svgWrapperProps = useSvgWrapperProps();
// return (
// <SvgWrapper {...svgWrapperProps}>
// ...
// </SvgWrapper>
// );
const useStyles = makeStyles(theme => ({
root: {
height: "100%",
display: "flex",
flexGrow: 1,
flexDirection: "column"
}
}));
export function useSvgWrapperProps() {
const [position, setPosition] = useState<[number, number]>([0, 0]);
const [zoom, setZoom] = useState(1);
function handleZoom(z: number) {
const normZ = z / 200;
setZoom(pz => (pz + normZ * pz > 0 ? pz + normZ * pz : 0));
}
function handlePan(x: number, y: number) {
setPosition(([px, py]) => [px + x * zoom, py + y * zoom]);
}
return { position, onZoom: handleZoom, zoom, onPan: handlePan };
}
interface IProps {
onZoom: (z: number) => void;
onPan: (x: number, y: number) => void;
position: [number, number];
zoom: number;
}
export default function SvgWrapper({
onZoom,
onPan,
children,
position,
zoom,
...rest
}: PropsWithChildren<IProps>) {
const classes = useStyles();
const divRef = useRef<HTMLDivElement>();
const divSize = useComponentSize(divRef);
const width = divSize.width * zoom;
const height = divSize.height * zoom;
useEffect(() => {
function preventDefault(e: Event) {
e.preventDefault();
}
window.addEventListener("gesturestart", preventDefault);
window.addEventListener("gesturechange", preventDefault);
window.addEventListener("gestureend", preventDefault);
return () => {
window.removeEventListener("gesturestart", preventDefault);
window.removeEventListener("gesturechange", preventDefault);
window.removeEventListener("gestureend", preventDefault);
};
}, []);
useEffect(() => {
function onWheel(e: WheelEvent) {
e.preventDefault();
if (e.ctrlKey) {
onZoom(e.deltaY);
} else {
onPan(e.deltaX, e.deltaY);
}
return false;
}
const div = divRef.current;
if (div) {
div.addEventListener("wheel", onWheel, {
passive: false,
capture: false
});
}
return () => {
if (div) {
div.removeEventListener("wheel", onWheel);
}
};
}, [onZoom, onPan]);
return (
<div className={classes.root} ref={divRef as any}>
<svg
{...divSize}
{...rest}
viewBox={`${position[0] - width / 2} ${position[1] -
height / 2} ${width} ${height}`}
>
{children}
</svg>
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment