【VUE】vue3 SFC方式实现九宫格效果

效果图

【VUE】vue3 SFC方式实现九宫格效果_第1张图片

调用方式

<template>
	<grid class="grid-demo" isScale>
	  <grid-item class="grid-demo-item">1bg-grid-item>
	  <grid-item class="grid-demo-item">2bg-grid-item>
	  <grid-item class="grid-demo-item">3bg-grid-item>
	  <grid-item class="grid-demo-item">4bg-grid-item>
	  <grid-item class="grid-demo-item">5bg-grid-item>
	  <grid-item class="grid-demo-item">6bg-grid-item>
	  <grid-item class="grid-demo-item">7bg-grid-item>
	  <grid-item class="grid-demo-item">8bg-grid-item>
	  <grid-item class="grid-demo-item">9bg-grid-item>
	grid>
<template>

<style scoped>
.grid-demo{
  background-color: #2b2d42;
}
.grid-demo-item{
  color: #fff;
  background-color: #41497D;
}
style>

grid-item组件代码

<script setup>
import {ref, inject, watch} from 'vue'

defineOptions({
  name: 'GridItem'
})
const isScale = inject("isScale");

// 自适应高度等于宽度
const liDom = ref();
const height = ref("");
watch(() => liDom.value, (dom) => {
  if (isScale) {
    height.value = dom.clientWidth + "px";
  } else {
    height.value = "";
  }
}, {
  deep: true,
});

</script>

<template>
  <li class="grid-layout-item" ref="liDom">
    <slot />
  </li>
</template>

<style scoped lang="scss">
.grid-layout-item {
  float: left;
  list-style: none;
  height: v-bind(height);
  line-height: v-bind(height);
  text-align: center;
  vertical-align: middle;
  margin-bottom: 10px;
}
</style>

grid组件代码

<script setup>
import {useSlots, provide, ref, computed, watch, onMounted} from "vue";

defineOptions({
  name: "Grid"
})
const props = defineProps({
  /** 间隔 */
  gutter: {type: Number, default: 10},
  /** 列数 */
  column: {type: Number, default: 3},
  /** 约束宽=高 */
  isScale: {type: Boolean, default: false},
})

provide('isScale', props.isScale)
// 插槽列表
const slotList = useSlots()?.default() || []
// 渲染列表
const renderList = ref([])
const columnNum = ref(props.column || 1)
// 尾行编号
const lastRow = ref(0);

const gridStyles = computed(() => {
  return {
    overflow: "hidden",
    padding: `${props.gutter}px`,
    margin: "0px"
  }
})

function _SlotListHandler(el, index) {
  if (typeof el.type === "object" && el.type.name === "GridItem") {
    if (!el.props) el.props = {};
    if (!el.props.style) el.props.style = {};
    el.props.style.width = `calc((100% - ${props.gutter * (columnNum.value - 1)}px) / ${columnNum.value})`;

    // 右边距设置和下边距设置
    el.props.style.marginRight = `${props.gutter}px`;
    el.props.style.marginBottom = `${props.gutter}px`;

    // 每行最后一个不加右边距
    if ((index + 1) % columnNum.value === 0) el.props.style.marginRight = "0px";
    // 最后一行不加下边距
    if ((index + 1) >= lastRow.value * columnNum.value){
      el.props.style.marginBottom = 0;
    }else{
      el.props.style.marginBottom = `${props.gutter}px`
    }

    return el;
  } else if (typeof el.type === 'symbol') {
    let _list = [];
    el.children.forEach((childrenEl, childrenIndex) => {
      _list.push(_SlotListHandler(childrenEl, childrenIndex));
    })
    return _list;
  }
  return false;
}

function renderSlot(list) {
  renderList.value = [];
  lastRow.value = Math.ceil((list[0]?.children||[]).length / columnNum.value) - 1;
  list.forEach((el, index) => {
    let _e = _SlotListHandler(el, index);
    if (_e instanceof Array) {
      renderList.value.push(..._e);
    } else if (typeof _e === 'object') {
      renderList.value.push(_e);
    }
  });
}

watch(
    () => slotList,
    (list) => {
      console.log('list:', list)
      renderSlot(list);
    },
    {deep: true}
)

onMounted(()=>{
  renderSlot(slotList);
})
</script>

<template>
  <div class="grid-layout">
    <ul :style="gridStyles">
      <component v-for="(element, key) in renderList" :key="key"
                 :is="element"/>
    </ul>
  </div>
</template>

<style scoped lang="scss">
.grid-layout {
  list-style: none;
}
</style>

你可能感兴趣的:(Web前端,vue.js,javascript,前端)