Blazing fast scrolling of any amount of data | Live demo | Video demo
For Vue 2 support, see here
⚡️ Blazing fast scrolling for any amount of data
Home Page: https://vue-virtual-scroller-demo.netlify.app
Blazing fast scrolling of any amount of data | Live demo | Video demo
For Vue 2 support, see here
Codepen: https://codepen.io/daniandl/pen/opZGMd?editors=1111
I hardcoded the css height of the items to 60px and set item-height to 60, but the scroll container isnt filled.
The desired result is somewhat close to when setting the item-height prop to 1. Where it fills the entire container and appends when scrolling + node recycling.
Why does it display this way when passing the proper item height?
Can I make a function that tracks the height of the item and dynamically change the item-height prop? (once the proper height displays correctly)
Thanks!
I've installed vue-virtual-scroller :
npm-install vue-virtual-scroller --save
I've imported it and ready to build, but webpack build gives me:
ERROR in ./~/vue-virtual-scroller/index.js
Module parse failed: U:\app\node_modules\vue-virtual-scroller\index.js Unexpected token (1:20)
You may need an appropriate loader to handle this file type.
| export default from './dist/vue-virtual-scroller'
| export * from './dist/vue-virtual-scroller'
| import './dist/vue-virtual-scroller.css'
@ ./~/babel-loader/lib!./~/vue-loader/lib/selector.js?type=script&index=0!./wwwroot/js/app/subapp/components/sidebar/ListModal.vue 41:0-55
@ ./wwwroot/js/app/subapp/components/sidebar/ListModal.vue
@ ./~/babel-loader/lib!./~/vue-loader/lib/selector.js?type=script&index=0!./wwwroot/js/app/subapp/components/sidebar/InfoPanel.vue
@ ./wwwroot/js/app/subapp/components/sidebar/InfoPanel.vue
@ ./~/babel-loader/lib!./~/vue-loader/lib/selector.js?type=script&index=0!./wwwroot/js/app/subapp/components/sidebar/AppSidebar.vue
@ ./wwwroot/js/app/subapp/components/sidebar/AppSidebar.vue
@ ./~/babel-loader/lib!./~/vue-loader/lib/selector.js?type=script&index=0!./wwwroot/js/app/subapp/components/App.vue
@ ./wwwroot/js/app/subapp/components/App.vue
@ ./wwwroot/js/app/subapp/index.js
My webpack.config is as follows:
var rootPath = path.resolve(__dirname, './wwwroot');
module.exports = {
entry: {
'subapp': './wwwroot/js/app/subapp/index.js'
},
output: {
path: path.resolve(rootPath, './dist'),
publicPath: path.resolve(rootPath, './dist'),
filename: '[name].js'
},
resolve: {
extensions: ['', '.js', '.json']
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
extractCSS: true,
postcss: [require('autoprefixer')()]
}
},
{
test: /\.css$/,
use: [
{ loader: 'vue-style-loader' },
{ loader: 'css-loader' }
]
},
{
test: /.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['es2015', 'stage-0', 'env']
}
},
{
test: /\.(png|jpg|gif|svg)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
},
{
loader: 'url-loader',
options: {
limit: 8192,
name: '[name].[ext]?[hash]'
}
}
]
}
]
},
plugins: [
new ExtractTextPlugin('[name].css')
]
}
And my .babelrc:
{
"presets": [
["env", { "modules": false }],
["es2015", { "modules": false }],
"stage-0"
],
"plugins": [ "transform-object-rest-spread"]
}
Any ideas on what I'm missing? I installed and added the es2015 and stage-0 to .babelrc and webpack.config, looks like it can't handle the export/import es6 syntax
Got this on demo page:
https://akryum.github.io/vue-virtual-scroller/
This happens when scrolling up and down rapidly.
Hi! Should not the scroller support keyboard scrolling? page, arrows? or it is like that by design?
If the keyboard navigation works, it is very useful component.
Hi,
I noticed that when using page-mode and scrolling, the list is not being rendered, it started from commit 3596071.
To reproduce, you can use docs-src and just add page-mode to recycle-list, and change .wrapper class to have overflow:scroll (instead of hidden).
because overflow:auto
will the splash screen on scrolling
I see that poolSize is calculated based on the visible screen height, is there a way to increase it ?
the use case is, when scrolling fast, I can see for a split second, a white area where rows should appear, I am guessing it's because the browser didn't have enough time to move the existing rows to that location.
It's not critical, We are already seeing major performance boost thanks to this great library.
Hello,
First off, thank you so much for creating and maintaining this library!
I'm currently having an issue where I'm trying to present > 10k items and making the scroll as smooth as possible. Looking at my output, I notice that for each item, a DOM element is created which contributes greatly to the lagginess in scroll (but still, your library makes the scroll much faster).
I contrast this with the demo you have on the README which was able to actually 're-use' or 'replace' elements such that scrolling basically updates the elements in place with the right data, and it's not really scrolling through numerous DOM elements.
I've tried using renderers and slot-scopes and for some reason, I can't seem to get the functionality that you have in your demo to show up.
Is there a chance you can share your demo page code?
In addition, I implement it in a way where the data to be rendered would have to first be queried from the server, and then passed on to another componenet which then utilizes the tag. I'm wondering if any of these steps may be causing an issue in the replacement element behaviour.
Thanks in advance!
Hi,
We are using this library, and we have a long list of rows,
If we modify some of the values that each row is using, it's not updated as expected.
If we scroll down, let the pool create new items, and then scroll back, the row is updated.
It looks like after the initial rows are rendered, they are not updated anymore.
It worked on version 0.10.1 and stopped working on 0.10.2.
Could be because of
"Some unnecessary updates are now skipped."
Another piece of info: the key is not modified.
How can i make table thead fixed ?
Hi! I'm trying to use this package in my Nuxt-based project. I've tried both local component import and global plugin import. Both are failing with this error:
ERROR Failed to compile with 1 errors 11:34:07 AM
error in ./~/vue-virtual-scroller/index.js
Module parse failed: ...myproject/frontend/node_modules/vue-virtual-scroller/index.js Unexpected token (1:20)
You may need an appropriate loader to handle this file type.
| export default from './dist/vue-virtual-scroller'
| export * from './dist/vue-virtual-scroller'
| import './dist/vue-virtual-scroller.css'
I've also tried to import a component directly from package's src
folder. Then it fails with a similar error, but mentioning vue-observe-visibility
package.
What bothers me is that I can't figure out which import exactly is failing, so I can't figure out which loader am I supposed to install. Both vue and style loaders are, obviously, installed. Also, my text editor underlines the first reexporting default line red, so I guess it may be relevant.
I'm integrating this with Meteor to fetch items on-demand from the database.
When it emits an update, I update the skip and limit of the subscription.
I created a pull request here: #22
Hi,
From what I understand, functional components are cheap to create, but don't really support updates, and they are actually re-created when changed, so it seems counter intuitive to use them with recycle-list.
Is that correct ?
"You need to set the size of the virtual-scroller element and the items elements (for example, with CSS). All items should have the same height to prevent display glitches."
On the hunt for a solution with variable height components. Using templates and even looking at a RecyclerView with tombstones is fine and dandy till you are confronted with the myriad of various sized components often found in a feed.
Still trying to gather best practices regarding variable height implementation. Wonder if you have any unique takes or are interested in adding the functionality here. Calculating the height of all indexed items in an array pre-render seems to be a method, a getRowHeight callback another option but neither are ideal.
Hi,
Can you elaborate what optimizations you did in the 0.10.6 version ?
It's nice to learn new tricks.
I tried reading the code, but couldn't quite understand.
I want to use BEM syntax for CSS classes so it would be nice to set a class for the
<virtual-scroller content-tag="ul" :items="items" item-height="70">
<template scope="props">
<li class="o-list__item" :key="props.itemKey">
…
</li>
</template>
</virtual-scroller>
Support head
slot, so we can insert something before the content tag (such as a table head).
<virtual-scroller class="scroller" :items="options" item-height="30" container-tag="table" content-tag="tbody" page-mode>
<thead slot="head">
<th>name</th>
<th>alias</th>
</thead>
<template scope="option">
<tr :key="option.item.id">
<td>{{option.item.name}}</td>
<td>{{option.item.alias}}</td>
</tr>
</template>
</virtual-scroller>
Could you please provide an example of this? Should I use slots? If so there only seems to be an after-container
slot with recycle-list
. So not sure how to get something like a position: sticky
element in there.
Thanks,
Thomas
i have an api which contains many rows, but returns the rows in chunks of 100.
how can i use the virtual scroller AND load the next pages only when they are going to be in view?
Hello, and thanks for all your hard work on this excellent component.
I'd like to use this in an infinite scroll capacity. Is there an event that is emitted when the end of the list is reached?
ex.
<recycle-list class="scroller" :items="items" >
<template slot-scope="props">
<div @click="props.item.height =50"> {{ item.value }} </div>
</template>
</recycle-list>
As I understood, I can change only content tag name, but for animation I have to define transition-tag with customs attributes like this:
<transition-group name="staggered-fade" tag="div" v-on:before-enter="beforeEnter" v-on:enter="enter" v-on:leave="leave">
</transition-group>
Is It possible to define list animation?
I can't find any documentation on the buffer
and poolSize
props. buffer
seems to be clear what it does, however, I have no clue what for poolSize
is. It would be nice if these props are added to the docs.
Hi! Thanks for your work and this great component! Is it possible to scroll to a specific item in the virtual list programmatically?
I was trying to use component's method scrollToItem(index), but it does not work.
<virtual-scroller :items="someItems" page-mode :item-height="100" ref="scroller"></virtual-scroller>
scrollTo() { this.$refs.scroller.scrollToItem(1000); }
I am using this lib, and it's amazing for large data sets, but when the data set is small, specifically when there are just 2 rows, the first 1 is not rendered, when there are 3 or more, everything is great again.
Is it possible to use custom scrollbar with JS animation of scrolling?
First, I'd like to say this component is great.
I would like to know if you could consider the option to add custom items before and after the item list.
My use case is that sometime in a list for example, I have a loading item in the end of the list.
If I add it in the after-content it will be outside of my UL and won't be seen as it won't be counted in the total height.
Here you don't add the before/after-content height: https://github.com/Akryum/vue-virtual-scroller/blob/master/src/components/VirtualScroller.vue#L164
We could also fing a way to say "I have 80 extra pixel you have to add"
[!] (vue plugin) Error:
Vue packages version mismatch:
- [email protected]
- [email protected]
npm install
npm 5.4.1
No items displayed on viewport when the method scrollToItem(index)
called with index which is greater then twice of the page size.
The debug info in console is:
[Vue warn]: Duplicate keys detected: ''. This may cause an update error.
I've been playing with rendering items offscreen, and not updating until a certain number need updating - as i am finding that when you scroll quickly, my items do not render in time (they are more complex than just a piece of text)
this is the idea
updateVisibleItems () {
const l = this.items.length
const scroll = this.getScroll()
if (scroll) {
let buffer = 20
let poolSize = 5
let startIndex = Math.floor((Math.floor(scroll.top / this.itemHeight) - buffer) / poolSize) * poolSize
let endIndex = Math.floor((Math.ceil(scroll.bottom / this.itemHeight) + buffer) / poolSize) * poolSize
the code was suprisingly simple - have a play and see how it feels for you
Loading the component on the server side to produce an initial render will give the error window is not defined
. Any idea how we could make this work with SSR?
TODO: Investigate technical solution and performance impact.
Hey Folks,
This is great package. I have tried with simple list. It work fine.
But I have another list with Date wise filter. To display that list we are using
<virtual-scroller :items="postGroup" content-tag="v-list" page-mode :item-height="56" :buffer="bufferSize" :pool-size="poolSize" >
<template slot-scope="props">
<v-expansion-panel-content
:key="props.itemKey"
:value="collapseToggle"
class="mb-2 elevation-0"
>
<div slot="actions"><v-icon>arrow_drop_up</v-icon></div>
<div slot="header"
:class="[i === todaytask ? 'today-task' : '', i === yesterdayTask ? 'yesterday-task' : '', 'text-xs-center']">{{ i | dateConversion }}</div>
<v-card class="transparent">
<v-card-text class="no-mrpd">
<v-container fluid class="threads px-2 py-0">
<v-layout row wrap v-for="(post, index) in props.item.group" :key="index" class="mb-2 thread-row" :id="post.id + '-post'">
<v-flex xs12 lg3 xl2 class="avatar-name">
<v-list-tile avatar @click="" v-if="post.is_bot">
<v-list-tile-avatar class="avatar-48px" v-if="post.is_bot">
<img src="https://s3-us-west-1.amazonaws.com/vabotu/default/square_vabotu-default-img.svg" alt="VB" class="text-avatar-style" >
</v-list-tile-avatar>
<v-list-tile-content>
<v-list-tile-title class="grey--text text--darken-4">VB</v-list-tile-title>
<v-list-tile-sub-title class="grey--text">{{ post.created_at | gettime }}</v-list-tile-sub-title>
</v-list-tile-content>
</v-list-tile>
... ... ... ... ... ... ... ... ... ... ... ...
... ... ... ... ... ... ... ... ... ... ... ...
... ... ... ... ... ... ... ... ... ... ... ...
</v-flex>
<v-flex xs12 lg7 xl8 class="post-content-flex post-content-xs-padding">
... ... ... ... ... ... ... ... ... ... ... ...
... ... ... ... ... ... ... ... ... ... ... ...
</v-flex>
<v-flex xs12 lg2 text-xs-right class="thread-options-flex" v-if="!post.is_bot">
... ... ... ... ... ... ... ... ... ... ... ...
... ... ... ... ... ... ... ... ... ... ... ...
... ... ... ... ... ... ... ... ... ... ... ...
</v-flex>
</v-layout>
</v-container>
</v-card-text>
</v-card>
</v-expansion-panel-content>
</template>
</virtual-scroller>
Refer below for postGroup data:
https://www.dropbox.com/s/q22zxhkhvwzyyh6/Screen%20Shot%202018-03-05%20at%203.16.17%20PM.png?dl=0
Am I missing anything?
It would be highly appreciated if you could assist me on this point.
--
Thanking you with anticipation,
Jaimin
I wanted to try it out in my Meteor project but I get this error:
"Uncaught SyntaxError: Unexpected token import"
node_modules/vue-virtual-scroller/src/index.js Line 1:
import _VirtualScroller from './components/VirtualScroller.vue'
So somehow it's not using ES6 or something? Got any ideas?
Hi,
I have a long scrolling area using vue-virtual-scroller, and I want to scroll the user to the top or bottom at certain times ('Go to Beginning' kind of button).
How can it be done ?
I am trying to change scrollTop but it's always zero.
Hello,
Could you help me out figure out why this is laggy?: https://codepen.io/jrast/pen/QqMGdR?editors=1010
I have implemented the virtual scroller, but there seems to be an issue.
When scrolling through a list of (only) 1000 items quickly, at some point (sooner or later, can happen also very quickly after beginning of scrolling) the items are gone and the margin-top gets bumped up infinitely.
Here's an example of the issue:
Watch the items disappear and the margin-top go up (please note that this gif is not in sync with the scrolling gif above, i had to record in two sessions)
The item height is set correctly, however I do have the feeling that the scrollbar handle position does not match the amount of scrolling that has been done.
I am not sure what causes this, if this is a bug or wrong implementation on my side. Have you experienced this behavior before or do you have any idea what can cause this?
Are there any ways to pass my custom props to the scroller items?
TL;DR: Ability to provide a "height function" instead of setting height on an item.
The API might be the following: <recycle-list :items="items" :height-fn="item => heights[item.id]">
.
My use case is the following. Items come from a computed property items
, and they have variable height with a known approximation (e.g. about 50±20px). The exact height is not known until the component is rendered.
Because of variable height, components emit their desired height for the given item once it is known.
But because items come from a computed property, I can not directly set the height on the item object. Instead I have a data field heights
, and the computed property items
populates heights from this data field.
The downside is that items
list is recalculated every time heights
is changing, which is quite often. With a "height function", this recalculation will be avoided.
How do i use it within an existing table that has a header?
Since it wraps the content with two divs, it doesnt work here:
<table>
<thead><tr><th>...</th><th>...</th></tr>
<virtual-scroller content-tag="tbody">
<template>
<tr>....</tr>
<template>
</virtual-scroller>
</table>
the content becomes treated as a cell, since it is a div, not a row (tr)
I see this library render the content with height and margin-top, so the scroll is always vertical. Is horizontal scroll supported?
As the docs mentioned
You need to set the size of the virtual-scroller element and the items elements (for example, with CSS). All items should have the same height to prevent display glitches
but , in some case ,items have different height,like chat message list
I have an idea
before add new items to list view ,
just render them in another container ,
then we can get new items height
and then remove them
thus , we don't need define item height
Hope to be helpful !
I need to access data items
used like props: ['item', 'items']
in an item component.
But may be you have a reason to reject my request. So give me some help, please.
The expected source of Item.vue
may looks like:
<template>
<div class="person" @click="edit">({{item.index}}) {{item.value.name}}</div>
</template>
<script>
export default {
props: ['item', 'items'],
methods: {
edit () {
this.item.value.name += '*'
},
},
}
</script>
Hi,
Is it possible to use a fixed header with recycle-list
and if so how can it be achieved?
Thanks,
Thomas
Is it possible to virtually scroll el-table without having to modify el-table's template source ? Has anyone attempted this before ?
It never ever executes. And I don't get why 😕
'items' is definitely changing.
The scroller (margin-top) is going to infinity when scroll fast down by using scrollbar thumb.
Hello!
I drag the scrollbar with my mouse up and down and everything works fine, no errors. I do the same again very fast, the virtual scroller breaks and I get the following error in my console:
vue.common.js?e881:521 [Vue warn]: It seems there are duplicate keys that is causing an update error. Make sure each v-for item has a unique key.
warn @ vue.common.js?e881:521
updateChildren @ vue.common.js?e881:4393
patchVnode @ vue.common.js?e881:4450
updateChildren @ vue.common.js?e881:4366
patchVnode @ vue.common.js?e881:4450
updateChildren @ vue.common.js?e881:4366
patchVnode @ vue.common.js?e881:4450
patch @ vue.common.js?e881:4574
Vue._update @ vue.common.js?e881:2642
updateComponent @ vue.common.js?e881:2609
get @ vue.common.js?e881:2934
run @ vue.common.js?e881:3003
flushSchedulerQueue @ vue.common.js?e881:2807
(anonymous) @ vue.common.js?e881:473
nextTickHandler @ vue.common.js?e881:422
vue.common.js?e881:435 TypeError: Cannot read property 'key' of undefined
at sameVnode (eval at <anonymous> (app.js:723), <anonymous>:4061:11)
at updateChildren (eval at <anonymous> (app.js:723), <anonymous>:4398:15)
at patchVnode (eval at <anonymous> (app.js:723), <anonymous>:4450:29)
at updateChildren (eval at <anonymous> (app.js:723), <anonymous>:4366:9)
at patchVnode (eval at <anonymous> (app.js:723), <anonymous>:4450:29)
at updateChildren (eval at <anonymous> (app.js:723), <anonymous>:4366:9)
at patchVnode (eval at <anonymous> (app.js:723), <anonymous>:4450:29)
at VueComponent.patch [as __patch__] (eval at <anonymous> (app.js:723), <anonymous>:4574:9)
at VueComponent.Vue._update (eval at <anonymous> (app.js:723), <anonymous>:2642:19)
at VueComponent.updateComponent (eval at <anonymous> (app.js:723), <anonymous>:2609:10)
logError @ vue.common.js?e881:435
OS: Windows 10
8 GB RAM
Browser: Google Chrome 56.0.2924.87
My template:
<virtual-scroller class="scroller" :items="listCompiled" item-height="58" typeField="type" content-tag="table">
<toolbar :pagination="pagination"
:groupBy="groupBy"
:sortBy="sortBy"
:checkedInvoices="checkedInvoices"
@update="handleToolbarUpdate"
slot="before-container"
></toolbar>
<template scope="props">
<tr v-if="props.item.type === 'groupHeader'" :key="props.itemKey">
<td class="listItem groupHeader" colspan="5">
<h3>{{props.item.label}}</h3>
</td>
</tr>
<tr v-if="props.item.type === 'invoice'" class="listItem invoice" :key="props.itemKey" :class="{'active': props.item.checked }">
<td class="checkBox">
<el-checkbox v-model="props.item.checked" :label="props.item.id" @change="checkedChange"></el-checkbox>
</td>
<td class="wrapClient" v-for="address in addresses" v-if="address.id == props.item.address">
<span class="client">
<span v-if="address.org" class="org">{{address.org}}</span>
<span v-if="address.firstname && address.lastname" class="name">{{address.firstname + ' ' + address.lastname}}</span>
</span>
<span class="nr">{{props.item.nr}}</span>
</td>
<td class="date">{{ props.item.date | moment("D. M. Y") }}</td>
<td class="dueDate">{{props.item.dueDateComputed}}</td>
<td class="wrapTotal">
<span class="total">{{props.item.total | currency}}</span>
<span :class="'status status-' + props.item.status">{{statusLabel[props.item.status]}}</span>
</td>
</tr>
</template>
</virtual-scroller>
The itemKey
s are unique and created like so:
new Date().toJSON() + Math.random()
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.