1 | const BLOCK_ROWS = 50
|
2 | const CLUSTER_BLOCKS = 4
|
3 |
|
4 | class 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 |
|
136 | export default VirtualScroll
|