重新加载页面时未在 Safari 上触发加载事件

Load event not fired on Safari when reloading page

我在 object 标签中加载了一个简单的 SVG,如下面的代码所示。在 Safari 上,load 事件只触发一次,当我打开浏览器后第一次加载页面时。所有其他时间都没有。我正在使用 load 事件通过 GSAP 初始化一些动画,所以我需要知道 SVG 何时完全加载才能 select DOM 节点。一个似乎有效的快速解决方法是使用 setTimeout 而不是附加到事件,但它似乎有点尴尬,因为较慢的网络无法在指定的时间内加载 object。我知道这个事件并没有真正标准化,但我认为我不是第一个遇到这个问题的人。你会怎么解决?

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

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

    ...
}

听起来更像是问题在于,缓存数据时,加载事件会在您附加处理程序之前触发。

您可以尝试在附加事件后重置数据属性:

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

我在开发 Electron 应用程序时也 运行 遇到过这个问题。在我的工作流程中,我在 VSCode 中编辑 index.htmlrenderer.js,然后点击 <Ctrl>+R 查看更改。我只重新启动调试器以捕获对 main.js 文件所做的更改。

我想加载一个 SVG,然后我可以从我的应用程序中对其进行操作。因为 SVG 很大,所以我更喜欢将它保存在从磁盘加载的外部文件中。为此,HTML 文件 index.html 包含以下声明:

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

renderer.js中的应用逻辑包含:

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

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

这个问题并不明显,因为它似乎是断断续续的:当 debugger/application 第一次启动时,这个工作正常。但是当通过 <Ctrl>+R 重新加载应用程序时, .contentDocument 属性 是 null.

经过大量调查和摸索,关于此的一些长篇笔记包括:

  • 使用 svgObj.addEventListener ('load', function() {...}) 而不是 svgObj.onload 没有区别。使用 addEventListener 更好 因为试图通过 'onload' 设置另一个处理程序 将替换当前的处理程序。与其他相反 Node.js 应用程序,您不需要 removeEventListener 当元素 从 DOM 中删除。旧版本的 IE (pre-11) 有问题但是 这现在应该被认为是安全的(并且不适用于 Electron)。
  • 最好使用this.contentDocument。有一个更好看的 getSVGDocument() 方法有效,但这似乎是为了向后 与旧的 Adob​​e 工具兼容,也许是 Flash。返回的DOM是一样的

SVG DOM 一旦按照@Kaiido 的描述加载后似乎会被永久缓存,除了我相信事件 永远不会 触发。更重要的是,在 Node.js 中,SVG DOM 仍然缓存在它被加载到的同一个 svgDOM 变量中。我完全不明白这一点。我的直觉表明 index.html 中的 require('renderer.js') 代码已将其缓存在模块系统中的某处,但对 renderer.js 的更改确实生效,因此这不是全部答案。

无论如何,这里有另一种在 Electron 的渲染过程中捕获 SVG DOM 的方法,它对我有用:

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

接下来,得知index.html加载的CSS规则无法访问SVGDOM内的元素,我感到非常失望。有多种方法可以通过编程方式将样式表注入 SVG DOM,但我最终将 index.html 更改为这种格式:

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

然后我将此代码添加到 renderer.js 中的 DOM 设置代码中,以将 SVG 直接加载到文档中。如果您使用压缩的 SVG 格式,我希望您需要自己进行解压。

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

我不一定喜欢它,但这是我要使用的解决方案,因为我现在可以更改 SVG 对象上的 类,并且我的 CSS 规则适用于其中的元素SVG。例如,index.css 中的这些规则现在可用于声明性地更改显示 SVG 的哪些部分:

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