Запретить браузер прокручивать всплывающее окно истории HTML5

83

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

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

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

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

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

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

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

Алекс Малет де Картерет
источник
У вас может быть некоторый успех при чтении измерения элемента (для принудительного перекомпоновки страницы) после установки положения прокрутки. например, document.body.clientHeightно я не видел, чтобы он работал во всех браузерах.
Шон Хоган,
Что, если бы вы управляли функцией прокрутки документа, по крайней мере, с этого я бы начал. Здесь есть решение: stackoverflow.com/questions/7035331/…
Джефф Вуден

Ответы:

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

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

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

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

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

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

Edge: поддерживается (с 79)

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

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

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

Тамаш Болвари
источник
1
Короче: history.scrollRestoration && history.scrollRestoration = 'manual';или дажеvar sr = 'scrollRestoration'; history[sr] && history[sr] = 'manual';
Бхарата
31

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

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

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

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

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

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

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

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

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

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

Beat Richartz
источник
Формулировка спецификации истории теперь немного изменилась. Вдобавок, если вы прокрутите вниз и посмотрите документацию pushState, там не будет упоминания о добавлении позиции прокрутки. Спецификация предполагает, что запись в истории сеанса может содержать сохраненную позицию прокрутки, но я не уверен, что это означает, что веб-разработчик сможет ее установить.
Walex
4

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

Натан Макиннес
источник
3

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

history.scrollRestoration = 'manual';

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

Роман Ушеренко
источник
2

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

Вот пример:

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

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

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

мшипов
источник
1

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

Вы можете установить позицию прокрутки на 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);
});
Бен Бад
источник
1

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

window.addEventListener('popstate', function(e) {
    var scrollTop = document.body.scrollTop;
    window.addEventListener('scroll', function(e) {
        document.body.scrollTop = scrollTop;
    });
});
рампы
источник
0

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

Прашант Сарасват
источник
0

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

Джордж Филиппакос
источник
0

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

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

пмротула
источник
-5

Не могли бы вы попробовать это?

window.onpopstate = function(event) {
  return false;
};
Дэн Барзилай
источник
3
Это не работает. Некоторые события невозможно предотвратить, и popstate - одно из них.
Натан Макиннес,