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

传统的做法可能是:
- 为每个Banner写独立的组件
- 使用内联样式动态计算位置
但这些方案要么代码冗余,要么维护困难。我采用了一种更优雅的方案:数据驱动 + CSS预定义样式。
核心思路
我们的方案包含两个关键部分:
- 数据配置层:通过配置数据定义每个Banner的图片数组和唯一标识
- 样式预定义层:在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-img1item.id = "two",cindex = 1→ 类名:two-img2item.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数组中添加配置,而且无需修改组件逻辑代码,配置与展示逻辑完全分离,产品提新的需求只需要改动数据,易于迭代