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

Load event not fired on Safari when reloading page

本文关键字:加载 Safari 事件 新加载      更新时间:2023-09-26

我在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');
    ...
}

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

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

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

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

经过大量调查和拉扯,关于这一点的一些长篇笔记包括:

  • 使用 svgObj.addEventListener ('load', function() {...}) 而不是 svgObj.onload没有区别。使用addEventListener更好,因为尝试通过"onload"设置另一个处理程序将替换当前处理程序。与其他Node.js相反应用程序,你不需要removeEventListener时元素旧版本的IE(11之前(有问题,但是这现在应该被认为是安全的(无论如何都不适用于Electron(。
  • 最好使用this.contentDocument。有一个更好看的 getSVGDocument()有效的方法,但这似乎是针对反向的与旧的Adobe工具兼容,也许是Flash。返回的 DOM 是相同的。

SVG DOM 似乎在加载后被永久缓存,如@Kaiido所述,除了我相信该事件永远不会触发。更重要的是,在Node.js中,SVG DOM仍然缓存在它被加载到的同一个svgDOM变量中。我完全不明白这一点。我的直觉表明,index.html中的require('renderer.js')代码已将其缓存在模块系统中的某个地方,但对渲染器的更改确实会生效.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规则无法访问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 中的元素。例如,index.css中的这些规则现在可用于以声明方式更改显示 SVG 的哪些部分:

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