没有使用jQuery的平滑滚动

74 html javascript

我正在编写一个页面,我只想在没有任何插件或框架干扰的情况下使用UI的原始JavaScript代码.

而现在我正在努力寻找一种在没有jQuery的情况下顺利滚动页面的方法.

Gab*_*iel 28

编辑:这个答案写于2013年.请查看CristianTraìna关于requestAnimationFrame 的评论

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

限制:锚点活动不会写入URL.

代码版本: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;
};
})();
Run Code Online (Sandbox Code Playgroud)

  • 我想你只需将锚点URL推送到历史堆栈,例如`var _updateURL = function(anchor,url){if((url === true || url ==='true')&& history.pushState ){history.pushState({pos:anchor.id},'',anchor); `([通过Chris Fernandi的Smooth Scroll js](https://github.com/cferdinandi/smooth-scroll))将锚写入URL.很高兴看到人们这样做而不仅仅是"使用JQuery"!为了记录,[`:target`选择器](http://stackoverflow.com/questions/17631417/css-pure-css-scroll-animation)也是一个简洁的解决方案. (2认同)

小智 25

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' 
});
Run Code Online (Sandbox Code Playgroud)

  • @Vishu Rana,行为支持只适用于[MDN](https://developer.mozilla.org/en/docs/Web/API/Element/scrollIntoView)中描述的firefox (2认同)
  • @EvandroCavalcateSantos:不再了!;-) https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView#Browser_compatibility (2认同)
  • 这个现代标准很好且可读,但需要一个“速度”选项。当前滚动速度太慢,不适合流动。 (2认同)

Kam*_*mal 24

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

  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++;
    }
}
Run Code Online (Sandbox Code Playgroud)

相关链接:

  • 好的大纲,但请不要创建一个字符串来执行超时,继续创建一个调用实际函数的匿名函数. (2认同)

has*_*sen 22

算法

滚动元素需要scrollTop随时间更改其值.对于给定的时间点,计算新scrollTop值.要平滑地制作动画,请使用平滑步算法进行插值.

计算scrollTop如下:

var point = smooth_step(start_time, end_time, now);
var scrollTop = Math.round(start_top + (distance * point));
Run Code Online (Sandbox Code Playgroud)

哪里:

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

一个强大的解决方案应该检测动画何时中断,等等.阅读我的帖子关于没有jQuery的Smooth Scrolling有关详细信息.

演示

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);
    });
}
Run Code Online (Sandbox Code Playgroud)


小智 7

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

  • 这应该是2018年接受的答案 (2认同)

小智 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;
        }
    }
 }
Run Code Online (Sandbox Code Playgroud)

  • 我不明白`window.setTimeout = window.setTimeout;` 的需要 (2认同)

Bri*_*ock 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');
Run Code Online (Sandbox Code Playgroud)

scroll()函数使用Revealing Module Pattern将目标元素的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()通过检查E其先前的位置(prev).

它下面的三元条件会使incrementE接近零.这会阻止页面以一种方式超调,然后反弹以超越另一方,然后反弹,再次超越另一方,乒乓球风格,无限和超越.

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

玩耍时duration,设置的除数increment和三元条件中的值scroller()应该允许您定制适合您页面的功能.

  • 的jsfiddle

  • NB在Lubuntu上使用最新版本的Firefox和Chrome,在Windows8上使用Firefox,Chrome和IE.


Sha*_*lam 5

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

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

html{
    scroll-behavior:smooth;
}
Run Code Online (Sandbox Code Playgroud)

这将导致原生平滑滚动功能。

在这里演示

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

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