Синхронизировать поле выбора HTML с повернутым элементом SVG (смещение вращения и неправильные границы)CSS

Разбираемся в CSS
Ответить
Anonymous
 Синхронизировать поле выбора HTML с повернутым элементом SVG (смещение вращения и неправильные границы)

Сообщение Anonymous »

Я создаю простой редактор SVG (перемещение, изменение размера или поворот с помощью пользовательского центра поворота).

Элемент 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
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «CSS»