Javascript 不使用 jQuery 的平滑滚动

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10063380/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 23:42:02  来源:igfitidea点击:

Smooth scroll without the use of jQuery

javascripthtml

提问by

I'm coding up a page where I only want to use raw JavaScript code for UI without any interference of plugins or frameworks.

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

And now I'm struggling with finding a way to scroll over the page smoothly without jQuery.

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

采纳答案by Kamal

Try this smooth scrolling demo, or an algorithm like:

试试这个平滑滚动演示,或者像这样的算法:

  1. Get the current top location using self.pageYOffset
  2. Get the position of element till where you want to scroll to: element.offsetTop
  3. Do a for loop to reach there, which will be quite fast or use a timer to do smooth scroll till that position using window.scrollTo
  1. 使用获取当前的最高位置 self.pageYOffset
  2. 获取元素的位置,直到您要滚动到的位置: element.offsetTop
  3. 做一个 for 循环到达那里,这会很快,或者使用计时器平滑滚动到那个位置 window.scrollTo


See also the other popular answerto this question.

另请参阅此问题的另一个流行答案



Andrew Johnson's original code:

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

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++;
    }
}


Related links:

相关链接:

回答by

Native browser smooth scrolling in JavaScript is like this:

原生浏览器在 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' 
});

回答by Gabriel

edit: this answer has been written in 2013. Please check Cristian Traìna's commentbelow about requestAnimationFrame

编辑:这个答案是在 2013 年写的。请查看下面关于 requestAnimationFrame 的Cristian Traìna 评论

I made it. The code below doesn't depend on any framework.

我做到了。下面的代码不依赖于任何框架。

Limitation : The anchor active is not written in the url.

限制:锚点 active 没有写在 url 中。

Version of the code : 1.0 | Github : https://github.com/Yappli/smooth-scroll

代码版本:1.0 | Github:https: //github.com/Yappli/smooth-scroll

(function() // Code in a function to create an isolate scope
{
var speed = 500;
var moving_frequency = 15; // Affects performance !
var links = document.getElementsByTagName('a');
var href;
for(var i=0; i<links.length; i++)
{   
    href = (links[i].attributes.href === undefined) ? null : links[i].attributes.href.nodeValue.toString();
    if(href !== null && href.length > 1 && href.substr(0, 1) == '#')
    {
        links[i].onclick = function()
        {
            var element;
            var href = this.attributes.href.nodeValue.toString();
            if(element = document.getElementById(href.substr(1)))
            {
                var hop_count = speed/moving_frequency
                var getScrollTopDocumentAtBegin = getScrollTopDocument();
                var gap = (getScrollTopElement(element) - getScrollTopDocumentAtBegin) / hop_count;

                for(var i = 1; i <= hop_count; i++)
                {
                    (function()
                    {
                        var hop_top_position = gap*i;
                        setTimeout(function(){  window.scrollTo(0, hop_top_position + getScrollTopDocumentAtBegin); }, moving_frequency*i);
                    })();
                }
            }

            return false;
        };
    }
}

var getScrollTopElement =  function (e)
{
    var top = 0;

    while (e.offsetParent != undefined && e.offsetParent != null)
    {
        top += e.offsetTop + (e.clientTop != null ? e.clientTop : 0);
        e = e.offsetParent;
    }

    return top;
};

var getScrollTopDocument = function()
{
    return document.documentElement.scrollTop + document.body.scrollTop;
};
})();

回答by hasen

Algorithm

算法

Scrolling an element requires changing its scrollTopvalue over time. For a given point in time, calculate a new scrollTopvalue. To animate smoothly, interpolate using a smooth-step algorithm.

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

Calculate scrollTopas follows:

计算scrollTop如下:

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

Where:

在哪里:

  • start_timeis the time the animation started;
  • end_timeis when the animation will end (start_time + duration);
  • start_topis the scrollTopvalue at the beginning; and
  • distanceis the difference between the desired end value and the start value (target - start_top).
  • start_time是动画开始的时间;
  • end_time是动画结束的时间(start_time + duration)
  • start_topscrollTop开始时的值;和
  • distance是期望的结束值和开始值之间的差值(target - start_top)

A robust solution should detect when animating is interrupted, and more. Read my post about Smooth Scrolling without jQueryfor details.

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

Demo

演示

See the JSFiddle.

请参阅JSFiddle

Implementation

执行

The code:

编码:

/**
    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);
    });
}

回答by SorinN

I've made an example without jQuery here : http://codepen.io/sorinnn/pen/ovzdq

我在这里做了一个没有 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;
        }
    }
 }

回答by KostaShah

Modern browsers has support for CSS "scroll-behavior: smooth" property. So, we even don't need any Javascript at all for this. Just add this to the body element, and use usual anchors and links. scroll-behavior MDN docs

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

回答by Brian Peacock

I recently set out to solve this problem in a situation where jQuery wasn't an option, so I'm logging my solution here just for posterity.

我最近开始在 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');

The scroll()function uses the Revealing Module Patternto pass the target element's id to its scrolling()function, via scroll.To('id'), which sets the values used by the scroller()function.

scroll()函数使用Revealing Module Pattern将目标元素的 id 传递给它的scrolling()函数, via scroll.To('id'),它设置函数使用的值scroller()

Breakdown

分解

In scrolling():

scrolling()

  • el: the target DOM object
  • elPos: returns a function via elememtPosition()which gives the position of the target element relative to the top of the page each time it's called.
  • duration: transition time in milliseconds.
  • increment: divides the starting position of the target element into 40 steps.
  • time: sets the timing of each step.
  • prev: the target element's previous position in scroller().
  • E: holds the target element's position in scroller().
  • el: 目标 DOM 对象
  • elPos: 返回一个函数,通过elememtPosition()它在每次调用时给出目标元素相对于页面顶部的位置。
  • duration: 以毫秒为单位的转换时间。
  • increment:将目标元素的起始位置分成40步。
  • time:设置每一步的时间。
  • prev:目标元素在scroller().
  • E: 保存目标元素在 中的位置scroller()

The actual work is done by the scroller()function which continues to call itself (via setTimeout()) until the target element is at the top of the page or the page can scroll no more.

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

Each time scroller()is called it checks the current position of the target element (held in variable E) and if that is > 1OR < -1and if the page is still scrollable shifts the window by incrementpixels - up or down depending if Eis a positive or negative value. When Eis neither > 1OR < -1, or E=== prevthe function stops. I added the DOMElement.scrollTo()method on completion just to make sure the target element was bang on the top of the window (not that you'd notice it being out by a fraction of a pixel!).

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

The ifstatement on line 2 of scroller()checks to see if the page is scrolling (in cases where the target might be towards the bottom of the page and the page can scroll no further) by checking Eagainst its previous position (prev).

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

The ternary condition below it reduce the incrementvalue as Eapproaches zero. This stops the page overshooting one way and then bouncing back to overshoot the other, and then bouncing back to overshoot the other again, ping-pong style, to infinity and beyond.

它下面的三元条件increment随着E接近零而减小值。这会阻止页面以一种方式超调,然后弹回以超调另一种,然后又弹回以超调另一种,乒乓风格,直至无穷大。

If your page is more that c.4000px high you might want to increase the values in the ternary expression's first condition (here at +/-20) and/or the divisor which sets the incrementvalue (here at 40).

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

Playing about with duration, the divisor which sets increment, and the values in the ternary condition of scroller()should allow you to tailor the function to suit your page.

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

  • JSFiddle

  • N.B.Tested in up-to-date versions of Firefox and Chrome on Lubuntu, and Firefox, Chrome and IE on Windows8.

  • JSFiddle

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

回答by Shakil Alam

You can also use Scroll BehaviourProperty. for example add below line to your css

您还可以使用滚动行为属性。例如将以下行添加到您的 css

html{
scroll-behavior:smooth;
}

and this will result a native smooth scrolling feature . Read More about Scroll behavior

这将导致原生平滑滚动功能。 阅读有关滚动行为的更多信息

回答by Antonio Brandao

You can use

您可以使用

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

If you want to scroll top the top of the page, you can just place an empty element in the top, and smooth scroll to that one.

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

回答by Skjal

I've made something like this. I have no idea if its working in IE8. Tested in IE9, Mozilla, Chrome, Edge.

我做了这样的事情。我不知道它是否在 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();
  });
}

Description

描述

  • pointer—get element and chceck if it has attribute "href" if yes, get rid of "#"
  • elem—pointer variable without "#"
  • elemOffset—offset of "scroll to" element from the top of the page
  • pointer— 获取元素并检查它是否具有属性“href” 如果是,则去掉“#”
  • elem——没有“#”的指针变量
  • elemOffset—“滚动到”元素从页面顶部的偏移量