JSFiddle - React, Tailwind, and code Playground
Vue Virtual Scroller with sort
by Christopher O
HTML
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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>
<!-- https://github.com/marak/Faker.js/ -->
<div id="app">
<!-- <div id="lefty"> -->
<virtual-scroller :items="items" :renderers="renderers" item-height="22"></virtual-scroller>
<!-- </div> -->
</div>
CSS
body {
font-family: sans-serif;
/* position: relative; */
}
#lefty {
/* position: relative;
width: 200px;
height: 300px;
overflow-y: scroll;
overflow-x: none;
border: 1px solid red;
font-size: 10px;
color: black; */
}
.virtual-scroller {
/* overflow: auto; */
/* position: relative; */
overflow-y: scroll;
overflow-x: none;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
height: 90%;
width: 250px;
border: 1px solid blue;
}
.item-container {
box-sizing: border-box;
}
.item {
height: 22px;
padding: 12px;
box-sizing: border-box;
cursor: pointer;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
/* border: 1px solid green; */
white-space: nowrap;
}
.letter {
text-transform: uppercase;
color: grey;
font-weight:bold;
}
.item:nth-of-type(odd) {
background: #bbb;
}
JavaScript
console.clear();
Vue.component('virtual-scroller', {
props: ['items', 'renderers', 'itemHeight'],
template: `<div class="virtual-scroller" @scroll="updateVisibleItems">
<div class="item-container" ref="container">
<div class="items">
<component class="item" v-for="item in visibleItems" :is="renderers[item.type]" :item="item"></component>
</div>
</div>
</div>`,
data: () => ({
visibleItems: [],
containerStyle: null,
}),
watch: {
items(val) {
this.l = this.items.length;
this.updateVisibleItems()
}
},
methods: {
updateVisibleItems() {
const el = this.$el;
const scrollTop = el.scrollTop;
const scrollBottom = scrollTop + el.clientHeight;
let startIndex = Math.floor(scrollTop / this.itemHeight) - 3;
if (startIndex < 0) {
startIndex = 0;
}
let endIndex = Math.ceil(scrollBottom / this.itemHeight) + 3;
if (endIndex >= this.l) {
endIndex = this.l;
}
const container = this.$refs.container;
container.style.paddingTop = startIndex * this.itemHeight + 'px';
container.style.height = this.l * this.itemHeight + 'px';
this.visibleItems = this.items.slice(startIndex, endIndex);
this.$forceUpdate();
},
},
created() {
this.l = this.items.length;
},
mounted() {
this.updateVisibleItems();
},
}); // END Vue
const renderers = {
letter: {
props: ['item'],
template: `<div class="letter">({{item.index}}) {{item.value}}</div>`,
},
person: {
props: ['item'],
template: `<div class="person" @click="edit">({{item.index}}) {{item.value.name}}</div>`,
methods: {
edit() {
this.item.value.name += '*';
},
},
},
}
const count = 10000;
console.log('Generating ' + count + ' items...');
let time = new Date().getTime(),
time2;
const items = Object.freeze(getData(count));
console.log('Generated ' + items.length + ' in ' + ((time2 = new Date().getTime()) -...