Javascript 防止浏览器在 HTML5 History popstate 上滚动

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10742422/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 02:45:08  来源:igfitidea点击:

Prevent browser scroll on HTML5 History popstate

javascripthtmldom-eventsbrowser-history

提问by Alex Malet de Carteret

Is it possible to prevent the default behaviour of scrolling the document when a popstate event occurs?

是否可以防止发生 popstate 事件时滚动文档的默认行为?

Our site uses jQuery animated scrolling and History.js, and state changes should scroll the user around to different areas of the page whether via pushstate or popstate. The trouble is the browser restores the scroll position of the previous state automatically when a popstate event occurs.

我们的网站使用 jQuery 动画滚动和 History.js,状态更改应该通过 pushstate 或 popstate 将用户滚动到页面的不同区域。问题是当popstate事件发生时浏览器会自动恢复之前状态的滚动位置。

I've tried using a container element set to 100% width and height of the document and scrolling the content inside that container. The problem with that I've found is it doesn't seem to be nearly as smooth as scrolling the document; especially if using lots of css3 like box-shadows and gradients.

我尝试使用设置为文档宽度和高度 100% 的容器元素并滚动该容器内的内容。我发现的问题是它似乎不像滚动文档那样平滑;尤其是在使用大量 css3 时,例如 box-shadows 和渐变。

I've also tried storing the document's scroll position during a user initiated scroll and restoring it after the browser scrolls the page (on popstate). This works fine in Firefox 12 but in Chrome 19 there is a flicker due to the page being scrolled and restored. I assume this is to do with a delay between the scroll and the scroll event being fired (where the scroll position is restored).

我还尝试在用户启动滚动期间存储文档的滚动位置,并在浏览器滚动页面(在 popstate 上)后恢复它。这在 Firefox 12 中工作正常,但在 Chrome 19 中,由于页面被滚动和恢复,会出现闪烁。我认为这与滚动和滚动事件被触发(滚动位置恢复)之间的延迟有关。

Firefox scrolls the page (and fires the scroll event) before popstate fires and Chrome fires popstate first then scrolls the document.

Firefox 在 popstate 触发之前滚动页面(并触发 scroll 事件),Chrome 首先触发 popstate 然后滚动文档。

All the sites I've seen that use the history API either use a solution similar to those above or just ignore the scroll position change when a user goes back/forward (e.g. GitHub).

我见过的所有使用历史 API 的网站要么使用与上述类似的解决方案,要么在用户后退/前进时忽略滚动位置的变化(例如 GitHub)。

Is it possible to prevent the document being scrolled at all on popstate events?

是否可以在 popstate 事件中完全阻止文档滚动?

采纳答案by Tamás Bolvári

if ('scrollRestoration' in history) {
  history.scrollRestoration = 'manual';
}

(Announced by Googleon September 2, 2015)

谷歌于 2015 年 9 月 2 日公布)

Browser support:

浏览器支持:

Chrome: supported (since 46)

Chrome:支持(自 46 起)

Firefox: supported (since 46)

Firefox:支持(自 46 起)

IE/Edge: not supported, ask for support(then leave link in comment)

IE/Edge:不支持,寻求支持(然后在评论中留下链接)

Opera: supported (since 33)

Opera:支持(自 33 起)

Safari: supported

Safari:支持

For more info, see Browser compatibility on MDN.

有关更多信息,请参阅MDN 上的浏览​​器兼容性

回答by Beat Richartz

This has been a reported issue with the mozilla developer core for more than a year now. Unfortunately, the ticket did not really progress. I think Chrome is the same: There is no reliable way to tackle the scroll position onpopstatevia js, since it's native browser behaviour.

一年多来,这一直是mozilla 开发人员核心报告问题。不幸的是,票并没有真正进展。我认为 Chrome 是一样的:没有可靠的方法onpopstate通过 js处理滚动位置,因为它是本机浏览器行为。

There is hope for the future though, if you look at the HTML5 history spec, which explicitly wishes for the scroll position to be represented on the state object:

但是,如果您查看HTML5 历史规范,它明确希望在状态对象上表示滚动位置,那么未来还是有希望的:

History objects represent their browsing context's session history as a flat list of session history entries. Each session history entry consists of a URL and optionally a state object, and may in addition have a title, a Document object, form data, a scroll position, and other information associated with it.

History 对象将其浏览上下文的会话历史表示为会话历史条目的平面列表。每个会话历史条目都包含一个 URL 和一个可选的状态对象,此外还可能有一个标题、一个文档对象、表单数据、滚动位置和其他与之相关的信息。

This, and if you read the comments on the mozilla ticket mentioned above, gives some indication that it is possible that in the near future scroll position will not be restored anymore onpopstate, at least for people using pushState.

这一点,如果您阅读上面提到的关于 mozilla 票证的评论,就会表明在不久的将来滚动位置可能不再恢复onpopstate,至少对于使用pushState.

Unfortunately, until then, the scroll position gets stored when pushStateis used, and replaceStatedoes not replace the scroll position. Otherwise, it would be fairly easy, and you could use replaceStateto set the current Scroll position everytime the user has scrolled the page (with some cautious onscroll handler).

不幸的是,在此之前,滚动位置在pushState使用时会被存储,并且replaceState不会替换滚动位置。否则,这将相当容易,并且您可以replaceState在每次用户滚动页面时设置当前 Scroll 位置(使用一些谨慎的 onscroll 处理程序)。

Also unfortunately, the HTML5 spec does not specify when exactly the popstateevent has to be fired, it just says: ?is fired in certain cases when navigating to a session history entry?, which does not clearly say if it's before or after; if it was always before, a solution with handling the scroll event occuring after the popstate would be possible.

同样不幸的是,HTML5 规范并没有具体说明什么时候popstate必须触发事件,它只是说: ? 在导航到会话历史记录条目时在某些情况下被触发?,并没有明确说明它是在之前还是之后;如果它总是在之前,则可以使用处理 popstate 之后发生的滚动事件的解决方案。

Cancel the scroll event?

取消滚动事件?

Furthermore, it would also be easy, if the scroll event where cancelable, which it isn't. If it was, you could just cancel the first scroll event of a series (user scroll events are like lemmings, they come in dozens, whereas the scroll event fired by the history repositioning is a single one), and you would be fine.

此外,如果滚动事件可取消,则它也很容易,而事实并非如此。如果是,您可以取消系列的第一个滚动事件(用户滚动事件就像旅鼠,它们有几十个,而历史重新定位触发的滚动事件是一个),这样就可以了。

There's no solution for now

暂时没有解决办法

As far as I see, the only thing I'd recommend for now is to wait for the HTML5 Spec to be fully implemented and to roll with the browser behaviour in this case, that means: animate the scrolling when the browser lets you do it, and let the browser reposition the page when there's a history event. The only thing you can influence position-wise is that you use pushStatewhen the page is positioned in a good way to go back to.Any other solution is either bound to have bugs, or to be too browser-specific, or both.

就我所见,我现在唯一建议的事情是等待 HTML5 规范完全实现并在这种情况下随浏览器行为滚动,这意味着:当浏览器允许您执行滚动动画时,并让浏览器在发生历史事件时重新定位页面。您唯一可以影响位置的事情是pushState当页面以良好的方式定位时使用。任何其他解决方案要么必然有错误,要么过于特定于浏览器,或两者兼而有之。

回答by Nathan MacInnes

You're going to have to use some kind of horrible browser sniffing here. For Firefox, I would go with your solution of storing the scroll position and restoring it.

你将不得不在这里使用某种可怕的浏览器嗅探。对于 Firefox,我会采用您存储滚动位置并恢复它的解决方案。

I thought I had a good Webkit solution based on your description, but I just tried in Chrome 21, and it seems that Chrome scrolls first, then fires the popstate event, then fires the scroll event. But for reference, here's what I came up with:

根据您的描述,我认为我有一个很好的 Webkit 解决方案,但我只是在 Chrome 21 中尝试过,似乎 Chrome 先滚动,然后触发 popstate 事件,然后触发滚动事件。但作为参考,这是我想出的:

function noScrollOnce(event) {
    event.preventDefault();
    document.removeEventListener('scroll', noScrollOnce);
}
window.onpopstate = function () {
    document.addEventListener('scroll', noScrollOnce);
};?

Black magic such as pretending the page is scrolling by moving an absolutepositioned element is ruled out by the screen repainting speed too.

absolute屏幕重绘速度也排除了诸如通过移动定位元素来假装页面正在滚动的黑魔法。

So I'm 99% sure that the answer is that you can't, and you're going to have to use one of the compromises you've mentioned in the question. Both browsers scroll before JavaScript knows anything about it, so JavaScript can only react after the event. The only difference is that Firefox doesn't paint the screen until after the Javascript has fired, which is why there's a workable solution in Firefox but not in WebKit.

所以我 99% 肯定答案是你不能,你将不得不使用你在问题中提到的妥协之一。两个浏览器都在 JavaScript 知道任何事情之前滚动,所以 JavaScript 只能在事件发生后做出反应。唯一的区别是 Firefox 在 Javascript 触发后才绘制屏幕,​​这就是为什么在 Firefox 中有一个可行的解决方案但在 WebKit 中没有的原因。

回答by Roman Usherenko

Now you can do

现在你可以做

history.scrollRestoration = 'manual';

and this should prevent browser scroll. This only works right now in Chrome 46 and above, but it seems that Firefox is planning to support it too

这应该可以防止浏览器滚动。这仅适用于 Chrome 46 及更高版本,但 Firefox 似乎也计划支持它

回答by mshipov

The solution is to use position: fixedand specify topequal to scroll position of page.

解决方案是使用position: fixed并指定top等于页面的滚动位置。

Here is an example:

下面是一个例子:

$(window).on('popstate', function()
{
   $('.yourWrapAroundAllContent').css({
      position: 'fixed',
      top: -window.scrollY
   });

   requestAnimationFrame(function()
   {
      $('.yourWrapAroundAllContent').css({
         position: 'static',
         top: 0
      });          
   });
});

Yes, you instead receive flickering scrollbar, but it is less evil.

是的,您会收到闪烁的滚动条,但它没有那么邪恶。

回答by Ben Bud

The following fix should work in all browsers.

以下修复应该适用于所有浏览器。

You can set scroll position to 0 on the unload event. You can read about this event here: https://developer.mozilla.org/en-US/docs/Web/Events/unload. Essentially, the unload event fires right before you leave the page.

您可以在卸载事件上将滚动位置设置为 0。您可以在此处阅读有关此事件的信息:https: //developer.mozilla.org/en-US/docs/Web/Events/unload。本质上,卸载事件会在您离开页面之前触发。

By setting scrollPosition to 0 on unload means when you leave the page with a set pushState it sets scrollPosition to 0. When you return to this page by refreshing or pressing back it will not autoscroll.

通过在卸载时将 scrollPosition 设置为 0 意味着当您以设置的 pushState 离开页面时,它会将 scrollPosition 设置为 0。当您通过刷新或按回返回此页面时,它不会自动滚动。

//Listen for unload event. This is triggered when leaving the page.
//Reference: https://developer.mozilla.org/en-US/docs/Web/Events/unload
window.addEventListener('unload', function(e) {
    //set scroll position to the top of the page.
    window.scrollTo(0, 0);
});

回答by rames

Setting scrollRestoration to manual not worked for me, here is my solution.

将 scrollRestoration 设置为手动对我不起作用,这是我的解决方案。

window.addEventListener('popstate', function(e) {
    var scrollTop = document.body.scrollTop;
    window.addEventListener('scroll', function(e) {
        document.body.scrollTop = scrollTop;
    });
});

回答by pmrotule

Like others said, there is no real way to do it, only ugly hacky way. Removing the scroll event listener didn't work for me, so here's my ugly way to do it:

就像其他人说的那样,没有真正的方法可以做到,只有丑陋的hacky方法。删除滚动事件侦听器对我不起作用,所以这是我的丑陋方法:

/// GLOBAL VARS ////////////////////////
var saveScrollPos = false;
var scrollPosSaved = window.pageYOffset;
////////////////////////////////////////

jQuery(document).ready(function($){

    //// Go back with keyboard shortcuts ////
    $(window).keydown(function(e){
        var key = e.keyCode || e.charCode;

        if((e.altKey && key == 37) || (e.altKey && key == 39) || key == 8)
        saveScrollPos = false;
    });
    /////////////////////////////////////////

    //// Go back with back button ////
    $("html").bind("mouseout",  function(){ saveScrollPos = false; });
    //////////////////////////////////

    $("html").bind("mousemove", function(){ saveScrollPos = true; });

    $(window).scroll(function(){
        if(saveScrollPos)
        scrollPosSaved = window.pageYOffset;
        else
        window.scrollTo(0, scrollPosSaved);
    });

});

It works in Chrome, FF and IE (it flashes the first time you go back in IE). Any improvement suggestions are welcome! Hope this helps.

它适用于 Chrome、FF 和 IE(第一次返回 IE 时它会闪烁)。欢迎任何改进建议!希望这可以帮助。

回答by Prashant Saraswat

Create a 'span' element somewhere at the top of the page and set focus to this on load. The browser will scroll to the focussed element. I understand that this is a workaround and focus on 'span' doesn't work in all browsers ( uhmmm.. Safari ). Hope this helps.

在页面顶部的某处创建一个“跨度”元素,并在加载时将焦点设置为此。浏览器将滚动到焦点元素。我知道这是一种解决方法,并且对“跨度”的关注不适用于所有浏览器(嗯……Safari)。希望这可以帮助。

回答by George Filippakos

Here is what I have implemented on a site that wanted the scroll position to focus to a specific element when the poststate is fired (back button):

这是我在一个站点上实现的,它希望在触发 poststate 时滚动位置聚焦到特定元素(后退按钮):

$(document).ready(function () {

     if (window.history.pushState) {
        //if push supported - push current page onto stack:
        window.history.pushState(null, document.title, document.location.href);
    }

    //add handler:
    $(window).on('popstate', PopStateHandler);
}

//fires when the back button is pressed:
function PopStateHandler(e) {
    emnt= $('#elementID');
    window.scrollTo(0, emnt.position().top);
    alert('scrolling to position');
}

Tested and works in firefox.

在 Firefox 中测试和工作。

Chrome will scroll to position but then repositions back to original place.

Chrome 将滚动到位置,然后重新定位回原始位置。