index.js 3.2 KB

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