This is a follow-on to a previous question I asked, Make HTML Canvas generate PDF-ready Line art?. At @Peter O.'s suggestion, I reworked my software to use SVG instead of HTML Canvas. I am now abler to create a high-quality image:
There is an outer <svg>
for the entire object. There is a translated <svg>
for each view. Instead that the arrow is drawn as a path.
Here is the relevant code that produces the arrows:
transformForPiece(p) {
return 'translate(' + this.radius + ',' + this.radius + ') rotate('+p.angle+')'
}
idForPiece(p) {
return "arrow-" + p.col + "," + p.row;
}
addPathForPiece(p) {
let x = this.x(p.col);
let y = this.y(p.row);
let r = this.radius;
var box = document.createElementNS('http://www.w3.org/2000/svg','svg');
box.setAttribute('x', x-r);
box.setAttribute('y', y-r);
box.setAttribute('width', r*2);
box.setAttribute('height', r*2);
this.svg.append(box)
/* linexy - draw a line to the X,Y position */
function lxy(x,y){
return "L " + x + " " + y + " ";
}
let arrow = document.createElementNS('http://www.w3.org/2000/svg','path');
arrow.setAttribute('style', 'stroke:none;fill:black');
arrow.setAttribute('transform', this.transformForPiece(p));
arrow.setAttribute('d',
'M 0 0 '+lxy(0,-r) + lxy(-r, 0) + lxy(-r/2,0)
+ lxy(-r/2, r) + lxy(+r/2,r) + lxy(+r/2,0) + lxy(r,0) + lxy(0,-r) + " Z ");
arrow.setAttribute('id', this.idForPiece(p));
p.path = arrow; // modify the piece with its svg path
box.append(arrow);
}
I can animate the rotation of these arrows using a JavaScript timer:
/* Given a board drawer db, a piece p, a start and and end rotation, rotate it */
const INCREMENT_DEGREES = 10;
const INCREMENT_MS = 10;
function rotatePieceTimer( db, p, start, end ) {
if (start > end) {
start = end;
}
p.angle = start; // set the current angle
db.drawPiece(p); // draw the piece at the curren totation
if (p.angle < end) { // if we have more to go
setTimeout( function () { rotatePieceTimer( db, p, start+INCREMENT_DEGREES, end); }, INCREMENT_MS);
}
}
That is tied to an event:
function roll (e) {
/* Pick a random piece */
let i = Math.floor(Math.random() * cells_wide);
let j = Math.floor(Math.random() * cells_high);
let p = board.piece(i,j); // get the piece
console.log("roll p=",p);
rotatePieceTimer(db, p, p.angle, Math.ceil(p.angle/180)*180+180) // rotate with timer
//rotatePieceSVG(db, p, p.angle, Math.ceil(p.angle/180)*180+180) // rotate with timer
//$('#time').text(boardHistory.length);
}
$('#roll').click( roll );
As you can tell from the code that is commented out, I would like to do the animation with an SVG animationTransform. What I have works just fine, but it would be neat to know how to make animate Transforms work.
I have abstracted away the working code to here:
<!DOCTYPE html>
<html lang="en" class="stylish" type="text/css>"
<body>
<svg width="400" height="400">
<svg x="203" y="203" width="60.66666666666667" height="60.66666666666667">
<path style="stroke:none;fill:black" transform="translate(30.333333333333336,30.333333333333336) rotate(45)"
d="M 0 0 L 0 -30.333333333333336 L -30.333333333333336 0 L -15.166666666666668 0 L -15.166666666666668 30.333333333333336 L 15.166666666666668
30.333333333333336 L 15.166666666666668 0 L 30.333333333333336 0 L 0 -30.333333333333336 Z "
id="arrow-3,3">
<animateTransform attributeName="transform" attributeType="XML" type="rotate" from="180 30.333333333333336 30.333333333333336"
to="360 30.333333333333336 30.333333333333336" dur="1s" repeatCount="100">
</animateTransform>
</path>
</svg>
</svg>
</body>
</html>
The problem with this use of animateTransform
is that the arrow doesn't rotate nicely, the way it does when I simply update the rotate(45)
CSS transform in the path
.
Another problem I have is that I want to make this happen when the user clicks a button, sort of with this code (which is broken):
function rotatePieceSVG( db, p, start, end){
// this just changes the attribute:
var noAnimation = false;
if (noAnimation) {
p.angle = end;
p.path.setAttribute('transform',db.transformForPiece(p));
return;
}
// This is the animation I can't get to work...
var t = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');
t.setAttribute('attributeName','transform');
t.setAttribute('attributeType','XML');
t.setAttribute('type','rotate');
t.setAttribute('from',p.angle+' '+db.radius+' '+db.radius);
p.angle = end;
t.setAttribute('to',p.angle+' '+db.radius+' '+db.radius);
t.setAttribute('dur','1s');
p.path.append(t);
console.log("after rotate: p=",p);
}
I've verified that this code adds the animateTransform
to the proper location of the DOM. However, it seems that adding the animateTransform
to an existing path object doesn't result in the path being animated. I've read the spec, but it's big and I may have missed something. Do I need to delete the old path object and add a new one, rather than just adding the animate transform? Probably. I haven't tried that yet.
So my question:
How can I get the
animateTransform
to properly rotate my arrow? I've tried fiddling with the values in thefrom=
and theto=
and I can never get the arrow rotating in the center.Is there a way to add the
animateTransform
to an existing path, or do I need to delete the current path and add a new one?
The full code is here: https://jsfiddle.net/simsong/9udcga6r/1/