virtualList.vue 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. <template>
  2. <view class="virtual-list" style="position: relative;">
  3. <movable-area style="position: absolute;right: 0;width: 30px;height: 100%;">
  4. <movable-view class="action-bar-box" direction="vertical" @change="change" :y="y" :animation="false">
  5. <view style="border-bottom: #000 solid 2px;width: 100%;"></view>
  6. <view style="border-bottom: #000 solid 2px;width: 100%;"></view>
  7. </movable-view>
  8. </movable-area>
  9. <scroll-view scroll-y="true" :style="{
  10. height: scrollHeight + 'px',
  11. position: 'relative',
  12. zIndex: 1
  13. }" @scroll="handleScroll" :scroll-top="scrollTop" :show-scrollbar="false">
  14. <view class="scroll-bar" :style="{
  15. height: localHeight + 'px'
  16. }"></view>
  17. <view class="list" :style="{
  18. transform: `translateY(${offset}px)`
  19. }">
  20. <view class="item-wrap" v-for="(item, index) in visibleData" :key="index">
  21. <slot :item="item" :active="active"></slot>
  22. </view>
  23. </view>
  24. </scroll-view>
  25. </view>
  26. </template>
  27. <script>
  28. export default {
  29. name: 'VirtualList',
  30. props: {
  31. // 所有的items
  32. items: Array,
  33. // 可视区域的item数量
  34. remain: Number,
  35. // item大小
  36. size: Number,
  37. // 当前章节
  38. active: Number,
  39. // 可使区域高度
  40. scrollHeight: Number
  41. },
  42. data() {
  43. return {
  44. // 起始
  45. start: 0,
  46. // 结束
  47. end: this.remain,
  48. // list 偏移量
  49. offset: 0,
  50. scrollTop: 0,
  51. y: 0
  52. };
  53. },
  54. created() {
  55. //当前章节滚动至顶部
  56. this.scrollTop = this.size * this.active;
  57. },
  58. computed: {
  59. // 预留项
  60. preCount() {
  61. return Math.min(this.start, this.remain);
  62. },
  63. nextCount() {
  64. return Math.min(this.items.length - this.end, this.remain);
  65. },
  66. // 可视区域的item
  67. visibleData() {
  68. const start = this.start - this.preCount;
  69. const end = this.end + this.nextCount;
  70. return this.items.slice(start, end);
  71. },
  72. localHeight() {
  73. return this.items.length * this.size;
  74. }
  75. },
  76. methods: {
  77. change(e) {
  78. if (e.detail.source !== 'touch') {
  79. return;
  80. }
  81. let y = e.detail.y;
  82. let scroll = (y / (this.scrollHeight - 40)) * (this.localHeight - this.scrollHeight);
  83. scroll = scroll < 0 ? 0 : scroll;
  84. this.scrollTop = scroll;
  85. },
  86. handleScroll(ev) {
  87. const scrollTop = ev.detail.scrollTop;
  88. this.y = (scrollTop / (this.localHeight - this.scrollHeight)) * (this.scrollHeight - 40);
  89. // 开始位置
  90. const start = Math.floor(scrollTop / this.size);
  91. this.start = start < 0 ? 0 : start;
  92. // 结束位置
  93. this.end = this.start + this.remain;
  94. // 计算偏移
  95. const offset = scrollTop - (scrollTop % this.size) - this.preCount * this.size;
  96. this.offset = offset < 0 ? 0 : offset;
  97. }
  98. }
  99. };
  100. </script>
  101. <style scoped>
  102. .list {
  103. position: absolute;
  104. top: 0;
  105. left: 0;
  106. width: 100%;
  107. }
  108. .action-bar-box {
  109. padding: 3px;
  110. display: flex;
  111. flex-flow: column;
  112. justify-content: space-around;
  113. align-items: center;
  114. position: absolute;
  115. right: 0;
  116. background-color: transparent;
  117. border-radius: 10rpx;
  118. box-shadow: 0 0 5px #000;
  119. width: 20px;
  120. height: 40px;
  121. z-index: 2;
  122. }
  123. </style>