Please help me understand the best practises of manipulating svg elements with their transforms using plain javascript.
I understand the coordinate system is passed down the nodes and chained etc etc.
What I'm trying to achieve is to continue the original translation on the element after rotation. Not along the axis after the rotation has been applied.
Do I have to clone-copy the first translate-transform and add it to the end of the transform list?
Many thanks if someone could shed some light on an eloquent way.
Please help me understand the best practises of manipulating svg elements with their transforms using plain javascript.
I understand the coordinate system is passed down the nodes and chained etc etc.
What I'm trying to achieve is to continue the original translation on the element after rotation. Not along the axis after the rotation has been applied.
Do I have to clone-copy the first translate-transform and add it to the end of the transform list?
Many thanks if someone could shed some light on an eloquent way.
Share Improve this question asked Sep 30, 2016 at 13:25 danieldaniel 551 silver badge9 bronze badges2 Answers
Reset to default 8The way you achieve this is by nesting the transforms. For example, have a look at the following sample SVG.
<svg width="600" height="200">
<g>
<rect x="0" y="50" width="100" height="100" fill="blue"/>
</g>
</svg>
You can apply one transform to the <g>
and another to the <rect>
. They will be independent, but will bine for the overall effect.
So for example if I want to move everything right, I can apply a translation transform to the group.
<svg width="600" height="200">
<g transform="translate(200,0)">
<rect x="0" y="50" width="100" height="100" fill="blue"/>
</g>
</svg>
Then if I want to rotate the rectangle in place, I can do so by applying a rotation transform to that.
<svg width="600" height="200">
<g transform="translate(200,0)">
<rect x="0" y="50" width="100" height="100" fill="blue"
transform="rotate(45,50,100)"/>
</g>
</svg>
Then, if I want, I can move the rect even further right, by updating the group's transform.
<svg width="600" height="200">
<g transform="translate(400,0)">
<rect x="0" y="50" width="100" height="100" fill="blue"
transform="rotate(45,50,100)"/>
</g>
</svg>
The way to think about this is that the <rect>
is in it's own little world ("coordinate space" is the official term :) and is blissfully unaware of what is going on in its parent elements.
So if we use what we have learned above we can easily create the sort of animation you are after. The following animation consists of three phases. First we move the rectangle right, then we rotate it, then we continue right again. The rotation in the middle phase does not affect the third phase where we again move to the right.
var outer = document.getElementById("outer");
var inner = document.getElementById("inner");
var tx = 0; // the animated X position
var angle = 0; // the animated angle
/*
* The first phase of the animation.
* Function to step to the right until we hit tx=200.
*/
var stepRightTo200 = function() {
setTimeout(function() {
tx += 4;
outer.setAttribute('transform', 'translate('+tx+',0)');
if (tx < 200) // schedule another step in this phase
stepRightTo200();
else // start next phase of animation
rotateTo45();
}, 32);
};
/*
* The second phase of the animation.
* Step the angle around until we hit 45 degrees.
*/
var rotateTo45 = function() {
setTimeout(function() {
angle += 1;
inner.setAttribute('transform', 'rotate('+angle+',50,100)');
if (angle < 45)
rotateTo45()
else
stepRightTo400(); // start third phase of animation
}, 32);
};
/*
* The third phase of the animation.
* Step to the right until we hit tx=400.
*/
var stepRightTo400 = function() {
setTimeout(function() {
tx += 4;
outer.setAttribute('transform', 'translate('+tx+',0)');
if (tx < 400)
stepRightTo400();
}, 32);
};
// Kick off first phase of animation
stepRightTo200();
<svg width="600" height="200">
<g id="outer">
<rect id="inner" x="0" y="50" width="100" height="100" fill="blue"/>
</g>
</svg>
In the examples above, I have separated the "outer" transform out onto a parent group, but we don't really have to do that. We can nest transform operations into a single transform.
So we could simplify the third SVG example above to:
<svg width="600" height="200">
<rect x="0" y="50" width="100" height="100" fill="blue"
transform="translate(400,0) rotate(45,50,100)"/>
</svg>
The "outer" transform bees the first one in the transform list. This is a good way to conceptualise a multipart transform if you ever need to create one. Start by creating (or imagining) a nested group structure, and apply your transforms to that from "outside" (left) to "inside" (right).
So finally, we can rewrite our animation script using this non-nested form.
var inner = document.getElementById("inner");
var tx = 0; // the animated X position
var angle = 0; // the animated angle
/*
* The first phase of the animation.
* Function to step to the right until we hit tx=200.
*/
var stepRightTo200 = function() {
setTimeout(function() {
tx += 4;
inner.setAttribute('transform',
'translate('+tx+',0) rotate('+angle+',50,100)');
if (tx < 200) // schedule another step in this phase
stepRightTo200();
else // start next phase of animation
rotateTo45();
}, 32);
};
/*
* The second phase of the animation.
* Step the angle around until we hit 45 degrees.
*/
var rotateTo45 = function() {
setTimeout(function() {
angle += 1;
inner.setAttribute('transform',
'translate('+tx+',0) rotate('+angle+',50,100)');
if (angle < 45)
rotateTo45()
else
stepRightTo400(); // start third phase of animation
}, 32);
};
/*
* The third phase of the animation.
* Step to the right until we hit tx=400.
*/
var stepRightTo400 = function() {
setTimeout(function() {
tx += 4;
inner.setAttribute('transform',
'translate('+tx+',0) rotate('+angle+',50,100)');
if (tx < 400)
stepRightTo400();
}, 32);
};
// Kick off first phase of animation
stepRightTo200();
<svg width="600" height="200">
<rect id="inner" x="0" y="50" width="100" height="100" fill="blue"/>
</svg>
Hope this helps you understand how transforms work.
The best approach is to create a matrix transform, then request various transforms and use the consolidate() method. Below is an example
<!DOCTYPE HTML>
<html>
<head>
<title>Transform Request</title>
</head>
<body onload=showSVGSource()>
<b><center>Transform Request Object + consolidate</b>
<div id=svgDiv style=background:lime;width:400px;height:400px; >
<svg id=mySVG width=400 height=400 >
<circle id=myCircle cx=0 cy=0 r=150 fill=yellow transform="translate(200,200)" />
</svg>
</div>
<br>
<button onClick=translateCircle()>translate</button>
<button onClick=scaleCircle()>scale</button>
<br>
<textarea id=svgSourceValue style=width:500px;height:100px;></textarea>
<center>
<script>
function translateCircle()
{
var objTransformRequestObj = mySVG.createSVGTransform()
//---attach new transform to element, init its transform list---
var myTransListAnim=myCircle.transform
var objTransList=myTransListAnim.baseVal
objTransformRequestObj.setTranslate(40,0)
objTransList.appendItem(objTransformRequestObj)
objTransList.consolidate()
showSVGSource()
}
function scaleCircle()
{
var objTransformRequestObj = mySVG.createSVGTransform()
//---attach new transform to element, init its transform list---
var myTransListAnim=myCircle.transform
var objTransList=myTransListAnim.baseVal
objTransformRequestObj.setScale(.5,.3)
objTransList.appendItem(objTransformRequestObj)
objTransList.consolidate()
showSVGSource()
}
function showSVGSource()
{
svgSourceValue.value=svgDiv.innerHTML
}
</script>
</body>
</html>