소스 검색

perf: 优化瀑布流组件

kailong321200875 1 년 전
부모
커밋
82eb7f16ad
1개의 변경된 파일114개의 추가작업 그리고 37개의 파일을 삭제
  1. 114 37
      src/components/Waterfall/src/Waterfall.vue

+ 114 - 37
src/components/Waterfall/src/Waterfall.vue

@@ -20,10 +20,13 @@ const prop = defineProps({
     src: 'src',
     height: 'height'
   }),
+  cols: propTypes.number.def(undefined),
   loadingText: propTypes.string.def('加载中...'),
   loading: propTypes.bool.def(false),
   end: propTypes.bool.def(false),
-  endText: propTypes.string.def('没有更多了')
+  endText: propTypes.string.def('没有更多了'),
+  autoCenter: propTypes.bool.def(true),
+  layout: propTypes.oneOf(['javascript', 'flex']).def('flex')
 })
 
 const wrapEl = ref<HTMLDivElement>()
@@ -37,7 +40,7 @@ const wrapWidth = ref(0)
 const loadMore = ref<HTMLDivElement>()
 
 // 首先确定列数 = 页面宽度 / 图片宽度
-const cols = ref(0)
+const innerCols = ref(0)
 
 const filterData = ref<any[]>([])
 
@@ -49,11 +52,11 @@ const filterWaterfall = async () => {
 
   const container = unref(wrapEl) as HTMLElement
   if (!container) return
-  cols.value = Math.floor(container.clientWidth / (width + gap))
+  innerCols.value = prop.cols ?? Math.floor(container.clientWidth / (width + gap))
 
   const length = data.length
   for (let i = 0; i < length; i++) {
-    if (i < unref(cols)) {
+    if (i < unref(innerCols)) {
       heights.value[i] = data[i][props.height as string]
       filterData.value.push({
         ...data[i],
@@ -66,7 +69,7 @@ const filterWaterfall = async () => {
       let minHeight = heights.value[0]
       let index = 0
       // 找出最小高度
-      for (let j = 1; j < unref(cols); j++) {
+      for (let j = 1; j < unref(innerCols); j++) {
         if (unref(heights)[j] < minHeight) {
           minHeight = unref(heights)[j]
           index = j
@@ -83,14 +86,42 @@ const filterWaterfall = async () => {
     }
   }
   wrapHeight.value = Math.max(...unref(heights))
-  wrapWidth.value = unref(cols) * (width + gap) - gap
+  wrapWidth.value = unref(innerCols) * (width + gap) - gap
 }
 
-watch(
-  () => prop.data,
-  async () => {
-    await nextTick()
+const flexWaterfall = async () => {
+  const { width, gap } = prop
+  const data = prop.data as any[]
+  await nextTick()
+
+  const container = unref(wrapEl) as HTMLElement
+  if (!container) return
+  innerCols.value = prop.cols ?? Math.floor(container.clientWidth / (width + gap))
+
+  const length = data.length
+  // 根据列数,创建数组
+  const arr = new Array(unref(innerCols)).fill([])
+  // 循环data,依次插入到arr中
+  for (let i = 0; i < length; i++) {
+    const index = i % unref(innerCols)
+    arr[index] = [...arr[index], data[i]]
+  }
+  filterData.value = arr
+}
+
+const initLayout = () => {
+  const { layout } = prop
+  if (layout === 'javascript') {
     filterWaterfall()
+  } else if (layout === 'flex') {
+    flexWaterfall()
+  }
+}
+
+watch(
+  () => [prop.data, prop.cols],
+  () => {
+    initLayout()
   },
   {
     immediate: true
@@ -99,7 +130,7 @@ watch(
 
 onMounted(() => {
   if (unref(prop.reset)) {
-    useEventListener(window, 'resize', debounce(filterWaterfall, 300))
+    useEventListener(window, 'resize', debounce(initLayout, 300))
   }
   useIntersectionObserver(
     unref(loadMore),
@@ -117,41 +148,87 @@ onMounted(() => {
 
 <template>
   <div
-    :class="[prefixCls, 'flex', 'justify-center', 'items-center']"
+    :class="[
+      prefixCls,
+      'flex',
+      'items-center',
+      {
+        'justify-center': autoCenter
+      }
+    ]"
     ref="wrapEl"
     :style="{
-      height: `${wrapHeight + 40}px`
+      height: `${layout === 'javascript' ? wrapHeight + 40 : 'auto'}px`
     }"
   >
-    <div
-      class="relative"
-      :style="{
-        width: `${wrapWidth}px`,
-        height: `${wrapHeight + 40}px`
-      }"
-    >
-      <div
-        v-for="(item, $index) in filterData"
-        :class="[`${prefixCls}-item__${$index}`, 'absolute']"
-        :key="`water-${$index}`"
-        :style="{
-          width: `${width}px`,
-          height: `${item[props.height as string]}px`,
-          top: `${item.top}px`,
-          left: `${item.left}px`
-        }"
-      >
-        <img :src="item[props.src as string]" class="w-full h-full block" alt="" srcset="" />
+    <template v-if="layout === 'javascript'">
+      <div class="relative" :style="{ width: `${wrapWidth}px`, height: `${wrapHeight + 40}px` }">
+        <div
+          v-for="(item, $index) in filterData"
+          :class="[
+            `${prefixCls}-item__${$index}`,
+            {
+              absolute: layout === 'javascript'
+            }
+          ]"
+          :key="`water-${$index}`"
+          :style="{
+            width: `${width}px`,
+            height: `${item[props.height as string]}px`,
+            top: `${item.top}px`,
+            left: `${item.left}px`
+          }"
+        >
+          <img :src="item[props.src as string]" class="w-full h-full block" alt="" srcset="" />
+        </div>
+        <div
+          ref="loadMore"
+          class="h-40px flex justify-center absolute w-full"
+          :style="{
+            top: `${wrapHeight + gap}px`
+          }"
+        >
+          {{ end ? endText : loadingText }}
+        </div>
       </div>
+    </template>
+    <template v-else-if="layout === 'flex'">
       <div
-        ref="loadMore"
-        class="h-40px flex justify-center absolute w-full"
+        class="relative flex pb-40px"
         :style="{
-          top: `${wrapHeight + gap}px`
+          width: cols ? '100%' : 'auto'
         }"
       >
-        {{ end ? endText : loadingText }}
+        <div
+          v-for="(item, $index) in filterData"
+          :key="`waterWrap-${$index}`"
+          class="flex-1"
+          :style="{
+            marginRight: $index === filterData.length - 1 ? '0' : `${gap}px`
+          }"
+        >
+          <div
+            v-for="(child, i) in item"
+            :key="`waterWrap-${$index}-${i}`"
+            :style="{
+              marginBottom: `${gap}px`,
+              width: cols ? '100%' : `${width}px`,
+              height: cols ? 'auto' : `${child[props.height as string]}px`
+            }"
+          >
+            <img :src="child[props.src as string]" class="w-full h-full block" alt="" srcset="" />
+          </div>
+        </div>
+        <div
+          ref="loadMore"
+          class="h-40px flex justify-center absolute w-full items-center"
+          :style="{
+            bottom: 0
+          }"
+        >
+          {{ end ? endText : loadingText }}
+        </div>
       </div>
-    </div>
+    </template>
   </div>
 </template>