index.vue 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. <template>
  2. <div :class="classes" :style="{ height: `${getContainerHeight}px` }" @scroll.passive="handleScrollEvent" ref="list">
  3. <div class="nut-list-phantom" :style="{ height: listHeight + 'px' }"></div>
  4. <div class="nut-list-container" :style="{ transform: getTransform }">
  5. <div class="nut-list-item" :style="{ height: height + 'px' }" v-for="(item, index) in visibleData" :key="item">
  6. <slot :item="item" :index="index"></slot>
  7. </div>
  8. </div>
  9. </div>
  10. </template>
  11. <script lang="ts">
  12. import { reactive, toRefs, computed, ref, Ref, watch, ComputedRef } from 'vue';
  13. import { createComponent } from '@/packages/utils/create';
  14. const { componentName, create } = createComponent('list');
  15. const clientHeight = document.documentElement.clientHeight || document.body.clientHeight || 667;
  16. export default create({
  17. props: {
  18. height: {
  19. type: [Number],
  20. default: 50
  21. },
  22. listData: {
  23. type: Array,
  24. default: () => {
  25. return [];
  26. }
  27. },
  28. containerHeight: {
  29. type: [Number],
  30. default: clientHeight
  31. }
  32. },
  33. emits: ['scroll', 'scroll-bottom'],
  34. setup(props, { emit }) {
  35. const list = ref(null) as Ref;
  36. const state = reactive({
  37. startOffset: 0,
  38. start: 0,
  39. list: props.listData.slice()
  40. });
  41. const getContainerHeight = computed(() => {
  42. return Math.min(props.containerHeight, clientHeight);
  43. });
  44. const visibleCount = computed(() => {
  45. return Math.ceil(getContainerHeight.value / props.height);
  46. });
  47. const end = computed(() => {
  48. return state.start + visibleCount.value;
  49. });
  50. const getTransform = computed(() => {
  51. return `translate3d(0, ${state.startOffset}px, 0)`;
  52. });
  53. const classes = computed(() => {
  54. const prefixCls = componentName;
  55. return {
  56. [prefixCls]: true
  57. };
  58. });
  59. const listHeight = computed(() => {
  60. return state.list.length * props.height;
  61. });
  62. const visibleData: ComputedRef = computed(() => {
  63. return state.list.slice(state.start, Math.min(end.value, state.list.length));
  64. });
  65. const handleScrollEvent = () => {
  66. const scrollTop = list.value?.scrollTop as number;
  67. state.start = Math.floor(scrollTop / props.height);
  68. if (end.value >= state.list.length) {
  69. emit('scroll-bottom');
  70. }
  71. state.startOffset = scrollTop - (scrollTop % props.height);
  72. };
  73. watch(
  74. () => props.listData,
  75. () => {
  76. state.list = props.listData.slice();
  77. }
  78. );
  79. return {
  80. ...toRefs(state),
  81. list,
  82. getTransform,
  83. listHeight,
  84. visibleData,
  85. classes,
  86. getContainerHeight,
  87. handleScrollEvent
  88. };
  89. }
  90. });
  91. </script>