数据驱动与CSS预定义样式:实现灵活多变的Banner布局

背景

在开发一个活动Banner组件时,每个Banner需要展示不同数量的图片,且每张图片的位置、大小都需要精确控制,以创造出独特的视觉效果

传统的做法可能是:

  • 为每个Banner写独立的组件
  • 使用内联样式动态计算位置

但这些方案要么代码冗余,要么维护困难。我采用了一种更优雅的方案:数据驱动 + CSS预定义样式

核心思路

我们的方案包含两个关键部分:

  1. 数据配置层:通过配置数据定义每个Banner的图片数组和唯一标识
  2. 样式预定义层:在CSS中为每个可能的图片位置预定义样式类

通过动态生成类名,将数据和样式完美连接。

实现细节

1. 数据配置

首先,我们定义一个数据数组,每个Banner项包含:

  • id: 唯一标识符,用于生成CSS类名
  • img: 图片数组,支持不同数量的图片
  • 其他业务数据(数量、提示文字、链接等)
tsx 复制代码
const list = [
  {
    id: "four",
    num: 3,
    hint: t("exclusiveEventDesc4"),
    img: [img1, img2],  // 2张图片
    link: "https://forms.gle/XshRsPmkrKnaQUdA8",
    targetTime: '2025-11-15T13:00:00Z' 
  },
  {
    id: "two",
    num: 9,
    hint: t("exclusiveEventDesc2"),
    img: [img1, img2, img3, img4],  // 4张图片
    link: "https://forms.gle/UJ1oPxGgDzRqJ1y6A",
    targetTime: '2025-11-13T13:00:00Z'
  },
  {
    id: "one",
    num: 50,
    hint: t("exclusiveEventDesc1"),
    img: [img1, img2, img3, img4],  // 4张图片
    link: "https://tinyurl.com/2025CCCCBREAKFAST",
    targetTime: '2025-11-13T13:00:00Z'
  },
];

2. 动态类名生成

在渲染时,我们通过模板字符串动态生成类名:

tsx 复制代码
<div className="kol-img">
  {item.img?.map((citem, cindex) => (
    < img 
      src={citem} 
      key={cindex} 
      className={`${item.id}-img${cindex + 1}`}  // 关键:动态生成类名
    />
  ))}
</div>

类名生成规则

  • item.id = "one", cindex = 0 → 类名:one-img1
  • item.id = "two", cindex = 1 → 类名:two-img2
  • item.id = "four", cindex = 0 → 类名:four-img1

每个Banner的每张图片都对应唯一的类名。

3. CSS预定义样式

在CSS文件中,我们为每个可能的类名组合预定义样式:

less 复制代码
.kol-img {
  position: relative;
  img {
    filter: grayscale(100%);
    border-radius: 100px;
  }
}

// Banner "one" 的4张图片布局
.one-img1 {
  width: 126px;
  position: absolute;
  top: 130px;
  left: 20px;
  @media @SmallScreen {
    width: 44px;
    top: 10px;
  }
}

.one-img2 {
  width: 88px;
  position: absolute;
  left: 130px;
  top: 85px;
  @media @SmallScreen {
    width: 72px;
    left: 56px;
    top: 28px;
  }
}

.one-img3 {
  width: 72px;
  position: absolute;
  left: 39px;
  top: 49px;
  @media @SmallScreen {
    width: 76px;
    top: 96px;
    left: 0;
  }
}

.one-img4 {
  width: 72px;
  position: absolute;
  left: 146px;
  top: 231px;
  @media @SmallScreen {
    width: 44px;
    left: 70px;
    top: 170px;
  }
}

// Banner "two" 的4张图片布局
.two-img1 {
  width: 125px;
  position: absolute;
  top: 56px;
  left: 100px;
  @media @SmallScreen {
    width: 75px;
    top: 80px;
    left: 60px;
  }
}

.two-img2 {
  width: 90px;
  position: absolute;
  left: 13px;
  top: 160px;
  @media @SmallScreen {
    width: 43px;
    top: 44px;
    left: 28px;
  }
}

// ... 更多样式定义

4. 完整组件代码

tsx 复制代码
export default function BannerKOL() {
  const [t] = useTranslation();
  const isMobile = useIsMobile();

  const list = [
    // ... 数据配置
  ];

  return (
    isMobile ? (
      <div className="bannerkol-wrap">
        {list.map((item, index) => (
          <div key={index}>
            <div className="kol-line"></div>
            <div className="kol-box df">
              <div className="kol-img">
                {item.img?.map((citem, cindex) => (
                  < img 
                    src={citem} 
                    key={cindex} 
                    className={`${item.id}-img${cindex + 1}`} 
                  />
                ))}
              </div>
              <div className="kol-info">
                {/* 其他内容 */}
              </div>
            </div>
          </div>
        ))}
      </div>
    ) : (
      <Marquee className="bannerkol-marquee" speed={60} autoFill gradient={false} pauseOnHover={true}>
        <div className="bannerkol-wrap">
          {list.map((item, index) => (
            <div key={index}>
              <div className="kol-line"></div>
              <div className={`kol-box df ${item.img?.length ? '' : 'no-img'}`}>
                <div className="kol-img">
                  {item.img?.map((citem, cindex) => (
                    < img 
                      src={citem} 
                      key={cindex} 
                      className={`${item.id}-img${cindex + 1}`} 
                    />
                  ))}
                </div>
                <div className="kol-info">
                  {/* 其他内容 */}
                </div>
              </div>
            </div>
          ))}
        </div>
      </Marquee>
    )
  );
}

总结

通过数据驱动 + CSS预定义样式 的组合,我们实现了一个既灵活又易维护的Banner组件。这种方案的核心在于:数据驱动,易于扩展 ,数据和视图解耦, 添加新的Banner只需在list数组中添加配置,而且无需修改组件逻辑代码,配置与展示逻辑完全分离,产品提新的需求只需要改动数据,易于迭代

相关推荐
xiaotao1312 小时前
第九章:Vite API 参考手册
前端·vite·前端打包
午安~婉2 小时前
Electron桌面应用聊天(续)
前端·javascript·electron
彧翎Pro2 小时前
基于 RO1 noetic 配置 robosense Helios 32(速腾) & xsense mti 300
前端·jvm
小码哥_常2 小时前
解锁系统设置新姿势:Activity嵌入全解析
前端
之歆3 小时前
前端存储方案对比:Cookie-Session-LocalStorage-IndexedDB
前端
哟哟耶耶3 小时前
vue3-单文件组件css功能(:deep,:slotted,:global,useCssModule,v-bind)
前端·javascript·css
是罐装可乐3 小时前
深入理解“句柄(Handle)“:从浏览器安全到文件系统访问
前端·javascript·安全
华科易迅3 小时前
Vue如何集成封装Axios
前端·javascript·vue.js
康一夏3 小时前
Next.js 13变化有多大?
前端·react·nextjs
糖炒栗子03263 小时前
前端项目标准环境搭建与启动
前端