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

IT技术 javascript html svg onload-event object-tag
2021-03-03 21:48:13

我在object标签中加载了一个简单的 SVG,如下面的代码。在 Safari 上,load当我打开浏览器后第一次加载页面时,该事件仅触发一次。所有其他时间都没有。我正在使用该load事件用 GSAP 初始化一些动画,所以我需要知道 SVG 何时完全加载才能选择 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');

    ...
}
2个回答

这听起来更像是问题,当数据被缓存时,加载事件你附加处理程序之前触发

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

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

我在开发 Electron 应用程序时也遇到了这个问题。在我的工作流编辑我index.htmlrenderer.js在VSCode,并创下<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)
}

这个问题并不明显,因为它看起来是间歇性的:当调试器/应用程序第一次启动时,这工作正常。但是当通过 重新加载应用程序时<Ctrl>+R.contentDocument属性为null

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

  • 使用svgObj.addEventListener ('load', function() {...})而不是 svgObj.onload没有区别。使用addEventListener更好的,因为尝试设置另一个处理程序通过“的onload”将取代目前的处理程序。与其他Node.js 应用程序相反removeEventListener当元素从 DOM 中移除时,您不需要这样做旧版本的 IE(11 之前)有问题,但现在应该被认为是安全的(无论如何不适用于 Electron)。
  • 的使用this.contentDocument是优选的。有一种更好看的 getSVGDocument()方法有效,但这似乎是为了向后兼容旧的 Adob​​e 工具,也许是 Flash。返回的 DOM 是相同的。

如@Kaiido 所述,SVG DOM 似乎在加载后永久缓存,但我相信该事件永远不会触发。此外,在 中Node.js,SVG DOM 仍然缓存在svgDOM它加载到的同一个变量中。我完全不明白这一点。我的直觉表明 中的require('renderer.js')代码index.html已将其缓存在module系统中的某处,但对 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)
}

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

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

然后我将此代码添加到我的 DOM 设置代码中,renderer.js以将 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 中的元素。例如,这些规则 fromindex.css现在可用于声明性地更改显示 SVG 的哪些部分:

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