UNPKG

3.61 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()
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.contentEl.innerHTML = html.join('')
58
59 if (fixedScroll) {
60 this.contentEl.scrollTop = this.cache.scrollTop
61 }
62 } else if (bottomOffsetChanged) {
63 this.contentEl.lastChild.style.height = `${data.bottomOffset}px`
64 }
65 }
66
67 getRowsHeight () {
68 if (typeof this.itemHeight === 'undefined') {
69 const nodes = this.contentEl.children
70 const node = nodes[Math.floor(nodes.length / 2)]
71 this.itemHeight = node.offsetHeight
72 }
73 this.blockHeight = this.itemHeight * BLOCK_ROWS
74 this.clusterRows = BLOCK_ROWS * CLUSTER_BLOCKS
75 this.clusterHeight = this.blockHeight * CLUSTER_BLOCKS
76 }
77
78 getNum (fixedScroll) {
79 this.scrollTop = fixedScroll ? this.cache.scrollTop : this.scrollEl.scrollTop
80 return Math.floor(this.scrollTop / (this.clusterHeight - this.blockHeight)) || 0
81 }
82
83 initData (rows, num) {
84 if (rows.length < BLOCK_ROWS) {
85 return {
86 topOffset: 0,
87 bottomOffset: 0,
88 rowsAbove: 0,
89 rows
90 }
91 }
92 const start = Math.max((this.clusterRows - BLOCK_ROWS) * num, 0)
93 const end = start + this.clusterRows
94 const topOffset = Math.max(start * this.itemHeight, 0)
95 const bottomOffset = Math.max((rows.length - end) * this.itemHeight, 0)
96 const thisRows = []
97 let rowsAbove = start
98 if (topOffset < 1) {
99 rowsAbove++
100 }
101 for (let i = start; i < end; i++) {
102 rows[i] && thisRows.push(rows[i])
103 }
104 return {
105 topOffset,
106 bottomOffset,
107 rowsAbove,
108 rows: thisRows
109 }
110 }
111
112 checkChanges (type, value) {
113 const changed = value !== this.cache[type]
114 this.cache[type] = value
115 return changed
116 }
117
118 getExtra (className, height) {
119 const tag = document.createElement('tr')
120 tag.className = `virtual-scroll-${className}`
121 if (height) {
122 tag.style.height = `${height}px`
123 }
124 return tag.outerHTML
125 }
126}
127
128export default VirtualScroll