图片响应式布局多种实现方案 保持外部容器和内部图片高宽比例不变

210 阅读2分钟

响应式卡片布局 保持外部容器和内部图片高宽比例不变 父容器card包裹子容器 子容器有多个图片情况下

方案一 网格布局grid-template-columns: repeat(auto-fit, minmax(200px, 0.7fr)); + 背景图 padding法

父card 外部wrapper容器

  :deep(.dataset-list-card-scroll) {
    display: grid;
    // 每行card自适应铺满 最小宽度为200px 最大为0.7倍剩余空间
    grid-template-columns: repeat(auto-fit, minmax(200px, 0.7fr));
    // gap: 10px;
    justify-content: center;

    .listcard {
      padding: 10px;
    }
  }

子组件card内部 图片需要为背景图

       <div class="banner-img" :style="`background:url( ${getPcImgUrl()})`">
        
      .banner-img-full{
        width: 100%;
         height: 0;
        padding-bottom: 67%;
       //覆盖全尺寸 超出部分被裁剪
        background-size: cover !important;
         //覆盖全尺寸 拉伸长宽适应容器 图片会变形   等于img标签属性 object-fit:fill
        background-size: cover !important;
        
      }
  

方案二: window.addEventListener('resize',()=>{}) 配合背景图或者img标签

创建窗口监听hook useFlowLayout.js 通过监听窗口变化 window.addEventListener('resize',()=>{})

  import { reactive, onMounted, toRefs } from 'vue';

/**
* 动态计算卡片宽高和间距
*
* @param className 卡片可视区域父节点元素类名
* @param extraHeight 卡片文字部分 或者其他部分 固定的额外高度 大小不会随着窗口变化改变
* @param beReducedWidth 基于非异步加载的父容器 需要减掉的两边padding值
*/

export function useFlowLayout(className: string, extraHeight = 0, beReducedWidth = 30) {
const listCardCss = reactive<{
  // 卡片动态宽度
  cardWidth: string;
  // 卡片动态高度
  cardHeight: string;
  // 卡片之间动态padding
  paddingX: string;
  // 可调节宽度进度条 card 最大宽度 保持和父级dom宽度一致,最多放大到每行一个卡片
  maxSliderWidth: number;
  // 卡片区域高度 (未添加extraHeight)
  cardHeightPure: string;
}>({
  cardWidth: '100px',
  cardHeight: '100px',
  cardHeightPure: '100px',
  paddingX: '10px',
  maxSliderWidth: 900,
});

// 页面设置卡片大小进度条改变时触发回调给宽度重新赋值
const changeWidth = (sliderValue: number) => {
  if (sliderValue <= 200) {
    sliderValue = 200;
  }
  listCardCss.cardWidth = sliderValue + 'px';
  listCardCss.cardHeight = sliderValue / 1.5 + extraHeight + 'px';
};
const getCssConfig = (contenWidthNum: number) => {
  if (contenWidthNum <= 200) {
    contenWidthNum = 200;
  }
  // const tagnum = Math.floor(contenWidthNum / 300);
  const tagnum = Math.ceil(contenWidthNum / 300);
  listCardCss.maxSliderWidth = contenWidthNum;
  const cardWidth = Math.floor(contenWidthNum / tagnum);
  listCardCss.cardWidth = cardWidth + 'px';
  listCardCss.cardHeight = Math.floor(contenWidthNum / tagnum) / 1.5 + extraHeight + 'px';
  listCardCss.cardHeightPure = Math.floor(contenWidthNum / tagnum) / 1.5 + 'px';
};
// 重置页面卡片宽度
const resetWidth = () => {
  getCssConfig(listCardCss.maxSliderWidth);
};
const { cardHeight, cardHeightPure, cardWidth, paddingX, maxSliderWidth } = toRefs(listCardCss);
onMounted(() => {
  setTimeout(() => {
    const box = document.getElementsByClassName(className)[0];

    let wrapwidth = window.getComputedStyle(box, null)['width'];
    if (!wrapwidth.includes('px')) {
      const boxHeader = document.getElementsByClassName('basic-layout-multiple-header')[0];
      const wrapwidthNum = parseInt(window.getComputedStyle(boxHeader, null)['width']);
      wrapwidth = wrapwidthNum - 230 + 'px';
    }
    let contenWidthNum = Number(wrapwidth.replaceAll('px', '')) - beReducedWidth;
    getCssConfig(contenWidthNum);
    window.addEventListener('resize', function () {
      wrapwidth = window.getComputedStyle(box, null)['width'];
      contenWidthNum = Number(wrapwidth.replaceAll('px', '')) - beReducedWidth;
      getCssConfig(contenWidthNum);
    });
  }, 0);
});

return {
  cardHeight,
  cardWidth,
  paddingX,
  changeWidth,
  cardHeightPure,
  resetWidth,
  maxSliderWidth,
};
}

父组件使用hook

  let { cardHeight, cardWidth, paddingX } = useFlowLayout('basic-datasetList-list', 105);

//样式绑定  

  :deep(.dataset-list-card-scroll) {
    display: grid;
    grid-template-columns: repeat(auto-fill, v-bind(cardWidth));
    // gap: 10px;
    justify-content: center;

    .listcard {
      padding: v-bind(paddingX);
      height: v-bind(cardHeight);
    }
  

子组件有2种处理方式

1.子组件card 中图片以背景图展示 同方案一子组件处理方式相同

2.子组件中图片以img标签展示

图片区域高宽设置百分比 
如果包含额外固定高度110px  
那么可以设置图片容器部分 height: calc(100% - 110px);