admin管理员组

文章数量:1399887

I have a simple SVG loaded inside a object tag like the code below. On Safari, the load event is fired just once, when I load the first time the page after opening the browser. All the other times it doesn't. I'm using the load event to initialize some animations with GSAP, so I need to know when the SVG is fully loaded before being able to select the DOM nodes. A quick workaround that seems to work is by using setTimeout instead of attaching to the event, but it seems a bit akward as slower networks could not have load the object in the specified amount of time. I know this event is not really standardized, but I don't think I'm the first person that faced this problem. How would you solve it?

var myElement = document.getElementById('my-element').getElementsByTagName('object')[0];

myElement.addEventListener('load', function () {
    var svgDocument = this.contentDocument;
    var myNode = svgDocument.getElementById('my-node');

    ...
}

I have a simple SVG loaded inside a object tag like the code below. On Safari, the load event is fired just once, when I load the first time the page after opening the browser. All the other times it doesn't. I'm using the load event to initialize some animations with GSAP, so I need to know when the SVG is fully loaded before being able to select the DOM nodes. A quick workaround that seems to work is by using setTimeout instead of attaching to the event, but it seems a bit akward as slower networks could not have load the object in the specified amount of time. I know this event is not really standardized, but I don't think I'm the first person that faced this problem. How would you solve it?

var myElement = document.getElementById('my-element').getElementsByTagName('object')[0];

myElement.addEventListener('load', function () {
    var svgDocument = this.contentDocument;
    var myNode = svgDocument.getElementById('my-node');

    ...
}
Share Improve this question asked Jan 8, 2016 at 12:54 StefanoStefano 3,25810 gold badges62 silver badges101 bronze badges 1
  • This is a great answer to a more specific question. It would be nice if the title specifically referenced problems loading an SVG DOM within an <object> tag, and the .contentDocument property returning null. – rand'Chris Commented Apr 24, 2020 at 15:19
Add a ment  | 

2 Answers 2

Reset to default 4

It sounds more like the problem is that, when the data is cached, the load event fires before you attached the handler.

What you can try is to reset the data attribute once you attached the event :

object.addEventListener('load', onload_handler);
// reset the data attribte so the load event fires again if it was cached
object.data = object.data;

I also ran into this problem while developing an Electron application. In my workflow I edit index.html and renderer.js in VSCode, and hit <Ctrl>+R to see the changes. I only restart the debugger to capture changes made to the main.js file.

I want to load an SVG that I can then manipulate from my application. Because the SVG is large I prefer to keep it in an external file that gets loaded from disk. To acplish this, the HTML file index.html contains this declaration:

<object id="svgObj" type="image/svg+xml" data="images/file.svg"></object>

The application logic in renderer.js contains:

let svgDOM     // global to access SVG DOM from other functions
const svgObj = document.getElementById('svgObj')

svgObj.onload = function () {
    svgDOM = this.contentDocument
    mySvgReady(this)
}

The problem is non-obvious because it appears intermittent: When the debugger/application first starts this works fine. But when reloading the application via <Ctrl>+R, the .contentDocument property is null.

After much investigation and hair-pulling, a few long-form notes about this include:

  • Using svgObj.addEventListener ('load', function() {...}) instead of svgObj.onload makes no difference. Using addEventListener is better because attempting to set another handler via 'onload' will replace the current handler. Contrary to other Node.js applications, you do not need to removeEventListener when the element is removed from the DOM. Old versions of IE (pre-11) had problems but this should now be considered safe (and doesn't apply to Electron anyway).
  • Usage of this.contentDocument is preferred. There is a nicer-looking getSVGDocument() method that works, but this appears to be for backwards patibility with old Adobe tools, perhaps Flash. The DOM returned is the same.

The SVG DOM appears to be permanently cached once loaded as described by @Kaiido, except that I believe the event never fires. What's more, in Node.js, the SVG DOM remains cached in the same svgDOM variable it was loaded into. I don't understand this at all. My intuition suggests that the require('renderer.js') code in index.html has cached this in the module system somewhere, but changes to renderer.js do take effect so this can't be the whole answer.

Regardless, here is an alternate approach to capturing the SVG DOM in Electron's render process that is working for me:

let svgDOM     // global to access from other functions
const svgObj = document.getElementById('svgObj')

svgObj.onload = function () {
    if (svgDOM) return mySvgReady(this) // Done: it already loaded, somehow
    if (!this.contentDocument) {        // Event fired before DOM loaded
        const oldDataUri = svgObj.data  // Save the original "data" attribute
        svgObj.data = ''                // Force it to a different "data" value
        // setImmediate() is too quick and this handler can get called many
        // times as the data value bounces between '' and the actual SVG data.
        // 50ms was chosen and seemed to work, and no other values were tested.
        setTimeout (x => svgObj.data = oldDataUri, 50)
        return;
    }
    svgDOM = this.contentDocument
    mySvgReady(this)
}

Next, I was very disappointed to learn that the CSS rules loaded by index.html can't access the elements within the SVG DOM. There are a number of ways to inject the stylesheet into the SVG DOM programmatically, but I ended up changing my index.html to this format:

<svg id="svgObj" class="svgLoader" src="images/file.svg"></svg>

I then added this code to my DOM setup code in renderer.js to load the SVG directly into the document. If you are using a pressed SVG format I expect you will need to do the depression yourself.

const fs = require ('fs')  // This is Electron/Node. Browsers need XHR, etc.

document.addEventListener('DOMContentLoaded', function() {
    ...
    document.querySelectorAll ('svg.svgLoader').forEach (el => {
        const src = el.getAttribute ('src')
        if (!src) throw "SVGLoader Element missing src"
        const svgSrc = fs.readFileSync (src)
        el.innerHTML = svgSrc
    })
    ...
})

I don't necessarily love it, but this is the solution I'm going with because I can now change classes on the SVG object and my CSS rules apply to the elements within the SVG. For example, these rules from index.css can now be used to declaritively alter which parts of the SVG are displayed:

...
#svgObj.cssClassBad #groupBad,
#svgObj.cssClassGood #groupGood {
    visibility: visible;
}
...

本文标签: javascriptLoad event not fired on Safari when reloading pageStack Overflow