Cuộn mượt mà không cần sử dụng jQuery


81

Tôi đang viết mã một trang mà tôi chỉ muốn sử dụng mã JavaScript thô cho giao diện người dùng mà không có bất kỳ sự can thiệp nào của các plugin hoặc khuôn khổ.

Và bây giờ tôi đang vật lộn với việc tìm cách cuộn trang mượt mà mà không cần jQuery.


3
Tương tự như cách jQuery sẽ làm. Chia hoạt ảnh thành một chuỗi các bước rất nhỏ, sử dụng bộ đếm thời gian ngắt quãng ở một khoảng thời gian nhỏ để thực hiện lần lượt từng bước này cho đến khi hoàn thành.
tvanfosson

Tôi đã nghĩ đến giải pháp này ngay từ đầu và điểm duy nhất tôi bỏ qua thực sự nằm trong liên kết của Kamal bên dưới, đó là cách tính toán vị trí Y của các đối tượng. Cảm ơn tvanfosson :)

Vui lòng xem câu trả lời của tôi về cách cuộn mượt mà với js đơn giản
lướt web

Điều này tương tự như một trong những câu hỏi của tôi. Kiểm tra stackoverflow.com/questions/40598955/… .
Cannicide

Câu trả lời:


27

Hãy thử bản trình diễn cuộn mượt mà này hoặc một thuật toán như:

  1. Nhận vị trí hàng đầu hiện tại bằng cách sử dụng self.pageYOffset
  2. Nhận vị trí của phần tử cho đến nơi bạn muốn cuộn đến: element.offsetTop
  3. Thực hiện một vòng lặp for để đến vị trí đó, điều này sẽ khá nhanh hoặc sử dụng bộ đếm thời gian để cuộn trơn tru cho đến vị trí đó bằng cách sử dụng window.scrollTo

Xem thêm câu trả lời phổ biến khác cho câu hỏi này.


Mã ban đầu của 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++;
    }
}

Liên kết liên quan:


2
Phác thảo tốt nhưng vui lòng không tạo một chuỗi để thực thi trong thời gian chờ, hãy tiếp tục và tạo một hàm ẩn danh gọi hàm thực.
tvanfosson

@nness thật đáng tiếc. Tôi thực sự muốn xem mã.
dzny

37

Thao tác cuộn mượt mà của trình duyệt gốc trong JavaScript giống như sau:

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

bạn có thể đưa vào liên kết demo không
DV Yogesh

2
@Vishu Rana, hỗ trợ hành vi chỉ dành cho firefox như được mô tả trên MDN
Evandro Cavalcate Santos.

2
@EvandroCavalcateSantos: không còn nữa! ;-) developer.mozilla.org/en-US/docs/Web/API/Element/…
Pedro Ferreira

1
Tiêu chuẩn hiện đại này rất hay và dễ đọc, nhưng cần có một speedtùy chọn. Tốc độ cuộn hiện tại quá chậm so với luồng.
Nils Lindemann

31

chỉnh sửa: câu trả lời này đã được viết vào năm 2013. Vui lòng kiểm tra bình luận của Cristian Traìna bên dưới về requestAnimationFrame

Tôi đã làm việc đó. Đoạn mã dưới đây không phụ thuộc vào bất kỳ khuôn khổ nào.

Giới hạn: Mỏ neo hoạt động không được viết trong url.

Phiên bản của mã: 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;
};
})();

2
Tôi nghĩ rằng bạn chỉ cần đẩy URL liên kết vào ngăn xếp lịch sử bằng một cái gì đó như var _updateURL = function ( anchor, url ) { if ( (url === true || url === 'true') && history.pushState ) { history.pushState( {pos:anchor.id}, '', anchor ); } };( thông qua Smooth Scroll js của Chris Fernandi ) để ghi liên kết vào URL. Rất vui khi thấy mọi người làm điều này thay vì chỉ “sử dụng JQuery”! Đối với bản ghi, :targetbộ chọn cũng là một giải pháp gọn gàng.
Louis Maddox

1
Câu trả lời này là từ năm 2013, khi nó đã được viết sẵn requestAnimationFrame, đây là một sự thúc đẩy cho hiệu suất. Tôi không biết tại sao ở đây không được sử dụng.
Cristian Traìna

23

Thuật toán

Việc cuộn một phần tử yêu cầu thay đổi scrollTopgiá trị của nó theo thời gian. Đối với một thời điểm nhất định, hãy tính một scrollTopgiá trị mới . Để tạo hoạt ảnh mượt mà, hãy nội suy bằng thuật toán từng bước mượt mà .

Tính toán scrollTopnhư sau:

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

Ở đâu:

  • start_time là thời gian hoạt ảnh bắt đầu;
  • end_timelà khi hoạt ảnh sẽ kết thúc (start_time + duration);
  • start_topscrollTopgiá trị ở đầu; và
  • distancelà sự khác biệt giữa giá trị cuối mong muốn và giá trị bắt đầu (target - start_top).

Một giải pháp mạnh mẽ sẽ phát hiện khi hoạt ảnh bị gián đoạn và hơn thế nữa. Đọc bài đăng của tôi về Smooth Scrolling mà không cần jQuery để biết chi tiết.

Bản giới thiệu

Xem JSFiddle .

Thực hiện

Mật mã:

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

Đây có vẻ như là một giải pháp thực sự tốt. Nhưng làm thế nào bạn có thể triển khai nó trên đối tượng window thay vì một phần tử như bạn có trong Fiddle của mình?
Mudlabs

Không sao đâu, tôi đã đoán ra. Phải sử dụng pageYOffsetscrollTo()trên elementthay vì scrollTop.
Mudlabs

6

Tôi đã tạo một ví dụ mà không có jQuery tại đây: 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;
        }
    }
 }

1
Vui lòng đăng các đoạn mã liên quan hoặc mô tả về cách nó hoạt động tại đây. Câu trả lời của bạn sẽ có thể có ý nghĩa mà tôi không cần phải nhấp vào bất cứ điều gì. Cảm ơn bạn!
Edward

cảm ơn - nhưng không - codeopen được tạo ra để chia sẻ các ví dụ - đây không phải là nơi thích hợp để đặt tất cả mã css / js / html - tận dụng stackoverflow sẽ thêm các bit cần thiết - hầu hết mọi người sẽ gửi bạn đến codepen / jsfiddle / hoặc nơi khác ..
SorinN

1
Dưới đây là một số cuộc trò chuyện về lý do tại sao chúng tôi yêu cầu điều đó: meta.stackexchange.com/q/149890
Edward

2
Tôi không hiểu sự cần thiết củawindow.setTimeout = window.setTimeout;
pmrotule

6

Các trình duyệt hiện đại có hỗ trợ thuộc tính CSS "scroll-behavior: Smooth". Vì vậy, chúng tôi thậm chí không cần bất kỳ Javascript nào cho việc này. Chỉ cần thêm cái này vào phần tử body và sử dụng các liên kết và neo thông thường. tài liệu MDN hành vi cuộn


Cả Edge và Safari đều chưa hỗ trợ thuộc tính này. Tôi hy vọng họ sẽ sớm.
6754534367 13/02/19

Nó không hoạt động tốt .. ngay cả trong Firefox. Vì vậy, nó không phải là một giải pháp.
huseyin39 16/12/19

5

Gần đây tôi đã đặt ra để giải quyết vấn đề này trong một tình huống mà jQuery không phải là một lựa chọn, vì vậy tôi ghi lại giải pháp của mình ở đây chỉ để lưu lại hậu thế.

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

Các scroll()chức năng sử dụng các mẫu mô-đun Tiết lộ để vượt qua id yếu tố mục tiêu để nó scrolling()chức năng, thông qua scroll.To('id'), thiết lập giá trị sử dụng bởi các scroller()chức năng.

Phá vỡ

Trong scrolling():

  • el : đối tượng DOM đích
  • elPos : trả về một hàm qua elememtPosition() đó cung cấp vị trí của phần tử mục tiêu so với đầu trang mỗi khi nó được gọi.
  • duration : thời gian chuyển đổi tính bằng mili giây.
  • increment : chia vị trí bắt đầu của phần tử đích thành 40 bước.
  • time : đặt thời gian của từng bước.
  • prev: vị trí trước đó của phần tử mục tiêu trong scroller().
  • E: giữ vị trí của phần tử mục tiêu trong scroller().

Công việc thực tế được thực hiện bởi scroller()hàm này tiếp tục gọi chính nó (qua setTimeout()) cho đến khi phần tử đích nằm ở đầu trang hoặc trang không thể cuộn nữa.

Mỗi lần scroller()được gọi, nó sẽ kiểm tra vị trí hiện tại của phần tử đích (được giữ trong biến E) và nếu đó là > 1HOẶC < -1và nếu trang vẫn có thể cuộn thì sẽ dịch chuyển cửa sổ theo incrementpixel - lên hoặc xuống tùy thuộc vào Egiá trị dương hay âm. Khi nào Ekhông > 1HOẶC < -1hoặc E=== prevthì hàm dừng. Tôi đã thêm DOMElement.scrollTo()phương thức khi hoàn thành chỉ để đảm bảo rằng phần tử mục tiêu nằm trên đầu cửa sổ (không phải là bạn sẽ nhận thấy nó bị lệch ra ngoài một phần nhỏ của pixel!).

Câu iflệnh ở dòng 2 scroller()kiểm tra xem trang có đang cuộn hay không (trong trường hợp mục tiêu có thể ở cuối trang và trang không thể cuộn nữa) bằng cách kiểm tra Evị trí trước đó của nó ( prev).

Điều kiện bậc ba bên dưới nó giảm incrementgiá trị khi Etiến gần đến 0. Thao tác này sẽ dừng trang chụp quá mức theo một chiều và sau đó bật trở lại để chụp quá mức theo chiều kia, rồi bật trở lại để chụp quá mức một lần nữa, kiểu bóng bàn, đến vô cực và xa hơn nữa.

Nếu trang của bạn cao hơn c.4000px, bạn có thể muốn tăng giá trị trong điều kiện đầu tiên của biểu thức bậc ba (ở đây là +/- 20) và / hoặc số chia sẽ đặt incrementgiá trị (ở đây là 40).

Chơi với duration, số chia sẽ đặt incrementvà các giá trị trong điều kiện bậc ba của scroller()sẽ cho phép bạn điều chỉnh hàm cho phù hợp với trang của mình.

  • JSFiddle

  • Đã được thử nghiệm trong các phiên bản cập nhật của Firefox và Chrome trên Lubuntu và Firefox, Chrome và IE trên Windows8.



2

Tôi đã làm một cái gì đó như thế này. Tôi không biết liệu nó có hoạt động trong IE8 hay không. Đã thử nghiệm trên 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();
  });
}

Sự miêu tả

  • pointer—Get phần tử và chceck nếu nó có thuộc tính "href" nếu có, hãy loại bỏ "#"
  • elem—Biến điểm con trỏ không có "#"
  • elemOffset— Tập hợp phần tử "cuộn tới" từ đầu trang

2

Bạn có thể dùng

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

Nếu bạn muốn cuộn lên đầu trang, bạn có thể chỉ cần đặt một phần tử trống ở trên cùng và cuộn mượt mà đến phần đó.


0

Bạn có thể sử dụng vòng lặp for với window.scrollTo và setTimeout để cuộn mượt mà với Javascript thuần. Để cuộn đến phần tử có scrollToSmoothlyhàm của tôi : scrollToSmoothly(elem.offsetTop)(giả sử elemlà phần tử DOM). Bạn có thể sử dụng điều này để cuộn mượt mà đến bất kỳ vị trí y nào trong tài liệu.

function scrollToSmoothly(pos, time){
/*Time is only applicable for scrolling upwards*/
/*Code written by hev1*/
/*pos is the y-position to scroll to (in pixels)*/
     if(isNaN(pos)){
      throw "Position must be a number";
     }
     if(pos<0){
     throw "Position can not be negative";
     }
    var currentPos = window.scrollY||window.screenTop;
    if(currentPos<pos){
    var t = 10;
       for(let i = currentPos; i <= pos; i+=10){
       t+=10;
        setTimeout(function(){
        window.scrollTo(0, i);
        }, t/2);
      }
    } else {
    time = time || 2;
       var i = currentPos;
       var x;
      x = setInterval(function(){
         window.scrollTo(0, i);
         i -= 10;
         if(i<=pos){
          clearInterval(x);
         }
     }, time);
      }
}

Bản giới thiệu:


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 = id của div được sử dụng để cuộn

Lưu ý: Để làm cho phần thân cuộn, hãy cung cấp cho phần thân một ID.


0

Đây là giải pháp của tôi. Hoạt động trên hầu hết các trình duyệt

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

Docs

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>



-1

Đây là mã phù hợp với tôi.

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

`


bạn có thể giải thích thêm một chút về mã của bạn làm gì không?
rhavelka

Chào mừng bạn đến với stackoverflow, cảm ơn bạn đã trả lời nhưng câu hỏi về việc không sử dụng jquery.
Stephane L
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.