Desplazamiento suave sin el uso de jQuery

81

Estoy codificando una página en la que solo quiero usar código JavaScript sin formato para la interfaz de usuario sin ninguna interferencia de complementos o marcos.

Y ahora estoy luchando por encontrar una manera de desplazarme por la página sin problemas sin jQuery.

Paolo Forgia
fuente
3
De la misma manera que lo haría jQuery. Divida la animación en una serie de pasos muy pequeños, use un temporizador de intervalos en un intervalo pequeño para realizar cada uno de estos pasos por turno hasta que termine.
tvanfosson
Pensé en esta solución al principio y el único punto que me perdí es en el enlace de Kamal a continuación, que es cómo calcular las posiciones Y de los objetos. Gracias tvanfosson :)
Por favor, eche un vistazo a mi respuesta sobre desplazamiento suave con js simple
surfmuggle
Esta es similar a una de mis preguntas. Consulte stackoverflow.com/questions/40598955/… .
Cannicidio

Respuestas:

27

Pruebe esta demostración de desplazamiento suave o un algoritmo como:

  1. Obtenga la ubicación superior actual usando self.pageYOffset
  2. Obtenga la posición del elemento hasta donde desea desplazarse: element.offsetTop
  3. Haga un bucle for para llegar allí, que será bastante rápido o use un temporizador para hacer un desplazamiento suave hasta esa posición usando window.scrollTo

Vea también la otra respuesta popular a esta pregunta.


Código original de Andrew Johnson:

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++;
    }
}

Enlaces relacionados:

Kamal
fuente
2
Buen esquema, pero no cree una cadena para ejecutar durante el tiempo de espera, siga adelante y cree una función anónima que llame a la función real.
tvanfosson
@nness es una pena. Tenía muchas ganas de ver el código.
dzny
37

El desplazamiento suave del navegador nativo en JavaScript es así:

// 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
fuente
¿Puedes poner el enlace de demostración
DV Yogesh
2
@Vishu Rana, el soporte de comportamiento solo va a firefox como se describe en MDN
Evandro Cavalcate Santos
2
@EvandroCavalcateSantos: ¡ya no! ;-) developer.mozilla.org/en-US/docs/Web/API/Element/…
Pedro Ferreira
1
Este estándar moderno es agradable y legible, pero necesita una speedopción. La velocidad de desplazamiento actual es demasiado lenta para el flujo.
Nils Lindemann
31

editar: esta respuesta se escribió en 2013. Por favor, consulte el comentario de Cristian Traìna a continuación sobre requestAnimationFrame

Lo hice. El siguiente código no depende de ningún marco.

Limitación: el ancla activa no está escrita en la URL.

Versión del código: 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
fuente
2
Creo que simplemente empujaría la URL de anclaje a la pila del historial con algo como var _updateURL = function ( anchor, url ) { if ( (url === true || url === 'true') && history.pushState ) { history.pushState( {pos:anchor.id}, '', anchor ); } };(a través de Smooth Scroll js de Chris Fernandi ) para escribir el ancla a la URL. ¡Es bueno ver a la gente haciendo esto en lugar de simplemente "usar JQuery"! Para que conste, los :targetselectores también son una buena solución.
Louis Maddox
1
Esta respuesta es de 2013, cuando se escribió que ya existía requestAnimationFrame, que es un impulso para el rendimiento. No sé por qué no se usa aquí.
Cristian Traìna
23

Algoritmo

El desplazamiento de un elemento requiere cambiar su scrollTopvalor con el tiempo. Para un momento dado, calcula un nuevo scrollTopvalor. Para animar sin problemas, interpole usando un algoritmo de pasos suaves .

Calcule scrollTopcomo sigue:

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

Dónde:

  • start_time es el momento en que comenzó la animación;
  • end_timees cuando terminará la animación (start_time + duration);
  • start_topes el scrollTopvalor al principio; y
  • distancees la diferencia entre el valor final deseado y el valor inicial (target - start_top).

Una solución robusta debería detectar cuándo se interrumpe la animación, y más. Lea mi publicación sobre Smooth Scrolling sin jQuery para obtener más detalles.

Manifestación

Vea el JSFiddle .

Implementación

El código:

/**
    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
fuente
Esta parece una muy buena solución. Pero, ¿cómo puede implementarlo en un objeto de ventana en lugar de un elemento como el que tiene en su Fiddle?
Mudlabs
No importa, lo descubrí. Tuve que usar pageYOffsety scrollTo()en elementlugar de scrollTop.
Mudlabs
6

He hecho un ejemplo sin jQuery aquí: 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
fuente
1
Publique los fragmentos de código relevantes o la descripción de cómo funciona aquí. Tu respuesta debería poder tener sentido sin necesidad de hacer clic en nada. ¡Gracias!
Edward
gracias - pero no - codeopen está hecho para compartir ejemplos - este no es el lugar correcto para poner todo el código css / js / html - util stackoverflow agregará los bits necesarios - la mayoría de las personas te enviarán a codepen / jsfiddle / o en otro lugar ..
SorinN
1
Aquí hay algo de la conversación sobre por qué pedimos eso: meta.stackexchange.com/q/149890
Edward
2
No entiendo la necesidad dewindow.setTimeout = window.setTimeout;
pmrotule
6

Los navegadores modernos admiten la propiedad CSS "scroll-behavior: smooth". Entonces, ni siquiera necesitamos ningún Javascript para esto. Solo agregue esto al elemento del cuerpo y use anclajes y enlaces habituales. documentos MDN de comportamiento de desplazamiento

KostaShah
fuente
Tanto Edge como Safari aún no admiten esta propiedad. Espero que lo hagan pronto.
6754534367
No funciona bien ... incluso en Firefox. Entonces no es una solución.
huseyin39
5

Recientemente me propuse resolver este problema en una situación en la que jQuery no era una opción, así que estoy registrando mi solución aquí solo para la posteridad.

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');

La scroll()función usa el patrón de módulo revelador para pasar la identificación del elemento de destino a su scrolling()función, via scroll.To('id'), que establece los valores utilizados por la scroller()función.

Descompostura

En scrolling():

  • el : el objeto DOM de destino
  • elPos: devuelve una función a través de la elememtPosition()cual proporciona la posición del elemento de destino en relación con la parte superior de la página cada vez que se llama.
  • duration : tiempo de transición en milisegundos.
  • increment : divide la posición inicial del elemento de destino en 40 pasos.
  • time : establece el tiempo de cada paso.
  • prev: la posición anterior del elemento de destino en scroller().
  • E: mantiene la posición del elemento de destino en scroller().

El trabajo real lo realiza la scroller()función que continúa llamándose a sí misma (via setTimeout()) hasta que el elemento de destino está en la parte superior de la página o la página no puede desplazarse más.

Cada vez que scroller()se llama, verifica la posición actual del elemento de destino (mantenido en la variable E) y si eso es > 1OR < -1y si la página todavía se puede desplazar, cambia la ventana en incrementpíxeles, hacia arriba o hacia abajo, dependiendo de si Ees un valor positivo o negativo. Cuando Eno es > 1OR < -1o E=== prevla función se detiene. Agregué el DOMElement.scrollTo()método al finalizar solo para asegurarme de que el elemento de destino golpeara en la parte superior de la ventana (¡no es que te des cuenta de que está fuera por una fracción de píxel!)

La ifdeclaración en la línea 2 de scroller()verifica si la página se está desplazando (en los casos en los que el objetivo podría estar hacia la parte inferior de la página y la página no puede desplazarse más) comprobando Esu posición anterior ( prev).

La condición ternaria por debajo reduce el incrementvalor a medida que se Eacerca a cero. Esto evita que la página se sobrepase en un sentido y luego rebote para sobrepasar en el otro, y luego rebote para sobrepasar al otro de nuevo, estilo ping-pong, hasta el infinito y más allá.

Si su página tiene más de c.4000px de altura, es posible que desee aumentar los valores en la primera condición de la expresión ternaria (aquí en +/- 20) y / o el divisor que establece el incrementvalor (aquí en 40).

Jugar con duration, el divisor que se establece incrementy los valores en la condición ternaria de scroller()debería permitirle adaptar la función para que se adapte a su página.

  • JSFiddle

  • Nota: Probado en versiones actualizadas de Firefox y Chrome en Lubuntu, y Firefox, Chrome e IE en Windows8.

Brian pavo real
fuente
3

También puede utilizar la propiedad de comportamiento de desplazamiento . por ejemplo, agregue la siguiente línea a su css

html{
scroll-behavior:smooth;
}

y esto dará como resultado una característica nativa de desplazamiento suave. Leer más sobre el comportamiento de desplazamiento

Shakil Alam
fuente
2

Hice algo como esto. No tengo idea de si está funcionando en IE8. Probado en 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();
  });
}

Descripción

  • pointer—Get element y chceck si tiene el atributo "href" en caso afirmativo, elimine "#"
  • elem: Variable de puntero sin "#"
  • elemOffset: Desplazamiento del elemento "desplazarse hasta" desde la parte superior de la página
Skjal
fuente
2

Puedes usar

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

Si desea desplazarse por la parte superior de la página, puede colocar un elemento vacío en la parte superior y desplazarse suavemente hasta ese.

Antonio Brandao
fuente
0

Puede usar un bucle for con window.scrollTo y setTimeout para desplazarse sin problemas con JavaScript simple. Para desplazarse a un elemento con mi scrollToSmoothlyfunción: scrollToSmoothly(elem.offsetTop)(asumiendo que elemes un elemento DOM). Puede usar esto para desplazarse suavemente a cualquier posición y en el documento.

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

Manifestación:

iota
fuente
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 del div que se usa para desplazarse

Nota: Para hacer que el cuerpo se desplace, déle una identificación al cuerpo.

Jacob Mathen Alex
fuente
0

Aquí está mi solución. Funciona en la mayoría de los navegadores.

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

Docs

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
fuente
0

Con el uso del siguiente desplazamiento suave funciona bien:

html {
  scroll-behavior: smooth;
}

Meghnath Das
fuente
-1

Aquí está el código que funcionó para mí.

`$('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
fuente
¿Puedes explicar un poco más sobre lo que hace tu código?
rhavelka
Bienvenido a stackoverflow, gracias por responder, pero la pregunta era sobre no usar jquery.
Stephane L