Chrome/Opera `window.onpopstate` bug



我正在尝试在我的单页应用程序中处理window.onpopstate事件,但在Google Chrome和Opera中它不起作用。

这是我的简单html页面

<html>
<head>
<meta charset="utf-8" />
<script>
window.onpopstate = function(e) {
alert('onpopstate');
}
setTimeout(function () {
history.pushState(1, 'Hello John', '/hello/john');
}, 2000);
setTimeout(function () {
history.pushState(2, 'Hello Emily', '/hello/emily');
}, 3000);
</script>
</head>
<body></body>
</html>

当我点击浏览器的后退按钮时,我希望看到带有onpopstate文本的警报。在Safari、Firefox和Vivaldi中,它可以正常工作。在Chrome和Opera中,它从未调用过,它只是通过重新加载它来转到上一页,这对我的SPA来说是不好的情况。

更奇怪的是,我发现了一些让它发挥作用的技巧:

  1. 转到开发工具,控制台选项卡
  2. 简单地执行console.log(history)甚至console.log(window)它神奇地开始工作了!但如果我在页面上的脚本中做同样的操作,无论是否超时,这个技巧都不起作用。因此,我发现使onpopstate工作的唯一方法是手动转到控制台并执行console.log(history)

也许我错过了什么?如有任何帮助,我们将不胜感激。

我的环境:

  • macOS 11.0.1(20B29(
  • Chrome 87.0.4280.67(官方版本((x86_64(
  • 歌剧72.0.3815.378

发布Chromium错误

解决方案:

好吧,这不是bug,这是一个特性!这是Chromium的interact first特性。如果用户首先以某种方式与页面交互,那么一切都会正常工作,否则back button将重新加载。

所以,例如,只需在正文中添加一些按钮,然后先点击它,然后尝试在浏览器中点击back

<body>
<button onclick="alert('hello');">alert</button>
</body>

要想运行一些javascript,比如在历史记录上移动、播放声音或弹出窗口,用户首先必须在网站上进行交互(点击(。

你可以在这里找到更多信息:

WebUpdates 2017

我在MDN上发现了一个注释。上面写着:

注意:调用history.pushState((或history.replaceState((不会触发popstate事件。popstate事件仅由执行浏览器操作,例如单击后退按钮(或在JavaScript中调用history.back(((,当在同一文档的历史记录条目。

您可以在此处了解更多信息。

我建议您将事件处理程序注册到addEventListener(),而不是将其注册到WindowEventHandlers的onopstate属性。

示例:

<html>
<head>
<meta charset="utf-8" />
<script>
// ref: https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event
window.addEventListener('popstate', function(e) {
alert('onpopstate');
}
setTimeout(function () {
history.pushState(1, 'Hello John', '/hello/john');
}, 2000);
setTimeout(function () {
history.pushState(2, 'Hello Emily', '/hello/emily');
}, 3000);
</script>
</head>
<body></body>
</html>

state使用Object。来自MDN

state对象是一个JavaScript对象,它与pushState((创建的新历史记录条目。每当用户导航时对于新状态,将触发popstate事件,并且的state属性该事件包含历史条目的状态对象的副本。

const log = Logger();
// create 3 'history pages'
history.pushState({page: 1, greet: "Hello John"}, '', "#John");
history.pushState({page: 2, greet: "Hello Emily"}, '', "#Emily");
history.pushState({page: 3, greet: "Hello James"}, '', "#James");
log(`current history state: ${JSON.stringify(history.state)}`);
// on popstate log the history state if applicable
window.addEventListener("popstate", () =>
history && history.state && log(`${history.state.greet || ""} @page ${
history.state.page} (location: ${location.hash})`)
);
// listener for the buttons
document.addEventListener("click", evt => {
if (evt.target.nodeName === "BUTTON") {
return evt.target.id === "back" ? history.back() : history.forward();
}
});
// helper function for logging in the document
function Logger() {
let logEl;
if (typeof window === "object") {
logEl = document.querySelector("#log") || (() => {
document.body.append(
Object.assign(document.createElement('pre'), 
{id: "log"}));
return document.querySelector("#log");
})();
return (...logLines) => 
logLines.forEach(s => logEl.textContent += `${s}n`);
} else {
return (...logLines) => 
logLines.forEach(ll => console.log(`* `, ll));
}
}
<button id="back">&lt;&lt; 1 back</button>
<button id="forward">1 forward &gt;&gt;</button>

最新更新