亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

目錄
Jump.js
快速了解屏幕背後
一些自定義
測試頁面
主腳本
事件委託
單個處理程序
可訪問性注意事項
使用CSS支持原生平滑滾動
結(jié)論
關(guān)於使用原生JavaScript進(jìn)行平滑滾動的常見問題解答 (FAQs)
如何在不使用任何庫的情況下使用原生JavaScript實現(xiàn)平滑滾動?
為什麼我的平滑滾動在Safari中不起作用?
如何平滑地滾動到特定元素?
我可以控制平滑滾動的速度嗎?
如何實現(xiàn)水平平滑滾動?
如何停止平滑滾動動畫?
如何實現(xiàn)具有固定頁眉的平滑滾動?
如何為錨鏈接實現(xiàn)平滑滾動?
如何使用鍵盤導(dǎo)航實現(xiàn)平滑滾動?
如何測試我的平滑滾動實現(xiàn)的兼容性?
首頁 web前端 js教程 如何在Vanilla JavaScript中實現(xiàn)光滑的滾動

如何在Vanilla JavaScript中實現(xiàn)光滑的滾動

Feb 18, 2025 am 10:49 AM

How to Implement Smooth Scrolling in Vanilla JavaScript

核心要點

  • 使用Jump.js庫實現(xiàn)原生JavaScript平滑滾動,簡化滾動動畫,無需外部依賴。
  • 修改Jump.js原始代碼,將其從ES6轉(zhuǎn)換為ES5,以確保與不同瀏覽器的更廣泛兼容性。
  • 使用requestAnimationFrame方法進(jìn)行平滑動畫更新,優(yōu)化性能並提供更流暢的用戶體驗。
  • 實現(xiàn)自定義JavaScript來攔截默認(rèn)的頁面內(nèi)鏈接行為,用平滑滾動動畫替換突然跳轉(zhuǎn)。
  • 集成CSS scroll-behavior屬性,以支持識別此功能的瀏覽器中的原生平滑滾動,如果瀏覽器不支持,則提供JavaScript後備機(jī)制。
  • 通過在滾動後將焦點設(shè)置到目標(biāo)元素來確??稍L問性,解決鍵盤導(dǎo)航的潛在問題,並增強(qiáng)所有用戶的可用性。

本文由Adrian Sandu、Chris Perry、Jérémy Heleine和Mallory van Achterberg同行評審。感謝所有SitePoint的同行評審者,使SitePoint的內(nèi)容達(dá)到最佳狀態(tài)!

平滑滾動是一種用戶界面模式,它逐步增強(qiáng)了默認(rèn)的頁面內(nèi)導(dǎo)航體驗,在滾動框(視口或可滾動元素)內(nèi)動畫地改變位置,從激活鏈接的位置到鏈接URL的哈希片段中指示的目標(biāo)元素的位置。

這並非什麼新鮮事物,多年來一直是一種已知的模式,例如,請查看這篇可追溯到2003年的SitePoint文章!順便說一句,這篇文章具有歷史價值,因為它展示了客戶端JavaScript編程,特別是DOM,多年來的變化和發(fā)展,允許開發(fā)更簡便的原生JavaScript解決方案。

在jQuery生態(tài)系統(tǒng)中,這種模式有很多實現(xiàn),可以直接使用jQuery或使用插件實現(xiàn),但在本文中,我們感興趣的是純JavaScript解決方案。具體來說,我們將探索和利用Jump.js庫。

在介紹該庫及其功能和特性的概述之後,我們將對原始代碼進(jìn)行一些更改以適應(yīng)我們的需求。在此過程中,我們將復(fù)習(xí)一些核心的JavaScript語言技能,例如函數(shù)和閉包。然後,我們將創(chuàng)建一個HTML頁面來測試平滑滾動行為,然後將其實現(xiàn)為自定義腳本。然後將添加對CSS原生平滑滾動的支持(如果可用),最後我們將對瀏覽器導(dǎo)航歷史記錄進(jìn)行一些觀察。

這是我們將創(chuàng)建的最終演示:

查看CodePen上的SitePoint (@SitePoint)的Smooth Scrolling筆。

完整的源代碼可在GitHub上找到。

Jump.js

Jump.js是用原生ES6 JavaScript編寫的,沒有任何外部依賴項。它是一個小型實用程序,只有大約42 SLOC,但提供的最小化包的大小約為2.67 KB,因為它必須進(jìn)行轉(zhuǎn)譯。 GitHub項目頁面上提供了一個演示。

顧名思義,它只提供跳轉(zhuǎn):滾動條位置從其當(dāng)前值到目標(biāo)位置的動畫變化,通過提供DOM元素、CSS選擇器或正數(shù)或負(fù)數(shù)值形式的距離來指定。這意味著在平滑滾動模式的實現(xiàn)中,我們必須自己執(zhí)行鏈接劫持。更多內(nèi)容請參見以下部分。

請注意,目前僅支持視口的垂直滾動。

我們可以使用一些選項配置跳轉(zhuǎn),例如持續(xù)時間(此參數(shù)是必需的)、緩動函數(shù)和在動畫結(jié)束時觸發(fā)的回調(diào)。我們稍後將在演示中看到它們的實際應(yīng)用。有關(guān)完整詳細(xì)信息,請參見文檔。

Jump.js在“現(xiàn)代”瀏覽器上運行沒有問題,包括Internet Explorer 10版或更高版本。同樣,請參考文檔以了解支持的瀏覽器完整列表。使用合適的requestAnimationFrame polyfill,它甚至可以在舊版瀏覽器上運行。

快速了解屏幕背後

在內(nèi)部,Jump.js源代碼使用window對象的requestAnimationFrame方法來安排在滾動動畫的每一幀中更新視口垂直位置的位置。此更新是通過將使用緩動函數(shù)計算的下一個位置值傳遞給window.scrollTo方法來實現(xiàn)的。有關(guān)完整詳細(xì)信息,請參見源代碼。

一些自定義

在深入研究演示以展示Jump.js的使用之前,我們將對原始代碼進(jìn)行一些細(xì)微的更改,但這不會修改其內(nèi)部工作方式。

源代碼是用ES6編寫的,需要與JavaScript構(gòu)建工具一起使用才能進(jìn)行轉(zhuǎn)譯和捆綁模塊。對於某些項目來說,這可能有點過分,因此我們將應(yīng)用一些重構(gòu)來將代碼轉(zhuǎn)換為ES5,以便在任何地方使用。

首先,讓我們刪除ES6語法和功能。腳本定義了一個ES6類:

<code>import easeInOutQuad from './easing'

export default class Jump {
  jump(target, options = {}) {
    this.start = window.pageYOffset

    this.options = {
      duration: options.duration,
      offset: options.offset || 0,
      callback: options.callback,
      easing: options.easing || easeInOutQuad
    }

    this.distance = typeof target === 'string'
      ? this.options.offset + document.querySelector(target).getBoundingClientRect().top
      : target

    this.duration = typeof this.options.duration === 'function'
      ? this.options.duration(this.distance)
      : this.options.duration

    requestAnimationFrame(time => this._loop(time))
  }

  _loop(time) {
    if(!this.timeStart) {
      this.timeStart = time
    }

    this.timeElapsed = time - this.timeStart
    this.next = this.options.easing(this.timeElapsed, this.start, this.distance, this.duration)

    window.scrollTo(0, this.next)

    this.timeElapsed       ? requestAnimationFrame(time => this._loop(time))
      : this._end()
  }

  _end() {
    window.scrollTo(0, this.start + this.distance)

    typeof this.options.callback === 'function' && this.options.callback()
    this.timeStart = false
  }
}
</code>

我們可以使用構(gòu)造函數(shù)和一堆原型方法將其轉(zhuǎn)換為ES5“類”,但請注意,我們永遠(yuǎn)不需要此類的多個實例,因此使用普通對象字面量實現(xiàn)的單例就可以了:

<code>var jump = (function() {

    var o = {

        jump: function(target, options) {
            this.start = window.pageYOffset

            this.options = {
              duration: options.duration,
              offset: options.offset || 0,
              callback: options.callback,
              easing: options.easing || easeInOutQuad
            }

            this.distance = typeof target === 'string'
              ? this.options.offset + document.querySelector(target).getBoundingClientRect().top
              : target

            this.duration = typeof this.options.duration === 'function'
              ? this.options.duration(this.distance)
              : this.options.duration

            requestAnimationFrame(_loop)
        },

        _loop: function(time) {
            if(!this.timeStart) {
              this.timeStart = time
            }

            this.timeElapsed = time - this.timeStart
            this.next = this.options.easing(this.timeElapsed, this.start, this.distance, this.duration)

            window.scrollTo(0, this.next)

            this.timeElapsed               ? requestAnimationFrame(_loop)
              : this._end()
        },

        _end: function() {
            window.scrollTo(0, this.start + this.distance)

            typeof this.options.callback === 'function' && this.options.callback()
            this.timeStart = false
        }

    };

    var _loop = o._loop.bind(o);

    // Robert Penner's easeInOutQuad - http://robertpenner.com/easing/
    function easeInOutQuad(t, b, c, d)  {
        t /= d / 2
        if(t         t--
        return -c / 2 * (t * (t - 2) - 1) + b
    }

    return o;

})();
</code>

除了刪除類之外,我們還需要進(jìn)行其他一些更改。 requestAnimationFrame的回調(diào)用於在每一幀中更新滾動條位置,在原始代碼中,它是通過ES6箭頭函數(shù)調(diào)用的,在初始化時預(yù)綁定到j(luò)ump單例。然後,我們將默認(rèn)緩動函數(shù)捆綁在同一個源文件中。最後,我們使用IIFE(立即調(diào)用函數(shù)表達(dá)式)包裝了代碼,以避免命名空間污染。

現(xiàn)在我們可以應(yīng)用另一個重構(gòu)步驟,注意借助嵌套函數(shù)和閉包,我們可以只使用函數(shù)而不是對象:

<code>function jump(target, options) {
    var start = window.pageYOffset;

    var opt = {
      duration: options.duration,
      offset: options.offset || 0,
      callback: options.callback,
      easing: options.easing || easeInOutQuad
    };

    var distance = typeof target === 'string' ? 
        opt.offset + document.querySelector(target).getBoundingClientRect().top : 
        target
    ;

    var duration = typeof opt.duration === 'function'
          ? opt.duration(distance)
          : opt.duration
    ;

    var 
        timeStart = null,
        timeElapsed
    ;

    requestAnimationFrame(loop);

    function loop(time) {
        if (timeStart === null)
            timeStart = time;

        timeElapsed = time - timeStart;

        window.scrollTo(0, opt.easing(timeElapsed, start, distance, duration));

        if (timeElapsed         requestAnimationFrame(loop)
        else
            end();
    }

    function end() {
        window.scrollTo(0, start + distance);

        typeof opt.callback === 'function' && opt.callback();
        timeStart = null;
    }

    // ...

}
</code>

單例現(xiàn)在變成了將被調(diào)用以動畫滾動的jump函數(shù),loop和end回調(diào)變成了嵌套函數(shù),而對象的屬性現(xiàn)在變成了局部變量(閉包)。我們不再需要IIFE,因為現(xiàn)在所有代碼都安全地包裝在一個函數(shù)中。

作為最後的重構(gòu)步驟,為了避免在每次調(diào)用loop回調(diào)時重複timeStart重置檢查,第一次調(diào)用requestAnimationFrame()時,我們將向其傳遞一個匿名函數(shù),該函數(shù)在調(diào)用loop函數(shù)之前重置timerStart變量:

<code>import easeInOutQuad from './easing'

export default class Jump {
  jump(target, options = {}) {
    this.start = window.pageYOffset

    this.options = {
      duration: options.duration,
      offset: options.offset || 0,
      callback: options.callback,
      easing: options.easing || easeInOutQuad
    }

    this.distance = typeof target === 'string'
      ? this.options.offset + document.querySelector(target).getBoundingClientRect().top
      : target

    this.duration = typeof this.options.duration === 'function'
      ? this.options.duration(this.distance)
      : this.options.duration

    requestAnimationFrame(time => this._loop(time))
  }

  _loop(time) {
    if(!this.timeStart) {
      this.timeStart = time
    }

    this.timeElapsed = time - this.timeStart
    this.next = this.options.easing(this.timeElapsed, this.start, this.distance, this.duration)

    window.scrollTo(0, this.next)

    this.timeElapsed       ? requestAnimationFrame(time => this._loop(time))
      : this._end()
  }

  _end() {
    window.scrollTo(0, this.start + this.distance)

    typeof this.options.callback === 'function' && this.options.callback()
    this.timeStart = false
  }
}
</code>

再次注意,在重構(gòu)過程中,核心滾動動畫代碼沒有改變。

測試頁面

現(xiàn)在我們已經(jīng)自定義了腳本以適應(yīng)我們的需求,我們準(zhǔn)備組裝一個測試演示。在本節(jié)中,我們將編寫一個使用下一節(jié)中介紹的腳本增強(qiáng)平滑滾動的頁面。

該頁麵包含一個包含指向文檔中後續(xù)部分的頁面內(nèi)鏈接的內(nèi)容表(TOC),以及指向TOC的其他鏈接。我們還將混合一些指向其他頁面的外部鏈接。這是此頁面的基本結(jié)構(gòu):

<code>var jump = (function() {

    var o = {

        jump: function(target, options) {
            this.start = window.pageYOffset

            this.options = {
              duration: options.duration,
              offset: options.offset || 0,
              callback: options.callback,
              easing: options.easing || easeInOutQuad
            }

            this.distance = typeof target === 'string'
              ? this.options.offset + document.querySelector(target).getBoundingClientRect().top
              : target

            this.duration = typeof this.options.duration === 'function'
              ? this.options.duration(this.distance)
              : this.options.duration

            requestAnimationFrame(_loop)
        },

        _loop: function(time) {
            if(!this.timeStart) {
              this.timeStart = time
            }

            this.timeElapsed = time - this.timeStart
            this.next = this.options.easing(this.timeElapsed, this.start, this.distance, this.duration)

            window.scrollTo(0, this.next)

            this.timeElapsed               ? requestAnimationFrame(_loop)
              : this._end()
        },

        _end: function() {
            window.scrollTo(0, this.start + this.distance)

            typeof this.options.callback === 'function' && this.options.callback()
            this.timeStart = false
        }

    };

    var _loop = o._loop.bind(o);

    // Robert Penner's easeInOutQuad - http://robertpenner.com/easing/
    function easeInOutQuad(t, b, c, d)  {
        t /= d / 2
        if(t         t--
        return -c / 2 * (t * (t - 2) - 1) + b
    }

    return o;

})();
</code>

在頭部,我們將包含一些CSS規(guī)則來設(shè)置基本的最簡佈局,而在body標(biāo)籤的末尾,我們將包含兩個JavaScript文件:前者是我們重構(gòu)後的Jump.js版本,後者是我們現(xiàn)在將討論的腳本。

主腳本

這是將使用我們自定義的Jump.js庫版本的動畫跳轉(zhuǎn)來增強(qiáng)測試頁面滾動體驗的腳本。當(dāng)然,此代碼也將用ES5 JavaScript編寫。

讓我們簡要概述一下它應(yīng)該完成的任務(wù):它必須劫持頁面內(nèi)鏈接上的點擊,禁用瀏覽器的默認(rèn)行為(突然跳轉(zhuǎn)到點擊鏈接的href屬性的哈希片段中指示的目標(biāo)元素),並將其替換為對我們的jump()函數(shù)的調(diào)用。

因此,首先要監(jiān)控頁面內(nèi)鏈接上的點擊。我們可以通過兩種方式做到這一點,使用事件委託或?qū)⑻幚沓绦蚋郊拥矫總€相關(guān)的鏈接。

事件委託

在第一種方法中,我們將點擊偵聽器添加到一個元素document.body。這樣,頁面上任何元素的每個點擊事件都將沿著其祖先的分支冒泡到DOM樹,直到到達(dá)document.body:

<code>function jump(target, options) {
    var start = window.pageYOffset;

    var opt = {
      duration: options.duration,
      offset: options.offset || 0,
      callback: options.callback,
      easing: options.easing || easeInOutQuad
    };

    var distance = typeof target === 'string' ? 
        opt.offset + document.querySelector(target).getBoundingClientRect().top : 
        target
    ;

    var duration = typeof opt.duration === 'function'
          ? opt.duration(distance)
          : opt.duration
    ;

    var 
        timeStart = null,
        timeElapsed
    ;

    requestAnimationFrame(loop);

    function loop(time) {
        if (timeStart === null)
            timeStart = time;

        timeElapsed = time - timeStart;

        window.scrollTo(0, opt.easing(timeElapsed, start, distance, duration));

        if (timeElapsed         requestAnimationFrame(loop)
        else
            end();
    }

    function end() {
        window.scrollTo(0, start + distance);

        typeof opt.callback === 'function' && opt.callback();
        timeStart = null;
    }

    // ...

}
</code>

當(dāng)然,現(xiàn)在在註冊的事件偵聽器(onClick)中,我們必須檢查傳入的click事件對象的target,以檢查它是否與頁面內(nèi)鏈接元素相關(guān)。這可以通過多種方式完成,因此我們將將其抽象為輔助函數(shù)isInPageLink()。我們稍後將看看此函數(shù)的機(jī)制。

如果傳入的點擊是在頁面內(nèi)鏈接上,我們將停止事件冒泡並阻止關(guān)聯(lián)的默認(rèn)操作。最後,我們調(diào)用jump函數(shù),為其提供目標(biāo)元素的哈希選擇器和配置所需動畫的參數(shù)。

這是事件處理程序:

<code>requestAnimationFrame(function(time) { timeStart = time; loop(time); });

function loop(time) {
    timeElapsed = time - timeStart;

    window.scrollTo(0, opt.easing(timeElapsed, start, distance, duration));

    if (timeElapsed         requestAnimationFrame(loop)
    else
        end();
}
</code>

單個處理程序

使用第二種方法來監(jiān)控鏈接點擊,將上面介紹的事件處理程序的稍微修改後的版本附加到每個頁面內(nèi)鏈接元素,因此沒有事件冒泡:

<code>import easeInOutQuad from './easing'

export default class Jump {
  jump(target, options = {}) {
    this.start = window.pageYOffset

    this.options = {
      duration: options.duration,
      offset: options.offset || 0,
      callback: options.callback,
      easing: options.easing || easeInOutQuad
    }

    this.distance = typeof target === 'string'
      ? this.options.offset + document.querySelector(target).getBoundingClientRect().top
      : target

    this.duration = typeof this.options.duration === 'function'
      ? this.options.duration(this.distance)
      : this.options.duration

    requestAnimationFrame(time => this._loop(time))
  }

  _loop(time) {
    if(!this.timeStart) {
      this.timeStart = time
    }

    this.timeElapsed = time - this.timeStart
    this.next = this.options.easing(this.timeElapsed, this.start, this.distance, this.duration)

    window.scrollTo(0, this.next)

    this.timeElapsed       ? requestAnimationFrame(time => this._loop(time))
      : this._end()
  }

  _end() {
    window.scrollTo(0, this.start + this.distance)

    typeof this.options.callback === 'function' && this.options.callback()
    this.timeStart = false
  }
}
</code>

我們查詢所有元素,並使用[].slice()技巧將返回的DOM NodeList轉(zhuǎn)換為JavaScript數(shù)組(如果目標(biāo)瀏覽器支持,更好的替代方法是使用ES6 Array.from()方法)。然後,我們可以使用數(shù)組方法過濾頁面內(nèi)鏈接,重新使用上面定義的相同輔助函數(shù),最後將偵聽器附加到剩餘的鏈接元素。

事件處理程序與之前幾乎相同,但當(dāng)然我們不需要檢查點擊目標(biāo):

<code>var jump = (function() {

    var o = {

        jump: function(target, options) {
            this.start = window.pageYOffset

            this.options = {
              duration: options.duration,
              offset: options.offset || 0,
              callback: options.callback,
              easing: options.easing || easeInOutQuad
            }

            this.distance = typeof target === 'string'
              ? this.options.offset + document.querySelector(target).getBoundingClientRect().top
              : target

            this.duration = typeof this.options.duration === 'function'
              ? this.options.duration(this.distance)
              : this.options.duration

            requestAnimationFrame(_loop)
        },

        _loop: function(time) {
            if(!this.timeStart) {
              this.timeStart = time
            }

            this.timeElapsed = time - this.timeStart
            this.next = this.options.easing(this.timeElapsed, this.start, this.distance, this.duration)

            window.scrollTo(0, this.next)

            this.timeElapsed               ? requestAnimationFrame(_loop)
              : this._end()
        },

        _end: function() {
            window.scrollTo(0, this.start + this.distance)

            typeof this.options.callback === 'function' && this.options.callback()
            this.timeStart = false
        }

    };

    var _loop = o._loop.bind(o);

    // Robert Penner's easeInOutQuad - http://robertpenner.com/easing/
    function easeInOutQuad(t, b, c, d)  {
        t /= d / 2
        if(t         t--
        return -c / 2 * (t * (t - 2) - 1) + b
    }

    return o;

})();
</code>

哪種方法最好取決於使用上下文。例如,如果在初始頁面加載後可能動態(tài)添加新的鏈接元素,那麼我們必須使用事件委託。

現(xiàn)在我們轉(zhuǎn)向isInPageLink()的實現(xiàn),我們在之前的事件處理程序中使用此輔助函數(shù)來抽象頁面內(nèi)鏈接的測試。正如我們所看到的,此函數(shù)接受DOM節(jié)點作為參數(shù),並返回一個布爾值以指示該節(jié)點是否表示頁面內(nèi)鏈接元素。僅檢查傳遞的節(jié)點是A標(biāo)籤並且設(shè)置了哈希片段是不夠的,因為鏈接可能是指向另一個頁面,在這種情況下,必須不禁用默認(rèn)瀏覽器操作。因此,我們檢查屬性href中存儲的值“減去”哈希片段是否等於頁面URL:

<code>function jump(target, options) {
    var start = window.pageYOffset;

    var opt = {
      duration: options.duration,
      offset: options.offset || 0,
      callback: options.callback,
      easing: options.easing || easeInOutQuad
    };

    var distance = typeof target === 'string' ? 
        opt.offset + document.querySelector(target).getBoundingClientRect().top : 
        target
    ;

    var duration = typeof opt.duration === 'function'
          ? opt.duration(distance)
          : opt.duration
    ;

    var 
        timeStart = null,
        timeElapsed
    ;

    requestAnimationFrame(loop);

    function loop(time) {
        if (timeStart === null)
            timeStart = time;

        timeElapsed = time - timeStart;

        window.scrollTo(0, opt.easing(timeElapsed, start, distance, duration));

        if (timeElapsed         requestAnimationFrame(loop)
        else
            end();
    }

    function end() {
        window.scrollTo(0, start + distance);

        typeof opt.callback === 'function' && opt.callback();
        timeStart = null;
    }

    // ...

}
</code>

stripHash()是另一個輔助函數(shù),我們也用它在腳本初始化時設(shè)置變量pageUrl的值:

<code>requestAnimationFrame(function(time) { timeStart = time; loop(time); });

function loop(time) {
    timeElapsed = time - timeStart;

    window.scrollTo(0, opt.easing(timeElapsed, start, distance, duration));

    if (timeElapsed         requestAnimationFrame(loop)
    else
        end();
}
</code>

此基於字符串的解決方案以及哈希片段的修剪即使在帶有查詢字符串的URL上也能正常工作,因為哈希部分在URL的一般結(jié)構(gòu)中位於它們之後。

正如我之前所說,這只是實現(xiàn)此測試的一種可能方法。例如,本教程開頭引用的文章使用了不同的解決方案,對鏈接href與location對象進(jìn)行了組件級比較。

應(yīng)該注意的是,我們在兩種事件訂閱方法中都使用了此函數(shù),但在第二種方法中,我們將其用作我們已經(jīng)知道是標(biāo)籤的元素的過濾器,因此對tagName屬性的第一次檢查是多餘的。這留給讀者作為練習(xí)。

可訪問性注意事項

就目前而言,我們的代碼容易受到已知錯誤(實際上是一對無關(guān)的錯誤,影響B(tài)link/WebKit/KHTML和一個影響IE的錯誤)的影響,這些錯誤會影響鍵盤用戶。當(dāng)通過製表鍵瀏覽TOC鏈接時,激活一個鏈接將平滑地向下滾動到選定的部分,但焦點將保留在鏈接上。這意味著在下一個製表鍵按下時,用戶將被送回TOC,而不是送往他們選擇的節(jié)中的第一個鏈接。

為了解決這個問題,我們將向主腳本添加另一個函數(shù):

<code>>
    <h1>></h1>Title>
    <nav> id="toc"></nav>
        <ul>></ul>
            <li>></li>
<a> href="http://ipnx.cn/link/db8229562f80fbcc7d780f571e5974ec"></a>Section 1>>
            <li>></li>
<a> href="http://ipnx.cn/link/ba2cf4148007ed8a8b041f8abd9bbf96"></a>Section 2>>
            ...
        >
    >
     id="sect-1">
        <h2>></h2>Section 1>
        <p>></p>Pellentesque habitant morbi tristique senectus et netus et <a> href="http://ipnx.cn/link/e1b97c787a5677efa5eba575c41e8688"></a>a link to another page> ac turpis egestas. <a> href="http://ipnx.cn/link/e1b97c787a5677efa5eba575c41e8688index.html#foo"></a>A link to another page, with an anchor> quam, feugiat vitae, ...>
        <a> href="http://ipnx.cn/link/7421d74f57142680e679057ddc98edf5"></a>Back to TOC>
    >
     id="sect-2">
        <h2>></h2>Section 2>
        ...
    >
    ...
     src="jump.js">>
     src="script.js">>
>
</code>

它將在我們將傳遞給jump函數(shù)的回調(diào)中運行,並將我們要滾動到的元素的哈希值傳遞過去:

<code>import easeInOutQuad from './easing'

export default class Jump {
  jump(target, options = {}) {
    this.start = window.pageYOffset

    this.options = {
      duration: options.duration,
      offset: options.offset || 0,
      callback: options.callback,
      easing: options.easing || easeInOutQuad
    }

    this.distance = typeof target === 'string'
      ? this.options.offset + document.querySelector(target).getBoundingClientRect().top
      : target

    this.duration = typeof this.options.duration === 'function'
      ? this.options.duration(this.distance)
      : this.options.duration

    requestAnimationFrame(time => this._loop(time))
  }

  _loop(time) {
    if(!this.timeStart) {
      this.timeStart = time
    }

    this.timeElapsed = time - this.timeStart
    this.next = this.options.easing(this.timeElapsed, this.start, this.distance, this.duration)

    window.scrollTo(0, this.next)

    this.timeElapsed       ? requestAnimationFrame(time => this._loop(time))
      : this._end()
  }

  _end() {
    window.scrollTo(0, this.start + this.distance)

    typeof this.options.callback === 'function' && this.options.callback()
    this.timeStart = false
  }
}
</code>

此函數(shù)的作用是獲取哈希值對應(yīng)的DOM元素,並測試它是否已經(jīng)是可以接收焦點的元素(例如錨點或按鈕元素)。如果元素不能默認(rèn)接收焦點(例如我們的容器),則它會將其tabIndex屬性設(shè)置為-1(允許通過編程方式接收焦點,但不能通過鍵盤接收)。然後焦點將設(shè)置為該元素,這意味著用戶的下一個tab按鍵將焦點移動到下一個可用鏈接。

您可以在此處查看主腳本的完整源代碼,其中包含所有先前討論的更改。

使用CSS支持原生平滑滾動

CSS對像模型視圖模塊規(guī)範(fàn)引入了一個新的屬性來原生實現(xiàn)平滑滾動:scroll-behavior

它可以取兩個值,auto表示默認(rèn)的瞬時滾動,smooth表示動畫滾動。該規(guī)範(fàn)沒有提供任何配置滾動動畫的方法,例如其持續(xù)時間和時間函數(shù)(緩動)。

我可以使用css-scroll-behavior嗎?來自caniuse.com的數(shù)據(jù)顯示主要瀏覽器對css-scroll-behavior功能的支持情況。

不幸的是,在撰寫本文時,支持非常有限。在Chrome中,此功能正在開發(fā)中,可以通過在chrome://flags屏幕中啟用它來使用部分實現(xiàn)。 CSS屬性尚未實現(xiàn),因此鏈接點擊上的平滑滾動不起作用。

無論如何,通過對主腳本進(jìn)行微小的更改,我們可以檢測用戶代理中是否可用此功能並避免運行我們的其餘代碼。為了在視口中使用平滑滾動,我們將CSS屬性應(yīng)用於根元素HTML(但在我們的測試頁面中,我們甚至可以將其應(yīng)用於body元素):

<code>var jump = (function() {

    var o = {

        jump: function(target, options) {
            this.start = window.pageYOffset

            this.options = {
              duration: options.duration,
              offset: options.offset || 0,
              callback: options.callback,
              easing: options.easing || easeInOutQuad
            }

            this.distance = typeof target === 'string'
              ? this.options.offset + document.querySelector(target).getBoundingClientRect().top
              : target

            this.duration = typeof this.options.duration === 'function'
              ? this.options.duration(this.distance)
              : this.options.duration

            requestAnimationFrame(_loop)
        },

        _loop: function(time) {
            if(!this.timeStart) {
              this.timeStart = time
            }

            this.timeElapsed = time - this.timeStart
            this.next = this.options.easing(this.timeElapsed, this.start, this.distance, this.duration)

            window.scrollTo(0, this.next)

            this.timeElapsed               ? requestAnimationFrame(_loop)
              : this._end()
        },

        _end: function() {
            window.scrollTo(0, this.start + this.distance)

            typeof this.options.callback === 'function' && this.options.callback()
            this.timeStart = false
        }

    };

    var _loop = o._loop.bind(o);

    // Robert Penner's easeInOutQuad - http://robertpenner.com/easing/
    function easeInOutQuad(t, b, c, d)  {
        t /= d / 2
        if(t         t--
        return -c / 2 * (t * (t - 2) - 1) + b
    }

    return o;

})();
</code>

然後,我們在腳本開頭添加一個簡單的功能檢測測試:

<code>function jump(target, options) {
    var start = window.pageYOffset;

    var opt = {
      duration: options.duration,
      offset: options.offset || 0,
      callback: options.callback,
      easing: options.easing || easeInOutQuad
    };

    var distance = typeof target === 'string' ? 
        opt.offset + document.querySelector(target).getBoundingClientRect().top : 
        target
    ;

    var duration = typeof opt.duration === 'function'
          ? opt.duration(distance)
          : opt.duration
    ;

    var 
        timeStart = null,
        timeElapsed
    ;

    requestAnimationFrame(loop);

    function loop(time) {
        if (timeStart === null)
            timeStart = time;

        timeElapsed = time - timeStart;

        window.scrollTo(0, opt.easing(timeElapsed, start, distance, duration));

        if (timeElapsed         requestAnimationFrame(loop)
        else
            end();
    }

    function end() {
        window.scrollTo(0, start + distance);

        typeof opt.callback === 'function' && opt.callback();
        timeStart = null;
    }

    // ...

}
</code>

因此,如果瀏覽器支持原生滾動,則腳本將不執(zhí)行任何操作並退出,否則它將像以前一樣繼續(xù)執(zhí)行,並且瀏覽器將忽略不受支持的CSS屬性。

結(jié)論

除了實現(xiàn)簡單和性能之外,剛才討論的CSS解決方案的另一個優(yōu)勢是瀏覽器歷史行為與使用瀏覽器默認(rèn)滾動時所體驗的行為一致。每個頁面內(nèi)跳轉(zhuǎn)都推送到瀏覽器歷史堆棧上,我們可以使用相應(yīng)的按鈕來回瀏覽這些跳轉(zhuǎn)(但至少在Firefox中沒有平滑滾動)。

在我們編寫的代碼中(我們現(xiàn)在可以將其視為CSS支持不可用時的後備方案),我們沒有考慮腳本相對於瀏覽器歷史記錄的行為。根據(jù)上下文和用例,這可能是或可能不是感興趣的事情,但如果我們認(rèn)為腳本應(yīng)該增強(qiáng)默認(rèn)滾動體驗,那麼我們應(yīng)該期望一致的行為,就像CSS一樣。

關(guān)於使用原生JavaScript進(jìn)行平滑滾動的常見問題解答 (FAQs)

如何在不使用任何庫的情況下使用原生JavaScript實現(xiàn)平滑滾動?

在不使用任何庫的情況下使用原生JavaScript實現(xiàn)平滑滾動非常簡單。您可以使用window.scrollTo方法,並將behavior選項設(shè)置為smooth。此方法通過給定數(shù)量滾動窗口中的文檔。這是一個簡單的示例:

<code>import easeInOutQuad from './easing'

export default class Jump {
  jump(target, options = {}) {
    this.start = window.pageYOffset

    this.options = {
      duration: options.duration,
      offset: options.offset || 0,
      callback: options.callback,
      easing: options.easing || easeInOutQuad
    }

    this.distance = typeof target === 'string'
      ? this.options.offset + document.querySelector(target).getBoundingClientRect().top
      : target

    this.duration = typeof this.options.duration === 'function'
      ? this.options.duration(this.distance)
      : this.options.duration

    requestAnimationFrame(time => this._loop(time))
  }

  _loop(time) {
    if(!this.timeStart) {
      this.timeStart = time
    }

    this.timeElapsed = time - this.timeStart
    this.next = this.options.easing(this.timeElapsed, this.start, this.distance, this.duration)

    window.scrollTo(0, this.next)

    this.timeElapsed       ? requestAnimationFrame(time => this._loop(time))
      : this._end()
  }

  _end() {
    window.scrollTo(0, this.start + this.distance)

    typeof this.options.callback === 'function' && this.options.callback()
    this.timeStart = false
  }
}
</code>

在此示例中,當(dāng)您點擊具有類your-element的元素時,頁面將平滑地滾動到頂部。

為什麼我的平滑滾動在Safari中不起作用?

使用scrollTo方法並將behavior選項設(shè)置為smooth的平滑滾動功能在Safari中不受支持。要使其正常工作,您可以使用polyfill,例如smoothscroll-polyfill。這將在原生不支持它的瀏覽器中啟用平滑滾動功能。

如何平滑地滾動到特定元素?

要平滑地滾動到特定元素,您可以使用Element.scrollIntoView方法,並將behavior選項設(shè)置為smooth。這是一個示例:

<code>var jump = (function() {

    var o = {

        jump: function(target, options) {
            this.start = window.pageYOffset

            this.options = {
              duration: options.duration,
              offset: options.offset || 0,
              callback: options.callback,
              easing: options.easing || easeInOutQuad
            }

            this.distance = typeof target === 'string'
              ? this.options.offset + document.querySelector(target).getBoundingClientRect().top
              : target

            this.duration = typeof this.options.duration === 'function'
              ? this.options.duration(this.distance)
              : this.options.duration

            requestAnimationFrame(_loop)
        },

        _loop: function(time) {
            if(!this.timeStart) {
              this.timeStart = time
            }

            this.timeElapsed = time - this.timeStart
            this.next = this.options.easing(this.timeElapsed, this.start, this.distance, this.duration)

            window.scrollTo(0, this.next)

            this.timeElapsed               ? requestAnimationFrame(_loop)
              : this._end()
        },

        _end: function() {
            window.scrollTo(0, this.start + this.distance)

            typeof this.options.callback === 'function' && this.options.callback()
            this.timeStart = false
        }

    };

    var _loop = o._loop.bind(o);

    // Robert Penner's easeInOutQuad - http://robertpenner.com/easing/
    function easeInOutQuad(t, b, c, d)  {
        t /= d / 2
        if(t         t--
        return -c / 2 * (t * (t - 2) - 1) + b
    }

    return o;

})();
</code>

在此示例中,當(dāng)您點擊具有類your-element的元素時,頁面將平滑地滾動到具有類target-element的元素。

我可以控制平滑滾動的速度嗎?

平滑滾動的速度不能直接控制,因為它由瀏覽器處理。但是,您可以使用window.requestAnimationFrame創(chuàng)建一個自定義平滑滾動函數(shù),以便更好地控制滾動動畫,包括其速度。

如何實現(xiàn)水平平滑滾動?

您可以通過與垂直平滑滾動類似的方式實現(xiàn)水平平滑滾動。 window.scrollToElement.scrollIntoView方法也接受left選項以指定要滾動到的水平位置。這是一個示例:

<code>function jump(target, options) {
    var start = window.pageYOffset;

    var opt = {
      duration: options.duration,
      offset: options.offset || 0,
      callback: options.callback,
      easing: options.easing || easeInOutQuad
    };

    var distance = typeof target === 'string' ? 
        opt.offset + document.querySelector(target).getBoundingClientRect().top : 
        target
    ;

    var duration = typeof opt.duration === 'function'
          ? opt.duration(distance)
          : opt.duration
    ;

    var 
        timeStart = null,
        timeElapsed
    ;

    requestAnimationFrame(loop);

    function loop(time) {
        if (timeStart === null)
            timeStart = time;

        timeElapsed = time - timeStart;

        window.scrollTo(0, opt.easing(timeElapsed, start, distance, duration));

        if (timeElapsed         requestAnimationFrame(loop)
        else
            end();
    }

    function end() {
        window.scrollTo(0, start + distance);

        typeof opt.callback === 'function' && opt.callback();
        timeStart = null;
    }

    // ...

}
</code>

這將使文檔向右平滑滾動100像素。

如何停止平滑滾動動畫?

不能直接停止平滑滾動動畫,因為它由瀏覽器處理。但是,如果您使用的是自定義平滑滾動函數(shù),則可以使用window.cancelAnimationFrame取消動畫幀來停止動畫。

如何實現(xiàn)具有固定頁眉的平滑滾動?

要實現(xiàn)具有固定頁眉的平滑滾動,您需要調(diào)整滾動位置以考慮頁眉的高度。您可以通過從目標(biāo)滾動位置減去頁眉的高度來實現(xiàn)此目的。

如何為錨鏈接實現(xiàn)平滑滾動?

要為錨鏈接實現(xiàn)平滑滾動,您可以向鏈接的點擊事件添加事件偵聽器,並使用Element.scrollIntoView方法平滑地滾動到目標(biāo)元素。這是一個示例:

<code>requestAnimationFrame(function(time) { timeStart = time; loop(time); });

function loop(time) {
    timeElapsed = time - timeStart;

    window.scrollTo(0, opt.easing(timeElapsed, start, distance, duration));

    if (timeElapsed         requestAnimationFrame(loop)
    else
        end();
}
</code>

這將使頁面上的所有錨鏈接平滑地滾動到其目標(biāo)元素。

如何使用鍵盤導(dǎo)航實現(xiàn)平滑滾動?

使用鍵盤導(dǎo)航實現(xiàn)平滑滾動比較複雜,因為它需要攔截鍵盤事件並手動滾動文檔。您可以通過向keydown事件添加事件偵聽器並使用window.scrollTo方法平滑地滾動文檔來實現(xiàn)此目的。

如何測試我的平滑滾動實現(xiàn)的兼容性?

您可以使用BrowserStack等在線工具測試平滑滾動實現(xiàn)的兼容性。這些工具允許您在不同的瀏覽器和不同的設(shè)備上測試您的網(wǎng)站,以確保您的實現(xiàn)可以在所有環(huán)境中正常工作。

以上是如何在Vanilla JavaScript中實現(xiàn)光滑的滾動的詳細(xì)內(nèi)容。更多資訊請關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

本網(wǎng)站聲明
本文內(nèi)容由網(wǎng)友自願投稿,版權(quán)歸原作者所有。本站不承擔(dān)相應(yīng)的法律責(zé)任。如發(fā)現(xiàn)涉嫌抄襲或侵權(quán)的內(nèi)容,請聯(lián)絡(luò)admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅(qū)動的應(yīng)用程序,用於創(chuàng)建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強(qiáng)大的PHP整合開發(fā)環(huán)境

Dreamweaver CS6

Dreamweaver CS6

視覺化網(wǎng)頁開發(fā)工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Laravel 教程
1597
29
PHP教程
1488
72
如何在node.js中提出HTTP請求? 如何在node.js中提出HTTP請求? Jul 13, 2025 am 02:18 AM

在Node.js中發(fā)起HTTP請求有三種常用方式:使用內(nèi)置模塊、axios和node-fetch。 1.使用內(nèi)置的http/https模塊無需依賴,適合基礎(chǔ)場景,但需手動處理數(shù)據(jù)拼接和錯誤監(jiān)聽,例如用https.get()獲取數(shù)據(jù)或通過.write()發(fā)送POST請求;2.axios是基於Promise的第三方庫,語法簡潔且功能強(qiáng)大,支持async/await、自動JSON轉(zhuǎn)換、攔截器等,推薦用於簡化異步請求操作;3.node-fetch提供類似瀏覽器fetch的風(fēng)格,基於Promise且語法簡單

JavaScript數(shù)據(jù)類型:原始與參考 JavaScript數(shù)據(jù)類型:原始與參考 Jul 13, 2025 am 02:43 AM

JavaScript的數(shù)據(jù)類型分為原始類型和引用類型。原始類型包括string、number、boolean、null、undefined和symbol,其值不可變且賦值時復(fù)制副本,因此互不影響;引用類型如對象、數(shù)組和函數(shù)存儲的是內(nèi)存地址,指向同一對象的變量會相互影響。判斷類型可用typeof和instanceof,但需注意typeofnull的歷史問題。理解這兩類差異有助於編寫更穩(wěn)定可靠的代碼。

JavaScript時間對象,某人構(gòu)建了一個eactexe,在Google Chrome上更快的網(wǎng)站等等 JavaScript時間對象,某人構(gòu)建了一個eactexe,在Google Chrome上更快的網(wǎng)站等等 Jul 08, 2025 pm 02:27 PM

JavaScript開發(fā)者們,大家好!歡迎閱讀本週的JavaScript新聞!本週我們將重點關(guān)注:Oracle與Deno的商標(biāo)糾紛、新的JavaScript時間對象獲得瀏覽器支持、GoogleChrome的更新以及一些強(qiáng)大的開發(fā)者工具。讓我們開始吧! Oracle與Deno的商標(biāo)之爭Oracle試圖註冊“JavaScript”商標(biāo)的舉動引發(fā)爭議。 Node.js和Deno的創(chuàng)建者RyanDahl已提交請願書,要求取消該商標(biāo),他認(rèn)為JavaScript是一個開放標(biāo)準(zhǔn),不應(yīng)由Oracle

什麼是緩存API?如何與服務(wù)人員使用? 什麼是緩存API?如何與服務(wù)人員使用? Jul 08, 2025 am 02:43 AM

CacheAPI是瀏覽器提供的一種緩存網(wǎng)絡(luò)請求的工具,常與ServiceWorker配合使用,以提升網(wǎng)站性能和離線體驗。 1.它允許開發(fā)者手動存儲如腳本、樣式表、圖片等資源;2.可根據(jù)請求匹配緩存響應(yīng);3.支持刪除特定緩存或清空整個緩存;4.通過ServiceWorker監(jiān)聽fetch事件實現(xiàn)緩存優(yōu)先或網(wǎng)絡(luò)優(yōu)先等策略;5.常用於離線支持、加快重複訪問速度、預(yù)加載關(guān)鍵資源及後臺更新內(nèi)容;6.使用時需注意緩存版本控制、存儲限制及與HTTP緩存機(jī)制的區(qū)別。

處理諾言:鏈接,錯誤處理和承諾在JavaScript中 處理諾言:鏈接,錯誤處理和承諾在JavaScript中 Jul 08, 2025 am 02:40 AM

Promise是JavaScript中處理異步操作的核心機(jī)制,理解鍊式調(diào)用、錯誤處理和組合器是掌握其應(yīng)用的關(guān)鍵。 1.鍊式調(diào)用通過.then()返回新Promise實現(xiàn)異步流程串聯(lián),每個.then()接收上一步結(jié)果並可返回值或Promise;2.錯誤處理應(yīng)統(tǒng)一使用.catch()捕獲異常,避免靜默失敗,並可在catch中返回默認(rèn)值繼續(xù)流程;3.組合器如Promise.all()(全成功才成功)、Promise.race()(首個完成即返回)和Promise.allSettled()(等待所有完成)

利用Array.Prototype方法用於JavaScript中的數(shù)據(jù)操作 利用Array.Prototype方法用於JavaScript中的數(shù)據(jù)操作 Jul 06, 2025 am 02:36 AM

JavaScript數(shù)組內(nèi)置方法如.map()、.filter()和.reduce()可簡化數(shù)據(jù)處理;1).map()用於一對一轉(zhuǎn)換元素生成新數(shù)組;2).filter()按條件篩選元素;3).reduce()用於聚合數(shù)據(jù)為單一值;使用時應(yīng)避免誤用導(dǎo)致副作用或性能問題。

JS綜述:深入研究JavaScript事件循環(huán) JS綜述:深入研究JavaScript事件循環(huán) Jul 08, 2025 am 02:24 AM

JavaScript的事件循環(huán)通過協(xié)調(diào)調(diào)用棧、WebAPI和任務(wù)隊列來管理異步操作。 1.調(diào)用棧執(zhí)行同步代碼,遇到異步任務(wù)時交由WebAPI處理;2.WebAPI在後臺完成任務(wù)後將回調(diào)放入相應(yīng)的隊列(宏任務(wù)或微任務(wù));3.事件循環(huán)檢查調(diào)用棧是否為空,若為空則從隊列中取出回調(diào)推入調(diào)用棧執(zhí)行;4.微任務(wù)(如Promise.then)優(yōu)先於宏任務(wù)(如setTimeout)執(zhí)行;5.理解事件循環(huán)有助於避免阻塞主線程並優(yōu)化代碼執(zhí)行順序。

了解事件在JavaScript DOM事件中冒泡和捕獲 了解事件在JavaScript DOM事件中冒泡和捕獲 Jul 08, 2025 am 02:36 AM

事件冒泡是從目標(biāo)元素向外傳播到祖先節(jié)點,事件捕獲則是從外層向內(nèi)傳播到目標(biāo)元素。 1.事件冒泡:點擊子元素後,事件依次向上觸發(fā)父級元素的監(jiān)聽器,例如點擊按鈕後先輸出Childclicked,再輸出Parentclicked。 2.事件捕獲:設(shè)置第三個參數(shù)為true,使監(jiān)聽器在捕獲階段執(zhí)行,如點擊按鈕前先觸發(fā)父元素的捕獲監(jiān)聽器。 3.實際用途包括統(tǒng)一管理子元素事件、攔截預(yù)處理和性能優(yōu)化。 4.DOM事件流分為捕獲、目標(biāo)和冒泡三個階段,默認(rèn)監(jiān)聽器在冒泡階段執(zhí)行。

See all articles