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