virtual_scroll

Kali ini kita akan bermain dengan salah satu teknik penyajian data. Teknik ini hanya sebagai alternatif lain dari teknik Ajax Pagination dan Infinite Scroll. Teknik seperti ini banyak contohnya di luaran sana, dan di sini penulis hanya menjelaskan teknik ini bekerja dengan bahasa yang mungkin lebih mudah dipahami dan tidak serumit apa yang kita bayangkan. Di akhir artikel, penulis sertakan contoh implementasinya.

Berbeda dengan teknik pagination pada umumnya yang menggunakan batasan angka yang sudah ditentukan, mekanisme pada Virtual Scroll cenderung menggunakan kalkulasi posisi scrollbar sebagai batasan dalam menampilkan data. Penggunaan teknik ini akan memberikan kesan cukup nyaman kepada user karena user tidak diharuskan menekan nomor halaman. Keuntungan lain dari teknik ini jika dibandingkan dengan Infinite Scroll adalah jumlah elemen yang dirender hanya sebatas ukuran viewport, sehingga mengurangi penggunaan memori. Namun, ada sedikit kelemahan dari teknik ini yaitu kita harus menentukan terlebih dahulu tinggi setiap elemen, sehingga akan cukup sulit diterapkan pada data dengan layout dinamis (auto height).

Data Preparation

Untuk memulai permainan ini, kita akan menyediakan data yang akan diproses. Sebagai contoh, di sini kita akan menyediakan 10 ribu data dalam bentuk array. Pada kenyataanya, data ini biasanya diperoleh dari database atau data-provider lainnya.

var data = [], i;

for (i = 1; i <= 10000; i++) {
    data.push({ id: i, text: 'data ke - ' + 1 });
}

Layout Preparation

Kita tidak bisa serta merta menerapkan teknik Virtual Scrolling tanpa memikirkan layout yang akan digunakan. Layout memiliki peran utama karena menentukan pembentukan scrollbar. Ada beberapa pilihan desain layout yang bisa digunakan antara lain:

Desain #1

Setiap elemen data diletakan langsung sebagai children dari viewport dengan posisi mengambang (floating). Agar terbentuk scrollbar, maka kita membutuhkan sebuah element tambahan berupa stub atau pengganjal vertikal dengan tinggi sebanyak data dikalikan dengan tinggi setiap elemen. Elemen stub ini diposisikan tersembunyi dari tampilan viewport agar tidak menggangu tampilan data. Bagian yang cukup sulit dari layout ini adalah kita harus menentukan posisi top untuk setiap elemen, karena elemen dibuat mengambang. Jadi ketika posisi scrollbar berubah, posisi top setiap elemen harus dihitung.

stub

Desain #2

Elemen data tidak diletakan langsung ke dalam viewport, melainkan di-wrap dalam sebuah elemen wrapper. Nantinya, elemen wrapper ini kita atur padding-nya sesuai dengan perhitungan total data sehingga akan membentuk scrollbar. Desain layout seperti ini rasanya lebih mudah dari pada desain #1. Jadi, kita akan coba menggunakan layout ini.

padding

How to Calculate ?

Kemudian setelah menentukan tipe layout yang digunakan, kita akan menghitung batas awal dan akhir data yang akan ditampilkan.

Agar terkesan tampilan data tidak terpotong dibagian atas atau bawah viewport ketika scrollbar digerakan naik turun, kita akan memberikan opsi nilai buffering yaitu elemen yang di-render di luar tampilan viewport. Jadi faktor perhitungan terdiri dari: tinggi viewport, tinggi setiap element (visualisasi data), posisi wrapper, dan nilai buffer. Dari perhitungan ini nantinya akan di dapat dua nilai penting yaitu: nilai slicing dan nilai padding.

function render (data) {
    var $wrapper = $('#wrapper');
    var height = 30;
    var buffer = 5 * height;
    var viewHeight = $('#viewport').height();
    var offset = 0 - $wrapper.position().top;
    var beginPixel = offset - buffer;
    var endPixel = beginPixel + buffer + viewHeight;

    beginPixel = beginPixel &lt; 0 ? 0 : beginPixel;

    var beginRow = Math.floor(beginPixel / height);
    var endRow = Math.ceil(endPixel / height);
    var padTop = height * beginRow;
    var padBtm = height * data.slice(endRow).length;

    $wrapper.css({
        paddingTop: padTop,
        paddingBottom: padBtm
    });

    var visibleRows = data.slice(beginRow, endRow);
    var items = [];

    $.each(visibleRows, function(i, row){
        items.push('
<div>'+row.text+'</div>

');
    });

    $wrapper.html(items);
}

Scrolling Event

Satu hal yang tidak boleh ketinggalan juga adalah menentukan kapan elemen data dirender. Sesuai dengan namanya, elemen data dirender ketika terjadi perubahan posisi scrollbar. Perubahan ini bisa terjadi karena user menggerakan scroll button pada mouse, menekan navigasi pada keyboard, melakukan touchmove (pada perangkat mobile) atau menggerakan slider scrollbar. Kita dapat menjalankan fungsi render() pada saat kejadian – kejadian tersebut. Untuk itu, kita harus mendaftarkan event listener pada saat terjadi scroll event.

$('#viewport').on('scroll', function(){
    render(data);
});

Tapi, yang perlu diperhatikan adalah event scroll terjadi sangat intensif. Jadi, sekali proses scrolling bisa saja fungsi render ini dipanggil ratusan kali dan itu tidak baik, karena memaksa cpu bekerja ekstra keras. Sehingga diperlukan mekanisme untuk merubah scrolling time sehingga fungsi render hanya dipanggil beberapa kali saja atau cukup sekali dalam setiap proses scroll. Mekanisme ini bisa disebut dengan throttling atau debouncing (defer) tergantung tekniknya. Kita dapat memanfaatkan beberapa librari yang menyediakan fungsi – fungsi tersebut.

// render akan dipanggil setiap 30 milidetik
// dalam proses scroll
$('#viewport').on('scroll', $.throttle(30, function(){
    render(data);
}));

// render akan dipanggil sekali setelah 30 milidetik
// ketika proses scroll berhenti
$('#viewport').on('scroll', $.debounce(30, function(){
    render(data);
}));

Atau, kita dapat membuat sendiri fungsi sederhananya.

function debounce(delay, handler) {
    var timer;
    return function() {
        timer = setTimeout(function(){
            clearTimeout(timer);
            timer = null;
            handler();
        }, delay);
    };
}

$('#viewport').on('scroll', debounce(30, function(){
    render(data);
}));

Dan, yang perlu perhatikan adalah dalam menggunakan fungsi penahan ini. Fungsi ini harus dijadikan sebagai event listener.

// hindari cara penggunaan seperti ini
// hal ini hanya akan menciptakan banyak antrian
// bukan merubah scrolling time
$(‘#viewport’).on(‘scroll’, function(){
$.debounce(30, function(){
render(data);
})();
});
[/code

Untuk lebih jelasnya, penulis buatkan contoh implementasi dari uraian diatas:
https://plnkr.co/edit/DIyn9JJeQlkkAy9wh8r8?p=preview