使用 SVG animateTransform 在 SVG 中的已翻译 SVG 中旋转路径
Use SVG animateTransform to rotate a path within a translated SVG within a SVG
这是我之前提出的问题 的后续问题。在@Peter O. 的建议下,我修改了我的软件以使用 SVG 而不是 HTML Canvas。我现在可以创建高质量的图像了:
整个对象有一个外<svg>
。每个视图都有一个已翻译的 <svg>
。而不是将箭头绘制为路径。
这里是产生箭头的相关代码:
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);
}
我可以使用 JavaScript 计时器为这些箭头的旋转设置动画:
/* 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);
}
}
这与一个事件有关:
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 );
从注释掉的代码中可以看出,我想用 SVG animationTransform 制作动画。我的东西工作得很好,但如果知道如何使动画变换工作就更好了。
我已经将工作代码提取到这里:
<!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>
使用 animateTransform
的问题是箭头不能很好地旋转,当我简单地更新 rotate(45)
CSS 变换时它的方式 path
.
我遇到的另一个问题是,我想在用户单击按钮时实现这一点,有点像使用以下代码(已损坏):
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);
}
我已经验证此代码将 animateTransform
添加到 DOM 的正确位置。但是,似乎将 animateTransform
添加到现有路径对象不会导致路径被动画化。我已经阅读了规范,但它很大,我可能错过了一些东西。我是否需要删除旧路径对象并添加一个新路径对象,而不是只添加动画转换?大概。我还没试过。
所以我的问题是:
如何让 animateTransform
正确旋转我的箭头?我试过摆弄 from=
和 to=
中的值,但我永远无法让箭头在中心旋转。
有没有办法将 animateTransform
添加到现有路径,或者我是否需要删除当前路径并添加新路径?
您的代码中最突出的是 <animateTransform>
中缺少 begin
的值。 begin
值非常通用。
您可以将它们设置为 "0s"
,这是默认设置,与文档时间线开始的时间一致(基本上是 window load
事件) .这不是您稍后以交互方式添加标签的时间。到时候0s就过去了。
您可以将它们设置为"indefinite"
,这样动画就不会开始了。这通常与基于脚本的触发器结合使用,例如 SVGAnimationElement.beginElement()
.
您可以将它们设置为任意事件。 "myButton.click"
将在带有 id="myButton"
的元素收到点击时启动动画。
语法还提供开始时间列表和延迟。非常值得研究 spec 中的语法以了解您可以做什么。 (浏览器未实现的时间值规范的唯一部分是 wallclock()
函数。)
对于旋转中心,你需要仔细观察你变换的是什么,旋转的是什么。正如您的代码所代表的那样,<animateTransform>
完全替换了 元素 上的 transform
属性 。您可能想要的是添加到现有变换列表末尾的 补充 变换。这是由属性 additive="sum"
引导的(默认值为 "replace"
)。
<path style="stroke:none;fill:black" transform="translate(30.333,30.333) rotate(45)"
d="M 0 0 L 0 -30.333 L -30.333 0 L -15.166 0 L -15.166 30.333 L 15.166
30.333 L 15.166 0 L 30.333 0 L 0 -30.333 Z "
id="arrow-3,3">
<animateTransform attributeName="transform" type="rotate"
from="0"
to="180"
begin="myButton.click" dur="1s" repeatCount="100"
additive="sum">
</animateTransform>
</path>
这将围绕本地用户空间坐标中的 (0, 0)
点旋转元素,连续 100 次,从 0 度到 180 度。需要仔细研究的三件事:
旋转中心必须在本地用户空间坐标中设置,在应用所有先前的变换之后,因为它被添加到变换列表的末尾。 ( 坐标系 的变换,而不是绘制到其中的对象的变换。)这相当于:它与路径数据使用的坐标系相同。该路径的中心位于坐标系原点。
转换 添加到基础元素转换 。这意味着,它不会取代最初的 45 度旋转,而是增加了它。因此,从恒等变换开始加法变换通常是个好主意。
按照你的定义,旋转从0度到180度,然后立即重新开始。这意味着它将立即跳回其初始位置。如果你想要一个平滑的运动,要么从0度旋转到360度,要么设置属性accumulate="sum"
,这将在上一个迭代结束时开始下一个迭代。
这是我之前提出的问题
整个对象有一个外<svg>
。每个视图都有一个已翻译的 <svg>
。而不是将箭头绘制为路径。
这里是产生箭头的相关代码:
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);
}
我可以使用 JavaScript 计时器为这些箭头的旋转设置动画:
/* 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);
}
}
这与一个事件有关:
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 );
从注释掉的代码中可以看出,我想用 SVG animationTransform 制作动画。我的东西工作得很好,但如果知道如何使动画变换工作就更好了。
我已经将工作代码提取到这里:
<!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>
使用 animateTransform
的问题是箭头不能很好地旋转,当我简单地更新 rotate(45)
CSS 变换时它的方式 path
.
我遇到的另一个问题是,我想在用户单击按钮时实现这一点,有点像使用以下代码(已损坏):
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);
}
我已经验证此代码将 animateTransform
添加到 DOM 的正确位置。但是,似乎将 animateTransform
添加到现有路径对象不会导致路径被动画化。我已经阅读了规范,但它很大,我可能错过了一些东西。我是否需要删除旧路径对象并添加一个新路径对象,而不是只添加动画转换?大概。我还没试过。
所以我的问题是:
如何让
animateTransform
正确旋转我的箭头?我试过摆弄from=
和to=
中的值,但我永远无法让箭头在中心旋转。有没有办法将
animateTransform
添加到现有路径,或者我是否需要删除当前路径并添加新路径?
您的代码中最突出的是 <animateTransform>
中缺少 begin
的值。 begin
值非常通用。
您可以将它们设置为
"0s"
,这是默认设置,与文档时间线开始的时间一致(基本上是 windowload
事件) .这不是您稍后以交互方式添加标签的时间。到时候0s就过去了。您可以将它们设置为
"indefinite"
,这样动画就不会开始了。这通常与基于脚本的触发器结合使用,例如SVGAnimationElement.beginElement()
.您可以将它们设置为任意事件。
"myButton.click"
将在带有id="myButton"
的元素收到点击时启动动画。
语法还提供开始时间列表和延迟。非常值得研究 spec 中的语法以了解您可以做什么。 (浏览器未实现的时间值规范的唯一部分是 wallclock()
函数。)
对于旋转中心,你需要仔细观察你变换的是什么,旋转的是什么。正如您的代码所代表的那样,<animateTransform>
完全替换了 元素 上的 transform
属性 。您可能想要的是添加到现有变换列表末尾的 补充 变换。这是由属性 additive="sum"
引导的(默认值为 "replace"
)。
<path style="stroke:none;fill:black" transform="translate(30.333,30.333) rotate(45)"
d="M 0 0 L 0 -30.333 L -30.333 0 L -15.166 0 L -15.166 30.333 L 15.166
30.333 L 15.166 0 L 30.333 0 L 0 -30.333 Z "
id="arrow-3,3">
<animateTransform attributeName="transform" type="rotate"
from="0"
to="180"
begin="myButton.click" dur="1s" repeatCount="100"
additive="sum">
</animateTransform>
</path>
这将围绕本地用户空间坐标中的 (0, 0)
点旋转元素,连续 100 次,从 0 度到 180 度。需要仔细研究的三件事:
旋转中心必须在本地用户空间坐标中设置,在应用所有先前的变换之后,因为它被添加到变换列表的末尾。 ( 坐标系 的变换,而不是绘制到其中的对象的变换。)这相当于:它与路径数据使用的坐标系相同。该路径的中心位于坐标系原点。
转换 添加到基础元素转换 。这意味着,它不会取代最初的 45 度旋转,而是增加了它。因此,从恒等变换开始加法变换通常是个好主意。
按照你的定义,旋转从0度到180度,然后立即重新开始。这意味着它将立即跳回其初始位置。如果你想要一个平滑的运动,要么从0度旋转到360度,要么设置属性
accumulate="sum"
,这将在上一个迭代结束时开始下一个迭代。