88

プラグインやフレームワークの干渉を受けずに、生の JavaScript コードを UI に使用するだけのページをコーディングしています。

そして今、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

この質問に対する他の一般的な回答も参照してください。


Andrew Johnson の元のコード:

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 に答える
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()関数は、Revealing Module パターンを使用して、関数で使用される値を設定するscrolling()を介して、ターゲット要素の ID をその関数に渡します。scroll.To('id')scroller()

壊す

scrolling()

  • el: 対象の DOM オブジェクト
  • elPoselememtPosition():呼び出されるたびに、ページの上部に対するターゲット要素の相対位置を与える関数を返します。
  • duration: 遷移時間 (ミリ秒)。
  • increment: 対象要素の開始位置を 40 段階に分割します。
  • time: 各ステップのタイミングを設定します。
  • prev: でのターゲット要素の以前の位置scroller()
  • E: 内のターゲット要素の位置を保持しscroller()ます。

実際の作業は、対象の要素がページの一番上に来るか、ページがスクロールできなくなるまでscroller()( を介して) 自分自身を呼び出し続ける関数によって行われます。setTimeout()

が呼び出されるたびscroller()に、ターゲット要素 ( variable に保持されている) の現在の位置をチェックし、Eそれが> 1OR< -1であり、ページがまだスクロール可能である場合は、 が正の値か負の値かincrementに応じて、ウィンドウをピクセル単位で上下にシフトします。ORも===でもないE場合、関数は停止します。ターゲット要素がウィンドウの上部にあることを確認するためだけに、完了時にメソッドを追加しました(ピクセルの何分の 1 かで外に出ていることに気付かないでください!)。E> 1< -1EprevDOMElement.scrollTo()

if2 行目のステートメントはscroller()、ページがスクロールしているかどうか (ターゲットがページの下部にあり、ページがそれ以上スクロールできない場合) をE、前の位置 ( prev) と照合して確認します。

その下の三項条件は、ゼロに近づくにつれてincrement値を減らします。Eこれにより、ページが一方向にオーバーシュートし、次に跳ね返ってもう一方をオーバーシュートし、次に跳ね返ってもう一方をオーバーシュートすることがなくなります。これは、ピンポン スタイルで、無限大とそれ以上になります。

ページの高さが c.4000px を超える場合、三項式の最初の条件の値 (ここでは +/-20) および/またはincrement値を設定する除数 (ここでは 40) を増やしたい場合があります。

duration、 を設定する除数、incrementおよび の三項条件の値scroller()をいじってみると、ページに合わせて関数を調整できるはずです。

  • JSFiddle

  • NBLubuntu の 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

ここに私のバリエーションがあります:

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 に答える