88

我正在编写一个页面,我只想对 UI 使用原始 JavaScript 代码,而不受插件或框架的任何干扰。

现在,我正在努力寻找一种无需 jQuery 即可顺利滚动页面的方法。

4

15 回答 15

48

JavaScript 中的原生浏览器平滑滚动是这样的:

// scroll to specific values,
// same as window.scroll() method.
// for scrolling a particular distance, use window.scrollBy().
window.scroll({
  top: 2500, 
  left: 0, 
  behavior: 'smooth' 
});

// scroll certain amounts from current position 
window.scrollBy({ 
  top: 100, // negative value acceptable
  left: 0, 
  behavior: 'smooth' 
});

// scroll to a certain element
document.querySelector('.hello').scrollIntoView({ 
  behavior: 'smooth' 
});
于 2017-04-30T09:00:19.910 回答
28

试试这个平滑滚动演示,或类似的算法:

  1. 使用获取当前顶部位置self.pageYOffset
  2. 获取元素的位置,直到您要滚动到的位置:element.offsetTop
  3. 做一个 for 循环到达那里,这将非常快,或者使用计时器来平滑滚动到那个位置window.scrollTo

另请参阅此问题的其他流行答案


安德鲁约翰逊的原始代码:

function currentYPosition() {
    // Firefox, Chrome, Opera, Safari
    if (self.pageYOffset) return self.pageYOffset;
    // Internet Explorer 6 - standards mode
    if (document.documentElement && document.documentElement.scrollTop)
        return document.documentElement.scrollTop;
    // Internet Explorer 6, 7 and 8
    if (document.body.scrollTop) return document.body.scrollTop;
    return 0;
}


function elmYPosition(eID) {
    var elm = document.getElementById(eID);
    var y = elm.offsetTop;
    var node = elm;
    while (node.offsetParent && node.offsetParent != document.body) {
        node = node.offsetParent;
        y += node.offsetTop;
    } return y;
}


function smoothScroll(eID) {
    var startY = currentYPosition();
    var stopY = elmYPosition(eID);
    var distance = stopY > startY ? stopY - startY : startY - stopY;
    if (distance < 100) {
        scrollTo(0, stopY); return;
    }
    var speed = Math.round(distance / 100);
    if (speed >= 20) speed = 20;
    var step = Math.round(distance / 25);
    var leapY = stopY > startY ? startY + step : startY - step;
    var timer = 0;
    if (stopY > startY) {
        for ( var i=startY; i<stopY; i+=step ) {
            setTimeout("window.scrollTo(0, "+leapY+")", timer * speed);
            leapY += step; if (leapY > stopY) leapY = stopY; timer++;
        } return;
    }
    for ( var i=startY; i>stopY; i-=step ) {
        setTimeout("window.scrollTo(0, "+leapY+")", timer * speed);
        leapY -= step; if (leapY < stopY) leapY = stopY; timer++;
    }
}

相关链接:

于 2012-04-08T14:07:36.240 回答
24

算法

滚动一个元素需要scrollTop随着时间的推移改变它的值。对于给定的时间点,计算一个新scrollTop值。要平滑地制作动画,请使用平滑步长算法进行插值。

计算scrollTop如下:

var point = smooth_step(start_time, end_time, now);
var scrollTop = Math.round(start_top + (distance * point));

在哪里:

  • start_time是动画开始的时间;
  • end_time是动画结束的时间(start_time + duration)
  • start_topscrollTop开始时的值;和
  • distance是期望的结束值和起始值之间的差(target - start_top)

一个强大的解决方案应该检测动画何时被中断,等等。阅读我关于没有 jQuery的平滑滚动的文章了解详细信息。

演示

请参阅JSFiddle

执行

编码:

/**
    Smoothly scroll element to the given target (element.scrollTop)
    for the given duration

    Returns a promise that's fulfilled when done, or rejected if
    interrupted
 */
var smooth_scroll_to = function(element, target, duration) {
    target = Math.round(target);
    duration = Math.round(duration);
    if (duration < 0) {
        return Promise.reject("bad duration");
    }
    if (duration === 0) {
        element.scrollTop = target;
        return Promise.resolve();
    }

    var start_time = Date.now();
    var end_time = start_time + duration;

    var start_top = element.scrollTop;
    var distance = target - start_top;

    // based on http://en.wikipedia.org/wiki/Smoothstep
    var smooth_step = function(start, end, point) {
        if(point <= start) { return 0; }
        if(point >= end) { return 1; }
        var x = (point - start) / (end - start); // interpolation
        return x*x*(3 - 2*x);
    }

    return new Promise(function(resolve, reject) {
        // This is to keep track of where the element's scrollTop is
        // supposed to be, based on what we're doing
        var previous_top = element.scrollTop;

        // This is like a think function from a game loop
        var scroll_frame = function() {
            if(element.scrollTop != previous_top) {
                reject("interrupted");
                return;
            }

            // set the scrollTop for this frame
            var now = Date.now();
            var point = smooth_step(start_time, end_time, now);
            var frameTop = Math.round(start_top + (distance * point));
            element.scrollTop = frameTop;

            // check if we're done!
            if(now >= end_time) {
                resolve();
                return;
            }

            // If we were supposed to scroll but didn't, then we
            // probably hit the limit, so consider it done; not
            // interrupted.
            if(element.scrollTop === previous_top
                && element.scrollTop !== frameTop) {
                resolve();
                return;
            }
            previous_top = element.scrollTop;

            // schedule next frame for execution
            setTimeout(scroll_frame, 0);
        }

        // boostrap the animation process
        setTimeout(scroll_frame, 0);
    });
}
于 2014-08-29T18:38:38.743 回答
6

我在这里做了一个没有 jQuery 的例子:http: //codepen.io/sorinnn/pen/ovzdq

/**
    by Nemes Ioan Sorin - not an jQuery big fan 
    therefore this script is for those who love the old clean coding style  
    @id = the id of the element who need to bring  into view

    Note : this demo scrolls about 12.700 pixels from Link1 to Link3
*/
(function()
{
      window.setTimeout = window.setTimeout; //
})();

      var smoothScr = {
      iterr : 30, // set timeout miliseconds ..decreased with 1ms for each iteration
        tm : null, //timeout local variable
      stopShow: function()
      {
        clearTimeout(this.tm); // stopp the timeout
        this.iterr = 30; // reset milisec iterator to original value
      },
      getRealTop : function (el) // helper function instead of jQuery
      {
        var elm = el; 
        var realTop = 0;
        do
        {
          realTop += elm.offsetTop;
          elm = elm.offsetParent;
        }
        while(elm);
        return realTop;
      },
      getPageScroll : function()  // helper function instead of jQuery
      {
        var pgYoff = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
        return pgYoff;
      },
      anim : function (id) // the main func
      {
        this.stopShow(); // for click on another button or link
        var eOff, pOff, tOff, scrVal, pos, dir, step;

        eOff = document.getElementById(id).offsetTop; // element offsetTop

        tOff =  this.getRealTop(document.getElementById(id).parentNode); // terminus point 

        pOff = this.getPageScroll(); // page offsetTop

        if (pOff === null || isNaN(pOff) || pOff === 'undefined') pOff = 0;

        scrVal = eOff - pOff; // actual scroll value;

        if (scrVal > tOff) 
        {
          pos = (eOff - tOff - pOff); 
          dir = 1;
        }
        if (scrVal < tOff)
        {
          pos = (pOff + tOff) - eOff;
          dir = -1; 
        }
        if(scrVal !== tOff) 
        {
          step = ~~((pos / 4) +1) * dir;

          if(this.iterr > 1) this.iterr -= 1; 
          else this.itter = 0; // decrease the timeout timer value but not below 0
          window.scrollBy(0, step);
          this.tm = window.setTimeout(function()
          {
             smoothScr.anim(id);  
          }, this.iterr); 
        }  
        if(scrVal === tOff) 
        { 
          this.stopShow(); // reset function values
          return;
        }
    }
 }
于 2014-05-20T21:19:36.567 回答
6

现代浏览器支持 CSS“滚动行为:平滑”属性。因此,我们甚至根本不需要任何 Javascript。只需将其添加到 body 元素,并使用通常的锚点和链接。 滚动行为 MDN 文档

于 2018-05-09T13:48:55.290 回答
6

您可以使用新的 滚动行为CSS 属性。

例如,将以下行添加到您的 CSS。

html{
    scroll-behavior:smooth;
}

这将产生原生的平滑滚动功能。

在此处查看演示

所有现代浏览器都支持滚动行为属性。

阅读有关滚动行为的更多信息

于 2019-08-16T11:20:12.307 回答
5

我最近开始在无法选择 jQuery 的情况下解决这个问题,所以我在这里记录我的解决方案只是为了后代。

var scroll = (function() {

    var elementPosition = function(a) {
        return function() {
            return a.getBoundingClientRect().top;
        };
    };

    var scrolling = function( elementID ) {

        var el = document.getElementById( elementID ),
            elPos = elementPosition( el ),
            duration = 400,
            increment = Math.round( Math.abs( elPos() )/40 ),
            time = Math.round( duration/increment ),
            prev = 0,
            E;

        function scroller() {
            E = elPos();

            if (E === prev) {
                return;
            } else {
                prev = E;
            }

            increment = (E > -20 && E < 20) ? ((E > - 5 && E < 5) ? 1 : 5) : increment;

            if (E > 1 || E < -1) {

                if (E < 0) {
                    window.scrollBy( 0,-increment );
                } else {
                    window.scrollBy( 0,increment );
                }

                setTimeout(scroller, time);

            } else {

                el.scrollTo( 0,0 );

            }
        }

        scroller();
    };

    return {
        To: scrolling
    }

})();

/* usage */
scroll.To('elementID');

scroll()函数使用显示模块模式将目标元素的 id 传递给其scrolling()函数 via scroll.To('id'),该函数设置函数使用的值scroller()

分解

scrolling()

  • el: 目标 DOM 对象
  • elPos:返回一个函数,通过elememtPosition()该函数每次调用目标元素相对于页面顶部的位置。
  • duration: 转换时间,以毫秒为单位。
  • increment:将目标元素的起始位置分为40步。
  • time:设置每一步的时间。
  • prev: 目标元素在scroller().
  • E: 保存目标元素在 中的位置scroller()

实际工作由scroller()继续调用自身(通过setTimeout())的函数完成,直到目标元素位于页面顶部或页面无法再滚动。

每次scroller()调用它都会检查目标元素的当前位置(保存在变量中E),如果它是> 1OR< -1并且页面是否仍然可滚动,则将窗口移动increment像素 - 向上或向下取决于E是正值还是负值。什么时候E既不是> 1OR < -1,也不是E===prev函数停止。我DOMElement.scrollTo()在完成时添加了该方法,只是为了确保目标元素位于窗口顶部(并不是说您会注意到它超出了一小部分像素!)。

if2 行的语句通过检查其先前位置 ( )scroller()来检查页面是否正在滚动(在目标可能朝向页面底部并且页面无法进一步滚动的情况下)。 Eprev

它下面的三元条件在接近零时减小该increment值。E这会阻止页面以一种方式超调,然后弹回以超调另一种方式,然后弹回以再次超调另一种方式,乒乓风格,无穷大甚至更远。

如果您的页面超过 c.4000px 高,您可能希望增加三元表达式的第一个条件(此处为 +/-20)和/或设置该increment值的除数(此处为 40)中的值。

玩弄duration,设置的除数increment,以及三元条件下的值scroller()应该允许您定制功能以适合您的页面。

  • JSFiddle

  • NBTested 在 Lubuntu 上的最新版本的 Firefox 和 Chrome 以及 Windows8 上的 Firefox、Chrome 和 IE 中进行测试。

于 2015-10-21T18:08:24.767 回答
2

我做了这样的东西。我不知道它是否在 IE8 中工作。在 IE9、Mozilla、Chrome、Edge 中测试。

function scroll(toElement, speed) {
  var windowObject = window;
  var windowPos = windowObject.pageYOffset;
  var pointer = toElement.getAttribute('href').slice(1);
  var elem = document.getElementById(pointer);
  var elemOffset = elem.offsetTop;

  var counter = setInterval(function() {
    windowPos;

    if (windowPos > elemOffset) { // from bottom to top
      windowObject.scrollTo(0, windowPos);
      windowPos -= speed;

      if (windowPos <= elemOffset) { // scrolling until elemOffset is higher than scrollbar position, cancel interval and set scrollbar to element position
        clearInterval(counter);
        windowObject.scrollTo(0, elemOffset);
      }
    } else { // from top to bottom
      windowObject.scrollTo(0, windowPos);
      windowPos += speed;

      if (windowPos >= elemOffset) { // scroll until scrollbar is lower than element, cancel interval and set scrollbar to element position
        clearInterval(counter);
        windowObject.scrollTo(0, elemOffset);
      }
    }

  }, 1);
}

//call example

var navPointer = document.getElementsByClassName('nav__anchor');

for (i = 0; i < navPointer.length; i++) {
  navPointer[i].addEventListener('click', function(e) {
    scroll(this, 18);
    e.preventDefault();
  });
}

描述

  • pointer— 获取元素并检查它是否具有属性“href”,如果是,则去掉“#”
  • elem——不带“#”的指针变量
  • elemOffset—“滚动到”元素从页面顶部的偏移量
于 2016-10-16T22:10:23.543 回答
2

您可以使用

document.querySelector('your-element').scrollIntoView({behavior: 'smooth'});

如果要滚动到页面顶部,只需在顶部放置一个空元素,然后平滑滚动到该元素即可。

于 2018-01-31T19:32:53.920 回答
0
<script>
var set = 0;

function animatescroll(x, y) {
    if (set == 0) {
        var val72 = 0;
        var val73 = 0;
        var setin = 0;
        set = 1;

        var interval = setInterval(function() {
            if (setin == 0) {
                val72++;
                val73 += x / 1000;
                if (val72 == 1000) {
                    val73 = 0;
                    interval = clearInterval(interval);
                }
                document.getElementById(y).scrollTop = val73;
            }
        }, 1);
    }
}
</script>

x = scrollTop
y = 用于滚动的 div 的 id

注意: 为了让 body 滚动,给 body 一个 ID。

于 2017-09-01T07:54:26.650 回答
0

JavaScript 中有许多不同的平滑滚动方法。下面列出了最常见的。


要在准确的时间内滚动到某个位置,window.requestAnimationFrame可以使用,每次计算出合适的当前位置。不支持setTimeout时可以使用类似的效果。requestAnimationFrame(要使用以下功能滚动到特定元素,只需将位置设置为element.offsetTop。)

/*
   @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 库,它可以处理垂直和水平平滑滚动、在其他容器元素内滚动、不同的缓动行为、从当前位置相对滚动等等。

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.scrollIntoViewwith behaviorset to 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-behaviorCSS 属性,该属性可用于使文档中的滚动平滑(无需 JavaScript)。锚标签可以通过给锚标签 ahref加上要滚动#id的元素来使用)。您还可以为scroll-behavior特定容器设置属性,如 adiv以使其内容平滑滚动。

演示:

html, body{
  scroll-behavior: smooth;
}
<a href="#elem">Scroll To Element</a>
<div id="elem" style="margin: 500px 0px;">Div</div>

于 2018-08-04T20:37:05.683 回答
0

这是我的解决方案。适用于大多数浏览器

document.getElementById("scrollHere").scrollIntoView({behavior: "smooth"});

文档

document.getElementById("end").scrollIntoView({behavior: "smooth"});
body {margin: 0px; display: block; height: 100%; background-image: linear-gradient(red, yellow);}
.start {display: block; margin: 100px 10px 1000px 0px;}
.end {display: block; margin: 0px 0px 100px 0px;}
<div class="start">Start</div>
<div class="end" id="end">End</div>

于 2019-06-20T14:11:57.677 回答
0

使用以下平滑滚动可以正常工作:

html {
  scroll-behavior: smooth;
}

于 2019-11-02T07:26:28.817 回答
0

这是我的变化:

let MenuItem = function ( _menuItem ) {

    // I had a sticky header, so its height had to be taken into account when scrolling
    let _header = document.querySelector('.site-header');

    let _scrollToBlock = function( e, menuItem ) {

            let id = menuItem.getAttribute('href'), // the href attribute stores the id of the block to which the scroll will be
                headerHeight = _header.offsetHeight; // determine the height of the header
            id = id.replace(/#/, ''); // remove the # sign from the id block
            let elem = document.getElementById( id ), // define the element to which we will scroll
                top = elem.getBoundingClientRect().top + window.scrollY - headerHeight; // determine the height of the scroll
            window.scroll({
                top: top,
                left: 0,
                behavior: 'smooth'
            });

        },
        _addEvents = function() {

            _menuItem.addEventListener('click', function (e){
                e.preventDefault(); // Disable redirect on click
                _scrollToBlock(e, _menuItem);
            });

        },
        _init = function() {
            _addEvents();
        };

    _init();

};

// Initialize the class MenuItem to all links with class .menu__item
document.querySelectorAll('.menu__item').forEach( function(item) {
    new MenuItem(item);
} );
于 2021-10-18T14:48:43.760 回答
-1

这是对我有用的代码。

`$('a[href*="#"]')

    .not('[href="#"]')
    .not('[href="#0"]')
    .click(function(event) {
     if (
       location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '')
       &&
       location.hostname == this.hostname
        ) {

  var target = $(this.hash);
  target = target.length ? target : $('[name=' + this.hash.slice(1) + ']');
  if (target.length) {

    event.preventDefault();
    $('html, body').animate({
      scrollTop: target.offset().top
    }, 1000, function() {

      var $target = $(target);
      $target.focus();
      if ($target.is(":focus")) { 
        return false;
      } else {
        $target.attr('tabindex','-1'); 
        $target.focus(); 
      };
    });
    }
   }
  });

`

于 2020-06-04T13:52:09.263 回答