Issue
I tried to create a little plugin that enables SVGelements to be scaled by dragging a little rect
on top-right of each existing rect
and circle
. While this works fine for the red elements, it fails to do so properly for the blue one.
The desired behaviour is to scale the element, while keeping its proportion, using the bottom-left point as origin. Yet on the blue element it also rotates it around.
The blue element is already roated:
matrix(1 -0.5 0.5 1 0 0)
While the red elements are not:
matrix(1 0 0 1 0 0)
My formula:
new m(a) = prev m(a) * largest movement of dx or dy as factor
new m(d) = new m(a) //to keep proportions
new m(e) = (prev m(a) - new m(a)) * prev(x) + prev m(e) //vertical position
new m(f) = (prev m(d) - new m(d)) * (prev(y) + prev(height)) + prev m(f) //horizontal position
Question
I assume that the already existing rotation on the blue element is causing my issue, however I fail to comprehend why. Since I merely adjust the translation(e,f) by the difference of scale(a,d), should that not work regardless of existing rotations?
For example I can just wrap the blue element in another g
and add the changes on it, witout it rotating:
<g transform="matrix(1.2 0 0 1.2 -115 -25)">
<rect x="380" y="320" width="40" height="40" fill="blue" transform="matrix(1 -0.5 0.5 1 0 0)"></rect>
</g>
Simplified example
const
_Drawing = document.querySelector('svg'),
_Parent = _Drawing.querySelector('.VWS_DragAndDrop');
let
_currentElement = null; //current "g.Dragging"
//add mousedown-event for dragging
_Drawing.onmousedown = function(event){
if(event.target.tagName === 'rect' && event.target.parentNode.parentNode === _Parent){
//set "g.Dragging" as dragging element
_currentElement = event.target.parentNode;
const
tBBoxE = _currentElement.getBBox(),
tBBoxR = _currentElement.referenceElement.getBBox(),
tMatrix = _currentElement.transform.baseVal.getItem(0).matrix;
//values on start to calculate the adjustments on move
_currentElement.referenceOptions = {
startX: event.clientX,
startY: event.clientY,
Bounds: {height: tBBoxR.height, w: tBBoxE.width * tMatrix.a, h: tBBoxE.height * tMatrix.d, x: tBBoxR.x, y: tBBoxR.y},
Matrix: {a: tMatrix.a, b: tMatrix.b, c: tMatrix.c, d: tMatrix.d, e: tMatrix.e, f: tMatrix.f}
}
}
else{
//reset dragging element
_currentElement = null
}
};
//add mousemove for dragging
_Drawing.onmousemove = function(event){
if(_currentElement){
const
tOriginalBBox = _currentElement.referenceOptions.Bounds,
tOriginalMatrix = _currentElement.referenceOptions.Matrix,
tCurrentMatrix = _currentElement.transform.baseVal.getItem(0).matrix,
tNewMatrix = _currentElement.ownerSVGElement.createSVGMatrix(),
tCTM = _currentElement.ownerSVGElement.getScreenCTM()
tC1 = {x: (_currentElement.referenceOptions.startX - tCTM.e) / tCTM.a, y: (_currentElement.referenceOptions.startY - tCTM.f) / tCTM.d},
tC2 = {x: (event.clientX - tCTM.e) / tCTM.a, y: (event.clientY - tCTM.f) / tCTM.d},
tDX = (tC2.x-tC1.x),
tDY = (tC2.y-tC1.y);
//current matrix as default
tNewMatrix.a = tCurrentMatrix.a;
tNewMatrix.b = tCurrentMatrix.b;
tNewMatrix.c = tCurrentMatrix.c;
tNewMatrix.d = tCurrentMatrix.d;
tNewMatrix.e = tCurrentMatrix.e;
tNewMatrix.f = tCurrentMatrix.f;
//adjust matrix around p(bottom-left) while keeping proportions
tNewMatrix.a = Math.max(tOriginalMatrix.a/100*((tOriginalBBox.w+tDX)*100/tOriginalBBox.w), tOriginalMatrix.d/100*((tOriginalBBox.h-tDY)*100/tOriginalBBox.h));
tNewMatrix.d = tNewMatrix.a;
tNewMatrix.e = (tOriginalMatrix.a - tNewMatrix.a) * tOriginalBBox.x + tOriginalMatrix.e; //vertical position
tNewMatrix.f = (tOriginalMatrix.d - tNewMatrix.d) * (tOriginalBBox.y + tOriginalBBox.height) + tOriginalMatrix.f; //horizontal position
//set new matrix to reference-element
_currentElement.referenceElement.transform.baseVal.getItem(0).setMatrix(tNewMatrix)
//set new matrix to dragging-element
_currentElement.transform.baseVal.getItem(0).setMatrix(tNewMatrix)
}
};
//add mouseup for dragging
_Drawing.onmouseup = _Drawing.onmouseleave = function(event){
_currentElement = null
};
//adding dragging boxes to each "rect" and "circle"
document.querySelectorAll('rect, circle').forEach(function(element){
const tElement = element;
//create a "g.Dragging" inside "g.VWS_DragAndDrop"
const tG = _Parent.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'g'));
tG.className = 'Dragging';
tG.setAttributeNS(null, 'transform', 'matrix(1 0 0 1 0 0)')
tG.referenceElement = tElement;
//apply the element's matrix to the new "g.Dragging"
tG.transform.baseVal.getItem(0).setMatrix(tElement.transform.baseVal.getItem(0).matrix)
//create a "rect" to scale the reference-element while dragging that "rect"
const
tBBox = tElement.getBBox(),
tLength = Math.floor(Math.min(tBBox.width, tBBox.height) / 10);
const tRect = tG.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'rect'));
tRect.setAttributeNS(null, 'x', tBBox.x + tBBox.width - tLength);
tRect.setAttributeNS(null, 'y', tBBox.y - tLength*3);
tRect.setAttributeNS(null, 'width', tLength * 4);
tRect.setAttributeNS(null, 'height', tLength * 4)
})
<svg width="800" height="800" style="background-color: aqua">
<style>
.VWS_DragAndDrop rect{
cursor: move;
fill: grey;
stroke: black
}
</style>
<circle cx="150" cy="150" r="15" fill="red" transform="matrix(1 0 0 1 0 0)" />
<rect x="300" y="300" width="40" height="40" fill="red" transform="matrix(1 0 0 1 5 5)" />
<rect x="380" y="320" width="40" height="40" fill="blue" transform="matrix(1 -0.5 0.5 1 0 0)" />
<g class="VWS_DragAndDrop" />
</svg>