https://github.com/tangbc/vue-virtual-scroll-list

    1. <!DOCTYPE html>
    2. <html lang="zh">
    3. <head>
    4. <meta charset="UTF-8">
    5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
    7. <style>
    8. .wrapper {
    9. height: 100%;
    10. overflow: auto;
    11. }
    12. .item {
    13. height: 50px;
    14. padding-left: 50px;
    15. }
    16. </style>
    17. </head>
    18. <body>
    19. <div id="app">
    20. <div class="wrapper" ref="wrapper" @scroll="handleScroll" :style="{ height: height + 'px' }">
    21. <div ref="w">
    22. <div :style="{ height: `${shimTopOffset}px` }"></div>
    23. <div
    24. class="item"
    25. v-for="item in showList"
    26. :key="item.index"
    27. :style="{
    28. height: `${itemHeight}px`,
    29. color: item.color,
    30. }"
    31. >
    32. {{ item.index }}
    33. </div>
    34. <div :style="{ height: `${shimBottomOffset}px` }"></div>
    35. </div>
    36. </div>
    37. </div>
    38. <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    39. <script>
    40. new Vue({
    41. el: '#app',
    42. data() {
    43. return {
    44. height: 500,
    45. itemHeight: 50,
    46. data: Array.from({ length: 6000 }).map((_, index) => ({
    47. index,
    48. color: `#${Math.random()
    49. .toString(16)
    50. .substr(2, 6)}`,
    51. })),
    52. showList: [],
    53. shimTopOffset: 0,
    54. shimBottomOffset: 0,
    55. };
    56. },
    57. mounted() {
    58. this.update(0);
    59. this.$nextTick(() => {
    60. this.maxHeight = this.$refs.w.offsetHeight;
    61. });
    62. },
    63. methods: {
    64. handleScroll() {
    65. const scrollTop = this.$refs.wrapper.scrollTop;
    66. if (scrollTop >= 0 && scrollTop + this.height <= this.maxHeight) {
    67. window.requestAnimationFrame(() => {
    68. this.update(scrollTop);
    69. });
    70. }
    71. },
    72. update(scrollTop) {
    73. const visibleStart = scrollTop;
    74. const visibleEnd = scrollTop + this.height;
    75. this.showList = this.getShowList(visibleStart, visibleEnd, this.data);
    76. },
    77. getShowList(start, end, data) {
    78. if (start < end) {
    79. const lo = this.getStartIndex(start, this.itemHeight);
    80. const hi = this.getEndIndex(end, this.itemHeight);
    81. this.shimTopOffset = lo >= 0 ? lo * this.itemHeight : 0;
    82. this.shimBottomOffset = hi >= 0 ? (data.length - hi) * this.itemHeight : 0;
    83. return data.slice(lo, hi);
    84. } else {
    85. this.shimTopOffset = 0;
    86. this.shimBottomOffset = 0;
    87. return [];
    88. }
    89. },
    90. getStartIndex(s, itemHeight) {
    91. const startIndex = ~~(s / itemHeight);
    92. return startIndex >= 0 ? startIndex : 0;
    93. },
    94. getEndIndex(e, itemHeight) {
    95. const endIndex = Math.ceil(e / itemHeight);
    96. return endIndex <= this.data.length ? endIndex : this.data.length;
    97. },
    98. },
    99. watch: {
    100. data: {
    101. handler(newVal, oldVal) {
    102. if (oldVal) {
    103. this.$nextTick(() => {
    104. this.$refs.wrapper.scrollTop = 0;
    105. this.handleScroll();
    106. });
    107. }
    108. },
    109. immediate: true,
    110. },
    111. itemHeight() {
    112. this.handleScroll();
    113. },
    114. },
    115. })
    116. </script>
    117. </body>
    118. </html>