Элемент SVG преобразуется с помощью преобразований SVG, но поле выбора НЕ SVG — это наложение HTML , расположенное поверх SVG.
После поворота элемента SVG:
- Поле выбора HTML не изменяется правильно следовать за повернутым элементом SVG.
- Ограничительная рамка смещена.
- Ручки отображаются в неправильных положениях.
- Ошибки увеличиваются при повороте на 90 / 180 °.
- Поворот не выполняется. мышь работает правильно
- Угол мыши смещается при перетаскивании
- Кажется, что центр вращения перемещается во время вращения
Код: Выделить всё
const svg = document.getElementById("svg");
const rect = document.getElementById("rect");
const selector = document.getElementById("selector");
const rotateHandle = selector.querySelector(".rotate");
const scaleHandle = selector.querySelector(".scale");
let mode = null;
let startMouse = null;
let startMatrix = null;
let startBBox = null;
let startAngle = 0;
function mouseSVG(evt) {
const pt = svg.createSVGPoint();
pt.x = evt.clientX;
pt.y = evt.clientY;
return pt.matrixTransform(svg.getScreenCTM().inverse());
}
function updateSelector() {
const bbox = rect.getBBox();
const ctm = rect.getCTM();
const p1 = svg.createSVGPoint();
p1.x = bbox.x;
p1.y = bbox.y;
const p2 = svg.createSVGPoint();
p2.x = bbox.x + bbox.width;
p2.y = bbox.y + bbox.height;
const t1 = p1.matrixTransform(ctm);
const t2 = p2.matrixTransform(ctm);
const m = rect.transform.baseVal.consolidate()?.matrix;
const angle = m ? Math.atan2(m.b, m.a) * 180 / Math.PI : 0;
selector.style.left = Math.min(t1.x, t2.x) + "px";
selector.style.top = Math.min(t1.y, t2.y) + "px";
selector.style.width = Math.abs(t2.x - t1.x) + "px";
selector.style.height = Math.abs(t2.y - t1.y) + "px";
selector.style.transform = `rotate(${angle}deg)`;
}
rect.addEventListener("mousedown", e => {
mode = "move";
startMouse = mouseSVG(e);
startMatrix =
rect.transform.baseVal.consolidate()?.matrix ||
svg.createSVGMatrix();
});
scaleHandle.addEventListener("mousedown", e => {
e.stopPropagation();
mode = "scale";
startMouse = mouseSVG(e);
startBBox = rect.getBBox();
startMatrix =
rect.transform.baseVal.consolidate()?.matrix ||
svg.createSVGMatrix();
});
rotateHandle.addEventListener("mousedown", e => {
e.stopPropagation();
mode = "rotate";
const mouse = mouseSVG(e);
const bbox = rect.getBBox();
const pivot = {
x: bbox.x + bbox.width / 2,
y: bbox.y + bbox.height / 2
};
const wp = svg.createSVGPoint();
wp.x = pivot.x;
wp.y = pivot.y;
const worldPivot = wp.matrixTransform(rect.getCTM());
startAngle = Math.atan2(
mouse.y - worldPivot.y,
mouse.x - worldPivot.x
);
startMatrix =
rect.transform.baseVal.consolidate()?.matrix ||
svg.createSVGMatrix();
});
window.addEventListener("mousemove", e => {
if (!mode) return;
const mouse = mouseSVG(e);
if (mode === "move") {
const dx = mouse.x - startMouse.x;
const dy = mouse.y - startMouse.y;
const m = startMatrix.translate(dx, dy);
rect.transform.baseVal.initialize(
svg.createSVGTransformFromMatrix(m)
);
}
if (mode === "scale") {
const dx = mouse.x - startMouse.x;
const dy = mouse.y - startMouse.y;
const sx = (startBBox.width + dx) / startBBox.width;
const sy = (startBBox.height + dy) / startBBox.height;
const cx = startBBox.x;
const cy = startBBox.y;
const m = startMatrix
.translate(cx, cy)
.scaleNonUniform(sx, sy)
.translate(-cx, -cy);
rect.transform.baseVal.initialize(
svg.createSVGTransformFromMatrix(m)
);
}
if (mode === "rotate") {
const bbox = rect.getBBox();
const pivot = {
x: bbox.x + bbox.width / 2,
y: bbox.y + bbox.height / 2
};
const wp = svg.createSVGPoint();
wp.x = pivot.x;
wp.y = pivot.y;
const worldPivot = wp.matrixTransform(rect.getCTM());
const angle = Math.atan2(
mouse.y - worldPivot.y,
mouse.x - worldPivot.x
);
const delta = angle - startAngle;
const m = startMatrix
.translate(pivot.x, pivot.y)
.rotate(delta * 180 / Math.PI)
.translate(-pivot.x, -pivot.y);
rect.transform.baseVal.initialize(
svg.createSVGTransformFromMatrix(m)
);
}
updateSelector();
});
window.addEventListener("mouseup", () => mode = null);
updateSelector();Код: Выделить всё
#wrap {
position: relative;
width: 400px;
}
svg {
border: 1px solid #ccc;
}
#selector {
position: absolute;
border: 1px dashed red;
transform-origin: 0 0;
pointer-events: none;
}
.rotate {
position: absolute;
width: 10px;
height: 10px;
background: red;
border-radius: 50%;
right: -5px;
top: -20px;
pointer-events: auto;
cursor: grab;
}Код: Выделить всё
Вопросы
- Достаточно ли getBBox() + getCTM() при синхронизации наложения HTML с повернутым элементом SVG?
- Почему вращение смещается, когда элемент SVG уже имеет преобразования применяется?
- Должны ли математические вычисления вращения выполняться в локальном пространстве SVG или мировом (экранном) пространстве?
- Существует ли рекомендуемый подход для полей выбора на основе HTML вместо SVG (используется такими редакторами, как Figma / Illustrator)?
Подробнее здесь: https://stackoverflow.com/questions/798 ... orrect-bou
Мобильная версия