286

I have a page where a scroll bar containing table rows with divs in them is dynamically generated from the database. Each table row acts like a link, sort of like you'd see on a YouTube playlist next to the video player.

When a user visits the page, the option they are on is supposed to go to the top of the scrolling div. This functionality is working. The issue is that it goes just a tad too far. Like the option they are on is about 10px too high. So, the page is visited, the url is used to identify which option was selected and then scrolls that option to the top of the scrolling div. Note: This is not the scroll bar for the window, it is a div with a scrollbar.

I am using this code to make it move the selected option to the top of the div:

var pathArray = window.location.pathname.split( '/' );

var el = document.getElementById(pathArray[5]);

el.scrollIntoView(true);

It moves it to the top of the div but about 10 pixels too far up. Anyone know how to fix that?

3
  • 197
    The lack of offset configuration for scrollIntoView is troubling.
    – zrooda
    Commented Jul 20, 2017 at 14:21
  • 15
    @mystrdat There are new CSS properties that deal with this, scroll-margin and scroll-padding.
    – Flimm
    Commented Jun 10, 2021 at 15:16
  • Do you have lazy loaded images or dynamic things that could change in height on scroll?
    – Alireza
    Commented Feb 21 at 0:57

27 Answers 27

350

Smooth scroll to the proper position

Here is better answer for the most cases. It uses scroll-margin and scroll-padding CSS rules. The solution below uses getBoundingClientRect which triggers an additional forced layout.

Get correct y coordinate and use window.scrollTo({top: y, behavior: 'smooth'})

const id = 'profilePhoto';
const yOffset = -10; 
const element = document.getElementById(id);
const y = element.getBoundingClientRect().top + window.pageYOffset + yOffset;

window.scrollTo({top: y, behavior: 'smooth'});
13
  • 4
    This is the most correctly answer - we can easly find position of scroll with jQuery('#el').offset().top and then to use window.scrollTo Commented Jul 6, 2019 at 23:37
  • 2
    +1 for this solution. It helped me to scroll to the element correctly when I had a container positioned relative with a top offset.
    – Liga
    Commented Aug 12, 2019 at 6:56
  • 2
    Anyone coming here using HashLink with React Router, this is what worked for me. In the scroll prop on your HashLink, scroll={el => { const yCoordinate = el.getBoundingClientRect().top + window.pageYOffset; const yOffset = -80; window.scrollTo({ top: yCoordinate + yOffset, behavior: 'smooth' }); }} -- where the offset is 10-15 pixels more than the size of your header just for better spacing
    – Rob B
    Commented Sep 4, 2019 at 14:27
  • 2
    Only issue with the getBoundingClientRect triggers a forced layout / reflow which is a performance bottleneck. That being said, if used with parcimony, it's quite ok !
    – GeorgesA
    Commented Aug 3, 2022 at 12:26
  • 3
    One-liner for inline HTML: window.scrollTo({top: document.getElementById("yourElem").getBoundingClientRect().top + window.scrollY - 50, behavior: "smooth"});
    – Alex Under
    Commented Nov 11, 2023 at 14:45
228

CSS scroll-margin and scroll-padding

You might want to have a look at new CSS properties scroll-padding and scroll-margin. You can use scroll-padding for the scrolling container (html in this case), and scroll-margin for the element within the container.

For your example, you would want to add scroll-margin-top for the element that you want to scroll into view, like this:

.example {
  scroll-margin-top: 10px;
}

This affects scrollIntoView code, like this code:

const el = document.querySelector(".example");
el.scrollIntoView({block: "start", behavior: "smooth"});

This will cause the viewport to scroll to align the top border of the viewport with the top border of the element, but with 10px of additional space. In other words, these properties of the element are taken into account:

  • padding-top
  • border-top
  • scroll-margin-top
  • (and not margin-top)

In addition, if the html element has scroll-padding-top set, then that is taken into account too.

13
  • 9
    If you're using a framework like Vue, you might want to make sure that all changes to the DOM are finished before running scrollIntoView by using something like Vue.nextTick(() => el.scrollIntoView())
    – Flimm
    Commented Jun 10, 2021 at 15:11
  • 1
    This is an awesome solution.. which is completely made sense :) Thanks @Flimm Commented Aug 28, 2021 at 13:27
  • perfect thanks mate! The selected answer didn't quite work for me. Commented Aug 30, 2021 at 15:44
  • 1
    Another troublshooting tip: if you're loading a URL with a fragment (for example example.com/#test) , then the browser will scroll to the element with that id or name when the page has loaded, but not before all AJAX has finished loading. If you're causing layout shifts after the page has loaded, navigating to a URL with a fragment might not scroll to the expected position.
    – Flimm
    Commented Dec 8, 2021 at 14:18
  • 3
    Promoted your answer as I believe it is better for performance reasons. Hope it will go higher soon :)
    – Arseniy-II
    Commented Aug 10, 2022 at 10:05
142

Position Anchor By Absolute Method

Another way to do this is to position your anchors exactly where you want on the page rather than relying on scrolling by offset. I find it allows better control for each element (eg. if you want a different offset for certain elements), and may also be more resistant to browser API changes/differences.

<div id="title-element" style="position: relative;">
  <div id="anchor-name" style="position: absolute; top: -100px; left: 0"></div>
</div>

Now the offset is specified as -100px relative to the element. Create a function to create this anchor for code reuse, or if you are using a modern JS framework such as React do this by creating a component that renders your anchor, and pass in the anchor name and alignment for each element, which may or may not be the same.

Then just use :

const element = document.getElementById('anchor-name')
element.scrollIntoView({ behavior: 'smooth', block: 'start' });

For smooth scrolling with an offset of 100px.

2
  • 5
    This is especially useful when a "page top" type link is below a nav bar, and you want to show the nav, but don't want any other links offset
    – Joe Moon
    Commented Mar 27, 2020 at 20:17
  • 1
    you can achieve the same using negative margin-top, you avoid of wrapping the element along with another with position relative
    – Sergi
    Commented Apr 21, 2020 at 15:15
122

You can do it in two steps :

el.scrollIntoView(true);
window.scrollBy(0, -10); // Adjust scrolling with a negative value here
4
  • 44
    If the scrollIntoView() hasn't finished scrolling before scrollBy() runs, the latter scroll will cancel the former. At least on Chrome 71. Commented Jan 6, 2019 at 4:04
  • 1
    In this case use the setTimeout as seen in an answer below: stackoverflow.com/a/51935129/1856258 .. For me it works without Timeout. Thanks!
    – leole
    Commented Feb 26, 2019 at 13:41
  • 3
    is there a way to directly inject a pixel's corrector into scrollIntoView in order to make the application move directly to the relevant place? Like scrollIntoView({adjustment:y}), I think it should be possible with a custom code at the end
    – DiaJos
    Commented May 15, 2019 at 17:26
  • Expanding on what @DaveHughes has already said. Scroll will cancel if scroll mode is smooth, this works well and apparently there is no stutter as long as scroll mode is instant. Commented Apr 27, 2023 at 18:48
105

I solved this problem by using,

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

This makes the element appear in the center after scrolling, so I don't have to calculate yOffset.

Hope it helps...

7
  • 1
    You should use scrollTo not on element but on window
    – Arseniy-II
    Commented Dec 4, 2019 at 15:38
  • @Arseniy-II Thanks for pointing that out!!. I missed that part! Commented Dec 8, 2019 at 11:08
  • does the block helps for top? Commented Apr 28, 2020 at 12:41
  • 1
    My scroll was just off a little bit! it worked with this! thanks! Commented May 19, 2021 at 12:46
  • 3
    Nice this should be top comment!
    – mango
    Commented May 24, 2022 at 21:09
71

If it's about 10px, then I guess you could simply manually adjust the containing div's scroll offset like that:

el.scrollIntoView(true);
document.getElementById("containingDiv").scrollTop -= 10;
8
  • 3
    Will this have the same animated effect as scrollIntoView?
    – 1252748
    Commented Jul 10, 2014 at 0:19
  • 1
    @thomas AFAIK, the plain DOM scrollIntoView function does not cause animation. Are you talking about the scrollIntoView jQuery plugin? Commented Jul 10, 2014 at 0:25
  • 2
    That worked man, except it was the wrong direction so all I had to do was change it from += 10 to -= 10 and now it's loading just right, thanks a lot for the help!!!! Commented Jul 10, 2014 at 1:38
  • Yep. I was confused. I thought it animated. Sorry!
    – 1252748
    Commented Jul 10, 2014 at 4:07
  • 26
    It can animate, just add el.scrollIntoView({ behavior: 'smooth' });. Support isn't great though! Commented Oct 6, 2016 at 11:54
57

This works for me in Chrome (With smooth scrolling and no timing hacks)

It just moves the element, initiates the scroll, then moves it back.

There is no visible "popping" if the element is already on the screen.

pos = targetEle.style.position;
top = targetEle.style.top;
targetEle.style.position = 'relative';
targetEle.style.top = '-20px';
targetEle.scrollIntoView({behavior: 'smooth', block: 'start'});
targetEle.style.top = top;
targetEle.style.position = pos;
4
  • 6
    This is the best solution... Wish it was marked as answer. Would have saved a couple hours.
    – Shivam
    Commented Aug 16, 2020 at 14:46
  • 2
    Tested it and it works out of the box. Not a hack and works great! Vote this one up! Commented Sep 14, 2020 at 15:20
  • Amazing. This way (storing style.top, setting to -x and reapplying it later after scrollIntoView is the only thing that works in Blazor and actually works. Using it with Virtualize component to show lot of data. Was shocked to learn there isn't scrolling supported by any means!! Thanks
    – That Marc
    Commented Jul 21, 2021 at 0:27
  • 1
    Perfect!! I had to login to like you :)))
    – user12557324
    Commented Jan 5, 2022 at 16:57
30

Fix it in 20 seconds:

This solution belongs to @Arseniy-II, I have just simplified it into a function.

function _scrollTo(selector, yOffset = 0){
  const el = document.querySelector(selector);
  const y = el.getBoundingClientRect().top + window.pageYOffset + yOffset;

  window.scrollTo({top: y, behavior: 'smooth'});
}

Usage (you can open up the console right here in StackOverflow and test it out):

_scrollTo('#question-header', 0);

I'm currently using this in production and it is working just fine.

1
  • 3
    This worked a treat for me, I also added a line to dynamically determine the offset required. My content was sitting under the nav, so I fetched the nav height to be passed in as yOffset const yOffset = document.getElementById('nav_id').getBoundingClientRect().height I also had to modify slightly to - the yOffset rather than +, as my content was scrolled too far up the page (under the nav)
    – Roxy Walsh
    Commented Jan 29, 2021 at 15:05
14

The simplest way to do this,

html, body {
    scroll-behavior: smooth;
}

    html [id], body [id] {
        scroll-margin: 50px !important;
    }

with your provided code

var pathArray = window.location.pathname.split( '/' );
var el = document.getElementById(pathArray[5]);
el.style.scrollMargin = '50px';
el.scrollIntoView(true);
1
  • 2
    this is the right solution. it is generic and thus applying to everything on the page, but it is the right concept. thanks @malik zahid
    – user566245
    Commented Oct 24, 2022 at 18:22
10

Another solution is to use "offsetTop", like this:

var elementPosition = document.getElementById('id').offsetTop;

window.scrollTo({
  top: elementPosition - 10, //add your necessary value
  behavior: "smooth"  //Smooth transition to roll
});
8

Assuming you want to scroll to the divs that are all at the same level in DOM and have class name "scroll-with-offset", then this CSS will solve the issue:

.scroll-with-offset {    
  padding-top: 100px;
  margin-bottom: -100px;
}

The offset from the top of the page is 100px. It will only work as intended with block: 'start':

element.scrollIntoView({ behavior: 'smooth', block: 'start' });

What's happening is that the divs' top point is at the normal location but their inner contents start 100px below the normal location. That's what padding-top:100px is for. margin-bottom: -100px is to offset the below div's extra margin. To make the solution complete also add this CSS to offset the margins/paddings for the top-most and bottom-most divs:

.top-div {
  padding-top: 0;
}
.bottom-div {
  margin-bottom: 0;
}
0
7

Building on an earlier answer, I am doing this in an Angular5 project.

Started with:

// el.scrollIntoView(true);
el.scrollIntoView({
   behavior: 'smooth',
   block: 'start'
});
window.scrollBy(0, -10); 

But this gave some problems and needed to setTimeout for the scrollBy() like this:

//window.scrollBy(0,-10);
setTimeout(() => {
  window.scrollBy(0,-10)
  }, 500);

And it works perfectly in MSIE11 and Chrome 68+. I have not tested in FF. 500ms was the shortest delay I would venture. Going lower sometimes failed as the smooth scroll had not yet completed. Adjust as required for your own project.

+1 to Fred727 for this simple but effective solution.

1
  • 11
    This feels a bit janky to smooth scroll to an element and then scroll up a bit after.
    – catch22
    Commented Oct 3, 2018 at 16:22
5

I've got this and it works brilliantly for me:

// add a smooth scroll to element
scroll(el) {
el.scrollIntoView({
  behavior: 'smooth',
  block: 'start'});

setTimeout(() => {
window.scrollBy(0, -40);
}, 500);}

Hope it helps.

1
  • doesnt work in vue, sometimes the scroll posisition missed
    – rickvian
    Commented Jul 16, 2021 at 2:44
4

So, perhaps this is a bit clunky but so far so good. Im working in angular 9.

file .ts

scroll(el: HTMLElement) {
  el.scrollIntoView({ block: 'start',  behavior: 'smooth' });   
}

file .html

<button (click)="scroll(target)"></button>
<div  #target style="margin-top:-50px;padding-top: 50px;" ></div>

I adjust the offset with margin and padding top.

Saludos!

4

2022 pure js, smooth scrooling, and also works with reloaded content

This is my solution for modern browsers:

document.addEventListener("click", e => {
   let anchorlink = e.target.closest('a[href^="#"]');
   
   if (anchorlink) {
      e.preventDefault();
      let hashval = anchorlink.getAttribute('href')
      let target = document.querySelector(hashval)
      const yOffset = -75;
      const y = target.getBoundingClientRect().top + window.pageYOffset + yOffset;
      window.scrollTo({ top: y, behavior: 'smooth' });
 
      history.pushState(null, null, hashval)
      e.preventDefault();
   }
})

The click-eventlistener listen to the document, so you don't have to worry about runtime loaded or created intern links (for example in wordpress with gutenberg)

Hope it helps ;)

2
  • This works flawlessly.
    – thaikolja
    Commented Nov 23, 2022 at 5:34
  • Works with react ref as well. I needed the very top of the component so I changed divRef.current.getBoundingClientRect().height * -1. Also pageYOffset marked deprecated, changed it to scrollY. Thank you. Commented Mar 22 at 12:27
2

You can also use the element.scrollIntoView() options

el.scrollIntoView(
  { 
    behavior: 'smooth', 
    block: 'start' 
  },
);

which most browsers support

1
  • 48
    However this doesn't support any offset option. Commented Aug 17, 2018 at 18:57
2

Solution if you are using Ionic Capacitor, Angular Material, and need to support iOS 11.

                document.activeElement.parentElement.parentElement.scrollIntoView({block: 'center', behavior: 'smooth'}); 

The key is to scroll to the parent of the parent which is the wrapper around the input. This wrapper includes the label for the input which is now no longer cut off.

If you only need to support iOS 14 the "block" center param actually works, so this is sufficient:

                document.activeElement.scrollIntoView({block: 'center', behavior: 'smooth'}); 
2

Leaving my solution here in case of someone wanted to achieve the same as me.

In my case, the error or notification appears at the top of the page after submit. (the page is mostly bit long for a couple of scrolls and even more in mobile.)

I used the below snippet to scroll the error or notification into view.

requestAnimationFrame(() =>
  errorOrNotification.scrollIntoView({
    block: 'end', behavior: 'smooth'
  })
);

block: 'end' is the trick. As per documentation:: It reflects the alignToTop: false scenario. In order to incorporate the smooth scrolling I added the replacement of the same.

In essense, it will try to align the bottom of the element to the bottom of the visible area of the scrollable ancestor (in my case the window).

1

Found a workaround solution. Say that you want to scroll to an div, Element here for example, and you want to have a spacing of 20px above it. Set the ref to a created div above it:

<div ref={yourRef} style={{position: 'relative', bottom: 20}}/> <Element />

Doing so will create this spacing that you want.

If you have a header, create an empty div as well behind the header and assign to it a height equal to the height of the header and reference it.

0

My main idea is creating a tempDiv above the view which we want to scroll to. It work well without lagging in my project.

scrollToView = (element, offset) => {
    var rect = element.getBoundingClientRect();
    var targetY = rect.y + window.scrollY - offset;

    var tempDiv;
    tempDiv = document.getElementById("tempDiv");
    if (tempDiv) {
        tempDiv.style.top = targetY + "px";
    } else {
        tempDiv = document.createElement('div');
        tempDiv.id = "tempDiv";
        tempDiv.style.background = "#F00";
        tempDiv.style.width = "10px";
        tempDiv.style.height = "10px";
        tempDiv.style.position = "absolute";
        tempDiv.style.top = targetY + "px";
        document.body.appendChild(tempDiv);
    }

    tempDiv.scrollIntoView({ behavior: 'smooth', block: 'start' });
}

Example using

onContactUsClick = () => {
    this.scrollToView(document.getElementById("contact-us"), 48);
}

Hope it help

0

Based on the answer of Arseniy-II: I had the Use-Case where the scrolling entity was not window itself but a inner Template (in this case a div). In this scenario we need to set an ID for the scrolling container and get it via getElementById to use its scrolling function:

<div class="scroll-container" id="app-content">
  ...
</div>
const yOffsetForScroll = -100
const y = document.getElementById(this.idToScroll).getBoundingClientRect().top;
const main = document.getElementById('app-content');
main.scrollTo({
    top: y + main.scrollTop + yOffsetForScroll,
    behavior: 'smooth'
  });

Leaving it here in case someone faces a similar situation!

0

Here's my 2 cents.

I've also had the issue of the scrollIntoView scrolling a bit past the element, so I created a script (native javascript) that prepends an element to the destination, positioned it a bit to the top with css and scrolled to that one. After scrolling, I remove the created elements again.

HTML:

//anchor tag that appears multiple times on the page
<a href="#" class="anchors__link js-anchor" data-target="schedule">
    <div class="anchors__text">
        Scroll to the schedule
    </div>
</a>

//The node we want to scroll to, somewhere on the page
<div id="schedule">
    //html
</div>

Javascript file:

(() => {
    'use strict';

    const anchors = document.querySelectorAll('.js-anchor');

    //if there are no anchors found, don't run the script
    if (!anchors || anchors.length <= 0) return;

    anchors.forEach(anchor => {
        //get the target from the data attribute
        const target = anchor.dataset.target;

        //search for the destination element to scroll to
        const destination = document.querySelector(`#${target}`);
        //if the destination element does not exist, don't run the rest of the code
        if (!destination) return;

        anchor.addEventListener('click', (e) => {
            e.preventDefault();
            //create a new element and add the `anchors__generated` class to it
            const generatedAnchor = document.createElement('div');
            generatedAnchor.classList.add('anchors__generated');

            //get the first child of the destination element, insert the generated element before it. (so the scrollIntoView function scrolls to the top of the element instead of the bottom)
            const firstChild = destination.firstChild;
            destination.insertBefore(generatedAnchor, firstChild);

            //finally fire the scrollIntoView function and make it animate "smoothly"
            generatedAnchor.scrollIntoView({
                behavior: "smooth",
                block: "start",
                inline: "start"
            });

            //remove the generated element after 1ms. We need the timeout so the scrollIntoView function has something to scroll to.
            setTimeout(() => {
                destination.removeChild(generatedAnchor);
            }, 1);
        })
    })
})();

CSS:

.anchors__generated {
    position: relative;
    top: -100px;
}

Hope this helps anyone!

0

I add this css tips for those who not resolved this issue with solutions above :

#myDiv::before {
  display: block;
  content: " ";
  margin-top: -90px; // adjust this with your header height
  height: 90px; // adjust this with your header height
  visibility: hidden;
}
0

UPD: I've created an npm package that works better than the following solution and easier to use.

My smoothScroll function

I've taken the wonderful solution of Steve Banton and wrote a function that makes it more convenient to use. It'd be easier just to use window.scroll() or even window.scrollBy(), as I've tried before, but these two have some problems:

  • Everything becomes junky after using them with a smooth behavior on.
  • You can't prevent them anyhow and have to wait till the and of the scroll. So I hope my function will be useful for you. Also, there is a lightweight polyfill that makes it work in Safari and even IE.

Here is the code

Just copy it and mess up with it how ever you want.

import smoothscroll from 'smoothscroll-polyfill';

smoothscroll.polyfill();

const prepareSmoothScroll = linkEl => {
  const EXTRA_OFFSET = 0;

  const destinationEl = document.getElementById(linkEl.dataset.smoothScrollTo);
  const blockOption = linkEl.dataset.smoothScrollBlock || 'start';

  if ((blockOption === 'start' || blockOption === 'end') && EXTRA_OFFSET) {
    const anchorEl = document.createElement('div');

    destinationEl.setAttribute('style', 'position: relative;');
    anchorEl.setAttribute('style', `position: absolute; top: -${EXTRA_OFFSET}px; left: 0;`);

    destinationEl.appendChild(anchorEl);

    linkEl.addEventListener('click', () => {
      anchorEl.scrollIntoView({
        block: blockOption,
        behavior: 'smooth',
      });
    });
  }

  if (blockOption === 'center' || !EXTRA_OFFSET) {
    linkEl.addEventListener('click', () => {
      destinationEl.scrollIntoView({
        block: blockOption,
        behavior: 'smooth',
      });
    });
  }
};

export const activateSmoothScroll = () => {
  const linkEls = [...document.querySelectorAll('[data-smooth-scroll-to]')];

  linkEls.forEach(linkEl => prepareSmoothScroll(linkEl));
};

To make a link element just add the following data attribute:

data-smooth-scroll-to="element-id"

Also you can set another attribute as an addtion

data-smooth-scroll-block="center"

It represents the block option of the scrollIntoView() function. By default, it's start. Read more on MDN.

Finally

Adjust the smoothScroll function to your needs.

For example, if you have some fixed header (or I call it with the word masthead) you can do something like this:

const mastheadEl = document.querySelector(someMastheadSelector);

// and add it's height to the EXTRA_OFFSET variable

const EXTRA_OFFSET = mastheadEl.offsetHeight - 3;

If you don't have such a case, then just delete it, why not :-D.

0

I tried using some of the answers from above, but the scrolling would not work. After some digging around I noticed that the scroll positions were in fact very odd (even negative values) and it was caused by how Angular was rendering the pages in the router-outlet.

My solution was to compute the scroll position of the element starting from the top of the page. I placed an element with id start-of-page at the beginning of the template and I obtained the scroll amount by subtracting the target fragment position.

 ngAfterViewInit(): void {
    this.route.fragment.subscribe(fragment => {
      try {
        // @ts-ignore
        // document.querySelector('#hero').scrollIntoView({behavior:smooth, block: "start", inline: "start"});
        // @ts-ignore
        // document.querySelector('#' + fragment).scrollIntoView({behavior:smooth, block: "start", inline: "start"});
        this._scrollTo('#' + fragment,0);
      } catch (e) { };
    });
  }

private _scrollTo(selector: any, yOffset = 0){
    const base = document.querySelector('#start-of-page');
    const el = document.querySelector(selector);
    // @ts-ignore
    const y = el.getBoundingClientRect().top - base.getBoundingClientRect().top; 
    window.scrollTo({top: y, behavior: 'smooth'});
  }
0

One solution is to create an invisible temporary element with an offset relative to the target, scroll to it, and then delete it. It's perhaps not ideal, but other solutions weren't working for me in the context I was working in. For example:

const createOffsetElement = (element) => {
    const offsetElement = document.createElement('div');
    offsetElement.style = `
        position: relative;
        top: -10em;
        height: 1px;
        width: 1px;
        visibility: hidden;
    `;
    const inputWrapper = element.closest('.ancestor-element-class');
    inputWrapper.appendChild(offsetElement);
    return offsetElement;
};

const scrollTo = (element) => {
    const offsetElement = createOffsetElement(element);
    offsetElement.scrollIntoView({
        behavior: 'smooth',
    });
    offsetElement.remove();
};
-1

For an element in a table row, use JQuery to get the row above the one you want, and simply scroll to that row instead.

Suppose I have multiple rows in a table, some of which should be reviewed by and admin. Each row requiring review has both and up and a down arrow to take you to the previous or next item for review.

Here's a complete example that should just run if you make a new HTML document in notepad and save it. There's extra code to detect the top and bottom of our items for review so we don't throw any errors.

<html>
<head>
    <title>Scrolling Into View</title>
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
    <style>
        div.scroll { height: 6em; width: 20em; overflow: auto; }
        thead th   { position: sticky; top: -1px; background: #fff; }
        .up, .down { cursor: pointer; }
        .up:hover, .down:hover { color: blue; text-decoration:underline; }
    </style>
</head>
<body>
<div class='scroll'>
<table border='1'>
    <thead>
        <tr>
            <th>Review</th>
            <th>Data</th>
        </tr>
    </thead>
    <tbody>
        <tr id='row_1'>
            <th></th>
            <td>Row 1 (OK)</td>
        </tr>
        <tr id='row_2'>
            <th></th>
            <td>Row 2 (OK)</td>
        </tr>
        <tr id='row_3'>
            <th id='jump_1'><span class='up'>UP</span> <span class='down'>DN</span></th>
            <td>Row 3 (REVIEW)</td>
        </tr>
        <tr id='row_4'>
            <th></th>
            <td>Row 4 (OK)</td>
        </tr>
        <tr id='row_5'>
            <th id='jump_2'><span class='up'>UP</span> <span class='down'>DN</span></th>
            <td>Row 5 (REVIEW)</td>
        </tr>
        <tr id='row_6'>
            <th></th>
            <td>Row 6 (OK)</td>
        </tr>
        <tr id='row_7'>
            <th></th>
            <td>Row 7 (OK)</td>
        </tr>
        <tr id='row_8'>
            <th id='jump_3'><span class='up'>UP</span> <span class='down'>DN</span></th>
            <td>Row 8 (REVIEW)</td>
        </tr>
        <tr id='row_9'>
            <th></th>
            <td>Row 9 (OK)</td>
        </tr>
        <tr id='row_10'>
            <th></th>
            <td>Row 10 (OK)</td>
        </tr>
    </tbody>
</table>
</div>
<script>
$(document).ready( function() {
    $('.up').on('click', function() {
        var id = parseInt($(this).parent().attr('id').split('_')[1]);
        if (id>1) {
            var row_id = $('#jump_' + (id - 1)).parent().attr('id').split('_')[1];
            document.getElementById('row_' + (row_id-1)).scrollIntoView({behavior: 'smooth', block: 'start'});
        } else {
            alert('At first');
        }
    });

    $('.down').on('click', function() {
        var id = parseInt($(this).parent().attr('id').split('_')[1]);
        if ($('#jump_' + (id + 1)).length) {
            var row_id = $('#jump_' + (id + 1)).parent().attr('id').split('_')[1];
            document.getElementById('row_' + (row_id-1)).scrollIntoView({behavior: 'smooth', block: 'start'});
        } else {
            alert('At last');
        }
    });
});
</script>
</body>
</html>

Not the answer you're looking for? Browse other questions tagged or ask your own question.