querySelectorAll() in external SVG breaks [NodeList] for attribute 'targets:'
See original GitHub issueI’m trying to animate all <rect> 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 <object> element), anime.js stopps working (only FireFox will work,still) when I set
elements2 = document.getElementById("SVG").contentDocument.querySelectorAll("rect");
targets: elements2,
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?
Issue Analytics
- State:
- Created 5 years ago
- Reactions:3
- Comments:6
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.
Setup
Your webpage with an external SVG:
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:Observed Problem
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’sdocument
and query within it:If we inspect
targets
, we see they are of typeNodeList
, 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.
Root Issue
I think that the root of the issue is in the
toArray()
function, where our targets fail the checko instanceof NodeList
:https://github.com/juliangarnier/anime/blob/3ebfd913a04f7dc59cc3d52e38275272a5a12ae6/src/index.js#L303-L308
You can verify this yourself in a page’s javascript REPL. If we check our
targets
above,targets instanceof NodeList
evaluates tofalse
.I have not dug into why this happens. A wild guess would be that Javascript has namespaces at the
document
-level.Effects
Here I’ll explain why I think the above is the root issue.
Since
toArray()
doesn’t recognize ourtargets
(thereo
) as aNodeList
, 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 fromparseTargets()
, which was called fromgetAnimatables()
. So now, in thecreateNewInstance()
function, we have set ouranimatables
to[NodeList]
(i.e., an array of length 1, containing theNodeList
).Where this fails is when we try to get our animations. Plugging along in
createNewInstance()
, we callgetAnimations()
, which tries to callcreateAnimation()
using each individual thing that can be animated by looping over theanimatables
that we passed in:https://github.com/juliangarnier/anime/blob/3ebfd913a04f7dc59cc3d52e38275272a5a12ae6/src/index.js#L800-L806
But remember that
animatables
is an array containing aNodeList
. So what gets passed intocreateAnimation()
is aNodeList
, when it’s expecting a single element.createAnimation()
callsgetAnimationType()
, which checks for every kind ofel
it can think of:https://github.com/juliangarnier/anime/blob/3ebfd913a04f7dc59cc3d52e38275272a5a12ae6/src/index.js#L433-L438
Our poor
NodeList
fails all of these checks, and we reach the end of the function without a return value. Soundefined
is returned.The
createAnimation()
function checks the result, and it fails theif (animType)
check, so it also returnsundefined
. Etc. Pop back up tocreateNewInstance()
, and ouranimations
are empty. So nothing happens.Why unpacking into our own array works
If we instead unpack the
NodeList
we got into our own array, this mitigates the root issue because thetoArray()
function correctly recognizes what we sent in as an array. All of the functions afterward are able to iterate over the selection properly.Recommendation
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:
To do the equivalent thing with an external SVG:
What do you think, @juliangarnier ? 😃
You could try d3, it’s more focused on animating selections.