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()) -...