使用带有固定位置标题的 scrollIntoView

Posted

技术标签:

【中文标题】使用带有固定位置标题的 scrollIntoView【英文标题】:Using scrollIntoView with a fixed position header 【发布时间】:2012-11-16 20:06:19 【问题描述】:

我有一个标头设置为position: fixed 的网站。在我的一个页面上,我在一个元素上使用了scrollIntoView(true)。我的问题是,当调用 scrollIntoView 时,元素被定位在标题下方。我该如何解决这个问题,以便元素显示在标题下方?

我正在使用 Bootstrap 框架,并且标头的样式为 navbar navbar-fixed-top

【问题讨论】:

好问题。谢谢 【参考方案1】:

这有点hacky,但这里有一个解决方法。

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

编辑:

现代解决方法是将 CSS scroll-margin-top 属性与 :target 选择器结合使用。详述:https://www.bram.us/2020/03/01/prevent-content-from-being-hidden-underneath-a-fixed-header-by-using-scroll-margin-top/

【讨论】:

我几天来一直在寻找这个答案。终于找到了。酷。 如果您的标题具有动态大小,您还可以设置一个虚构的 'yourHeight',例如 1000。 你可以通过window.scroll(0, node.offsetTop - yourHeight);而不是scrollIntoView来简化你的代码 它可以工作,但它不会尊重浏览器的原生平滑滚动行为(如果存在)。 我认为最有用的解决方法(而不是滚动到顶部,滚动到中心):node.scrollIntoViewIfNeeded(block: 'center')【参考方案2】:

编辑:虽然兼容性较差(不兼容 Edge + IE),scroll-margin-top 也将解决这个问题,正如 Arye Eidelman 中的 the answer 所述。

您可以使用 CSS 解决此问题,方法是将 padding-top 和否定的 margin-top 应用于要滚动到的元素。

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. *** 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. *** 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. *** 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>

【讨论】:

非常好的纯 CSS 解决方案。 不敢相信效果这么好,非常感谢!【参考方案3】:

如果有人在 scrollIntoView 之后遇到容器 div 的上边距被忽略的问题,那么不要将元素滚动到视图中,只需相对于其父滚动容器执行 scrollTop,如下所示:

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

在此线程上从 user113716 那里得到答案:How to go to a specific element on page?

【讨论】:

假设 jquery 可用。【参考方案4】:

以下代码会平滑滚动到元素顶部,并带有固定标题的偏移量:

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

XX 是固定页眉的高度。

【讨论】:

为我节省了一天!我使用mat-card-content 作为我的可滚动咏叹调,mat-toolbar 作为我的固定标题。我使用ngx-scroll-to 进行滚动,但它在移动设备中不起作用。但是,您的解决方案可以在移动设备中使用,但不能在独立环境中使用,而上述软件包则反之亦然。两者结合,瞧!谢谢! 可能应该是正确/接受的答案。谢谢! 这是唯一适用于我的具体案例的解决方案。谢谢。【参考方案5】:

如果有人修复了滚动后隐藏 Header\Title 的导航栏,这里是解决方案(基于@coco puffs 的回答和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()
  )

在代码中使用70px

【讨论】:

【参考方案6】:

我怀疑对个人有用的东西很大程度上取决于他们的页面布局,所以这个答案旨在作为一个额外的选择,而不是篡夺任何人。

我需要做的就是传递 false 以滚动到视图 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

        );
    );
);

感谢reboot jeff

【讨论】:

我知道这是一个较旧的线程,但这是唯一对我有用的解决方案,它有一个固定的标题。根据 MDN 使用 element.scrollIntoView(behavior: "smooth", block: "end", inline: "nearest");相当于使用 false 但提供更平滑的滚动。【参考方案7】:

试试下面的。它对我很有效:

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

【讨论】:

【参考方案8】:

scroll-margin-top: $header-height;

新的scroll-margin-top CSS 属性提供了一种简单的方法来实现这一点,适用于标准 html 链接和 scrollIntoView。

scroll-margin-top 更改垂直滚动目标,使目标元素远离视口顶部。将其设置为与页眉相同的高度将确保它不会在页眉后面结束。

/*
  Add a scroll-margin-top to all elements that you want to be able to link to.
  (you may need to select items that don't have an id as well)
*/

* 
  scroll-margin-top: 100px;

Can I use scroll-margin-top? 这适用于大多数现代浏览器,latest Safari since May 2021,而不是 IE 或旧版 Edge。

MDN docs scroll-margin-top


我在下面的演示中添加了平滑滚动,因此很明显这是在使用 scrollIntoView。

View in codepen

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>

【讨论】:

不错的解决方案!虽然它似乎对我不起作用,但当我使用 p 作为 scroll-margin-top 的 css 选择器时它运行良好。 我已将其更新为只选择 * 的所有内容,适用于您使用 javascript 或使用元素名称进行链接的情况。【参考方案9】:

一个很棒的简单解决方案(受 Sanyam Jain 的评论启发)是使用 block: 'center' 将选择垂直居中,如下所示:

scrollIntoView(block: 'center')

编辑 - 我很遗憾地在 MDN 页面上发现此功能是“实验性的 - 不应在生产中使用”。此外,IE 不支持它(如果需要)。

【讨论】:

【参考方案10】:

对我来说,将元素滚动到窗口中心就可以了。

scrollIntoViewIfNeeded( block: "center" ) 很好地做到了这一点,但不幸的是只有有限的浏览器兼容性(https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded)。它不适用于 IE 和 Firefox。

不过,您可以自己轻松构建此函数:

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

【讨论】:

【参考方案11】:

我是这样做的

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>

【讨论】:

以上是关于使用带有固定位置标题的 scrollIntoView的主要内容,如果未能解决你的问题,请参考以下文章

Chrome 渲染问题。体内带有 UL 的固定位置锚

使用带有位置的背景滑块时,菜单上的悬停消失:固定和 z-index

带有固定元素的 jQuery .animate() 和 css 位置

带有固定节点的 Javascript cytoscape.js 自动布局

带有固定标题的JQuery页面滚动问题

带有固定按钮的片段