平滑滚动,无需使用 jQuery

Smooth scroll without the use of jQuery

本文关键字:jQuery 滚动 平滑      更新时间:2023-09-26

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

现在我正在努力寻找一种在没有 jQuery 的情况畅地滚动页面的方法。

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' 
});

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

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

相关链接:

  • https://www.sitepoint.com/smooth-scrolling-vanilla-javascript/
  • https://github.com/zengabor/zenscroll/blob/dist/zenscroll.js
  • https://github.com/cferdinandi/smooth-scroll/blob/master/src/js/smooth-scroll.js
  • https://github.com/alicelieutier/smoothScroll/blob/master/smoothscroll.js

算法

滚动元素需要随时间更改其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_top是开头的scrollTop值;和
  • 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);
    });
}

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

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

html{
    scroll-behavior:smooth;
}

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

在此处查看演示

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

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

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

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

我最近开始在 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() 函数使用揭示模块模式通过 scroll.To('id') 将目标元素的 id 传递给其scrolling()函数,该函数设置scroller()函数使用的值。

故障

scrolling()

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

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

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

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

E接近零时,它下面的三元条件会减小increment值。 这会阻止页面以一种方式超调,然后反弹回来以超过另一种方式,然后反弹以再次以乒乓球风格超越另一个,直到无限和更远。

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

duration,设置increment的除数,以及三元条件的值scroller()应该允许你定制函数以适应你的页面。

  • JSFiddle

  • 注意:在最新版本的 Firefox 和 Chrome on Lubuntu 以及 Firefox、Chrome 和 IE on Windows8 中进行测试。

您可以使用

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

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

我做了这样的东西。我不知道它是否在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 — get element 和 chceck 如果它有属性 "href" 如果是,摆脱"#"
  • elem —不带"#"的指针变量
  • elemOffset - "滚动到"元素与页面顶部的偏移量

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

html {
  scroll-behavior: smooth;
}

<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 = 滚动顶部
y = 用于滚动
的div 的 id
注意: 要使身体滚动,请给身体一个ID。

这是我

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

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>

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


要在确切的时间内滚动到某个位置,可以使用window.requestAnimationFrame,每次计算适当的当前位置。 当不支持requestAnimationFrame时,setTimeout可用于类似的效果。(要使用下面的函数滚动到特定元素,只需将位置设置为 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.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 属性,该属性可用于使文档中的滚动变得平滑(无需 JavaScript)。 为此,可以通过为锚标记提供# href加上要滚动到的元素的id来执行此操作。还可以为特定容器(如div)设置 scroll-behavior 属性,使其内容平滑滚动。

演示:

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

这是我

的变化:

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

这是对我有用的代码。

`$('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(); 
      };
    });
    }
   }
  });

'