UNPKG

3.74 kBJavaScriptView Raw
1const BLOCK_ROWS = 50
2const CLUSTER_BLOCKS = 4
3
4class VirtualScroll {
5
6 constructor (options) {
7 this.rows = options.rows
8 this.scrollEl = options.scrollEl
9 this.contentEl = options.contentEl
10 this.callback = options.callback
11 this.itemHeight = options.itemHeight
12
13 this.cache = {}
14 this.scrollTop = this.scrollEl.scrollTop
15
16 this.initDOM(this.rows, options.fixedScroll)
17
18 this.scrollEl.scrollTop = this.scrollTop
19 this.lastCluster = 0
20
21 const onScroll = () => {
22 if (this.lastCluster !== (this.lastCluster = this.getNum())) {
23 this.initDOM(this.rows)
24 this.callback(this.startIndex, this.endIndex)
25 }
26 }
27
28 this.scrollEl.addEventListener('scroll', onScroll, false)
29 this.destroy = () => {
30 this.contentEl.innerHtml = ''
31 this.scrollEl.removeEventListener('scroll', onScroll, false)
32 }
33 }
34
35 initDOM (rows, fixedScroll) {
36 if (typeof this.clusterHeight === 'undefined') {
37 this.cache.scrollTop = this.scrollEl.scrollTop
38 this.cache.data = this.contentEl.innerHTML = rows[0] + rows[0] + rows[0]
39 this.getRowsHeight(rows)
40 }
41
42 const data = this.initData(rows, this.getNum(fixedScroll))
43 const thisRows = data.rows.join('')
44 const dataChanged = this.checkChanges('data', thisRows)
45 const topOffsetChanged = this.checkChanges('top', data.topOffset)
46 const bottomOffsetChanged = this.checkChanges('bottom', data.bottomOffset)
47 const html = []
48
49 if (dataChanged && topOffsetChanged) {
50 if (data.topOffset) {
51 html.push(this.getExtra('top', data.topOffset))
52 }
53 html.push(thisRows)
54 if (data.bottomOffset) {
55 html.push(this.getExtra('bottom', data.bottomOffset))
56 }
57 this.startIndex = data.start
58 this.endIndex = data.end
59 this.contentEl.innerHTML = html.join('')
60
61 if (fixedScroll) {
62 this.contentEl.scrollTop = this.cache.scrollTop
63 }
64 } else if (bottomOffsetChanged) {
65 this.contentEl.lastChild.style.height = `${data.bottomOffset}px`
66 }
67 }
68
69 getRowsHeight () {
70 if (typeof this.itemHeight === 'undefined') {
71 const nodes = this.contentEl.children
72 const node = nodes[Math.floor(nodes.length / 2)]
73
74 this.itemHeight = node.offsetHeight
75 }
76 this.blockHeight = this.itemHeight * BLOCK_ROWS
77 this.clusterRows = BLOCK_ROWS * CLUSTER_BLOCKS
78 this.clusterHeight = this.blockHeight * CLUSTER_BLOCKS
79 }
80
81 getNum (fixedScroll) {
82 this.scrollTop = fixedScroll ? this.cache.scrollTop : this.scrollEl.scrollTop
83 return Math.floor(this.scrollTop / (this.clusterHeight - this.blockHeight)) || 0
84 }
85
86 initData (rows, num) {
87 if (rows.length < BLOCK_ROWS) {
88 return {
89 topOffset: 0,
90 bottomOffset: 0,
91 rowsAbove: 0,
92 rows
93 }
94 }
95 const start = Math.max((this.clusterRows - BLOCK_ROWS) * num, 0)
96 const end = start + this.clusterRows
97 const topOffset = Math.max(start * this.itemHeight, 0)
98 const bottomOffset = Math.max((rows.length - end) * this.itemHeight, 0)
99 const thisRows = []
100 let rowsAbove = start
101
102 if (topOffset < 1) {
103 rowsAbove++
104 }
105 for (let i = start; i < end; i++) {
106 rows[i] && thisRows.push(rows[i])
107 }
108 return {
109 start,
110 end,
111 topOffset,
112 bottomOffset,
113 rowsAbove,
114 rows: thisRows
115 }
116 }
117
118 checkChanges (type, value) {
119 const changed = value !== this.cache[type]
120
121 this.cache[type] = value
122 return changed
123 }
124
125 getExtra (className, height) {
126 const tag = document.createElement('tr')
127
128 tag.className = `virtual-scroll-${className}`
129 if (height) {
130 tag.style.height = `${height}px`
131 }
132 return tag.outerHTML
133 }
134}
135
136export default VirtualScroll