Skip to content

Commit e3dbaf4

Browse files
committed
Initial commit.
0 parents  commit e3dbaf4

File tree

10 files changed

+428
-0
lines changed

10 files changed

+428
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
npm-debug.log
3+
demo/build.js
4+
demo/build.js.map

demo/demo.vue

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<template>
2+
<div>
3+
<virtual-list
4+
:unit="30"
5+
:remain="10"
6+
:pageCounts="20"
7+
v-on:bottom="onBottom"
8+
>
9+
<Item v-for="item in items" :item="item" :key="item.id" />
10+
</virtual-list>
11+
</div>
12+
</template>
13+
14+
<script>
15+
import 'virtual-list';
16+
import Item from './item.vue';
17+
import { fetchData } from './request_mock';
18+
19+
export default {
20+
name: 'apptest',
21+
22+
components: { Item },
23+
24+
data () {
25+
return {
26+
items: fetchData()
27+
}
28+
},
29+
30+
methods: {
31+
onBottom () {
32+
this.items = this.items.concat(fetchData());
33+
}
34+
}
35+
}
36+
</script>

demo/index.html

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Vue virtual scroll list demo</title>
6+
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<style type="text/css">
8+
* {
9+
margin: 0;
10+
padding: 0;
11+
}
12+
body {
13+
position: relative;
14+
}
15+
.box {
16+
width: 440px;
17+
height: 350px;
18+
position: absolute;
19+
top: 50%;
20+
margin-top: 175px;
21+
left: 50%;
22+
margin-left: -220px;
23+
padding: 15px;
24+
border: 1px solid #aaa;
25+
box-sizing: border-box;
26+
}
27+
</style>
28+
</head>
29+
<body>
30+
<div class="box">
31+
<div id="app"></div>
32+
</div>
33+
<script src="build.js"></script>
34+
</body>
35+
</html>

demo/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import Vue from 'vue';
2+
import Demo from './demo.vue';
3+
4+
Vue.use(Demo);
5+
6+
new Vue({
7+
el: '#app',
8+
template: '<Demo />',
9+
components: { Demo }
10+
});

demo/item.vue

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<template>
2+
<div class="user">
3+
<ul class="user-list">
4+
<li class="user-item indx">{{ item.indx }}</li>
5+
<li class="user-item name">{{ item.name }}</li>
6+
<li class="user-item urid">{{ item.urid }}</li>
7+
<li class="user-item date">{{ item.date }}</li>
8+
</ul>
9+
</div>
10+
</template>
11+
12+
<script>
13+
export default {
14+
props: {
15+
item: Object
16+
}
17+
}
18+
</script>
19+
20+
<style scoped>
21+
.user {
22+
height: 30px;
23+
line-height: 30px;
24+
}
25+
.user:nth-child(2n) {
26+
background: #efefef;
27+
}
28+
.user:nth-child(2n-1) {
29+
background: #d1e3f5;
30+
}
31+
.user-list {
32+
list-style: none;
33+
}
34+
.user-item {
35+
display: inline-block;
36+
text-align: center;
37+
}
38+
.indx {
39+
width: 50px;
40+
}
41+
.urid {
42+
width: 60px;
43+
}
44+
.name {
45+
width: 120px;
46+
}
47+
.date {
48+
width: 130px;
49+
}
50+
</style>

demo/request_mock.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Mock from 'mockjs';
2+
3+
let user_count = 0;
4+
export function fetchData (count) {
5+
var list = [];
6+
let Random = Mock.Random;
7+
8+
if (!count) {
9+
count = 20;
10+
}
11+
12+
while (count--) {
13+
list.push({
14+
indx: user_count++,
15+
urid: Random.natural(1000, 9000),
16+
name: Random.cname(),
17+
date: Random.date()
18+
});
19+
}
20+
21+
return list;
22+
}

package.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "vue-virtual-scroll-list",
3+
"version": "1.0.0",
4+
"description": "A vue component that support big data list scroll.",
5+
"main": "src/index.js",
6+
"scripts": {
7+
"demo": "webpack --watch --config webpack.dev.config.js"
8+
},
9+
"keywords": [
10+
"Vue",
11+
"component",
12+
"big-data",
13+
"big-list",
14+
"scroll"
15+
],
16+
"author": "TANG",
17+
"license": "MIT",
18+
"devDependencies": {
19+
"babel-core": "^6.24.0",
20+
"babel-loader": "^6.4.0",
21+
"css-loader": "^0.27.2",
22+
"mockjs": "^1.0.1-beta3",
23+
"vue": "^2.2.2",
24+
"vue-loader": "^11.1.4",
25+
"vue-template-compiler": "^2.2.2",
26+
"webpack": "^2.2.1"
27+
}
28+
}

src/index.js

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import Vue from 'vue';
2+
import { throttle } from './util';
3+
4+
let delta = {
5+
total: 0,
6+
direct: '',
7+
last_top: 0,
8+
page_type: '',
9+
start_index: 0,
10+
all_padding: 0,
11+
padding_top: 0,
12+
bench_padding: 0
13+
};
14+
15+
Vue.component('virtual-list', {
16+
17+
props: {
18+
unit: {
19+
type: Number,
20+
required: true
21+
},
22+
remain: {
23+
type: Number,
24+
required: true
25+
},
26+
pageCounts: {
27+
type: Number,
28+
required: true
29+
}
30+
},
31+
32+
methods: {
33+
onScroll: throttle(function () {
34+
let cont = this.$refs.container;
35+
let listbox = this.$refs.listbox;
36+
37+
let scrollTop = cont.scrollTop;
38+
let viewHeight = cont.offsetHeight;
39+
let listHeight = listbox.offsetHeight;
40+
41+
this.recordDirect(scrollTop);
42+
43+
// scroll to top
44+
if (scrollTop === 0) {
45+
this.$emit('top');
46+
}
47+
48+
// scroll to bottom
49+
let paddingBottom = delta.all_padding - delta.padding_top;
50+
if (listHeight <= scrollTop + viewHeight + paddingBottom) {
51+
this.showNext();
52+
}
53+
54+
if (delta.direct === 'UP' && scrollTop < delta.padding_top) {
55+
this.showPrev();
56+
}
57+
}, 16, true, true),
58+
59+
recordDirect (scrollTop) {
60+
if (!delta.last_top) {
61+
delta.last_top = scrollTop;
62+
} else {
63+
delta.direct = delta.last_top > scrollTop ? 'UP' : 'DOWN';
64+
delta.last_top = scrollTop;
65+
}
66+
},
67+
68+
showNext () {
69+
delta.page_type = 'NEXT';
70+
this.$emit('bottom');
71+
},
72+
73+
showPrev () {
74+
delta.page_type = 'PREV';
75+
this.$forceUpdate();
76+
this.$emit('prev');
77+
},
78+
79+
filter (items) {
80+
let length = items.length;
81+
let nowStartIndex, udf, list = [];
82+
83+
if (!delta.total) {
84+
delta.total = length;
85+
}
86+
87+
if (delta.page_type === 'PREV') {
88+
// already the first page
89+
if (delta.start_index === 0) {
90+
list = items.slice(0, this.pageCounts);
91+
} else {
92+
list = items.filter((item, index) => {
93+
if (index === delta.start_index - this.pageCounts) {
94+
nowStartIndex = index;
95+
}
96+
97+
return index >= (delta.start_index - this.pageCounts)
98+
&& index <= (delta.start_index - 1);
99+
});
100+
101+
if (nowStartIndex !== udf) {
102+
delta.start_index = nowStartIndex;
103+
}
104+
105+
delta.padding_top = delta.start_index * this.unit;
106+
}
107+
} else {
108+
// virtual list has no any increase
109+
if (length === delta.total) {
110+
list = items;
111+
} else {
112+
list = items.filter((item, index) => {
113+
if (index === delta.start_index + this.pageCounts) {
114+
nowStartIndex = index;
115+
}
116+
117+
return index >= (delta.start_index + this.pageCounts)
118+
&& index < (delta.start_index + this.pageCounts * 2);
119+
});
120+
121+
if (nowStartIndex !== udf) {
122+
delta.start_index = nowStartIndex;
123+
}
124+
125+
// item counts of all virtual list
126+
delta.total = length;
127+
// all padding pixel, except remain in viewport items
128+
delta.all_padding = (length - this.remain) * this.unit;
129+
// padding-top piexl
130+
delta.padding_top = delta.start_index * this.unit;
131+
}
132+
}
133+
134+
return list;
135+
}
136+
},
137+
138+
mounted () {
139+
delta.bench_padding = Math.ceil(this.remain / 2) * this.unit;
140+
},
141+
142+
updated () {
143+
window.requestAnimationFrame(() => {
144+
this.$refs.container.scrollTop = delta.padding_top + delta.bench_padding;
145+
});
146+
},
147+
148+
render (createElement) {
149+
let slots = this.$slots.default;
150+
let showList = this.filter(slots);
151+
152+
return createElement('div', {
153+
'ref': 'container',
154+
'class': 'virtual-list',
155+
'style': {
156+
'overflow-y': 'auto',
157+
'height': (this.remain * this.unit) + 'px'
158+
},
159+
'on': {
160+
'scroll': this.onScroll
161+
}
162+
}, [
163+
createElement('div', {
164+
'ref': 'listbox',
165+
'class': 'virtual-list-box',
166+
}, [
167+
createElement('div', {
168+
'class': 'virtual-list-box-padding',
169+
'style': {
170+
'padding-top': delta.padding_top + 'px',
171+
'padding-bottom': (delta.all_padding - delta.padding_top) + 'px'
172+
}
173+
}, showList)
174+
])
175+
]);
176+
}
177+
});

0 commit comments

Comments
 (0)