Chrome/Opera `window.onpopstate` 错误

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

也许我遗漏了什么?任何帮助将不胜感激。

我的环境:

已发布 Chromium bug

解决方案:

好的,这不是错误,这是一个功能!这是 Chromium 的 interact first 特性。如果用户首先以某种方式与页面进行交互,那么一切都会正常,否则 back button 将返回并重新加载。

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

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

我在 MDN 上找到了注释。它说:

Note: Calling history.pushState() or history.replaceState() won't trigger a popstate event. The popstate event is only triggered by performing a browser action, such as clicking on the back button (or calling history.back() in JavaScript), when navigating between two history entries for the same document.

你可以了解更多here

我建议您将事件处理程序注册到 addEventListener(),而不是注册到 onpopstate property of WindowEventHandlers

示例:

<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

The state object is a JavaScript object which is associated with the new history entry created by pushState(). Whenever the user navigates to the new state, a popstate event is fired, and the state property of the event contains a copy of the history entry's state object.

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>

为了能够 运行 一些 javascript 像移动历史记录或播放声音或弹出 windows 用户首先必须在网站上进行交互(点击)。

您可以在此处找到更多信息:

WebUpdates 2017