返回

Vue列表组件自适应宽度填充父容器的3种方法

vue.js

如何实现列表项组件的自适应宽度以填充父容器

瞧瞧,这是现在遇到的一个情况:列表项没法自动撑满父容器的宽度,看着别扭。想要的效果是,不管父容器多宽,列表项都能乖乖地填满它。

目前的状况是这样的:

目前的状况

而咱想要的是这样的:

期望的效果

问题出在哪儿呢? 问题可能出在 UserItem 组件的宽度设置上,它没有被正确地扩展以填充外部容器。把 width:839px 改成 width:100% 没用,为了兼容性,又不能写死宽度。

这就来好好分析一下,再给出几种解决办法。

一、 问题原因分析

根本原因在于, UserItem 组件设置成了flex 布局, 并且通过justify-content: space-between; 使得子元素两端贴边。这直接固定了元素的宽度,从而阻止了UserItem被父元素撑开。 而如果父级元素的宽度设置的不是100%, 那么其本身的内容将不会占满一行,而是会尽可能的缩小。从而导致这种情况发生。

二、 解决方案

下面提供几种可行的解决方案,并附带代码示例和原理说明。

方案一:移除 justify-content: space-between;, 使用margin-right: auto;

  • 原理:

    • UserItem 不应强制子元素水平两端对齐。block1 使用自动右外边距会将其推到左侧, "关注/已关注" 按钮自然就到了右侧。
  • 代码示例 (UserItem.vue):

    <template>
        <div class="userItem">
            <div class="block1">
                <t-avatar :image="avatarImage" size="50px" style="margin-right: 20px"/>
                <div>
                    <span class="username">{{ userName }}</span>
                    <div class="tip-wrapper">
                        <span class="tip">{{ numberOfNewReviews }}条新点评</span>
                    </div>
                </div>
            </div>
            <t-button v-if="follow" theme="default" @click="cancelFollow">已关注</t-button>
            <t-button v-if="!follow" theme="primary" @click="goonFollow" style="padding: 0 22px">关注</t-button>
        </div>
    </template>
    
    
    <style scoped lang="scss">
        .userItem {
            display: flex;
            align-items: center;
            // justify-content: space-between;  删除这行!
            margin-top: 5;
            margin-bottom: 20px;
            width: 100%;
        }
        .block1 {
            display: flex;
            flex-direction: row;
            align-items: center;
            margin-right: auto; // 添加这行!
        }
        .tip-wrapper {
            background-color: #f6f8f9;
            border-radius: 8px;
            padding: 0px 10px;
            margin-top: 5px;
    
        }
        .tip {
            font-size: 12px;
            color: #666;
        }
    </style>
    
  • 额外建议:
    确保userItemList 类的容器有明确的宽度(例如,通过设置其父元素或自身为 width: 100%)。

方案二: 使用 CSS Grid 布局

  • 原理:

    • 使用 CSS Grid 布局, 可以更容易地控制 UserItem 的宽度。
  • 代码示例 (UserItem.vue):

    <template>
        <div class="userItem">
            <div class="block1">
                <t-avatar :image="avatarImage" size="50px" style="margin-right: 20px" />
                <div>
                    <span class="username">{{ userName }}</span>
                    <div class="tip-wrapper">
                        <span class="tip">{{ numberOfNewReviews }}条新点评</span>
                    </div>
                </div>
            </div>
            <div class="button-container">
              <t-button v-if="follow" theme="default" @click="cancelFollow">已关注</t-button>
              <t-button v-if="!follow" theme="primary" @click="goonFollow" style="padding: 0 22px">关注</t-button>
            </div>
        </div>
    </template>
    
    <style scoped lang="scss">
    .userItem {
      display: grid;
      grid-template-columns: 1fr auto; /* 第一个元素占满剩余空间,第二个元素自适应 */
      align-items: center;
      margin-top: 5px;
      margin-bottom: 20px;
      width: 100%;
    }
    
    .block1 {
      display: flex;
      align-items: center;
    }
    
    .tip-wrapper {
      background-color: #f6f8f9;
      border-radius: 8px;
      padding: 0px 10px;
      margin-top: 5px;
    }
    
    .tip {
      font-size: 12px;
      color: #666;
    }
      .button-container{
        display: flex;
        align-items: center;
      }
    </style>
    
  • 原理说明:

    • grid-template-columns: 1fr auto;.userItem 分成两列。
    • 第一列 (1fr) 占据剩余的全部可用空间。
    • 第二列 (auto) 根据内容自动调整大小。

方案三(针对更复杂情况的进阶技巧):使用 ResizeObserver

  • 原理:

    • 如果父容器的宽度变化不是由窗口大小变化引起的(比如,侧边栏的展开/收起),上述方法可能失效。这时,可以用 ResizeObserver 来监听父容器的尺寸变化,并动态更新子组件的宽度。
    • 使用 ResizeObserver 来观察 userItemList的父容器,并给列表一个合适的宽度
  • 代码示例:

    1. 首先, 我们需要让UserItemList 给他的子元素,也就是 UserItem传递一个宽度信息.
     <template>
        <div class="userItemList">
            <UserItem
                v-for="(user, index) in users"
                :key="index"
                :avatarImage="user.avatarImage"
                :userName="user.userName"
                :numberOfNewReviews="user.numberOfNewReviews"
                :itemWidth="itemWidth"
            />
        </div>
    </template>
    <script setup lang="ts">
        import {ref, onMounted, onBeforeUnmount} from 'vue';
    
        const itemWidth = ref('')
    
    
        defineExpose({
            itemWidth
        })
    
    </script>
    
    
    1. 然后我们给ProfileFollowUserList 添加一个 ResizeObserver, 当组件被挂载后,就启动对父组件的宽度变化的监测

    
    3. 最后,修改UserItem.vue
    
      ```vue
      <template>
          <div class="userItem" :style="{ width: itemWidth }">
            <!-- 其余部分保持不变 -->
          </div>
      </template>
      <script setup lang="ts">
    
        defineProps({
          itemWidth: {
            type: String,
            required: true
          }
        })
      </script>
      ```
    
    
  • 原理说明:

    • ResizeObserver 监听指定元素的大小变化。
    • 在回调函数中, entry.contentRect.width 就能获得新宽度。
    • 通过props 将width 传递给UserItem,并最终在内联样式中生效.
    • onBeforeUnmount生命周期取消观察.
  • 安全性:
    无特殊安全考虑, 因为 ResizeObserver 只涉及DOM 尺寸。

三、 总结一下

一般情况,用前两种方案就够了。要是布局复杂,父容器宽度变化莫测,再考虑 ResizeObserver。 记住,改完代码记得多测测,各种尺寸都试试,保证兼容性!