javascript - SVG 动画在加载时触发,而不是在 DOM 插入时触发
问题描述
下面的代码动画 SVG 圆圈改变颜色并按预期工作。
如果调用SVG.addAnimatedCircle(this.root)
是从callback
方法内部(而不是它在下面的位置,在 内部constructor
)进行的,则动画在文档加载时开始 - 因此除非单击窗口,否则它是不可见的 - 而不是在触发事件时。
class SVG {
constructor() {
const root = document.createElementNS(
'http://www.w3.org/2000/svg', 'svg');
root.setAttribute('viewBox', '-50 -50 100 100');
this.root = root;
this.callback = this.callback.bind(this);
window.addEventListener('click', this.callback);
SVG.addAnimatedCircle(this.root);
}
callback() {
// SVG.addAnimatedCircle(this.root);
}
static addAnimatedCircle(toElement) {
const el = document.createElementNS(
'http://www.w3.org/2000/svg', 'circle');
el.setAttribute('cx', '0');
el.setAttribute('cy', '0');
el.setAttribute('r', '10');
el.setAttribute('fill', 'red');
toElement.appendChild(el);
const anim = document.createElementNS(
'http://www.w3.org/2000/svg', 'animate');
anim.setAttribute('attributeName', 'fill');
anim.setAttribute('from', 'blue');
anim.setAttribute('to', 'red');
anim.setAttribute('dur', '3s');
el.appendChild(anim);
}
}
const svg = new SVG();
document.body.appendChild(svg.root);
(当然,上面不需要在里面class
,我正在简化一个更复杂的类)。
这是为什么?当元素被创建并添加到 DOM 时,动画不应该开始吗?
解决方案
您创建的<animate>
元素将计算其begin
属性0s
(因为未设置)。
这个0s
值是相对于“文档开始时间”的,它本身在这个 HTML 文档中对应于根<svg>
的当前时间。
这意味着如果你<animate>
在它的根<svg>
元素已经在 DOM 中之后创建这样的元素,它的动画状态将取决于根<svg>
元素在 DOM 中的时间:
const root = document.querySelector("svg");
const circles = document.querySelectorAll("circle");
const duration = 3;
// will fully animate
circles[0].append(makeAnimate());
// will produce only half of the animation
setTimeout(() => {
circles[1].append(makeAnimate());
}, duration * 500);
// will not animate
setTimeout(() => {
circles[2].append(makeAnimate());
}, duration * 1000);
function makeAnimate() {
const anim = document.createElementNS("http://www.w3.org/2000/svg", "animate");
anim.setAttribute("attributeName", "fill");
anim.setAttribute("from", "blue");
anim.setAttribute("to", "red");
anim.setAttribute("fill", "freeze");
anim.setAttribute("dur", duration + "s");
return anim;
}
circle { fill: blue }
<svg height="60">
<circle cx="30" cy="30" r="25"/>
<circle cx="90" cy="30" r="25"/>
<circle cx="150" cy="30" r="25"/>
</svg>
<p>left circle starts immediately, and fully animates</p>
<p>middle circle starts after <code>duration / 2</code> and matches the same position as left circle</p>
<p>right circle starts after <code>duration</code>, the animation is already completed by then, nothing "animates"</p>
我们可以通过<svg>
它的方法设置 的当前时间SVGSVGElement.setCurrentTime()
。
因此,要创建一个<animate>
在创建时开始的,无论何时,我们都可以使用它,但是,这也会影响<animate>
已经在 中的<svg>
所有其他:
const root = document.querySelector("svg");
const circles = document.querySelectorAll("circle");
const duration = 3;
circles[0].append(makeAnimate());
root.setCurrentTime(0); // reset <animate> time
setTimeout(() => {
circles[1].append(makeAnimate());
root.setCurrentTime(0); // reset <animate> time
}, duration * 500);
setTimeout(() => {
circles[2].append(makeAnimate());
root.setCurrentTime(0); // reset <animate> time
}, duration * 1000);
function makeAnimate() {
const anim = document.createElementNS("http://www.w3.org/2000/svg", "animate");
anim.setAttribute("attributeName", "fill");
anim.setAttribute("from", "blue");
anim.setAttribute("to", "red");
anim.setAttribute("fill", "freeze");
anim.setAttribute("dur", duration + "s");
return anim;
}
circle { fill: blue }
<svg height="60">
<circle cx="30" cy="30" r="25"/>
<circle cx="90" cy="30" r="25"/>
<circle cx="150" cy="30" r="25"/>
</svg>
因此,虽然它可能对某些用户有用,但在大多数情况下,最好只设置<animate>
'begin
属性。
幸运的是,我们还可以通过该SVGSVGElement.getCurrentTime()
方法获取当前时间。
const root = document.querySelector("svg");
const circles = document.querySelectorAll("circle");
const duration = 3;
circles[0].append(makeAnimate());
setTimeout(() => {
circles[1].append(makeAnimate());
}, duration * 500);
setTimeout(() => {
circles[2].append(makeAnimate());
}, duration * 1000);
function makeAnimate() {
const anim = document.createElementNS("http://www.w3.org/2000/svg", "animate");
anim.setAttribute("attributeName", "fill");
anim.setAttribute("from", "blue");
anim.setAttribute("to", "red");
anim.setAttribute("fill", "freeze");
anim.setAttribute("dur", duration + "s");
// set the `begin` to "now"
anim.setAttribute("begin", root.getCurrentTime() + "s");
return anim;
}
circle { fill: blue }
<svg height="60">
<circle cx="30" cy="30" r="25"/>
<circle cx="90" cy="30" r="25"/>
<circle cx="150" cy="30" r="25"/>
</svg>
但是我们通常这样做的方式是完全使用API并通过JS来控制它,因为你已经开始使用JS了。
为此,我们将begin
属性设置为“indefinite”,这样它就不会自动启动,然后我们调用SVGAnimateElement ( <animate>
) 的beginElement()
方法,它会在需要时手动启动动画:
const root = document.querySelector("svg");
const circles = document.querySelectorAll("circle");
const duration = 3;
{
const animate = makeAnimate();
circles[0].appendChild(animate);
animate.beginElement();
}
setTimeout(() => {
const animate = makeAnimate();
circles[1].appendChild(animate);
animate.beginElement();
}, duration * 500);
setTimeout(() => {
const animate = makeAnimate();
circles[2].appendChild(animate);
animate.beginElement();
}, duration * 1000);
function makeAnimate() {
const anim = document.createElementNS("http://www.w3.org/2000/svg", "animate");
anim.setAttribute("attributeName", "fill");
anim.setAttribute("from", "blue");
anim.setAttribute("to", "red");
anim.setAttribute("fill", "freeze");
anim.setAttribute("dur", duration + "s");
// set the `begin` to "manual"
anim.setAttribute("begin", "indefinite");
return anim;
}
circle { fill: blue }
<svg height="60">
<circle cx="30" cy="30" r="25"/>
<circle cx="90" cy="30" r="25"/>
<circle cx="150" cy="30" r="25"/>
</svg>
推荐阅读
- c - 使用 sscanf 时检查空/空浮点值
- java - 如何在 java-fx 中禁用选定的按钮悬停
- python - 规范化 [0,1] 范围内数据框中的数据
- excel - 行集群值的条件下拉列表
- javascript - 如何使用 React Router 覆盖 Express API 路由
- typescript - Angular 9没有设置动态输入字段值
- swift - 如何将 '(Data?, URLResponse, Error?) 转换为预期的参数类型 '(Data?, URLResponse?, Error?) -> Void'
- lua - 获取“IntValue”的值总是得到默认值
- amazon-web-services - AWS Systems Manager Parameter Store 和 AWS Secrets Manager 之间的区别?
- mysql - MySQL - 聚合和 GROUP BY 与子查询