index.js 3.7 KB

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