Open hirschferkel opened 5 years ago
So the only workaround for anime.js and an external SVG seems to be, to collect all elements in a new array and use this as targets: attribute.
var elements3 = new Array();
elements2 = document.getElementById("SVG").contentDocument.querySelectorAll("rect");
for (i = 0; i<elements2.length; i++) {
elements3.push(elements2[i]);
}
targets: elements3,
But I do not understand why this is necessary...
@hirschferkel You can do this with ES6 shorthand like this:
const elements2 = document.getElementById("SVG").contentDocument.querySelectorAll("rect");
const elements3 = [...elements2]
Oh your'e right and it's a kind of coincidence as I really learned that kind of shorthand half an hour ago...
Thanks
@hirschferkel You can do this with ES6 shorthand like this:
const elements2 = document.getElementById("SVG").contentDocument.querySelectorAll("rect"); const elements3 = [...elements2]
Any progress on this? Spreading the NodeList doesn't seem to work anymore. You actually have to do something like
const elements = [];
nodeListElements.forEach(nodeListElement => elements.push(nodeListElement));
EDIT: I can actually do Array.from(letters)
Is the problem related with iterables? 🤔
You could try d3, it's more focused on animating selections.
I ran into this issue as well, and devoted a chunk of this Sunday to figuring it out. I think I know where the problem lies, and why the workaround works.
Your webpage with an external SVG:
<object id="my-diagram" type="image/svg+xml" data="picture.svg"></object>
Just for completeness, say that picture.svg
has a group with two <rect>
s in it. (Doesn't matter, just the example I tested with.) So it looks roughly like:
<svg xmlns="http://www.w3.org/2000/svg">
<g id="fun-group">
<rect ...>
<rect ...>
</g>
</svg>
The surface-level problem is that anime.js cannot query from an external SVG, because the SVG gets put inside its own document
within the <object>
element. This is reasonable. So, to work around that, as beautifully illustrated by @hirschferkel, we get the SVG's document
and query within it:
// Wait until SVG has loaded. Otherwise, the document will be empty.
document.getElementById('my-diagram').addEventListener("load", function() {
const innerDoc = document.getElementById('my-diagram').contentDocument;
const targets = innerDoc.querySelectorAll("#fun-group *");
// Now, we can send these targets to anime.js
};
If we inspect targets
, we see they are of type NodeList
, a type that's supported by anime.js.
However, we now encounter the deeper problem: passing in these targets does not work. No animation happens.
I think that the root of the issue is in the toArray()
function, where our targets fail the check o instanceof NodeList
:
You can verify this yourself in a page's javascript REPL. If we check our targets
above, targets instanceof NodeList
evaluates to false
.
I have not dug into why this happens. A wild guess would be that Javascript has namespaces at the document
-level.
Here I'll explain why I think the above is the root issue.
Since toArray()
doesn't recognize our targets
(there o
) as a NodeList
, it wraps them in an array, returning [o]
. This puts anime.js in an unexpected state: now the targets are a nested iterable.
toArray()
was called from parseTargets()
, which was called from getAnimatables()
. So now, in the createNewInstance()
function, we have set our animatables
to [NodeList]
(i.e., an array of length 1, containing the NodeList
).
Where this fails is when we try to get our animations. Plugging along in createNewInstance()
, we call getAnimations()
, which tries to call createAnimation()
using each individual thing that can be animated by looping over the animatables
that we passed in:
But remember that animatables
is an array containing a NodeList
. So what gets passed into createAnimation()
is a NodeList
, when it's expecting a single element. createAnimation()
calls getAnimationType()
, which checks for every kind of el
it can think of:
Our poor NodeList
fails all of these checks, and we reach the end of the function without a return value. So undefined
is returned.
The createAnimation()
function checks the result, and it fails the if (animType)
check, so it also returns undefined
. Etc. Pop back up to createNewInstance()
, and our animations
are empty. So nothing happens.
If we instead unpack the NodeList
we got into our own array, this mitigates the root issue because the toArray()
function correctly recognizes what we sent in as an array. All of the functions afterward are able to iterate over the selection properly.
I am new to this project, so I'm not really in the position to make a recommendation! But I struggled with this issue for days before digging in to solve it. I thought there was no way that I was the only one animating an external SVG. (After all, they are often huge---much cleaner to keep in a separate file!)
As such, I would humbly suggest that the documentation gives an example of how to work with an external SVG. This ends up being significantly more complicated than an inline SVG.
Here's a minimal example, first with inline SVG:
// inline svg
anime({
targets: "#fun-group *",
...
});
To do the equivalent thing with an external SVG:
// external svg
// Wait for the <object> element to load.
document.addEventListener('DOMContentLoaded', function () {
// Wait for the contents of the SVG document to load.
document.getElementById('my-diagram').addEventListener("load", function() {
// Grab the inner document and manually run our query.
const innerDoc = document.getElementById('my-diagram').contentDocument;
const targets = innerDoc.querySelectorAll("#fun-group *");
anime({
// Bug workaround: repackage targets as a vanilla array.
targets: [...targets],
...
});
});
});
What do you think, @juliangarnier ? :-)
Hey @juliangarnier, I saw Anime v4 is in early access, huge congrats and great to hear! If you have a minute to spare, I'd love it if you might consider ergonomics of external SVG. Extensive writeup of rough edge in v3 in previous comment. Thank you for your hard work and the great library!
I'm trying to animate all within an SVG with anime.js.
As long as the SVG is inline, I can call a querySelectorAll() or getElementsByTagName() and take this nodelist as the attribute for targets: and all selected elements will be animated.
elements = document.querySelectorAll("rect");
inside anime code
targets: elements,
BUT: When I link the same SVG code to an external SVG (embeded with an
When I get single elements for the targets: attribute like
targets: elements2[0|,
anime.js will animate the single element.A function will not work, too.
targets: function(i){return elements2[i];},
How can I get the elements as targets in an external SVG in Edge/IE/Safari/Chrome to be animated with anime.js?
https://stackoverflow.com/questions/54970635/anime-js-queryselectorall-in-external-svg-breaks-nodelist-for-attribute-tar/54971795#54971795