Add konva portal for transformer controls

This commit is contained in:
Mitchell McCaffrey 2021-08-06 12:24:23 +10:00
parent 6e7794c38e
commit 1182c9fe1e
4 changed files with 87 additions and 72 deletions

View File

@ -46,6 +46,7 @@
"react-dom": "^17.0.2",
"react-intersection-observer": "^8.32.0",
"react-konva": "^17.0.2-5",
"react-konva-utils": "^0.1.7",
"react-markdown": "4",
"react-media": "^2.0.0-rc.1",
"react-modal": "^3.12.1",

View File

@ -2,6 +2,7 @@ import Konva from "konva";
import { Transform } from "konva/lib/Util";
import { useEffect, useMemo, useRef, useState } from "react";
import { Transformer as KonvaTransformer } from "react-konva";
import { Portal } from "react-konva-utils";
import {
useGridCellPixelSize,
@ -200,80 +201,85 @@ function Transformer({
}
return (
<KonvaTransformer
ref={transformerRef}
boundBoxFunc={(oldBox, newBox) => {
let snapBox = { ...newBox };
const movingAnchor = movingAnchorRef.current;
if (movingAnchor === "middle-left" || movingAnchor === "middle-right") {
// Account for grid snapping
const nearestCellWidth = roundTo(
snapBox.width,
gridCellAbsoluteSize.x
);
const distanceToSnap = Math.abs(snapBox.width - nearestCellWidth);
let snapping = false;
<Portal selector="#portal" enabled>
<KonvaTransformer
ref={transformerRef}
boundBoxFunc={(oldBox, newBox) => {
let snapBox = { ...newBox };
const movingAnchor = movingAnchorRef.current;
if (
distanceToSnap <
gridCellAbsoluteSize.x * gridSnappingSensitivity
movingAnchor === "middle-left" ||
movingAnchor === "middle-right"
) {
snapBox.width = nearestCellWidth;
snapping = true;
// Account for grid snapping
const nearestCellWidth = roundTo(
snapBox.width,
gridCellAbsoluteSize.x
);
const distanceToSnap = Math.abs(snapBox.width - nearestCellWidth);
let snapping = false;
if (
distanceToSnap <
gridCellAbsoluteSize.x * gridSnappingSensitivity
) {
snapBox.width = nearestCellWidth;
snapping = true;
}
const deltaWidth = snapBox.width - oldBox.width;
// Account for node ratio
const inverseRatio =
Math.round(oldBox.height) / Math.round(oldBox.width);
const deltaHeight = inverseRatio * deltaWidth;
// Account for node rotation
// Create a transform to unrotate the x,y position of the Box
const rotator = new Transform();
rotator.rotate(-snapBox.rotation);
// Unrotate and add the resize amount
let rotatedMin = rotator.point({ x: snapBox.x, y: snapBox.y });
rotatedMin.y = rotatedMin.y - deltaHeight / 2;
// Snap x position if needed
if (snapping) {
const snapDelta = newBox.width - nearestCellWidth;
rotatedMin.x = rotatedMin.x + snapDelta / 2;
}
// Rotated back
rotator.invert();
rotatedMin = rotator.point(rotatedMin);
snapBox = {
...snapBox,
height: snapBox.height + deltaHeight,
x: rotatedMin.x,
y: rotatedMin.y,
};
}
const deltaWidth = snapBox.width - oldBox.width;
// Account for node ratio
const inverseRatio =
Math.round(oldBox.height) / Math.round(oldBox.width);
const deltaHeight = inverseRatio * deltaWidth;
// Account for node rotation
// Create a transform to unrotate the x,y position of the Box
const rotator = new Transform();
rotator.rotate(-snapBox.rotation);
// Unrotate and add the resize amount
let rotatedMin = rotator.point({ x: snapBox.x, y: snapBox.y });
rotatedMin.y = rotatedMin.y - deltaHeight / 2;
// Snap x position if needed
if (snapping) {
const snapDelta = newBox.width - nearestCellWidth;
rotatedMin.x = rotatedMin.x + snapDelta / 2;
if (snapBox.width < 5 || snapBox.height < 5) {
return oldBox;
}
// Rotated back
rotator.invert();
rotatedMin = rotator.point(rotatedMin);
snapBox = {
...snapBox,
height: snapBox.height + deltaHeight,
x: rotatedMin.x,
y: rotatedMin.y,
};
}
if (snapBox.width < 5 || snapBox.height < 5) {
return oldBox;
}
return snapBox;
}}
onTransformStart={handleTransformStart}
onTransform={handleTrasform}
onTransformEnd={handleTransformEnd}
centeredScaling={true}
rotationSnaps={[...Array(24).keys()].map((n) => n * 15)}
rotateAnchorOffset={16}
enabledAnchors={["middle-left", "middle-right"]}
flipEnabled={false}
ignoreStroke={true}
borderStroke="invisible"
anchorStroke="invisible"
anchorCornerRadius={24}
borderStrokeWidth={0}
anchorSize={48}
useSingleNodeRotation={true}
/>
return snapBox;
}}
onTransformStart={handleTransformStart}
onTransform={handleTrasform}
onTransformEnd={handleTransformEnd}
centeredScaling={true}
rotationSnaps={[...Array(24).keys()].map((n) => n * 15)}
rotateAnchorOffset={16}
enabledAnchors={["middle-left", "middle-right"]}
flipEnabled={false}
ignoreStroke={true}
borderStroke="invisible"
anchorStroke="invisible"
anchorCornerRadius={24}
borderStrokeWidth={0}
anchorSize={48}
useSingleNodeRotation={true}
/>
</Portal>
);
}

View File

@ -1,7 +1,7 @@
import React, { useRef, useEffect, useState } from "react";
import { Box } from "theme-ui";
import ReactResizeDetector from "react-resize-detector";
import { Stage, Layer, Image } from "react-konva";
import { Stage, Layer, Image, Group } from "react-konva";
import Konva from "konva";
import { EventEmitter } from "events";
@ -215,8 +215,8 @@ function MapInteraction({
id="mapImage"
ref={mapImageRef}
/>
{mapLoaded && children}
<Group id="portal" />
</Layer>
</KonvaBridge>
</ReactResizeDetector>

View File

@ -11254,7 +11254,15 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
react-konva@^17.0.2-5:
react-konva-utils@^0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/react-konva-utils/-/react-konva-utils-0.1.7.tgz#f3feb6c0dd5a930020fd10a0f57d5c7a904166c8"
integrity sha512-wjNq4TsPXaig2Zufg6nBeFQpijR3FJ4iL2YrmtlwQN7Ase158eWHrpHKNySdMo2E4OSls8220sIaTY5TlrzqyA==
dependencies:
react-konva "^17.0.2-4"
use-image "^1.0.7"
react-konva@^17.0.2-4, react-konva@^17.0.2-5:
version "17.0.2-5"
resolved "https://registry.yarnpkg.com/react-konva/-/react-konva-17.0.2-5.tgz#e70b0acf323402de0a540f27b300fbe7ed151849"
integrity sha512-IyzdfqRDK8r1ulp/jbLPX18AuO+n5yNtL0+4T0QEUsgArRqIl/VRCG1imA5mYJBk0cBNC5+fWDHN+HWEW62ZEQ==