128

I have a site with a header set to position: fixed. On one of my pages, I use scrollIntoView(true) on an element. My problem is that when scrollIntoView is called, the element gets positioned underneath the header. How would I fix this so that the element is shown just below the header?

I'm using the Bootstrap framework and the header is styled with navbar navbar-fixed-top.

Cheran Shunmugavel
  • 8,319
  • 1
  • 33
  • 40
Corne Beukes
  • 1,759
  • 4
  • 14
  • 18

11 Answers11

138

It's a bit hacky but here's a workaround.

var node = 'select your element';
var yourHeight = 'height of your fixed header';

// scroll to your element
node.scrollIntoView(true);

// now account for fixed header
var scrolledY = window.scrollY;

if(scrolledY){
  window.scroll(0, scrolledY - yourHeight);
}

Edit:

A modern workaround is to use the CSS scroll-margin-top property in combination with the :target selector. Described in detail: https://www.bram.us/2020/03/01/prevent-content-from-being-hidden-underneath-a-fixed-header-by-using-scroll-margin-top/

dontmentionthebackup
  • 2,775
  • 1
  • 21
  • 18
  • I was looking for this answer for days. At last found it. Cool. – Faisal Mq Jul 16 '15 at 11:15
  • You can also set a imaginary 'yourHeight' like 1000 if your header has dynamic size. – Pietro Coelho Apr 12 '18 at 01:00
  • It does not work when the node is located at the bottom of the page. – Weihang Jian Jul 10 '18 at 08:17
  • 7
    You could simplify your code by doing `window.scroll(0, node.offsetTop - yourHeight);` and not using `scrollIntoView` – jhujhul Feb 12 '19 at 15:29
  • 1
    It works, but it will not respect the native smooth scrolling behavior of the browser, if present. – msun Mar 25 '20 at 03:31
  • 7
    a workaround that I find most useful(instead of scrolling to top, scroll to center): `node.scrollIntoViewIfNeeded({block: 'center'})` – Sanyam Jain Apr 29 '20 at 07:21
  • @SanyamJain {block: 'center'} options works great on scrollIntoView - thank you! Please be warned though - MDN warns that scrollIntoViewIfNeeded should not be used in prod https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded – java-addict301 Dec 10 '20 at 21:18
  • It is not working in firefox. Can you please guide? – Apoorva Shah Feb 13 '21 at 10:21
  • Note that `scroll-margin-top` with `scrollIntoView` only works in Safari 14.1 or later, released in April 2021. – tao_oat Oct 05 '21 at 14:05
50

scroll-margin-top: $header-height;

Set the scroll-margin-top css property to the desired top offset, on elements that you're linking/scrolling to. (it should probably have been called scroll-offset-top.)

* {
  scroll-margin-top: 100px;
}

Docs - full browser support - Codepen demo

links = [...document.getElementsByClassName("js-link")]
links.forEach(element => {
  element.addEventListener("click", e => {
    e.preventDefault()
    document.getElementById(e.target.dataset.target).scrollIntoView({
      behavior: "smooth",
      block: "start",
      inline: "nearest"
    })
  })
})
body {
  margin: 0;
}

header {
  position: sticky;
  top: 0;
  left: 0;
  right: 0;
  height: 100px;
  background: #eee;
  display: flex;
  align-items: center;
}

header a {
  padding: 0.5em;
}

h1 {
  padding: 0.7em;
}

* {
  scroll-margin-top: 100px;
}

p {
  padding: 1em;
}
<header>
  <h1>page title</h1>
  <nav>
    <a href="#p1" class="js-link" data-target="p1">1</a>
    <a href="#p2" class="js-link" data-target="p2">2</a>
    <a href="#p3" class="js-link" data-target="p3">3</a>
    <a href="#p4" class="js-link" data-target="p4">4</a>
    <a href="#p5" class="js-link" data-target="p5">5</a>
  </nav>
</header>

<main>
  <p id="p1">
    paragraph 1.
    <br> Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
    It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop
    publishing software like Aldus PageMaker including versions of Lorem Ipsu
  </p>
  <p id="p2">
    paragraph 2.
    <br> Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
    It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop
    publishing software like Aldus PageMaker including versions of Lorem Ipsu
  </p>
  <p id="p3">
    paragraph 3.
    <br> Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
    It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop
    publishing software like Aldus PageMaker including versions of Lorem Ipsu
  </p>
  <p id="p4">
    paragraph 4.
    <br> Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
    It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop
    publishing software like Aldus PageMaker including versions of Lorem Ipsu
  </p>
  <p id="p5">
    paragraph 5.
    <br> Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
    It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop
    publishing software like Aldus PageMaker including versions of Lorem Ipsu
  </p>
</main>
Arye Eidelman
  • 1,579
  • 16
  • 22
  • Nice solution! Although It seems not working for me, it works well when I used `p` as css selector for `scroll-margin-top`. – WillBC Dec 20 '20 at 00:33
  • 1
    I've updated it to just select everything `*` for cases where your using javascript or when using the element's name for linking. – Arye Eidelman Dec 31 '20 at 15:22
42

The following code yields a smooth scroll to the top of the element with an offset for fixed header:

var topOfElement = document.querySelector('#targetElement').offsetTop - XX;
window.scroll({ top: topOfElement, behavior: "smooth" });

Where XX is the height of your fixed header.

Thomas Doucette
  • 421
  • 4
  • 2
  • Saved A DAY for me! I was using `mat-card-content` as my scrollable aria with a `mat-toolbar` as my fixed header. I used `ngx-scroll-to` for my scrolling but it didn't work in mobile. However, your solution worked in mobile but not in standalone, whereas the mentioned package worked vice verca. Combined both and Voila! THANKS! – imans77 Feb 04 '19 at 20:37
  • probably should be the correct/accepted answer. Thanks! – Generaldeep Aug 05 '20 at 16:16
  • This was the only solution that worked for my specific case. Thanks. – Word Rearranger Feb 16 '21 at 01:26
41

Edit: Although less compatible (no IE compatibility), scroll-margin-top will also solve this, as mentioned in Arye Eidelman's answer.

You can solve this with CSS, by applying a padding-top and a negative margin-top to the elements that you want to scroll to.

Demo

// For demo only, no JS needed for the solution
document.querySelector('.scroll-to-working-inline').addEventListener('click', function() {
  document.querySelector('.working-inline').scrollIntoView();
});

document.querySelector('.scroll-to-working-block').addEventListener('click', function() {
  document.querySelector('.working-block').scrollIntoView();
});

document.querySelector('.scroll-to-broken').addEventListener('click', function() {
  document.querySelector('.broken').scrollIntoView();
});
/* Relevant styles */
.working-inline {
  padding-top: 60px;
  margin-top: -60px;
}

.working-block {
  padding-top: 60px;
  margin-top: -60px;
}

/* Allow scrolling to the top */
body {
  padding-top: 60px;
}

/* Only for the demo */
body { margin: 0; }

header {
  position: fixed;
  top: 0;
  background-color: tomato;
  color: white;
  width: 100%;
  height: 60px;
  line-height: 60px;
  text-align: center;
}

[class^='working'],
[class^='broken'] {
  font-size: 3rem;
}
<header>
  scroll to...
  <button class="scroll-to-working-inline">working inline element</button>
  <button class="scroll-to-working-block">working block element</button>
  <button class="scroll-to-broken">broken element</button>
</header>

<main>
  <p>Sql daemon epoch all your base are belong to us packet system perl semaphore. Interpreter warez pragma kilo worm back door baz continue chown blob unix Dennis Ritchie stack mutex bar throw fopen man pages linux. Sql suitably small values bit infinite loop pwned rm -rf.</p>
  
  <a class="working-inline">Working inline</a>
  
  <p>Syn baz man pages unix vi crack leapfrog semaphore fail pwned afk null socket cd long leet emacs Donald Knuth bin grep todo pragma stdio.h January 1, 1970. Alloc gc system new finally sql stack trace syn mainframe cat machine code memory leak server salt flood tunnel in back door thread. Bytes fatal throw ctl-c Dennis Ritchie over clock eof tera perl regex.</p>
  
  <div class="working-block">Working block element</div>
  
  <p>Public injection class unix malloc error script kiddies packet less fail int I'm sorry Dave, I'm afraid I can't do that. Tarball memory leak double rsa pwned public all your base are belong to us. False bytes bang bar tarball semaphore warez cd port daemon exception mountain dew sql mainframe gcc ifdef chown private.</p>
  
  <div class="broken">Broken element</div>
  
  <p>Daemon bubble sort protected mutex overflow grep snarf crack warez I'm compiling bit if memory leak Starcraft nak script kiddies long it's a feature. Hello world public server James T. Kirk injection terminal wannabee race condition syn alloc. Gobble leapfrog finally bypass concurrently while irc gurfle do back door blob man pages sql over clock.</p>
  
  <p>Char hello world then man pages ascii long salt while char fatal do boolean tunnel in system else foo packet sniffer float terminal int default. Trojan horse ssh ifdef /dev/null chown cache error protocol afk todo rm -rf mainframe piggyback pwned regex xss warez Starcraft try catch stdio.h bubble sort. It's a feature I'm sorry Dave, I'm afraid I can't do that *.* port bypass ip.</p>
  
  <p>Stdio.h epoch mutex flood wannabee do race condition sql access exception. Bar pragma man pages dereference flush todo highjack while buffer bit nak big-endian syn xss salt for d00dz. Leslie Lamport linux server error hexadecimal snarf tunnel in rm -rf firewall then shell all your base are belong to us.</p>

  <p>Ascii gcc grep int flood kilo linux access mailbomb hash *.* fork semaphore frack else win bar ssh Leslie Lamport. Man pages strlen cache gnu segfault tarball race condition perl packet sniffer root cookie private chown d00dz January 1, 1970. Rsa public crack bit warez throw for void concurrently ip mutex.</p>
  
  <p>Char hello world then man pages ascii long salt while char fatal do boolean tunnel in system else foo packet sniffer float terminal int default. Trojan horse ssh ifdef /dev/null chown cache error protocol afk todo rm -rf mainframe piggyback pwned regex xss warez Starcraft try catch stdio.h bubble sort. It's a feature I'm sorry Dave, I'm afraid I can't do that *.* port bypass ip.</p>
  
  <p>Stdio.h epoch mutex flood wannabee do race condition sql access exception. Bar pragma man pages dereference flush todo highjack while buffer bit nak big-endian syn xss salt for d00dz. Leslie Lamport linux server error hexadecimal snarf tunnel in rm -rf firewall then shell all your base are belong to us.</p>

  <p>Ascii gcc grep int flood kilo linux access mailbomb hash *.* fork semaphore frack else win bar ssh Leslie Lamport. Man pages strlen cache gnu segfault tarball race condition perl packet sniffer root cookie private chown d00dz January 1, 1970. Rsa public crack bit warez throw for void concurrently ip mutex.</p>
  
  <p>Char hello world then man pages ascii long salt while char fatal do boolean tunnel in system else foo packet sniffer float terminal int default. Trojan horse ssh ifdef /dev/null chown cache error protocol afk todo rm -rf mainframe piggyback pwned regex xss warez Starcraft try catch stdio.h bubble sort. It's a feature I'm sorry Dave, I'm afraid I can't do that *.* port bypass ip.</p>
  
  <p>Stdio.h epoch mutex flood wannabee do race condition sql access exception. Bar pragma man pages dereference flush todo highjack while buffer bit nak big-endian syn xss salt for d00dz. Leslie Lamport linux server error hexadecimal snarf tunnel in rm -rf firewall then shell all your base are belong to us.</p>

  <p>Ascii gcc grep int flood kilo linux access mailbomb hash *.* fork semaphore frack else win bar ssh Leslie Lamport. Man pages strlen cache gnu segfault tarball race condition perl packet sniffer root cookie private chown d00dz January 1, 1970. Rsa public crack bit warez throw for void concurrently ip mutex.</p>
</main>
Karl Horky
  • 4,410
  • 2
  • 31
  • 35
9

A great simple solution (inspired by Sanyam Jain's comment) is to use {block: 'center'} to vertically center the selection like this:

scrollIntoView({block: 'center'})

https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView

java-addict301
  • 3,220
  • 2
  • 25
  • 37
8

Try the following. It works well for me:

  const headerHeight = 50; /* PUT HEADER HEIGHT HERE */
  const buffer = 25; /* MAY NOT BE NEEDED */
  const scrollToEl = document.querySelector("#YOUR-ELEMENT-SELECTOR");

  const topOfElement = window.pageYOffset + scrollToEl.getBoundingClientRect().top - headerHeight - buffer;
  window.scroll({ top: topOfElement, behavior: "smooth" });
dipole_moment
  • 5,266
  • 4
  • 39
  • 55
1

If anybody runs into issues with the top margin of your container div being ignored after a scrollIntoView, then instead of scrolling your element into view, just do a scrollTop relative to its parent scrolling container, as such:

var topOfElementToView= $('#elementToScroll').position().top;
$('#parentScrollingContainer').scrollTop(topOfElementToView);

Got the answer from user113716 on this thread: How to go to a specific element on page?

Community
  • 1
  • 1
Johnny
  • 2,503
  • 6
  • 21
  • 22
1

I suspect that what will work for the individual will greatly depend on their page layout, so this answer is intended as an additional option rather than to usurp anyone.

All I needed to do was pass false to scroll into view scrollIntoView(false)

it('should be able to click a button selector', function () {
    let EC = protractor.ExpectedConditions;
    let button = element(by.css('.my-button-css));

    browser.executeScript('arguments[0].scrollIntoView(false)', button.getWebElement()).then(function () {
        browser.wait(EC.elementToBeClickable(button), 3000).then(function () {
            expect(button.isDisplayed()).toBeTruthy();
            button.click();

            // more test logic here

        });
    });
});

Thanks to reboot jeff

CodeMonkey
  • 3,271
  • 25
  • 50
  • I know this is an older thread but this the only solution that worked for me with a fixed header. As per the MDN using element.scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"}); is equivalent to using false but offers a smoother scroll. – user558720 Feb 10 '21 at 00:12
0

In case if someone has fixed navbar which hides Header\Title after scrolling, here is the solution (based on @coco puffs's answer and this one):

let anchorLinks = document.querySelectorAll('a[href^="#"]')

for (let item of anchorLinks) {
  item.addEventListener('click', (e) => {
    let hashVal = item.getAttribute('href')
    let topOfElement = document.querySelector(hashVal).offsetTop - 70

    window.scroll({ top: topOfElement, behavior: "smooth" })
    history.pushState(null, null, hashVal)
    e.preventDefault()
  })
}

In the code 70px are used.

TitanFighter
  • 4,582
  • 3
  • 45
  • 73
0

For me scrolling the element into the center of the window would have worked.

scrollIntoViewIfNeeded({ block: "center" }) does this nicely but unfortunately only has limited browser compatibility (https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded). It does not work on IE and Firefox.

You can, however, build this function pretty easily yourself:

function scrollIntoCenter(element) {
  // first scroll element into view. This means element is at the very top.
  element.scrollIntoView();
  
  // calculate new scrollYPosition by subtracting half of window-height
  let y = window.scrollY - window.innerHeight/2;
  
  // scroll up by half of window height
  window.scroll(0, y);
}
IceRevenge
  • 1,451
  • 18
  • 33
0

I did it like this

var messageContainer
var message
var autoScroll

var messageCounter = 0
var messageQuantity = 20
while(messageQuantity >1){
        messageQuantity--
        messageContainer = document.createElement('div')
        messageContainer.className = 'messageContainer'

        message = document.createElement('div')
        message.className = 'message'

        message.innerHTML = `message: ${messageCounter++}`
        messageContainer.append(message)

        document.getElementsByClassName('overflow_section')[0].append(messageContainer)
        
        document.getElementById('messageQuantity').textContent = `counter: ${messageCounter}`
}

var appendMessage = document.getElementsByClassName('appendMessage')[0]
        appendMessage.addEventListener("click", ()=>{
            autoScroll = document.getElementById('autoScroll')
                    
            messageContainer = document.createElement('div')
            messageContainer.className = 'messageContainer'
            
            message = document.createElement('div')
            message.className = 'message'
        
            message.innerHTML = `message: ${messageCounter++}`
            messageContainer.append(message)
            
            document.getElementsByClassName('overflow_section')[0].append(messageContainer)
            
            if(autoScroll.checked){
                var count = document.getElementsByClassName('overflow_section')[0].getElementsByClassName('messageContainer')
                document.getElementsByClassName('messageContainer')[count.length - 1].scrollIntoView({
                    behavior: 'smooth', block: 'nearest', inline: 'start' 
                })
            }
            document.getElementById('messageQuantity').textContent = `counter: ${messageCounter}`
                
        })
        
        var scrollUp = document.getElementsByClassName('scrollUp')[0]       
        scrollUp.addEventListener("click", ()=>{
            var count = document.getElementsByClassName('overflow_section')[0].getElementsByClassName('messageContainer')   
                if(count.length > 0){
                   document.getElementsByClassName('messageContainer')[0].scrollIntoView({
                        behavior: 'smooth', block: 'center', inline: 'start' 
                    })  
                }           
                        
        })
        
        var scrollDown = document.getElementsByClassName('scrollDown')[0]       
        scrollDown.addEventListener("click", ()=>{
            var count = document.getElementsByClassName('overflow_section')[0].getElementsByClassName('messageContainer')   
                if(count.length > 0){
                     document.getElementsByClassName('messageContainer')[count.length - 1].scrollIntoView({
                        behavior: 'smooth', block: 'nearest', inline: 'start' 
                    })
                }           
                            
        })
button{
    background-color: rgb(0 0 0 / 80%);
    border-radius:5px;
    padding:10px;
    border:none;
    color:white;
}

input{
   background-color: rgb(179 229 249 / 50%);
   position:relative;
   top:10px;
    width:30px;
    height:30px;
}

.overflow_section{
    overflow: auto;
    font-size:20px;
    background-color: rgb(179 229 249 / 50%);
    width:50%;
    height:200px;
    margin:15px;
    margin-left:auto;
    margin-right:auto;
}

.messageContainer{
    background-color: rgb(255 120 0 / 50%);
    border-radius:5px;
    margin:5px;
    padding:15px;
}


#messageQuantity{
    background-color: rgb(255 120 0 / 100%);
    font-weight:700;
    border-radius:5px;
    padding:10px;
    color:black;
}
<div align="center">
    <span align="center" id='messageQuantity'></span>
    <button class='appendMessage'>Append message</button>
    <button class='scrollUp'>Scroll Up</button>
    <button class='scrollDown'>Scroll Down</button>

    <input type="checkbox" id="autoScroll" />
    <label for="vehicle1">Auto scroll</label>
</div>
<div class='overflow_section'></div>
AllanRibas
  • 678
  • 5
  • 14