Płynne przewijanie bez użycia jQuery

83

Koduję stronę, na której chcę używać tylko surowego kodu JavaScript dla interfejsu użytkownika bez żadnych ingerencji wtyczek lub frameworków.

A teraz walczę ze znalezieniem sposobu na płynne przewijanie strony bez jQuery.

Paolo Forgia
źródło
3
W ten sam sposób zrobiłby to jQuery. Podziel animację na serię bardzo małych kroków, użyj timera interwału w małych odstępach czasu, aby wykonać każdy z tych kroków po kolei, aż zostaną wykonane.
tvanfosson
Myślałem o tym rozwiązaniu na początku i jedyny punkt, który przegapiłem, to w rzeczywistości link Kamala poniżej, który mówi o tym, jak obliczyć pozycje Y obiektów. Dzięki tvanfosson :)
Proszę spojrzeć na moją odpowiedź na temat płynnego przewijania za pomocą zwykłego js
surfmuggle
To jest podobne do jednego z moich pytań. Sprawdź stackoverflow.com/questions/40598955/… .
Cannicide

Odpowiedzi:

27

Wypróbuj to demo z płynnym przewijaniem lub algorytm, taki jak:

  1. Uzyskaj aktualną najwyższą lokalizację za pomocą self.pageYOffset
  2. Uzyskaj pozycję elementu do miejsca, do którego chcesz przewinąć: element.offsetTop
  3. Wykonaj pętlę for, aby tam dotrzeć, co będzie dość szybkie lub użyj timera, aby płynnie przewijać do tej pozycji za pomocą window.scrollTo

Zobacz także inną popularną odpowiedź na to pytanie.


Oryginalny kod Andrew Johnsona:

function currentYPosition() {
    // Firefox, Chrome, Opera, Safari
    if (self.pageYOffset) return self.pageYOffset;
    // Internet Explorer 6 - standards mode
    if (document.documentElement && document.documentElement.scrollTop)
        return document.documentElement.scrollTop;
    // Internet Explorer 6, 7 and 8
    if (document.body.scrollTop) return document.body.scrollTop;
    return 0;
}


function elmYPosition(eID) {
    var elm = document.getElementById(eID);
    var y = elm.offsetTop;
    var node = elm;
    while (node.offsetParent && node.offsetParent != document.body) {
        node = node.offsetParent;
        y += node.offsetTop;
    } return y;
}


function smoothScroll(eID) {
    var startY = currentYPosition();
    var stopY = elmYPosition(eID);
    var distance = stopY > startY ? stopY - startY : startY - stopY;
    if (distance < 100) {
        scrollTo(0, stopY); return;
    }
    var speed = Math.round(distance / 100);
    if (speed >= 20) speed = 20;
    var step = Math.round(distance / 25);
    var leapY = stopY > startY ? startY + step : startY - step;
    var timer = 0;
    if (stopY > startY) {
        for ( var i=startY; i<stopY; i+=step ) {
            setTimeout("window.scrollTo(0, "+leapY+")", timer * speed);
            leapY += step; if (leapY > stopY) leapY = stopY; timer++;
        } return;
    }
    for ( var i=startY; i>stopY; i-=step ) {
        setTimeout("window.scrollTo(0, "+leapY+")", timer * speed);
        leapY -= step; if (leapY < stopY) leapY = stopY; timer++;
    }
}

Powiązane linki:

Kamal
źródło
2
Dobry konspekt, ale proszę nie tworzyć ciągu znaków do wykonania po przekroczeniu limitu czasu, idź dalej i utwórz anonimową funkcję, która wywoła właściwą funkcję.
tvanfosson
@nness to wstyd. Naprawdę chciałem zobaczyć kod.
dzny
39

Natywne płynne przewijanie w przeglądarce w JavaScript wygląda następująco:

// scroll to specific values,
// same as window.scroll() method.
// for scrolling a particular distance, use window.scrollBy().
window.scroll({
  top: 2500, 
  left: 0, 
  behavior: 'smooth' 
});

// scroll certain amounts from current position 
window.scrollBy({ 
  top: 100, // negative value acceptable
  left: 0, 
  behavior: 'smooth' 
});

// scroll to a certain element
document.querySelector('.hello').scrollIntoView({ 
  behavior: 'smooth' 
});
ZachB
źródło
czy możesz wstawić link do wersji demonstracyjnej
DV Yogesh
2
@Vishu Rana, obsługa zachowań trafia tylko do przeglądarki Firefox, jak opisano w MDN
Evandro Cavalcate Santos,
2
@EvandroCavalcateSantos: już nie! ;-) developer.mozilla.org/en-US/docs/Web/API/Element/…
Pedro Ferreira
1
Ten nowoczesny standard jest ładny i czytelny, ale wymaga speedopcji. Bieżąca prędkość przewijania jest zbyt niska dla przepływu.
Nils Lindemann
31

edycja: ta odpowiedź została napisana w 2013 roku. Sprawdź poniższy komentarz Cristiana Traìny na temat requestAnimationFrame

Ja to zrobiłem. Poniższy kod nie zależy od żadnej platformy.

Ograniczenie: aktywna kotwica nie jest zapisana w adresie URL.

Wersja kodu: 1.0 | Github: https://github.com/Yappli/smooth-scroll

(function() // Code in a function to create an isolate scope
{
var speed = 500;
var moving_frequency = 15; // Affects performance !
var links = document.getElementsByTagName('a');
var href;
for(var i=0; i<links.length; i++)
{   
    href = (links[i].attributes.href === undefined) ? null : links[i].attributes.href.nodeValue.toString();
    if(href !== null && href.length > 1 && href.substr(0, 1) == '#')
    {
        links[i].onclick = function()
        {
            var element;
            var href = this.attributes.href.nodeValue.toString();
            if(element = document.getElementById(href.substr(1)))
            {
                var hop_count = speed/moving_frequency
                var getScrollTopDocumentAtBegin = getScrollTopDocument();
                var gap = (getScrollTopElement(element) - getScrollTopDocumentAtBegin) / hop_count;

                for(var i = 1; i <= hop_count; i++)
                {
                    (function()
                    {
                        var hop_top_position = gap*i;
                        setTimeout(function(){  window.scrollTo(0, hop_top_position + getScrollTopDocumentAtBegin); }, moving_frequency*i);
                    })();
                }
            }

            return false;
        };
    }
}

var getScrollTopElement =  function (e)
{
    var top = 0;

    while (e.offsetParent != undefined && e.offsetParent != null)
    {
        top += e.offsetTop + (e.clientTop != null ? e.clientTop : 0);
        e = e.offsetParent;
    }

    return top;
};

var getScrollTopDocument = function()
{
    return document.documentElement.scrollTop + document.body.scrollTop;
};
})();
Gabriel
źródło
2
Myślę, że po prostu wrzuciłbyś zakotwiczony adres URL do stosu historii za pomocą czegoś takiego var _updateURL = function ( anchor, url ) { if ( (url === true || url === 'true') && history.pushState ) { history.pushState( {pos:anchor.id}, '', anchor ); } };( przez Smooth Scroll js Chrisa Fernandi ), aby zapisać kotwicę do adresu URL. Miło widzieć, że ludzie robią to, a nie tylko „używają JQuery”! Dla przypomnienia, :targetselektory są również zgrabnym rozwiązaniem.
Louis Maddox
1
Ta odpowiedź pochodzi z 2013 roku, kiedy została napisana, już istniała requestAnimationFrame, która zwiększa wydajność. Nie wiem, dlaczego tutaj nie jest używane.
Cristian Traìna
24

Algorytm

Przewijanie elementu wymaga zmiany jego scrollTopwartości w czasie. Oblicz nową scrollTopwartość dla danego punktu w czasie . Aby animować płynnie, interpoluj za pomocą algorytmu płynnego kroku .

Oblicz scrollTopw następujący sposób:

var point = smooth_step(start_time, end_time, now);
var scrollTop = Math.round(start_top + (distance * point));

Gdzie:

  • start_time to czas rozpoczęcia animacji;
  • end_timekiedy animacja się zakończy (start_time + duration);
  • start_topjest scrollTopwartością na początku; i
  • distancejest różnicą między żądaną wartością końcową a wartością początkową (target - start_top).

Solidne rozwiązanie powinno wykrywać przerwanie animacji i nie tylko. Przeczytaj mój post o płynnym przewijaniu bez jQuery, aby uzyskać szczegółowe informacje.

Próbny

Zobacz JSFiddle .

Realizacja

Kod:

/**
    Smoothly scroll element to the given target (element.scrollTop)
    for the given duration

    Returns a promise that's fulfilled when done, or rejected if
    interrupted
 */
var smooth_scroll_to = function(element, target, duration) {
    target = Math.round(target);
    duration = Math.round(duration);
    if (duration < 0) {
        return Promise.reject("bad duration");
    }
    if (duration === 0) {
        element.scrollTop = target;
        return Promise.resolve();
    }

    var start_time = Date.now();
    var end_time = start_time + duration;

    var start_top = element.scrollTop;
    var distance = target - start_top;

    // based on http://en.wikipedia.org/wiki/Smoothstep
    var smooth_step = function(start, end, point) {
        if(point <= start) { return 0; }
        if(point >= end) { return 1; }
        var x = (point - start) / (end - start); // interpolation
        return x*x*(3 - 2*x);
    }

    return new Promise(function(resolve, reject) {
        // This is to keep track of where the element's scrollTop is
        // supposed to be, based on what we're doing
        var previous_top = element.scrollTop;

        // This is like a think function from a game loop
        var scroll_frame = function() {
            if(element.scrollTop != previous_top) {
                reject("interrupted");
                return;
            }

            // set the scrollTop for this frame
            var now = Date.now();
            var point = smooth_step(start_time, end_time, now);
            var frameTop = Math.round(start_top + (distance * point));
            element.scrollTop = frameTop;

            // check if we're done!
            if(now >= end_time) {
                resolve();
                return;
            }

            // If we were supposed to scroll but didn't, then we
            // probably hit the limit, so consider it done; not
            // interrupted.
            if(element.scrollTop === previous_top
                && element.scrollTop !== frameTop) {
                resolve();
                return;
            }
            previous_top = element.scrollTop;

            // schedule next frame for execution
            setTimeout(scroll_frame, 0);
        }

        // boostrap the animation process
        setTimeout(scroll_frame, 0);
    });
}
hasen
źródło
Wydaje się, że to naprawdę dobre rozwiązanie. Ale jak zaimplementować to na obiekcie okna zamiast elementu takiego jak w Fiddle?
Mudlabs
Nieważne, że to rozgryzłem. Musiałem użyć pageYOffseti scrollTo()na elementzamiast scrollTop.
Mudlabs
6

Zrobiłem przykład bez jQuery tutaj: http://codepen.io/sorinnn/pen/ovzdq

/**
    by Nemes Ioan Sorin - not an jQuery big fan 
    therefore this script is for those who love the old clean coding style  
    @id = the id of the element who need to bring  into view

    Note : this demo scrolls about 12.700 pixels from Link1 to Link3
*/
(function()
{
      window.setTimeout = window.setTimeout; //
})();

      var smoothScr = {
      iterr : 30, // set timeout miliseconds ..decreased with 1ms for each iteration
        tm : null, //timeout local variable
      stopShow: function()
      {
        clearTimeout(this.tm); // stopp the timeout
        this.iterr = 30; // reset milisec iterator to original value
      },
      getRealTop : function (el) // helper function instead of jQuery
      {
        var elm = el; 
        var realTop = 0;
        do
        {
          realTop += elm.offsetTop;
          elm = elm.offsetParent;
        }
        while(elm);
        return realTop;
      },
      getPageScroll : function()  // helper function instead of jQuery
      {
        var pgYoff = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
        return pgYoff;
      },
      anim : function (id) // the main func
      {
        this.stopShow(); // for click on another button or link
        var eOff, pOff, tOff, scrVal, pos, dir, step;

        eOff = document.getElementById(id).offsetTop; // element offsetTop

        tOff =  this.getRealTop(document.getElementById(id).parentNode); // terminus point 

        pOff = this.getPageScroll(); // page offsetTop

        if (pOff === null || isNaN(pOff) || pOff === 'undefined') pOff = 0;

        scrVal = eOff - pOff; // actual scroll value;

        if (scrVal > tOff) 
        {
          pos = (eOff - tOff - pOff); 
          dir = 1;
        }
        if (scrVal < tOff)
        {
          pos = (pOff + tOff) - eOff;
          dir = -1; 
        }
        if(scrVal !== tOff) 
        {
          step = ~~((pos / 4) +1) * dir;

          if(this.iterr > 1) this.iterr -= 1; 
          else this.itter = 0; // decrease the timeout timer value but not below 0
          window.scrollBy(0, step);
          this.tm = window.setTimeout(function()
          {
             smoothScr.anim(id);  
          }, this.iterr); 
        }  
        if(scrVal === tOff) 
        { 
          this.stopShow(); // reset function values
          return;
        }
    }
 }
SorinN
źródło
1
W tym miejscu zamieść odpowiednie fragmenty kodu lub opis, jak to działa. Twoja odpowiedź powinna mieć sens bez konieczności klikania. Dziękuję Ci!
Edward
dzięki - ale nie - codeopen jest stworzony do dzielenia się przykładami - tutaj nie jest właściwe miejsce na umieszczanie całego kodu css / js / html - wykorzystując stackoverflow doda niezbędne bity - większość ludzi wyśle ​​cię do codepen / jsfiddle / lub gdzie indziej.
SorinN
1
Oto część rozmowy o tym, dlaczego o to prosimy: meta.stackexchange.com/q/149890
Edward
2
Nie rozumiem potrzebywindow.setTimeout = window.setTimeout;
pmrotule
6

Nowoczesne przeglądarki obsługują właściwość CSS „scroll-zachowanie: płynne”. Więc nawet nie potrzebujemy do tego żadnego Javascript. Po prostu dodaj to do elementu body i użyj zwykłych kotwic i linków. Dokumentacja MDN z zachowaniem przewijania

KostaShah
źródło
Zarówno Edge, jak i Safari nie obsługują jeszcze tej właściwości. Mam nadzieję, że wkrótce.
6754534367
Nie działa dobrze ... nawet w Firefoksie. Więc to nie jest rozwiązanie.
huseyin39
5

Niedawno postanowiłem rozwiązać ten problem w sytuacji, gdy jQuery nie wchodziło w grę, więc loguję tutaj moje rozwiązanie tylko dla potomności.

var scroll = (function() {

    var elementPosition = function(a) {
        return function() {
            return a.getBoundingClientRect().top;
        };
    };

    var scrolling = function( elementID ) {

        var el = document.getElementById( elementID ),
            elPos = elementPosition( el ),
            duration = 400,
            increment = Math.round( Math.abs( elPos() )/40 ),
            time = Math.round( duration/increment ),
            prev = 0,
            E;

        function scroller() {
            E = elPos();

            if (E === prev) {
                return;
            } else {
                prev = E;
            }

            increment = (E > -20 && E < 20) ? ((E > - 5 && E < 5) ? 1 : 5) : increment;

            if (E > 1 || E < -1) {

                if (E < 0) {
                    window.scrollBy( 0,-increment );
                } else {
                    window.scrollBy( 0,increment );
                }

                setTimeout(scroller, time);

            } else {

                el.scrollTo( 0,0 );

            }
        }

        scroller();
    };

    return {
        To: scrolling
    }

})();

/* usage */
scroll.To('elementID');

scroll()Funkcja używa ujawniających modułu Wzorzec przekazać identyfikator docelowy element do jej scrolling()funkcji, za pośrednictwem scroll.To('id'), którego ustawia wartości wykorzystywane przez scroller()funkcję.

Awaria

W scrolling():

  • el : docelowy obiekt DOM
  • elPos: zwraca funkcję, poprzez elememtPosition()którą podaje pozycję elementu docelowego względem początku strony za każdym razem, gdy jest on wywoływany.
  • duration : czas przejścia w milisekundach.
  • increment : dzieli pozycję początkową elementu docelowego na 40 kroków.
  • time : ustawia czas każdego kroku.
  • prev: poprzednia pozycja elementu docelowego w scroller().
  • E: utrzymuje pozycję elementu docelowego w scroller().

Właściwa praca jest wykonywana przez scroller()funkcję, która kontynuuje wywoływanie siebie (przez setTimeout()), dopóki element docelowy nie znajdzie się na górze strony lub strona nie może się już przewijać.

Za każdym razem, gdy scroller()jest wywoływana, sprawdza aktualną pozycję elementu docelowego (trzymanego w zmiennej E) i jeśli jest to > 1OR < -1i jeśli strona jest nadal przewijalna, przesuwa okno o incrementpiksele - w górę lub w dół w zależności od tego, czy Ejest to wartość dodatnia czy ujemna. Jeśli Enie ma > 1OR < -1lub E===, prevfunkcja zatrzymuje się. Dodałem tę DOMElement.scrollTo()metodę po zakończeniu, aby upewnić się, że element docelowy uderzył w górę okna (nie żebyś zauważył, że jest wychylony o ułamek piksela!).

ifOświadczenie na linii 2 scroller()sprawdza, czy strona jest przewijanie (w przypadkach, w których cel może być w kierunku dolnej części strony, a strona nie może przejść dalej) za sprawdzenie Eprzeciwko swojej poprzedniej pozycji ( prev).

Warunek trójskładnikowy poniżej tego zmniejsza incrementwartość Ezbliżając się do zera. Dzięki temu strona nie przeskakuje w jedną stronę, a następnie odbija się z powrotem i przeskakuje w drugą stronę, a następnie odbija się, by ponownie prześcignąć drugą stronę, w stylu ping-ponga, do nieskończoności i dalej.

Jeśli Twoja strona ma wysokość ponad 4000 pikseli, możesz chcieć zwiększyć wartości w pierwszym warunku wyrażenia trójskładnikowego (tutaj +/- 20) i / lub dzielnik, który ustawia incrementwartość (tutaj 40).

Zabawa z durationdzielnikiem, który ustawia incrementi wartościami w warunku trójskładnikowym, scroller()powinno pozwolić na dostosowanie funkcji do strony.

  • JSFiddle

  • NB Przetestowano w aktualnych wersjach przeglądarek Firefox i Chrome na Lubuntu oraz Firefox, Chrome i IE na Windows8.

Brian Peacock
źródło
3

Możesz także użyć właściwości zachowania przewijania . na przykład dodaj poniższą linię do swojego css

html{
scroll-behavior:smooth;
}

a to spowoduje natywną funkcję płynnego przewijania. Przeczytaj więcej o zachowaniu przewijania

Shakil Alam
źródło
2

Zrobiłem coś takiego. Nie mam pojęcia, czy działa w IE8. Przetestowano w IE9, Mozilla, Chrome, Edge.

function scroll(toElement, speed) {
  var windowObject = window;
  var windowPos = windowObject.pageYOffset;
  var pointer = toElement.getAttribute('href').slice(1);
  var elem = document.getElementById(pointer);
  var elemOffset = elem.offsetTop;

  var counter = setInterval(function() {
    windowPos;

    if (windowPos > elemOffset) { // from bottom to top
      windowObject.scrollTo(0, windowPos);
      windowPos -= speed;

      if (windowPos <= elemOffset) { // scrolling until elemOffset is higher than scrollbar position, cancel interval and set scrollbar to element position
        clearInterval(counter);
        windowObject.scrollTo(0, elemOffset);
      }
    } else { // from top to bottom
      windowObject.scrollTo(0, windowPos);
      windowPos += speed;

      if (windowPos >= elemOffset) { // scroll until scrollbar is lower than element, cancel interval and set scrollbar to element position
        clearInterval(counter);
        windowObject.scrollTo(0, elemOffset);
      }
    }

  }, 1);
}

//call example

var navPointer = document.getElementsByClassName('nav__anchor');

for (i = 0; i < navPointer.length; i++) {
  navPointer[i].addEventListener('click', function(e) {
    scroll(this, 18);
    e.preventDefault();
  });
}

Opis

  • pointer- pobierz element i sprawdź, czy ma atrybut „href”, jeśli tak, usuń „#”
  • elem- zmienna wskaźnika bez znaku „#”
  • elemOffset- odsunięcie elementu „przewiń do” od góry strony
Skjal
źródło
2

Możesz użyć

document.querySelector('your-element').scrollIntoView({behavior: 'smooth'});

Jeśli chcesz przewijać u góry strony, możesz po prostu umieścić pusty element na górze i płynnie przewinąć do tego.

Antonio Brandao
źródło
0

Możesz użyć pętli for z window.scrollTo i setTimeout, aby płynnie przewijać za pomocą zwykłego JavaScript. Aby przewinąć do elementu z moją scrollToSmoothlyfunkcją: scrollToSmoothly(elem.offsetTop)(zakładając, że elemjest to element DOM). Możesz użyć tego do płynnego przewijania do dowolnej pozycji Y w dokumencie.

function scrollToSmoothly(pos, time){
/*Time is only applicable for scrolling upwards*/
/*Code written by hev1*/
/*pos is the y-position to scroll to (in pixels)*/
     if(isNaN(pos)){
      throw "Position must be a number";
     }
     if(pos<0){
     throw "Position can not be negative";
     }
    var currentPos = window.scrollY||window.screenTop;
    if(currentPos<pos){
    var t = 10;
       for(let i = currentPos; i <= pos; i+=10){
       t+=10;
        setTimeout(function(){
        window.scrollTo(0, i);
        }, t/2);
      }
    } else {
    time = time || 2;
       var i = currentPos;
       var x;
      x = setInterval(function(){
         window.scrollTo(0, i);
         i -= 10;
         if(i<=pos){
          clearInterval(x);
         }
     }, time);
      }
}

Próbny:

odrobina
źródło
0
<script>
var set = 0;

function animatescroll(x, y) {
    if (set == 0) {
        var val72 = 0;
        var val73 = 0;
        var setin = 0;
        set = 1;

        var interval = setInterval(function() {
            if (setin == 0) {
                val72++;
                val73 += x / 1000;
                if (val72 == 1000) {
                    val73 = 0;
                    interval = clearInterval(interval);
                }
                document.getElementById(y).scrollTop = val73;
            }
        }, 1);
    }
}
</script>

x = scrollTop
y = id elementu div używanego do przewijania

Uwaga: Aby przewinąć treść, nadaj jej identyfikator.

Jacob Mathen Alex
źródło
0

Oto moje rozwiązanie. Działa w większości przeglądarek

document.getElementById("scrollHere").scrollIntoView({behavior: "smooth"});

Dokumenty

document.getElementById("end").scrollIntoView({behavior: "smooth"});
body {margin: 0px; display: block; height: 100%; background-image: linear-gradient(red, yellow);}
.start {display: block; margin: 100px 10px 1000px 0px;}
.end {display: block; margin: 0px 0px 100px 0px;}
<div class="start">Start</div>
<div class="end" id="end">End</div>

Edvard Åkerberg
źródło
0

Przy użyciu następującego płynnego przewijania działa dobrze:

html {
  scroll-behavior: smooth;
}

Meghnath Das
źródło
-1

Oto kod, który zadziałał dla mnie.

`$('a[href*="#"]')

    .not('[href="#"]')
    .not('[href="#0"]')
    .click(function(event) {
     if (
       location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '')
       &&
       location.hostname == this.hostname
        ) {

  var target = $(this.hash);
  target = target.length ? target : $('[name=' + this.hash.slice(1) + ']');
  if (target.length) {

    event.preventDefault();
    $('html, body').animate({
      scrollTop: target.offset().top
    }, 1000, function() {

      var $target = $(target);
      $target.focus();
      if ($target.is(":focus")) { 
        return false;
      } else {
        $target.attr('tabindex','-1'); 
        $target.focus(); 
      };
    });
    }
   }
  });

`

Syed Anique
źródło
czy możesz wyjaśnić trochę więcej o tym, co robi twój kod?
rhavelka
Witamy w stackoverflow, dziękujemy za odpowiedź, ale pytanie dotyczyło nieużywania jquery.
Stephane L