Virtual scroller in Vue.js
by Edward
HTML
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/lodash/4.16.6/lodash.min.js"></script>
<script src="https://cdn.rawgit.com/Marak/faker.js/master/examples/browser/js/faker.js"></script>
<div id="app">
<virtual-scroller :items="items" :renderers="renderers" item-height="42"></virtual-scroller>
</div>
CSS
body {
font-family: sans-serif;
}
.virtual-scroller {
overflow: auto;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.item-container {
box-sizing: border-box;
}
.item {
height: 42px;
padding: 12px;
box-sizing: border-box;
cursor: pointer;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
.letter {
text-transform: uppercase;
color: grey;
}
JavaScript
function getData(count) {
const raw = {}
const alphabet = 'abcdefghijklmnopqrstuvwxyz'.split('')
for(var l of alphabet) {
raw[l] = []
}
for(var i = 0; i < count; i++) {
const item = {
name: faker.name.findName(),
}
const letter = item.name.charAt(0).toLowerCase()
raw[letter].push(item)
}
const data = []
let index = 0
for(var l of alphabet) {
raw[l] = _.sortBy(raw[l], 'name')
data.push({
index: index++,
type: 'letter',
value: l,
})
for(var item of raw[l]) {
data.push({
index: index++,
type: 'person',
value: item,
sex: 'f',
})
}
}
return data;
}
Vue.component('virtual-scroller', {
props: ['items', 'renderers', 'itemHeight'],
template: `<div class="virtual-scroller" @scroll="updateVisibleItems">
<div class="item-container" :style="containerStyle">
<div class="items">
<div class="item" v-for="item in visibleItems" :key="item.index">
<component :is="renderers[item.type]" :item="item"></component>
</div>
</div>
</div>
</div>`,
data: () => ({
visibleItems: [],
containerStyle: null,
itemsStyle: null,
}),
methods: {
updateVisibleItems() {
const el = this.$el;
const scroll = {
top: el.scrollTop,
bottom: el.scrollTop + el.clientHeight,
}
const startIndex = Math.floor(scroll.top/this.itemHeight)
const endIndex = Math.ceil(scroll.bottom/this.itemHeight)
this.visibleItems = this.items.slice(startIndex, endIndex)
this.containerStyle = {
paddingTop: startIndex * this.itemHeight + 'px',
height: this.items.length * this.itemHeight + 'px',
}
console.log(items.length, startIndex, endIndex)
},
},
mounted() {
this._lastUpdate = new Date().getTime()
this.updateVisibleItems()
},
updated() {
const time = new Date().getTime()
console.log('updated', time - this._lastUpdate)
...