question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

querySelectorAll() in external SVG breaks [NodeList] for attribute 'targets:'

See original GitHub issue

I’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?

https://stackoverflow.com/questions/54970635/anime-js-queryselectorall-in-external-svg-breaks-nodelist-for-attribute-tar/54971795#54971795

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:3
  • Comments:6

github_iconTop GitHub Comments

6reactions
mbforbescommented, Aug 3, 2021

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:

<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>

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’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.

image

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 check o 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 to false.

image

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 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:

https://github.com/juliangarnier/anime/blob/3ebfd913a04f7dc59cc3d52e38275272a5a12ae6/src/index.js#L800-L806

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:

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. 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.

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 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.

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:

// 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 ? 😃

2reactions
hirschferkelcommented, Nov 8, 2019

You could try d3, it’s more focused on animating selections.

Read more comments on GitHub >

github_iconTop Results From Across the Web

anime.js querySelectorAll() in external SVG breaks [NodeList ...
js and an external SVG seems to be, to collect all elements in a new array and use this as targets: attribute. var...
Read more >
5 Ways To Loop Over DOM Elements With QuerySelectorAll in ...
There are a lot of ways to loop over a NodeList Object (List of DOM elements) that is returned by a querySelectorAll method!...
Read more >
Changelog.md - jest-environment-jsdom-fourteen - GitLab
JSDOM.fragment() now creates fragments whose document has no ... (liqwid); Fixed querySelector() / querySelectorAll() behavior for SVG ...
Read more >
HTML 5.3 - W3C
For example, limiting the values of the target attribute that start ... Browsers should take extreme care when interacting with external ...
Read more >
HTML DOM Element appendChild() Method - W3Schools
More examples below. Definition and Usage. The appendChild() method appends a node (element) as the last child of an element.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found