如何在纯 JavaScript 中平滑滚动到元素

Posted

技术标签:

【中文标题】如何在纯 JavaScript 中平滑滚动到元素【英文标题】:How to smoothly scroll to an element in pure JavaScript 【发布时间】:2019-01-12 08:27:18 【问题描述】:

我想在不使用 jQuery 的情况下平滑滚动到一个元素——只是纯 javascript。我想要一个通用函数能够向下滚动和向上滚动到文档中的特定位置。

我知道我可以在 jQuery 中使用以下内容:

$('html, body').animate(
     scrollTop: $('#myelementid').offset().top
, 500);

我将如何只使用 javascript?

这就是我想要做的:

function scrollToHalf()
  //what do I do?

function scrollToSection()
 //What should I do here?
<input type="button" onClick="scrollToHalf()" value="Scroll To 50% of Page">
    <br>
    <input type="button" onClick="scrollToSection()" value="Scroll To Section1">
    <section style="margin-top: 1000px;" id="section1">
      This is a section
</section>

在 jquery 中我会这样做:

html, body
  height: 3000px;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="button" onClick="scrollToHalf()" value="Scroll To 50% of Page">
<br>
<input type="button" onClick="scrollToSection()" value="Scroll To Section1">
<section style="margin-top: 1000px;" id="section1">
  This is a section
</section>
<script>
function scrollToHalf()
  var height = $('body').height();
	$('html, body').animate(
         scrollTop: height/2
    , 500);

function scrollToSection()
	$('html, body').animate(
         scrollTop: $('#section1').offset().top
    , 500);

</script>

编辑:我还希望能够平滑滚动到页面上的某个位置

编辑:也欢迎 CSS 解决方案(虽然我更喜欢 javascript 解决方案)

【问题讨论】:

scrollIntoView 是您的案例的可行选择。查看我的answer,了解其使用的详细说明。 现代浏览器对您问题的第一部分也有一个纯 CSS 解决方案:scroll-behavior: smooth;。然后,您的 JavaScript 解决方案可以作为备用方案。 【参考方案1】:

要在准确的时间内滚动到某个位置,可以使用window.requestAnimationFrame,每次计算适当的当前位置。在不支持requestAnimationFrame 时,可以使用setTimeout 达到类似的效果。

/*
   @param pos: the y-position to scroll to (in pixels)
   @param time: the exact amount of time the scrolling will take (in milliseconds)
*/
function scrollToSmoothly(pos, time) 
    var currentPos = window.pageYOffset;
    var start = null;
    if(time == null) time = 500;
    pos = +pos, time = +time;
    window.requestAnimationFrame(function step(currentTime) 
        start = !start ? currentTime : start;
        var progress = currentTime - start;
        if (currentPos < pos) 
            window.scrollTo(0, ((pos - currentPos) * progress / time) + currentPos);
         else 
            window.scrollTo(0, currentPos - ((currentPos - pos) * progress / time));
        
        if (progress < time) 
            window.requestAnimationFrame(step);
         else 
            window.scrollTo(0, pos);
        
    );

演示:

/*
   @param time: the exact amount of time the scrolling will take (in milliseconds)
   @param pos: the y-position to scroll to (in pixels)
*/
function scrollToSmoothly(pos, time) 
    var currentPos = window.pageYOffset;
    var start = null;
    if(time == null) time = 500;
    pos = +pos, time = +time;
    window.requestAnimationFrame(function step(currentTime) 
        start = !start ? currentTime : start;
        var progress = currentTime - start;
        if (currentPos < pos) 
            window.scrollTo(0, ((pos - currentPos) * progress / time) + currentPos);
         else 
            window.scrollTo(0, currentPos - ((currentPos - pos) * progress / time));
        
        if (progress < time) 
            window.requestAnimationFrame(step);
         else 
            window.scrollTo(0, pos);
        
    );
<button onClick="scrollToSmoothly(document.querySelector('div').offsetTop, 300)">
Scroll To Div (300ms)
</button>
<button onClick="scrollToSmoothly(document.querySelector('div').offsetTop, 200)">
Scroll To Div (200ms)
</button>
<button onClick="scrollToSmoothly(document.querySelector('div').offsetTop, 100)">
Scroll To Div (100ms)
</button>
<button onClick="scrollToSmoothly(document.querySelector('div').offsetTop, 50)">
Scroll To Div (50ms)
</button>
<button onClick="scrollToSmoothly(document.querySelector('div').offsetTop, 1000)">
Scroll To Div (1000ms)
</button>
<div style="margin: 500px 0px;">
DIV<p/>
<button onClick="scrollToSmoothly(0, 500)">
Back To Top
</button>
<button onClick="scrollToSmoothly(document.body.scrollHeight)">
Scroll To Bottom
</button>
</div>
<div style="margin: 500px 0px;">
</div>
<button style="margin-top: 100px;" onClick="scrollToSmoothly(500, 3000)">
Scroll To y-position 500px (3000ms)
</button>

对于更复杂的情况,可以使用SmoothScroll.js library,它可以处理垂直和水平平滑滚动、在其他容器元素内滚动、不同的缓动行为、从当前位置相对滚动等等。

var easings = document.getElementById("easings");
for(var key in smoothScroll.easing)
    if(smoothScroll.easing.hasOwnProperty(key))
        var option = document.createElement('option');
        option.text = option.value = key;
        easings.add(option);
    

document.getElementById('to-bottom').addEventListener('click', function(e)
    smoothScroll(yPos: 'end', easing: easings.value, duration: 2000);
);
document.getElementById('to-top').addEventListener('click', function(e)
    smoothScroll(yPos: 'start', easing: easings.value, duration: 2000);
);
<script src="https://cdn.jsdelivr.net/gh/LieutenantPeacock/SmoothScroll@1.2.0/src/smoothscroll.min.js" integrity="sha384-UdJHYJK9eDBy7vML0TvJGlCpvrJhCuOPGTc7tHbA+jHEgCgjWpPbmMvmd/2bzdXU" crossorigin="anonymous"></script>
<!-- Taken from one of the library examples -->
Easing: <select id="easings"></select>
<button id="to-bottom">Scroll To Bottom</button>
<br>
<button id="to-top" style="margin-top: 5000px;">Scroll To Top</button>

或者,您可以将选项对象传递给 window.scroll 滚动到特定的 x 和 y 位置,window.scrollBy 从当前位置滚动一定量:

// Scroll to specific values
// scrollTo is the same
window.scroll(
  top: 2500, 
  left: 0, 
  behavior: 'smooth' 
);

// Scroll certain amounts from current position 
window.scrollBy( 
  top: 100, // could be negative value
  left: 0, 
  behavior: 'smooth' 
);

演示:

<button onClick="scrollToDiv()">Scroll To Element</button>
<div style="margin: 500px 0px;">Div</div>
<script>
function scrollToDiv()
var elem = document.querySelector("div");
window.scroll(
      top: elem.offsetTop, 
      left: 0, 
      behavior: 'smooth' 
);

</script>

如果您只需要滚动到一个元素,而不是文档中的特定位置,您可以使用Element.scrollIntoView 并将behavior 设置为smooth

document.getElementById("elemID").scrollIntoView( 
  behavior: 'smooth' 
);

演示:

<button onClick="scrollToDiv()">Scroll To Element</button>
<div id="myDiv" style="margin: 500px 0px;">Div</div>
<script>
function scrollToDiv()
    document.getElementById("myDiv").scrollIntoView( 
      behavior: 'smooth' 
   );

</script>

现代浏览器支持scroll-behavior CSS property,可用于使文档滚动顺畅(无需JavaScript)。锚标签可以通过给锚标签一个href# 加上要滚动到的元素的id 来使用)。您还可以为div 等特定容器设置scroll-behavior 属性,以使其内容平滑滚动。

演示:

html, body
  scroll-behavior: smooth;

a, a:visited
  color: initial;
<a href="#elem">Scroll To Element</a>
<div id="elem" style="margin: 500px 0px;">Div</div>

在使用window.scrollTo 时,CSS scroll-behavior 属性也适用于 JavaScript。

演示:

html, body
  scroll-behavior: smooth;
<button onClick="scrollToDiv()">Scroll To Element</button>
<div style="margin: 500px 0px;">Div</div>
<script>
function scrollToDiv()
  var elem = document.querySelector("div");
  window.scrollTo(0, elem.offsetTop);

</script>

要检查 scroll-behavior 属性是否受支持,您可以检查它是否作为 HTML 元素样式中的键存在。

var scrollBehaviorSupported = 'scroll-behavior' in document.documentElement.style;
console.log('scroll-behavior supported:', scrollBehaviorSupported);

【讨论】:

非常好的答案!特别喜欢我不知道的 scrollBy :) @Baldráni 谢谢!很高兴为您提供帮助!【参考方案2】:

考虑使用Element.scrollIntoView()

【讨论】:

这在带有对象参数的 Safari 浏览器中不起作用,还有其他选择吗? @SnowBases 查看我的回答。【参考方案3】:

正如我在评论中提到的,scrollIntoView 是一个不错的选择——当你尝试滚动到指定元素时,比如你显然想用 @987654324 做的事情,它会获得越来越多的浏览器支持@函数。

要滚动到页面中间,您可以将body 和/或html 元素的scrollTop 属性设置为主体scrollHeightinnerHeight 之差的一半窗户。将上面的计算与requestAnimationFrame 结合起来,你就设置好了。

您可以通过以下方式将上述建议合并到您的代码中:

function scrollToHalf(duration) 
  var
    heightDiff = document.body.scrollHeight - window.innerHeight,
    endValue = heightDiff / 2,
    start = null;
    
  /* Set a default for the duration, in case it's not given. */
  duration = duration || 300;
  
  /* Start the animation. */
  window.requestAnimationFrame(function step (now) 
    /* Normalise the start date and calculate the current progress. */
    start = !start ? now : start;
    var progress = now - start;
    
    /* Increment by a calculate step the value of the scroll top. */
    document.documentElement.scrollTop = endValue * progress / duration;
    document.body.scrollTop = endValue * progress / duration;
    
    /* Check whether the current progress is less than the given duration. */
    if (progress < duration) 
      /* Execute the function recursively. */
      window.requestAnimationFrame(step);
    
    else 
      /* Set the scroll top to the end value. */
      document.documentElement.scrollTop = endValue;
      document.body.scrollTop = endValue;
    
  );


function scrollToSection(element) 
  /* Scroll until the button's next sibling comes into view. */
  element.nextElementSibling.scrollIntoView(block: "start", behavior: "smooth");
#section1 
  margin: 1000px 0;
  border: 1px solid red
<input type="button" onClick="scrollToHalf()" value="Scroll To 50% of Page">
<br>
<input type="button" onClick="scrollToSection(this)" value="Scroll To Section1">
<section id="section1">
  This is a section
</section>

【讨论】:

【参考方案4】:

你可以滚动任何你想要的节点对象,使用一个简单的 polyfill,像这样:

Node.prototype.scroll = window.scroll

它会给你同样的访问滚动对象,但是对于任何 DOM 元素,你可以像这样使用它:

document.querySelector('.scrollable-div').scroll(
  top: 500, 
  left: 0, 
  behavior: 'smooth' 
);

【讨论】:

【参考方案5】:

这个问题已经有很多答案了,但我想我可能会分享我使用的东西。

以下允许您在指定的时间内平滑滚动到页面上的任何位置,向下或向上。我不确定它是否与所有浏览器兼容,但我很确定它是。 (如果我错了,有人纠正我。)

重要修改:确保您的 CSS 中没有 html scroll-behavior: smooth;。否则,这将不起作用。

function scrollToInTime(element, duration) 
  const endPoint = document.querySelector(element).offsetTop,
    distance = endPoint - window.pageYOffset,
    rate = (distance * 4) / duration, // px/4ms
    interval = setInterval(scrollIncrement, 4) //4ms is minimum interval for browser

  function scrollIncrement() 
    const yOffset = Math.ceil(window.pageYOffset)

    if (
      (yOffset >= endPoint && rate >= 0) ||
      (yOffset <= endPoint && rate <= 0)
    ) 
      clearInterval(interval)
     else 
      //keep in mind that scrollBy doesn't work with decimal pixels < 1 like 0.4px, so
      //if duration is too big, function won't work. rate must end up being >= 1px
      window.scrollBy(0, rate)
    
  

这里以一个 codepen 为例:https://codepen.io/isaac-svi/pen/xxZgPZp?editors=0110

【讨论】:

【参考方案6】:

使用此 CSS 属性可以将滚动行为切换为平滑。

html 
  scroll-behavior: smooth;

这也将通过哈希&lt;a href="#about"&gt;&lt;section id="about"&gt;平滑滚动默认html导航,这里不需要js。

如果您想为滚动添加自己的逻辑,请考虑此示例

https://jsfiddle.net/arsenlol/Ls3f2ujq/54/

这里我不是直接滚动到滚动目标元素,而是考虑到固定的标题高度高 90 像素。

TL;DR

document.querySelectorAll("nav a").forEach(function (a) 
        a.addEventListener("click", function (event) 
          event.preventDefault();
          const hash = event.target.getAttribute("href");
          const scrollTarget = document.querySelector(hash);
          
          // Some additional logic
          const headerHeight = 90;
          window.scrollTo(0, scrollTarget.offsetTop - headerHeight);
        );
      );

【讨论】:

以上是关于如何在纯 JavaScript 中平滑滚动到元素的主要内容,如果未能解决你的问题,请参考以下文章

H5页面在ios中平滑滚动

如何在 Java 中平滑 JFrame 的滚动

在 Android 中平滑滚动画布

是啥导致无法在纯 JavaScript 中检测到滚动到 HTML 元素的底部?

在 XCode 中平滑滚动视图和表格视图之间的滚动过渡

在 UICollectionViewCell 内的 UICollectionView 中平滑滚动