封装宽高比容器组件:优雅解决动态宽度下的同宽高比图片展示问题

引言

在项目开发中,我遇到了一个令人头疼的问题:当需要展示一组具有相同宽高比的图片时,如果列表容器采用 flex 布局,图片容器的宽度会根据屏幕尺寸动态变化,传统的图片展示方式(如 object-fit: covercontain)往往无法满足需求。例如,object-fit: cover 会导致图片被裁剪过多,而 object-fit: contain 则可能在图片周围产生大量白边。这些问题不仅影响用户体验,也对视觉效果造成了严重干扰。

虽然现代 CSS 提供了 aspect-ratio 属性,能够直接设置元素的宽高比,从而优雅地解决这一问题,但遗憾的是,我所在的项目需要考虑浏览器兼容性,无法直接使用这一新特性。因此,我选择封装一个宽高比容器组件,通过传统的 CSS 技术(如 padding-top 百分比技巧)来模拟宽高比的效果。这样,既能实现动态宽度下的同宽高比图片展示,又能确保兼容性,从而为用户提供美观且高效的图片列表布局。

实现细节

定义 props

ts 复制代码
props: {
  width: {
    type: [Number, String],
    default: "100%"
  },
  aspectRatio: {
    type: String,
    default: "4:3"
  }
},

计算属性处理

ts 复制代码
computed: {
  wrapperStyle(){
    // 如果 width 是字符串,检查是否包含百分号或 px
    if (typeof this.width === 'string') {
      // 如果没有百分号或 px,添加 px
      if (!this.width.includes('%') && !this.width.includes('px')) {
        return { width: `${this.width}px` };
      }
      // 如果已经包含百分号或 px,直接使用
      return { width: this.width };
    }
    // 如果 width 是数字,直接添加 px
    if (typeof this.width === 'number') {
      return { width: `${this.width}px` };
    }
    // 默认返回对象
    return {
      width: "100%"
    };
  },
  innerStyle(){
    // 解析宽高比字符串,例如 "4:3"
    const [widthPart, heightPart] = this.aspectRatio.split(':').map(Number);
    // 计算 padding-top 的百分比值
    const ratioPercentage = (heightPart / widthPart) * 100;
    return {
      "padding-top": `${ratioPercentage}%`
    };
  }
}

编写 template

html 复制代码
<div class="aspect-wrapper" :style="wrapperStyle">
  <div class="aspect-inner" :style="innerStyle">
    <div class="aspect-content">
      <slot></slot>
    </div>
  </div>
</div>

书写 style

css 复制代码
.aspect-inner {
  width: 100%;
  position: relative;
}

.aspect-content {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

.aspect-content > :first-child {
  width: 100%;
  height: 100%;
}

组件源码

ts 复制代码
<script>
export default {
  name: 'AspectRatioWrapper',
  props: {
    width: {
      type: [Number, String],
      default: "100%"
    },
    aspectRatio: {
      type: String,
      default: "4:3"
    }
  },
  computed: {
    wrapperStyle(){
      // 如果 width 是字符串,检查是否包含百分号或 px
      if (typeof this.width === 'string') {
        // 如果没有百分号或 px,添加 px
        if (!this.width.includes('%') && !this.width.includes('px')) {
          return { width: `${this.width}px` };
        }
        // 如果已经包含百分号或 px,直接使用
        return { width: this.width };
      }
      // 如果 width 是数字,直接添加 px
      if (typeof this.width === 'number') {
        return { width: `${this.width}px` };
      }
      // 默认返回对象
      return {
        width: "100%"
      };
    },
    innerStyle(){
      // 解析宽高比字符串,例如 "4:3"
      const [widthPart, heightPart] = this.aspectRatio.split(':').map(Number);
      // 计算 padding-top 的百分比值
      const ratioPercentage = (heightPart / widthPart) * 100;
      return {
        "padding-top": `${ratioPercentage}%`
      };
    }
  }
}
</script>

<template>
  <div class="aspect-wrapper" :style="wrapperStyle">
    <div class="aspect-inner" :style="innerStyle">
      <div class="aspect-content">
        <slot></slot>
      </div>
    </div>
  </div>
</template>

<style scoped>
.aspect-inner {
  width: 100%;
  position: relative;
}

.aspect-content {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

.aspect-content > :first-child {
  width: 100%;
  height: 100%;
}
</style>

FAQ

AspectRatioWrapper 组件设计为仅容纳一个子元素,以确保其内部的布局逻辑能够正常工作。当将子元素(如 <img> 标签)放入该组件时,组件会自动将第一个子元素的宽度和高度设置为 100%,从而使其完全填充容器的内部空间。

为了进一步优化图片的展示效果,建议在使用 <img> 标签时,为图片设置 object-fit 属性为 covercontain

html 复制代码
<AspectRatioWrapper aspectRatio="16:9"> 
    <img src="your-image-url.jpg" alt="示例图片" style="object-fit: cover;">
</AspectRatioWrapper>

感谢阅读,敬请斧正!

相关推荐
江城开朗的豌豆1 分钟前
在写vue公用组件的时候,怎么提高可配置性
前端·javascript·vue.js
江城开朗的豌豆2 分钟前
Vue路由跳转的N种姿势,总有一种适合你!
前端·javascript·vue.js
江城开朗的豌豆2 分钟前
Vue路由玩法大揭秘:三种路由模式你Pick谁?
前端·javascript·vue.js
江城开朗的豌豆3 分钟前
Vue路由守卫全攻略:给页面访问装上'安检门'
前端·javascript·vue.js
小磊哥er10 分钟前
【前端工程化】前端组件模版构建那些事
前端
前端 贾公子11 分钟前
monorepo + Turborepo --- 开发应用程序
java·前端·javascript
江城开朗的豌豆15 分钟前
Vue路由传参避坑指南:params和query的那些猫腻
前端·javascript·vue.js
十里青山23 分钟前
超好用的vue图片预览插件更新啦,hevue-img-preview 7.0.0版本正式发布,支持vue2/vue3/移动/pc,增加缩略图、下载、自定义样式等
前端·javascript·vue.js
lichenyang45332 分钟前
css模块化以及rem布局
前端·javascript·css
小熊哥^--^34 分钟前
条件渲染 v-show与v-if
前端