Rumah >hujung hadapan web >tutorial js >Cara Melaksanakan Menatal Lancar di Vanilla JavaScript

Cara Melaksanakan Menatal Lancar di Vanilla JavaScript

William Shakespeare
William Shakespeareasal
2025-02-18 10:49:09492semak imbas

How to Implement Smooth Scrolling in Vanilla JavaScript

mata teras

    Gunakan Perpustakaan Jump.js untuk melaksanakan JavaScript Smooth Scrolling asli, memudahkan animasi menatal tanpa kebergantungan luaran.
  • Ubah suai Jump.js Kod Asal untuk menukarnya dari ES6 hingga ES5 untuk memastikan keserasian yang lebih luas dengan pelayar yang berbeza.
  • Gunakan kaedah
  • untuk melakukan kemas kini animasi yang lancar, mengoptimumkan prestasi dan memberikan pengalaman pengguna yang lebih lancar. requestAnimationFrame
  • Melaksanakan JavaScript tersuai untuk memintas tingkah laku pautan dalam halaman lalai, dan menggantikan lompatan tiba-tiba dengan animasi menatal yang lancar.
  • CSS bersepadu
  • Ciri -ciri untuk menyokong skrol lancar asli dalam penyemak imbas yang mengiktiraf ciri ini, dan mekanisme sandaran JavaScript disediakan jika penyemak imbas tidak menyokongnya. scroll-behavior
  • Memastikan kebolehcapaian dengan menetapkan fokus untuk menargetkan elemen selepas menatal, menangani isu -isu yang berpotensi dengan navigasi papan kekunci, dan meningkatkan kebolehgunaan untuk semua pengguna.

Artikel ini dikaji semula oleh Adrian Sandu, Chris Perry, Jérémy Heleine dan Mallory Van Achterberg. Terima kasih kepada semua pengulas rakan sebaya SitePoint untuk mendapatkan kandungan terbaik di sitepoint!

Smooth Scrolling adalah mod antara muka pengguna yang secara beransur-ansur meningkatkan pengalaman navigasi dalam halaman lalai, menghidupkan kedudukan dalam kotak tatal (viewport atau elemen scrollable), dari kedudukan pautan pengaktif ditunjukkan dalam klip.

Ini bukan sesuatu yang baru dan telah menjadi corak yang diketahui selama bertahun -tahun, contohnya, lihat artikel SitePoint ini sejak tahun 2003! Dengan cara ini, jawatan ini secara historis berharga kerana ia menunjukkan bagaimana pengaturcaraan JavaScript, terutamanya DOM, telah berubah dan berkembang selama bertahun-tahun, yang membolehkan pembangunan penyelesaian JavaScript asli yang lebih mudah.

Dalam ekosistem jQuery, terdapat banyak pelaksanaan corak ini, yang boleh dilaksanakan secara langsung dengan jQuery atau dengan pemalam, tetapi dalam artikel ini, kami berminat dengan penyelesaian JavaScript tulen. Khususnya, kami akan meneroka dan menggunakan perpustakaan Jump.js.

Selepas memperkenalkan gambaran keseluruhan perpustakaan dan ciri -cirinya, kami akan membuat beberapa perubahan kepada kod asal yang sesuai dengan keperluan kami. Dalam proses ini, kami akan mengkaji beberapa kemahiran bahasa JavaScript teras seperti fungsi dan penutupan. Kami kemudian akan membuat halaman HTML untuk menguji tingkah laku menatal yang lancar dan kemudian melaksanakannya sebagai skrip tersuai. Sokongan untuk menatal lancar asli di CSS akan ditambah (jika ada), dan akhirnya kami akan membuat beberapa pemerhatian mengenai sejarah navigasi penyemak imbas.

Ini adalah demonstrasi terakhir yang akan kita buat:

Lihat pena skrol lancar untuk SitePoint (@SitePoint) pada CodePen.

Kod sumber lengkap boleh didapati di GitHub.

jump.js

jump.js ditulis dalam ES6 JavaScript asli dan tidak mempunyai kebergantungan luaran. Ia adalah utiliti kecil dengan hanya kira -kira 42 SLOC, tetapi pakej pengurangan yang disediakan adalah kira -kira 2.67 kb dalam saiz, kerana ia perlu diterjemahkan. Demo disediakan di halaman Projek GitHub.

seperti namanya, ia hanya memberikan lompatan: perubahan animasi kedudukan bar tatal dari nilai semasa ke kedudukan sasaran, yang ditentukan dengan menyediakan jarak dalam bentuk elemen DOM, pemilih CSS, atau positif atau negatif Nilai berangka. Ini bermakna bahawa dalam pelaksanaan mod tatal lancar, kita perlu melakukan pautan merampas diri kita sendiri. Lihat bahagian di bawah untuk maklumat lanjut.

Sila ambil perhatian bahawa hanya menatal menegak viewport yang disokong pada masa ini.

kita boleh mengkonfigurasi melompat dengan beberapa pilihan, seperti tempoh (parameter ini diperlukan), fungsi pelonggaran, dan panggilan balik yang dicetuskan pada akhir animasi. Kami akan melihat aplikasi praktikal mereka kemudian dalam demo. Lihat dokumentasi untuk butiran lengkap.

jump.js berjalan pada pelayar "moden" tanpa sebarang masalah, termasuk Internet Explorer versi 10 atau lebih baru. Sekali lagi, sila rujuk dokumentasi untuk senarai lengkap penyemak imbas yang disokong. Dengan Polyfill RequestAnimationFrame yang sesuai, ia juga boleh dijalankan pada pelayar yang lebih tua.

Cepat memahami di sebalik skrin

Secara dalaman, kod sumber jump.js menggunakan kaedah RequestAnimationFrame objek tetingkap untuk mengatur kedudukan kedudukan menegak Viewport yang dikemas kini dalam setiap bingkai animasi tatal. Kemas kini ini dicapai dengan lulus nilai kedudukan seterusnya yang dikira menggunakan fungsi pelonggaran ke kaedah window.scrollto. Lihat kod sumber untuk butiran lengkap.

Beberapa penyesuaian

Kami akan membuat beberapa perubahan kecil pada kod asal sebelum menyelidiki demo untuk menunjukkan bagaimana lompat.js digunakan, tetapi itu tidak akan mengubah cara ia berfungsi secara dalaman.

Kod sumber ditulis dalam ES6 dan perlu digunakan dengan alat binaan JavaScript untuk menterjemahkan dan modul bundle. Ini mungkin terlalu banyak untuk beberapa projek, jadi kami akan menggunakan beberapa refactoring untuk menukar kod ke ES5 untuk digunakan di mana sahaja.

Pertama, mari kita keluarkan sintaks dan ciri ES6. Skrip mentakrifkan kelas 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>

kita boleh menukarnya kepada ES5 "kelas" menggunakan pembina dan sekumpulan kaedah prototaip, tetapi ambil perhatian bahawa kita tidak memerlukan banyak contoh kelas ini, jadi singleton dilaksanakan dengan objek biasa literal adalah baik:

<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>

Selain memadam kelas, kita perlu membuat beberapa perubahan lain. Panggilan balik digunakan untuk mengemas kini kedudukan scrollbar dalam setiap bingkai, dan dalam kod asal ia dipanggil melalui fungsi Arrow ES6, pra-terikat ke singleton melompat pada permulaan. Kami kemudian membungkus fungsi pelonggaran lalai dalam fail sumber yang sama. Akhirnya, kami membungkus kod menggunakan IIFE (ekspresi fungsi panggilan segera) untuk mengelakkan pencemaran ruang nama. requestAnimationFrame

Sekarang kita boleh menggunakan langkah pembinaan semula yang lain, ambil perhatian bahawa dengan bantuan fungsi dan penutupan bersarang, kita hanya boleh menggunakan fungsi dan bukannya objek:

<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>
Singleton kini menjadi fungsi lompat yang akan dipanggil untuk menghidupkan menatal, gelung dan penghujungnya menjadi fungsi bersarang, dan sifat objek kini menjadi pemboleh ubah tempatan (penutupan). Kami tidak lagi memerlukan Iife, kerana sekarang semua kod itu dibalut dengan selamat.

sebagai langkah refactoring akhir, untuk mengelakkan pemeriksaan tetapan semula timestart berulang setiap kali panggilan balik gelung dipanggil, kali pertama requestanimationframe () dipanggil, kita akan lulus fungsi tanpa nama kepadanya, yang berfungsi sebelum memanggil Fungsi gelung menetapkan semula pembolehubah 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>

Nota sekali lagi bahawa semasa proses pembinaan semula, kod animasi tatal teras tidak berubah.

halaman ujian

Sekarang kita telah menyesuaikan skrip untuk memenuhi keperluan kita, kita bersedia untuk memasang demo ujian. Dalam bahagian ini, kami akan menulis halaman yang meningkatkan skrol lancar menggunakan skrip yang diterangkan dalam bahagian seterusnya.

Halaman ini mengandungi jadual kandungan (TOC) yang menunjukkan pautan ke dalam halaman di bahagian berikutnya dokumen, serta pautan lain ke TOC. Kami juga akan mencampur beberapa pautan luaran ke halaman lain. Ini adalah struktur asas halaman ini:

<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>

Di kepala, kami akan memasukkan beberapa peraturan CSS untuk menetapkan susun atur asas yang paling mudah, dan pada akhir tag badan, kami akan memasukkan dua fail JavaScript: yang pertama adalah versi refactored kami jump.js, dan yang terakhir Adakah skrip itu akan dibincangkan sekarang.

skrip utama

Ini adalah skrip yang akan meningkatkan pengalaman menatal halaman ujian menggunakan lompatan animasi dari versi lompat.js versi tersuai kami. Sudah tentu, kod ini juga akan ditulis dalam ES5 JavaScript.

mari kita gambaran ringkas tentang apa yang harus dilakukan: ia mesti rampas klik pada pautan dalam halaman, matikan tingkah laku lalai penyemak imbas (tiba -tiba melompat ke serpihan hash atribut HREF dari pautan klik yang klik pautan, dan menggantikannya dengan panggilan ke fungsi lompat kami (). Oleh itu, pertama sekali, anda perlu memantau klik pada pautan di halaman. Kita boleh melakukan ini dalam dua cara, menggunakan perwakilan acara atau melampirkan pengendali ke setiap pautan yang berkaitan.

Suruhanjaya Acara

Dalam kaedah pertama, kami menambah pendengar klik ke dokumen elemen.body. Dengan cara ini, setiap peristiwa klik mana -mana elemen di halaman akan menggelegak di sepanjang cawangan nenek moyangnya ke dalam pokok Dom sehingga sampai ke dokumen.body:

Sudah tentu, sekarang dalam pendengar acara berdaftar (onclick), kita perlu menyemak sasaran objek acara klik masuk untuk memeriksa sama ada ia berkaitan dengan elemen pautan di halaman. Ini boleh dilakukan dalam beberapa cara, jadi kita akan abstrak sebagai fungsi penolong isInpageLink (). Kami akan melihat mekanisme fungsi ini kemudian.
<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>

Jika klik masuk berada di pautan dalam halaman, kami akan menghentikan gelembung acara dan menyekat tindakan lalai yang berkaitan. Akhirnya, kami memanggil fungsi lompat, menyediakannya dengan pemilih hash elemen sasaran dan parameter untuk mengkonfigurasi animasi yang diperlukan.

ini adalah pengendali acara:

pengendali tunggal
<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>

Gunakan kaedah kedua untuk memantau klik pautan, melampirkan versi yang sedikit diubahsuai dari pengendali acara yang diterangkan di atas ke elemen pautan dalam setiap halaman, jadi tidak ada gelembung peristiwa:

<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>

Kami menanyakan semua unsur dan menggunakan tipu daya []. kaedah). Kami kemudian boleh menggunakan kaedah array untuk menapis pautan dalam halaman, menggunakan semula fungsi pembantu yang sama yang ditakrifkan di atas, dan akhirnya melampirkan pendengar ke elemen pautan yang selebihnya.

pengendali acara hampir sama seperti sebelumnya, tetapi sudah tentu kita tidak perlu menyemak sasaran klik:

<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>

Kaedah mana yang terbaik bergantung pada konteks penggunaan. Sebagai contoh, jika elemen pautan baru boleh ditambah secara dinamik selepas beban halaman awal, kita mesti menggunakan perwakilan acara.

Sekarang kita beralih kepada pelaksanaan IsInpageLink (), yang kami gunakan fungsi penolong ini dalam pengendali acara sebelumnya untuk ujian abstrak pautan dalam halaman. Seperti yang dapat kita lihat, fungsi ini mengambil nod DOM sebagai parameter dan mengembalikan nilai boolean untuk menunjukkan sama ada nod mewakili elemen pautan dalam halaman. Ia tidak mencukupi untuk memeriksa bahawa nod yang diluluskan adalah tag dan serpihan hash ditetapkan, kerana pautan mungkin menunjuk ke halaman lain, di mana tindakan penyemak imbas lalai tidak boleh dilumpuhkan. Oleh itu, kita periksa sama ada nilai "tolak" hash yang disimpan dalam harta href adalah sama dengan URL halaman:

<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 () adalah fungsi penolong lain, yang juga kami gunakan untuk menetapkan nilai pageArl berubah apabila skrip dimulakan:

<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>

Penyelesaian berasaskan rentetan ini dan pemangkasan serpihan hash berfungsi dengan baik walaupun pada URL dengan rentetan pertanyaan, kerana bahagian hash berada di belakangnya dalam struktur umum URL.

Seperti yang saya katakan sebelum ini, ini adalah satu cara yang mungkin untuk melaksanakan ujian ini. Sebagai contoh, artikel yang disebutkan pada permulaan tutorial ini menggunakan penyelesaian yang berbeza untuk melakukan perbandingan tahap komponen pautan HREFS ke objek lokasi.

Harus diingat bahawa kita menggunakan fungsi ini dalam kedua -dua kaedah langganan acara, tetapi dalam kaedah kedua kita menggunakannya sebagai penapis untuk unsur -unsur yang kita sudah tahu adalah tag, jadi pemeriksaan pertama harta tagname adalah berlebihan. Ini ditinggalkan kepada pembaca sebagai latihan.

Pertimbangan Kebolehcapaian

Buat masa ini, kod kami mudah terdedah kepada kesilapan yang diketahui (sebenarnya sepasang kesilapan yang tidak berkaitan yang mempengaruhi Blink/WebKit/KHTML dan satu yang mempengaruhi IE) yang mempengaruhi pengguna papan kekunci. Apabila melayari pautan TOC melalui kekunci TAB, mengaktifkan pautan akan lancar menatal ke bahagian yang dipilih, tetapi tumpuan akan kekal pada pautan. Ini bermakna apabila kekunci tab seterusnya ditekan, pengguna akan dihantar kembali ke TOC dan bukannya pautan pertama di bahagian pilihan mereka.

Untuk menyelesaikan masalah ini, kami akan menambah fungsi lain ke skrip utama:

<code>>
    <h1>></h1>Title>
    <nav> id="toc"></nav>
        <ul>></ul>
            <li>></li>
<a> href="https://www.php.cn/link/db8229562f80fbcc7d780f571e5974ec"></a>Section 1>>
            <li>></li>
<a> href="https://www.php.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="https://www.php.cn/link/e1b97c787a5677efa5eba575c41e8688"></a>a link to another page> ac turpis egestas. <a> href="https://www.php.cn/link/e1b97c787a5677efa5eba575c41e8688index.html#foo"></a>A link to another page, with an anchor> quam, feugiat vitae, ...>
        <a> href="https://www.php.cn/link/7421d74f57142680e679057ddc98edf5"></a>Back to TOC>
    >
     id="sect-2">
        <h2>></h2>Section 2>
        ...
    >
    ...
     src="jump.js">>
     src="script.js">>
>
</code>

ia akan berjalan dalam panggilan balik yang kita akan lulus ke fungsi lompat dan lulus hash unsur yang kita mahu tatal ke masa lalu:

<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>

Fungsi fungsi ini adalah untuk mendapatkan elemen DOM yang sepadan dengan nilai hash dan menguji sama ada ia sudah menjadi elemen yang dapat menerima fokus (seperti elemen anchor atau butang). Jika elemen tidak dapat menerima fokus secara lalai (seperti bekas kami), ia menetapkan harta tabIndexnya kepada -1 (membolehkan untuk menerima fokus secara programatik, tetapi tidak melalui papan kekunci). Fokus kemudian akan ditetapkan ke elemen itu, yang bermaksud bahawa Tab Kunci pengguna seterusnya menggerakkan fokus ke pautan yang tersedia seterusnya.

anda boleh melihat kod sumber penuh skrip utama di sini, dengan semua perubahan yang dibincangkan sebelumnya.

menyokong skrol lancar asli menggunakan CSS

Spesifikasi Modul Modul Model Objek CSS memperkenalkan harta baru untuk melaksanakan skrol lancar:

. scroll-behavior

Ia boleh mengambil dua nilai,

mewakili tatal seketika lalai, dan auto mewakili skrol animasi. Spesifikasi ini tidak menyediakan apa -apa cara untuk mengkonfigurasi animasi tatal, seperti fungsi tempoh dan masa (pelonggaran). smooth

Bolehkah saya menggunakan tingkah laku CSS-scroll? Data dari Caniuse.com menunjukkan sokongan fungsi tingkah laku CSS-scroll oleh pelayar utama.

Malangnya, pada masa penulisan, sokongan sangat terhad. Di Chrome, ciri ini sedang dibangunkan dan boleh dilaksanakan sebahagiannya dengan membolehkannya dalam skrin Chrome: // Flags. Harta CSS belum dilaksanakan, jadi tatal lancar pada klik pautan tidak berfungsi.

Bagaimanapun, dengan membuat perubahan kecil pada skrip utama, kita dapat mengesan jika ciri ini tersedia dalam ejen pengguna dan elakkan menjalankan kod kami yang lain. Untuk menggunakan tatal lancar di viewport, kami menggunakan atribut CSS ke elemen akar HTML (tetapi dalam halaman ujian kami, kami juga boleh memohon kepada elemen badan):

<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>
Kemudian, kami menambah ujian pengesanan fungsional yang mudah pada permulaan skrip:

<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>
Jadi jika penyemak imbas menyokong menatal asli, skrip tidak akan melakukan apa -apa dan keluar, jika tidak, ia akan terus melaksanakan seperti sebelumnya dan penyemak imbas akan mengabaikan sifat CSS yang tidak disokong.

Kesimpulan

Selain daripada pelaksanaan kesederhanaan dan prestasi, satu lagi kelebihan penyelesaian CSS yang baru dibincangkan adalah bahawa tingkah laku sejarah penyemak imbas adalah konsisten dengan tingkah laku yang dialami ketika menggunakan menatal lalai penyemak imbas. Setiap lompatan dalam halaman ditolak ke timbunan sejarah penyemak imbas, dan kami boleh melayari lompatan ini ke belakang dan sebagainya dengan butang yang sepadan (tetapi sekurang-kurangnya tidak ada skrol lancar di Firefox).

Dalam kod yang kami tulis (kini kita boleh memikirkannya sebagai skema sandaran apabila sokongan CSS tidak tersedia), kami tidak menganggap tingkah laku skrip berbanding sejarah penyemak imbas. Bergantung pada konteks dan kes penggunaan, ini mungkin atau mungkin bukan sesuatu yang menarik, tetapi jika kita fikir skrip harus meningkatkan pengalaman menatal lalai, maka kita harus mengharapkan tingkah laku yang konsisten, seperti CSS.

Soalan Lazim (Soalan Lazim) pada Smooth Scrolling dengan JavaScript asli

Bagaimana untuk mencapai tatal lancar menggunakan JavaScript asli tanpa menggunakan mana -mana perpustakaan?

Gunakan JavaScript asli untuk mencapai tatal lancar tanpa menggunakan mana -mana perpustakaan sangat mudah. Anda boleh menggunakan kaedah window.scrollTo dan tetapkan pilihan behavior untuk smooth. Kaedah ini menatal dokumen dalam tetingkap dengan beberapa kali. Berikut adalah contoh mudah:

<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>

Dalam contoh ini, apabila anda mengklik elemen dengan kelas your-element, halaman akan tatal dengan lancar ke bahagian atas.

Mengapa tatal lancar saya tidak berfungsi di safari?

fungsi tatal lancar menggunakan kaedah scrollTo dan menetapkan pilihan behavior untuk smooth tidak disokong dalam safari. Untuk menjadikannya berfungsi, anda boleh menggunakan polyfill, seperti smoothscroll-polyfill. Ini akan membolehkan menatal lancar dalam penyemak imbas yang tidak menyokongnya secara asli.

Bagaimana untuk menatal dengan lancar ke elemen tertentu?

untuk menatal dengan lancar ke elemen tertentu, anda boleh menggunakan kaedah Element.scrollIntoView dan tetapkan pilihan behavior untuk smooth. Berikut adalah contoh:

<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>

Dalam contoh ini, apabila anda mengklik elemen dengan kelas your-element, halaman akan lancar menatal ke elemen dengan kelas target-element.

Bolehkah saya mengawal kelajuan skrol licin?

Kelajuan skrol licin tidak boleh dikawal secara langsung kerana ia dikendalikan oleh penyemak imbas. Walau bagaimanapun, anda boleh menggunakan window.requestAnimationFrame untuk membuat fungsi skrol lancar tersuai untuk kawalan yang lebih baik ke atas animasi menatal, termasuk kelajuannya.

Bagaimana untuk mencapai tatal lancar mendatar?

Anda boleh mencapai skrol lancar mendatar dengan cara yang sama dengan menatal lancar menegak. Kaedah window.scrollTo dan Element.scrollIntoView juga menerima pilihan left untuk menentukan kedudukan mendatar untuk menatal ke. Berikut adalah contoh:

<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>

Ini akan menjadikan dokumen itu lancar tatal 100 piksel ke kanan.

Bagaimana untuk menghentikan animasi menatal yang lancar?

Animasi menatal yang lancar tidak boleh dihentikan secara langsung kerana ia dikendalikan oleh penyemak imbas. Walau bagaimanapun, jika anda menggunakan fungsi skrol lancar tersuai, anda boleh menggunakan window.cancelAnimationFrame untuk membatalkan bingkai animasi untuk menghentikan animasi.

Bagaimana untuk mencapai tatal lancar dengan tajuk tetap?

Untuk mencapai tatal lancar dengan tajuk tetap, anda perlu menyesuaikan kedudukan tatal untuk mengambil kira ketinggian tajuk. Anda boleh melakukan ini dengan menolak ketinggian tajuk dari kedudukan tatal sasaran.

Bagaimana untuk mencapai tatal lancar untuk pautan utama?

3 Berikut adalah contoh:

Element.scrollIntoView

Ini akan lancar menatal semua pautan utama pada halaman ke elemen sasarannya.
<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>

Bagaimana menggunakan navigasi papan kekunci untuk mencapai skrol lancar?

Menggunakan navigasi papan kekunci untuk mencapai skrol lancar lebih rumit kerana ia memerlukan memintas peristiwa papan kekunci dan dokumen menatal secara manual. Anda boleh melakukan ini dengan menambahkan pendengar acara ke acara keydown dan menatal dokumen dengan lancar menggunakan kaedah window.scrollTo.

Bagaimana untuk menguji keserasian pelaksanaan tatal lancar saya?

Anda boleh menggunakan alat dalam talian seperti BrowserStack untuk menguji keserasian skrol lancar. Alat ini membolehkan anda menguji laman web anda pada pelayar yang berbeza dan pada peranti yang berbeza untuk memastikan pelaksanaan anda berfungsi dengan baik di semua persekitaran.

Atas ialah kandungan terperinci Cara Melaksanakan Menatal Lancar di Vanilla JavaScript. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn