index.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  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. }
  33. const data = this.initData(rows, this.getNum(fixedScroll))
  34. const thisRows = data.rows.join('')
  35. const dataChanged = this.checkChanges('data', thisRows)
  36. const topOffsetChanged = this.checkChanges('top', data.topOffset)
  37. const bottomOffsetChanged = this.checkChanges('bottom', data.bottomOffset)
  38. const html = []
  39. if (dataChanged && topOffsetChanged) {
  40. if (data.topOffset) {
  41. html.push(this.getExtra('top', data.topOffset))
  42. }
  43. html.push(thisRows)
  44. if (data.bottomOffset) {
  45. html.push(this.getExtra('bottom', data.bottomOffset))
  46. }
  47. this.startIndex = data.start
  48. this.endIndex = data.end
  49. this.contentEl.innerHTML = html.join('')
  50. if (fixedScroll) {
  51. this.contentEl.scrollTop = this.cache.scrollTop
  52. }
  53. } else if (bottomOffsetChanged) {
  54. this.contentEl.lastChild.style.height = `${data.bottomOffset}px`
  55. }
  56. }
  57. getRowsHeight () {
  58. if (typeof this.itemHeight === 'undefined') {
  59. const nodes = this.contentEl.children
  60. const node = nodes[Math.floor(nodes.length / 2)]
  61. this.itemHeight = node.offsetHeight
  62. }
  63. this.blockHeight = this.itemHeight * BLOCK_ROWS
  64. this.clusterRows = BLOCK_ROWS * CLUSTER_BLOCKS
  65. this.clusterHeight = this.blockHeight * CLUSTER_BLOCKS
  66. }
  67. getNum (fixedScroll) {
  68. this.scrollTop = fixedScroll ? this.cache.scrollTop : this.scrollEl.scrollTop
  69. return Math.floor(this.scrollTop / (this.clusterHeight - this.blockHeight)) || 0
  70. }
  71. initData (rows, num) {
  72. if (rows.length < BLOCK_ROWS) {
  73. return {
  74. topOffset: 0,
  75. bottomOffset: 0,
  76. rowsAbove: 0,
  77. rows
  78. }
  79. }
  80. const start = Math.max((this.clusterRows - BLOCK_ROWS) * num, 0)
  81. const end = start + this.clusterRows
  82. const topOffset = Math.max(start * this.itemHeight, 0)
  83. const bottomOffset = Math.max((rows.length - end) * this.itemHeight, 0)
  84. const thisRows = []
  85. let rowsAbove = start
  86. if (topOffset < 1) {
  87. rowsAbove++
  88. }
  89. for (let i = start; i < end; i++) {
  90. rows[i] && thisRows.push(rows[i])
  91. }
  92. return {
  93. start,
  94. end,
  95. topOffset,
  96. bottomOffset,
  97. rowsAbove,
  98. rows: thisRows
  99. }
  100. }
  101. checkChanges (type, value) {
  102. const changed = value !== this.cache[type]
  103. this.cache[type] = value
  104. return changed
  105. }
  106. getExtra (className, height) {
  107. const tag = document.createElement('tr')
  108. tag.className = `virtual-scroll-${className}`
  109. if (height) {
  110. tag.style.height = `${height}px`
  111. }
  112. return tag.outerHTML
  113. }
  114. }
  115. export default VirtualScroll