繁体   English   中英

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

[英]Prevent browser scroll on HTML5 History popstate

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

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

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

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

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

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

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

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

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

浏览器支持:

Chrome:支持(自 46 起)

Firefox:支持(自 46 起)

IE:不支持

边缘:支持(自 79 起)

Opera:支持(自 33 起)

Safari:支持

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

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

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

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

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

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

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

取消滚动事件?

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

暂时没有解决办法

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

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

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

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

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

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

现在你可以做

history.scrollRestoration = 'manual';

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

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

下面是一个例子:

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

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

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

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

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

通过在卸载时将 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);
});

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

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

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

这是我在一个站点上实现的,它希望在触发 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');
}

在 Firefox 中测试和工作。

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

就像其他人说的那样,没有真正的方法可以做到,只有丑陋的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);
    });

});

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

可能想试试这个?

window.onpopstate = function(event) {
  return false;
};

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM