56

My code is at http://jsfiddle.net/mannagod/QT3v5/7/.

The JS is:

function delay() {
    var INTENDED_MONTH = 7 //August
    // INTENDED_MONTH is zero-relative
    now = new Date().getDate(),
rows = document.getElementById('scripture').rows;
    if (new Date().getMonth() != INTENDED_MONTH) {
        // need a value here less than 1, 
        // or the box for the first of the month will be in Red
        now = 0.5
    };
    for (var i = 0, rl = rows.length; i < rl; i++) {
        var cells = rows[i].childNodes;
        for (j = 0, cl = cells.length; j < cl; j++) {
            if (cells[j].nodeName == 'TD'
  && cells[j].firstChild.nodeValue != ''
  && cells[j].firstChild.nodeValue == now) {
                // 'ffff99' // '#ffd700' // TODAY - red
                rows[i].style.backgroundColor = 'red' 
                rows[i].scrollIntoView();
            }
        }
    }
}

I need to find a way to smooth the .scrollintoview(). Right now it 'jumps' to the highlighted row. I need it to smoothly transition to that row. It needs to be done dynamically in replacement of scrollintoview. Any ideas? Thanks.

Mosh Feu
  • 28,354
  • 16
  • 88
  • 135
Ted Lederer
  • 567
  • 1
  • 5
  • 9

8 Answers8

146

In most modern browsers (Chrome and Firefox, but not Safari, UC, or IE) you can pass options in an object to .scrollIntoView().

Try this:

elm.scrollIntoView({ behavior: 'smooth', block: 'center' })

Other values are behavior: 'instant' or block: 'start' or block: 'end'. See https://developer.mozilla.org/en/docs/Web/API/Element/scrollIntoView

Bob Stein
  • 16,271
  • 10
  • 88
  • 101
Chris78
  • 1,611
  • 1
  • 9
  • 7
  • 8
    huh, the one thing that Firefox does better than Chrome – Wish May 10 '17 at 20:31
  • 2
    Nowadays it works in all main browsers. The only compatibility issue is the smooth behavior which is not totally compatible: https://caniuse.com/#feat=scrollintoview – viery365 Jun 30 '18 at 23:00
  • 2
    Make scrollintoviewoptions browser independent with a polyfill and you are done :) https://stackoverflow.com/questions/42503599/how-to-make-javascript-scrollintoview-smooth/55357885#55357885 – metamagikum Mar 26 '19 at 13:05
  • Sugestion first line of code must be >>> var elmnt = document.getElementById("content"); – eeerrrttt Feb 03 '22 at 17:48
  • 1
    behavior: 'instant' doesn't exist. It's either 'auto' or 'smooth' – Andrew West May 24 '22 at 10:40
44

I was searching this issue too and found this solution:

$('html, body').animate({
    scrollTop: $("#elementId").offset().top
}, 1000);

resource: http://www.abeautifulsite.net/smoothly-scroll-to-an-element-without-a-jquery-plugin-2/

Sahil Singh
  • 3,352
  • 39
  • 62
Sarkhan
  • 1,281
  • 1
  • 11
  • 33
15

Maybe you don't want to add jQuery just for implementing this feature. elem is the element to be scrolled. The destination pos can be taken from the offsetTop property of the element to be moved into view.

function Scroll_To(elem, pos)
{
    var y = elem.scrollTop;
    y += (pos - y) * 0.3;
    if (Math.abs(y-pos) < 2)
    {
        elem.scrollTop = pos;
        return;
    }
    elem.scrollTop = y;
    setTimeout(Scroll_To, 40, elem, pos);   
}
Klaus Heyne
  • 251
  • 3
  • 5
  • 2
    I was having an issue with infinite recursion. I fixed it with `Math.round( ( pos - y ) * 0.3 );` and `Math.abs(y-pos) <= 2` – tastybytes Jun 18 '14 at 20:45
7

Smooth scrolling using requestAnimationFrame over a specific duration with no jQuery.

Demo: http://codepen.io/Shadeness/pen/XXyvKG?editors=0010

window.bringIntoView_started = 0;
window.bringIntoView_ends = 0;
window.bringIntoView_y = 0;
window.bringIntoView_tick = function() {
  var distanceLeft, dt, duration, t, travel;
  t = Date.now();
  if (t < window.bringIntoView_ends) {
    dt = t - window.bringIntoView_started;
    duration = window.bringIntoView_ends - window.bringIntoView_started;
    distanceLeft = window.bringIntoView_y - document.body.scrollTop;
      travel = distanceLeft * (dt / duration);
      document.body.scrollTop += travel;
      window.requestAnimationFrame(window.bringIntoView_tick);
  } else {
    document.body.scrollTop = window.bringIntoView_y;
  }
};
window.bringIntoView = function(e, duration) {
  window.bringIntoView_started = Date.now();
  window.bringIntoView_ends = window.bringIntoView_started + duration;
  window.bringIntoView_y = Math.min(document.body.scrollTop + e.getBoundingClientRect().top, document.body.scrollHeight - window.innerHeight);
  window.requestAnimationFrame(window.bringIntoView_tick);
};

Eg:

bringIntoView(document.querySelector('#bottom'), 400)

It should speed up as dt (deltaTime) gets bigger, and slows down as distanceLeft get's smaller. I considered breaking the loop if the user scrolled but meh. Global variables prevent the previous call from completely taking over, but doesn't cancel the previous recursive loop so it'll animate twice as fast.

Zren
  • 759
  • 7
  • 13
  • Odd. Looks you need to use `window.scrollTo(x, 0)` instead of assigning to `.scrollTop`. [Here's another answer](http://stackoverflow.com/questions/29978616/jquerys-animate-scrolltop-property-doesnt-work-on-ipad-safari) on StackOverflow. – Zren Mar 16 '16 at 21:15
4

You just need to include this polyfill and it works.

https://github.com/iamdustan/smoothscroll

<script src="js/smoothscroll.js"></script>

Or require it if you use npm.

require('smoothscroll-polyfill').polyfill();

Use native scrollIntoView Method.

document.getElementById('parallax-group-logo').scrollIntoView({
    block: "start",
    behavior: "smooth"
});
Ilario Engler
  • 2,419
  • 2
  • 27
  • 40
3

Just to add to this in case it helps someone,

I was working on a PWA for iOS and Android and was using the scrollIntoView() method until I found out the scrollIntoViewOptions object was not supported by Safari and thus wouldn't smooth scroll or anything etc.

I was able to mimic the functionality of scrollIntoView, with smooth scroll & 'nearest' option, for iOS with plain JS...well, plain TypeScript...

Click handler or w/e:

const element = *elementToScrollIntoView*;
const scrollLayer = *layerToDoTheScrolling*

if (/iPad|iPhone|iPod/.test(navigator.userAgent) {
    let position;
    const top = element.offsetTop - scrollLayer.scrollTop;
    if (element.offsetTop < scrollLayer.scrollTop) {
            // top of element is above top of view - scroll to top of element
        position = element.offsetTop;
    } else if (element.scrollHeight + top < scrollLayer.offsetHeight) {
            // element is in view - don't need to scroll
        return;
    } else if (element.scrollHeight > scrollLayer.offsetHeight) {
            // element is bigger than view - scroll to top of element
        position = element.offsetTop;
    } else {
            // element partially cut-off - scroll remainder into view
        const difference = scrollLayer.offsetHeight - (element.scrollHeight + top);
        position = scrollLayer.scrollTop - difference;
    }
        // custom function for iOS
    scrollToElement(scrollLayer, position, 200);
} else {
        // just use native function for Android
    element.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
}

Manual scroll function:

scrollToElement(scrollLayer, destination, duration) {
    if (duration <= 0) {
        return;
    }
    const difference = destination - scrollLayer.scrollTop;
    const perTick = (difference / duration) * 10;

    setTimeout(() => {
        scrollLayer.scrollTop = scrollLayer.scrollTop + perTick;
        if (scrollLayer.scrollTop === destination) {
            return;
        }
        scrollToElement(scrollLayer, destination, duration - 10);
    }, 10);
}

NOTE: The big nested if and calculations in the handler is just to find the 'nearest' position as I was trying to replicate that behaviour, but just using the scrollToElement function to animate scrolling to top (default behaviour with no Options object) you can use:

scrollToElement(scrollLayer, element.offsetTop, 200);
HazeyAce
  • 361
  • 3
  • 12
2

Try this:

function scroll_into_view_smooth(elem)
{   var FPS = 48; // frames per second
    var DURATION = 0.6; // sec
    var e = elem;
    var left = e.offsetLeft;
    var top = e.offsetTop;
    var width = e.offsetWidth;
    var height = e.offsetHeight;
    var body = document.body;
    var to_scroll = [];
    var p, offset;
    while ((p = e.offsetParent))
    {   var client_width = p.clientWidth;
        var client_height = p!=body ? p.clientHeight : Math.min(document.documentElement.clientHeight, window.innerHeight);
        if (client_width<p.scrollWidth-1 && ((offset=left-p.scrollLeft)<0 || (offset=left+width-p.scrollLeft-client_width)>0))
        {   to_scroll.push({elem: p, prop: 'scrollLeft', from: p.scrollLeft, offset: offset});
        }
        if (client_height<p.scrollHeight-1 && ((offset=top-p.scrollTop)<0 || (offset=top+height-p.scrollTop-client_height)>0))
        {   to_scroll.push({elem: p, prop: 'scrollTop', from: p.scrollTop, offset: offset});
        }
        e = p;
        left += e.offsetLeft;
        top += e.offsetTop;
    }
    var x = 0;
    function apply()
    {   x = Math.min(x+1/(DURATION * FPS), 1);
        for (var i=to_scroll.length-1; i>=0; i--)
        {   to_scroll[i].elem[to_scroll[i].prop] = to_scroll[i].from + to_scroll[i].offset*x*x;
        }
        if (x < 1)
        {   setTimeout(apply, 1000/FPS);
        }
    }
    apply();
}
jeremiah
  • 81
  • 2
  • 7
1

You might try adding evt.preventDefault() to the function. This should override the normal "click link" functionality. Example:

    // Scroll to anchor ID using scrollTO event
function linkClicked(evt) { // function to listen for when a link is clicked
  evt.preventDefault();
  evt.scrollIntoView({behavior: 'smooth'});

}

// Scroll to section on link click
navBar.addEventListener('click', linkClicked);