Запретить прокрутку браузера по истории HTML5

Можно ли предотвратить поведение по умолчанию прокрутки документа при возникновении события popstate?

Наш сайт использует анимированную прокрутку jQuery и History.js, а изменения состояния должны прокручивать пользователя в разные области страницы, будь то через pushstate или popstate. Проблема заключается в том, что браузер автоматически восстанавливает положение прокрутки предыдущего состояния, когда происходит событие popstate.

Я попытался использовать элемент контейнера, установленный на 100% ширину и высоту документа и прокрутку содержимого внутри этого контейнера. Проблема с тем, что я обнаружил, так это не так гладко, как прокрутка документа; особенно при использовании большого количества css3, таких как тени и градиенты.

Я также попытался сохранить позицию прокрутки документа во время прокрутки, инициированной пользователем, и восстановить ее после того, как браузер прокручивает страницу (по popstate). Это отлично работает в Firefox 12, но в Chrome 19 есть мерцание из-за прокрутки и восстановления страницы. Я предполагаю, что это связано с задержкой между прокруткой и запущенным событием прокрутки (где позиция прокрутки восстанавливается).

Firefox прокручивает страницу (и запускает событие прокрутки) перед тем, как срабатывает всплывающее окно, и Chrome сначала запускает popstate, затем прокручивает документ.

Все сайты, которые я видел, которые используют API истории, используют либо решение, аналогичное приведенному выше, либо просто игнорируют изменение положения прокрутки, когда пользователь возвращается/пересылает (например, GitHub).

Возможно ли предотвратить прокрутку документа на всплывающие события?

Ответ 1

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

(объявлено Google 2 сентября 2015 г.)

Поддержка браузера:

Chrome: поддерживается (с 46 лет)

Firefox: поддерживается (с 46 лет)

IE/Edge: не поддерживается, обратитесь за поддержкой (затем оставьте ссылку в комментарии)

Опера: поддерживается (с 33)

Safari: поддерживается

Для получения дополнительной информации см. Совместимость браузера с MDN.

Ответ 2

Это была сообщаемая проблема с ядром разработчика mozilla уже более года. К сожалению, билет действительно не продвинулся. Я думаю, что Chrome тот же: нет надежного способа решить позицию прокрутки onpopstate через js, так как это поведение собственного браузера.

Есть надежда на будущее, хотя, если вы посмотрите на спецификацию истории HTML5, которая явно хочет, чтобы позиция прокрутки была представлена ​​на объект состояния:

Объекты истории представляют историю сеансов контекста просмотра в виде плоского списка записей истории сеансов. Каждая запись истории сеанса состоит из URL-адреса и, необязательно, объекта состояния, а также может иметь заголовок, объект документа, данные формы, положение прокрутки и другую связанную с ним информацию.

Это, и если вы прочтете комментарии к вышеупомянутому билету mozilla, дайте некоторые указания на то, что в ближайшем будущем положение прокрутки больше не будет восстановлено onpopstate, по крайней мере для людей, использующих pushState.

К сожалению, до тех пор позиция прокрутки сохраняется, когда используется pushState, а replaceState не заменяет позицию прокрутки. В противном случае было бы довольно легко, и вы могли бы использовать replaceState для установки текущей позиции прокрутки каждый раз, когда пользователь прокручивал страницу (с некоторым осторожным обработчиком onscroll).

К сожалению, спецификация HTML5 не указывает, когда нужно только запустить событие popstate, он просто говорит: "в некоторых случаях запускается при переходе на запись истории сеанса", что явно не говорит о том, до или после; если бы это было всегда раньше, было бы возможно решение с обработкой события прокрутки после popstate.

Отменить событие прокрутки?

Кроме того, было бы также легко, если событие прокрутки, где отменено, а это не так. Если бы это было так, вы могли бы просто отменить первое событие прокрутки серии (события прокрутки пользователя похожи на лемминги, они входят в десятки, тогда как событие прокрутки, выпущенное перестановкой истории, является одним), и все будет в порядке.

На данный момент решения нет

Насколько я вижу, единственное, что я бы рекомендовал на данный момент, - это дождаться полной реализации спецификации HTML5 и рутинга с поведением браузера в этом случае, что означает: анимировать прокрутку, когда браузер позволяет вы делаете это, и пусть браузер переустанавливает страницу, когда есть событие истории. Единственное, на что вы можете влиять по-положению, - это то, что вы используете pushState, когда страница позиционируется с хорошим обращением к.. Любое другое решение либо связано с ошибками, либо должно быть слишком специфичные для браузера, или и то, и другое.

Ответ 3

Вам придется использовать какой-то ужасный браузер, обнюхивающий здесь. Для 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.

Ответ 4

Теперь вы можете сделать

history.scrollRestoration = 'manual';

и это должно предотвратить прокрутку браузера. Это работает только сейчас в Chrome 46 и выше, но похоже, что Firefox тоже будет его поддерживать.

Ответ 5

Решение состоит в том, чтобы использовать position: fixed и указать top, равную позиции прокрутки страницы.

Вот пример:

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

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

Да, вместо этого вы получаете мерцающую полосу прокрутки, но она менее злая.

Ответ 6

Следующее исправление должно работать во всех браузерах.

Вы можете установить положение прокрутки в 0 в событии разгрузки. Вы можете прочитать об этом событии здесь: https://developer.mozilla.org/en-US/docs/Web/Events/unload. По сути, событие выгрузки запускается прямо перед тем, как вы покидаете страницу.

Установив scrollPosition на 0 при выгрузке, когда вы покидаете страницу с помощью set 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);
});

Ответ 7

Настройка scrollRestoration на ручную не сработала для меня, вот мое решение.

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

Ответ 8

Создайте элемент "span" где-нибудь в верхней части страницы и установите фокус на это при загрузке. Браузер перейдет к фокусовому элементу. Я понимаю, что это обходное решение, и сосредоточиться на "span" не работает во всех браузерах (uhmmm.. Safari). Надеюсь, это поможет.

Ответ 9

Вот что я реализовал на сайте, который хотел, чтобы положение прокрутки фокусировалось на конкретном элементе при запуске 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 будет прокручиваться до положения, но затем переупорядочивает обратно в исходное место.

Ответ 10

Как и другие, нет реального способа сделать это, только уродливый хакерский путь. Удаление прослушивателя событий прокрутки не сработало для меня, поэтому здесь мой уродливый способ сделать это:

/// 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). Любые предложения по улучшению приветствуются! Надеюсь, это поможет.

Ответ 11

Можете попробовать это?

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