JavaScript滚动到视图平滑滚动和偏移

145

我有一个用于我的网站的代码:

function clickMe() {
  var element = document.getElementById('about');
  element.scrollIntoView({
    block: 'start',
    behavior: 'smooth',
  });
}

这个方法很好用,但我有一个固定的页眉,所以当代码滚动到该元素时,页眉会挡住它。

有没有一种方法可以设置偏移量并使其平稳滚动?


我通过给滚动到视图中的组件添加一些“padding-top”来解决了这个问题,它充当偏移量。 - Kokodoko
3
使用scroll-margin:https://dev59.com/questions/nVUL5IYBdhLWcg3wv6Rx=_ - Diwakar Raja
14个回答

211

有没有一种方法可以设置偏移,并使其平滑滚动?

#有,但不能使用scrollIntoView()

Element.scrollIntoView()scrollIntoViewOptions不允许您使用偏移量。它仅在您想要滚动到元素的精确位置时有用。

但是,您可以使用Window.scrollTo()和选项来滚动到偏移位置,并且实现平滑过渡效果。

例如,如果您的标题高度为30px,则可以执行以下操作:

function scrollToTargetAdjusted(){
    var element = document.getElementById('targetElement');
    var headerOffset = 45;
    var elementPosition = element.getBoundingClientRect().top;
    var offsetPosition = elementPosition + window.pageYOffset - headerOffset;
  
    window.scrollTo({
         top: offsetPosition,
         behavior: "smooth"
    });
}

这将平滑地滚动到您的元素,以便它不会被页眉遮挡而看不到。

注意:您要减去偏移量,因为您希望在滚动标题超过您的元素之前停止。

#在下面的片段中查看效果

您可以在下面的片段中比较两个选项。

<script type="text/javascript">
  function scrollToTarget() {

    var element = document.getElementById('targetElement');
    element.scrollIntoView({
      block: "start",
      behavior: "smooth",
    });
  }

  function scrollToTargetAdjusted() {
        var element = document.getElementById('targetElement');
      var headerOffset = 45;
        var elementPosition = element.getBoundingClientRect().top;
      var offsetPosition = elementPosition + window.pageYOffset - headerOffset;
      
      window.scrollTo({
          top: offsetPosition,
          behavior: "smooth"
      });   
  }

  function backToTop() {
    window.scrollTo(0, 0);
  }
</script>

<div id="header" style="height:30px; width:100%; position:fixed; background-color:lightblue; text-align:center;"> <b>Fixed Header</b></div>

<div id="mainContent" style="padding:30px 0px;">

  <button type="button" onclick="scrollToTarget();">element.scrollIntoView() smooth, header blocks view</button>
  <button type="button" onclick="scrollToTargetAdjusted();">window.scrollTo() smooth, with offset</button>

  <div style="height:1000px;"></div>
  <div id="targetElement" style="background-color:red;">Target</div>
  <br/>
  <button type="button" onclick="backToTop();">Back to top</button>
  <div style="height:1000px;"></div>
</div>

编辑

window.pageYOffset已添加以解决与@coreyward评论相关的问题。


38
getBoundingClientRect().top 指的是元素与视口顶部之间的距离。当您想要使用 scrollBy 相对于当前位置滚动时这更有用,而在使用绝对值滚动的 scrollTo 中则不太有用。如果元素没有被相对定位的父元素包围,请考虑使用 offsetTop。否则,您需要将 window.pageYOffset 添加到 getBoundingClientRect().top 上以获取适用于 scrollTo 的正确值。 - coreyward
1
这对我非常有效,但为了简化事情,请用 document.getElementById('header').offsetHeight 替换 headerOffset 值(45)。这可以弥补任何响应式高度。 - Suyog
3
感谢您的选择,我会尽力进行翻译。以下是需要翻译的内容:@coreyward +1 for the window.pageYOffset hint. - Anurag Srivastava
4
您的解决方案会根据视图当前位置滚动到随机位置。 - Black
1
我的容器高度固定为100vh,因此我无法使用窗口滚动,而必须使用scrollIntoView。但是,有一个粘性标题,它会悬停在滚动到的元素上方。在这种情况下,我可以采取什么方法? - Negin Basiri
太棒了。这正是我在寻找的。谢谢。 - Titus Sutio Fanpula

101
Søren D. Ptæus的回答让我走上了正确的道路,但当不在window顶部时,我使用getBoundingClientRect()遇到了问题。
我的解决方案比他的多了一些内容,使getBoundingClientRect()更加具有一致性和多样性。 我使用了这里概述的方法来实现它,以使其按照预期工作。
const element = document.getElementById('targetElement');
const offset = 45;
const bodyRect = document.body.getBoundingClientRect().top;
const elementRect = element.getBoundingClientRect().top;
const elementPosition = elementRect - bodyRect;
const offsetPosition = elementPosition - offset;

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

Codepen示例

在实现时,请记得包含polyfill


我希望你的const定义在函数的命名空间中,而不是全局命名空间中,因为它将只计算相对于页面加载时视口的距离。 - PaulCo
对我有用,谢谢。我也遇到了 Søren D. Ptæus 的回答问题。你必须在顶部才能使其正常工作。 - Alexander Kim
谢谢你,我有同样的问题,因为我使用了一个固定导航栏,这解决了一切,再次感谢! - JeFawk
这是一个非常好的可配置解决方案。特别是为智能标题定制偏移量可以得到良好的结果。 - KeshavDulal

67

如果元素的高度很小(低于视窗),那么简单而优雅的解决方案如下:

element.scrollIntoView({ behavior: 'auto' /*or smooth*/, block: 'center' });

block: center会滚动元素,使其中心位于视口的垂直中心位置,以便顶部标题不会覆盖它。

编辑8.5.22:过去使用了behavior: instant,但已从浏览器中删除。


行为(可选)定义了过渡动画。可以是自动或平滑中的一个。默认为自动。那么“instant”从哪里来? - drooh
不确定为什么这个评论有这么多赞,因为之前的评论关于虚构行为选项是正确的。'instant'不存在。它只能是'auto'或者'smooth'。 - Andrew West
“instant”曾在过去被使用,但已被W3C或类似机构移除。我将更新答案。 - Moshe L
这不合理。我的元素非常长。我想滚动到它的开头。 - canbax

55

您可以像示例中那样使用scrollIntoView()

function clickMe() {
  var element = document.getElementById('about');
  element.scrollIntoView({
    block: 'start',
    behavior: 'smooth',
  });
}

如果您将标题的高度与scroll-margin属性添加到目标元素(about)中:

.about {
  scroll-margin: 100px;
}

没有其它必要的了。scroll-margin所有现代浏览器中均得到支持


7
这是比之前的解决方案更简单的方法,最好的部分是它有效! - Jonathan Taylor
2
这是最好的答案 - 最干净的解决方案。非常感谢! - Will Kim
非常巧妙!谢谢! - Иванцов Дмитрий
1
这样一个巧妙的方法,谢谢! - Xiao Hanyu
这应该是被接受的答案。谢谢。 - undefined
运作得非常顺利 - undefined

14

Søren D. Ptæus的回答几乎是正确的,但仅适用于用户在顶部时。这是因为getBoundingClientRect将始终获取相对高度,并且使用相对高度的window.scrollTo不起作用。

ekfuhrmann通过从body元素获取总高度并计算实际高度来改进了答案。但是,我认为它可以更容易,我们可以简单地使用相对位置并使用window.scrollBy。

注意:关键区别在于window.scrollBy。

const HEADER_HEIGHT = 45;

function scrollToTargetAdjusted(){
    const element = document.getElementById('targetElement');
    const elementPosition = element.getBoundingClientRect().top;
    const offsetPosition = elementPosition - HEADER_HEIGHT;

    window.scrollBy({
         top: offsetPosition,
         behavior: "smooth"
    });
}

12

我尝试了其他解决方案,但是我得到了一些奇怪的行为。不过,这个对我有用。

function scrollTo(id) {
    var element = document.getElementById(id);
    var headerOffset = 60;
    var elementPosition = element.offsetTop;
    var offsetPosition = elementPosition - headerOffset;
    document.documentElement.scrollTop = offsetPosition;
    document.body.scrollTop = offsetPosition; // For Safari
}

并且风格:

html {
    scroll-behavior: smooth;
}

9
我知道这是一种hack方法,而且肯定需要谨慎使用,但是您可以为元素添加padding和负的margin。由于我没有您的标记和代码,无法保证它适用于您,但是当我遇到类似问题时,我使用了这个解决方法。
假设您的标题是30px,并且您想要一个偏移量为15px,那么:
  #about {
     padding-top: 45px; // this will allow you to scroll 15px below your 30px header
     margin-top: -45px; // and this will make sure that you don't change your layout because of it
  }

聪明的黑客! - kirschkern
或者你可以使用scroll-margin-top: 45px; - Alireza

8

还有 scroll-margin 和 scroll-padding。

对于我来说,scroll-padding 在这种情况下是最有用的。

/* Keyword values */
scroll-padding-top: auto;

/* <length> values */
scroll-padding-top: 10px;
scroll-padding-top: 1em;
scroll-padding-top: 10%;

/* Global values */
scroll-padding-top: inherit;
scroll-padding-top: initial;
scroll-padding-top: unset;

此外,您可以通过将滚动行为设置为平滑来使用平滑滚动。

/* Keyword values */
scroll-behavior: auto;
scroll-behavior: smooth;

/* Global values */
scroll-behavior: inherit;
scroll-behavior: initial;
scroll-behavior: revert;
scroll-behavior: unset;

不过,很可能不兼容Internet Explorer。


经过很多搜索,这帮了我大忙!谢谢你! - Faizan Saiyed

7
为了防止任何元素与固定顶部相交,实际上有很多方法可以做到这一点。最近我在CSS文件中使用了scroll-padding-top。
* {
    scroll-behavior: smooth;
    scroll-padding-top: 100px; /* this pixel should match fixed header height */
  }

什么是平滑滚动?只需在CSS中添加scroll-behavior: smooth;即可。

如果你想要的是在打开一个新页面后平滑滚动,那么这就是另一种方法。你可以在这里查看我的回答:here

如果你想要检查元素是否在视口内,那就是另外一回事了。我不确定你想找哪个。如果是这个问题,请确认一下,我会花更多时间为你总结答案。我也曾遇到过这个问题,最终解决了。


1
elementRef.current!.scrollIntoView({ 
     behavior: 'smooth', 
     block: 'center' 
})

只有在不需要确切结果时才能使用。一个快速、简单但通常有效的解决方案。 - ALZlper

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接