Verhindern Sie das Scrollen des Browsers im HTML5-Verlaufsstatus


83

Ist es möglich, das Standardverhalten beim Scrollen des Dokuments zu verhindern, wenn ein Popstate-Ereignis auftritt?

Unsere Website verwendet jQuery-animiertes Scrollen und History.js. Statusänderungen sollten den Benutzer über Pushstate oder Popstate zu verschiedenen Bereichen der Seite führen. Das Problem ist, dass der Browser die Bildlaufposition des vorherigen Status automatisch wiederherstellt, wenn ein Popstate-Ereignis auftritt.

Ich habe versucht, ein Containerelement zu verwenden, das auf 100% Breite und Höhe des Dokuments eingestellt ist, und den Inhalt in diesem Container zu scrollen. Das Problem dabei ist, dass es nicht annähernd so reibungslos zu sein scheint wie das Scrollen des Dokuments. vor allem, wenn Sie viel CSS3 wie Box-Shadows und Farbverläufe verwenden.

Ich habe auch versucht, die Bildlaufposition des Dokuments während eines vom Benutzer initiierten Bildlaufs zu speichern und wiederherzustellen, nachdem der Browser die Seite gescrollt hat (im Popstate). Dies funktioniert in Firefox 12 einwandfrei, in Chrome 19 tritt jedoch ein Flimmern auf, da die Seite gescrollt und wiederhergestellt wird. Ich gehe davon aus, dass dies mit einer Verzögerung zwischen dem Bildlauf und dem ausgelösten Bildlaufereignis zu tun hat (bei dem die Bildlaufposition wiederhergestellt wird).

Firefox scrollt die Seite (und löst das Bildlaufereignis aus), bevor Popstate ausgelöst wird und Chrome zuerst Popstate auslöst und dann das Dokument scrollt.

Alle Websites, die ich gesehen habe und die die Verlaufs-API verwenden, verwenden entweder eine ähnliche Lösung wie oben oder ignorieren einfach die Änderung der Bildlaufposition, wenn ein Benutzer vor / zurück geht (z. B. GitHub).

Ist es möglich zu verhindern, dass das Dokument bei Popstate-Ereignissen überhaupt gescrollt wird?


Möglicherweise haben Sie Erfolg beim Lesen einer Elementdimension (um einen Seitenrückfluss zu erzwingen), nachdem Sie die Bildlaufposition festgelegt haben. zB document.body.clientHeightaber ich habe nicht gesehen, dass es in allen Browsern funktioniert.
Sean Hogan

Was wäre, wenn Sie die Bildlauffunktion des Dokuments manipulieren würden? Dort würde ich zumindest anfangen. Hier gibt es eine Lösung dafür: stackoverflow.com/questions/7035331/…
Jeff Wooden

Antworten:


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

( Angekündigt von Google am 2. September 2015)

Browserunterstützung:

Chrome: unterstützt (seit 46)

Firefox: unterstützt (seit 46)

IE / Edge: nicht unterstützt, um Unterstützung bitten (dann Link im Kommentar hinterlassen)

Oper: unterstützt (seit 33)

Safari: unterstützt

Weitere Informationen finden Sie unter Browserkompatibilität auf MDN .


1
Kürzere: history.scrollRestoration && history.scrollRestoration = 'manual';oder sogarvar sr = 'scrollRestoration'; history[sr] && history[sr] = 'manual';
Bharata

31

Dies ist seit mehr als einem Jahr ein gemeldetes Problem mit dem Mozilla-Entwicklerkern . Leider hat sich das Ticket nicht wirklich weiterentwickelt. Ich denke, Chrome ist dasselbe: Es gibt keine zuverlässige Möglichkeit, die onpopstateBildlaufposition über js zu ändern, da es sich um ein natives Browserverhalten handelt.

Es gibt jedoch Hoffnung für die Zukunft, wenn Sie sich die HTML5-Verlaufsspezifikation ansehen , die ausdrücklich wünscht, dass die Bildlaufposition auf dem Statusobjekt dargestellt wird:

Verlaufsobjekte stellen den Sitzungsverlauf ihres Browserkontexts als flache Liste von Sitzungsverlaufseinträgen dar. Jeder Sitzungsverlaufseintrag besteht aus einer URL und optional einem Statusobjekt und kann zusätzlich einen Titel, ein Dokumentobjekt, Formulardaten, eine Bildlaufposition und andere damit verbundene Informationen enthalten.

Dies und wenn Sie die oben erwähnten Kommentare zum Mozilla-Ticket lesen, gibt dies einen Hinweis darauf, dass es möglich ist, dass die Bildlaufposition in naher Zukunft nicht mehr wiederhergestellt wird onpopstate, zumindest für Benutzer pushState.

Leider wird bis dahin die Bildlaufposition gespeichert, wenn sie pushStateverwendet wird, und ersetzt replaceStatenicht die Bildlaufposition. Andernfalls wäre es ziemlich einfach, und Sie könnten replaceStatedie aktuelle Bildlaufposition jedes Mal festlegen, wenn der Benutzer die Seite gescrollt hat (mit einem vorsichtigen Onscroll-Handler).

Leider gibt die HTML5-Spezifikation auch nicht an, wann genau das popstateEreignis ausgelöst werden muss, sondern nur: «wird in bestimmten Fällen ausgelöst, wenn zu einem Sitzungsverlaufseintrag navigiert», was nicht eindeutig angibt, ob es vorher oder nachher ist; Wenn es immer vorher war, wäre eine Lösung mit der Behandlung des Scroll-Ereignisses möglich, das nach dem Popstate auftritt.

Scroll-Ereignis abbrechen?

Darüber hinaus wäre es auch einfach, wenn das Bildlaufereignis stornierbar wäre, was es nicht ist. Wenn dies der Fall wäre, könnten Sie einfach das erste Bildlaufereignis einer Serie abbrechen (Benutzer-Bildlaufereignisse sind wie Lemminge, sie kommen in Dutzenden vor, während das durch die Neupositionierung des Verlaufs ausgelöste Bildlaufereignis ein einzelnes ist), und es wäre in Ordnung.

Derzeit gibt es keine Lösung

Soweit ich sehe, würde ich vorerst nur empfehlen, auf die vollständige Implementierung der HTML5-Spezifikation zu warten und in diesem Fall mit dem Browserverhalten zu rollen. Das bedeutet: Animieren Sie das Scrollen, wenn der Browser dies zulässt und lassen Sie den Browser die Seite neu positionieren, wenn ein Verlaufsereignis vorliegt. Das einzige, was Sie in Bezug auf die Position beeinflussen können, ist, dass Sie verwenden, pushStatewenn die Seite in einer guten Position positioniert ist, zu der Sie zurückkehren können. Jede andere Lösung muss entweder Fehler aufweisen oder zu browserspezifisch sein oder beides.


Der Wortlaut der Geschichtsspezifikation ist jetzt etwas anders. Wenn Sie nach unten scrollen und sich die Dokumentation von pushState ansehen, wird das Hinzufügen einer Bildlaufposition nicht erwähnt. Die Spezifikation schlägt vor, dass der Sitzungsverlaufseintrag möglicherweise eine gespeicherte Bildlaufposition enthält, aber ich bin nicht sicher, ob dies bedeutet, dass der Webentwickler diese festlegen kann.
Walex

4

Sie müssen hier einen schrecklichen Browser verwenden, der schnüffelt. Für Firefox würde ich mich für Ihre Lösung entscheiden, die Bildlaufposition zu speichern und wiederherzustellen.

Ich dachte, ich hätte eine gute Webkit-Lösung basierend auf Ihrer Beschreibung, aber ich habe es gerade in Chrome 21 versucht, und es scheint, dass Chrome zuerst einen Bildlauf durchführt, dann das Popstate-Ereignis und dann das Bildlauf-Ereignis auslöst. Aber als Referenz habe ich mir Folgendes ausgedacht:

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

Schwarze Magie, wie das Vorgeben, dass die Seite durch Verschieben eines absolutepositionierten Elements gescrollt wird, wird auch durch die Neulackiergeschwindigkeit des Bildschirms ausgeschlossen.

Ich bin mir also zu 99% sicher, dass die Antwort lautet, dass Sie dies nicht können, und dass Sie einen der Kompromisse eingehen müssen, die Sie in der Frage erwähnt haben. Beide Browser scrollen, bevor JavaScript etwas darüber weiß, sodass JavaScript erst nach dem Ereignis reagieren kann. Der einzige Unterschied besteht darin, dass Firefox den Bildschirm erst nach dem Auslösen von Javascript malt. Deshalb gibt es in Firefox eine praktikable Lösung, in WebKit jedoch nicht.


3

Jetzt können Sie tun

history.scrollRestoration = 'manual';

und dies sollte das Scrollen des Browsers verhindern. Dies funktioniert derzeit nur in Chrome 46 und höher, aber es scheint, dass Firefox plant, es auch zu unterstützen


2

Die Lösung besteht darin position: fixed, topdie Bildlaufposition der Seite zu verwenden und anzugeben .

Hier ist ein Beispiel:

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

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

Ja, Sie erhalten stattdessen eine flackernde Bildlaufleiste, aber es ist weniger böse.


1

Das folgende Update sollte in allen Browsern funktionieren.

Sie können die Bildlaufposition beim Entladen auf 0 setzen. Sie können über diese Veranstaltung hier lesen: https://developer.mozilla.org/en-US/docs/Web/Events/unload . Im Wesentlichen wird das Entladeereignis unmittelbar vor dem Verlassen der Seite ausgelöst.

Wenn Sie beim Entladen scrollPosition auf 0 setzen, bedeutet dies, dass beim Verlassen der Seite mit einem festgelegten pushState die scrollPosition auf 0 gesetzt wird. Wenn Sie durch Aktualisieren oder Zurückdrücken zu dieser Seite zurückkehren, wird kein automatischer Bildlauf durchgeführt.

//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

Das Einstellen von scrollRestoration auf manuell hat bei mir nicht funktioniert. Hier ist meine Lösung.

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

0

Erstellen Sie irgendwo oben auf der Seite ein 'span'-Element und konzentrieren Sie sich beim Laden darauf. Der Browser blättert zum fokussierten Element. Ich verstehe, dass dies eine Problemumgehung ist und der Fokus auf "span" nicht in allen Browsern funktioniert (uhmmm .. Safari). Hoffe das hilft.


0

Folgendes habe ich auf einer Site implementiert, auf der die Bildlaufposition beim Auslösen des Postzustands auf ein bestimmtes Element fokussiert werden soll (Zurück-Schaltfläche):

$(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');
}

Getestet und funktioniert in Firefox.

Chrome scrollt zur Position, positioniert sich dann aber wieder an der ursprünglichen Position.


0

Wie andere sagten, gibt es keinen wirklichen Weg, nur einen hässlichen, hackigen Weg. Das Entfernen des Scroll-Ereignis-Listeners hat bei mir nicht funktioniert. Hier ist meine hässliche Vorgehensweise:

/// 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);
    });

});

Es funktioniert in Chrome, FF und IE (es blinkt, wenn Sie zum ersten Mal in IE zurückkehren). Verbesserungsvorschläge sind willkommen! Hoffe das hilft.


-5

Vielleicht möchten Sie das versuchen?

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

3
Das funktioniert nicht. Einige Ereignisse können nicht verhindert werden, und Popstate ist eines davon.
Nathan MacInnes
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.