
本文介绍如何通过抽象共用样式、使用 data 属性区分实例,并封装可复用的激活/关闭逻辑,消除 css 重复代码,同时保持两个 banner 在触发时机(立即显示 vs 滚动触发)和内容上的独立性。
在前端开发中,当页面需要多个视觉一致但行为逻辑不同的 UI 组件(如两个样式相同、仅触发条件不同的提示横幅 Banner)时,直接复制 CSS 类极易导致维护困难和样式不一致。本文提供一套语义清晰、结构解耦、CSS 零重复的实践方案。
✅ 核心思路:样式统一 + 行为分离 + 数据驱动
- CSS 层面:所有共享样式只定义一次,使用单一类名 .exponea-banner 作为基础样式容器;
- HTML 层面:通过 data-banner="first" / data-banner="second" 区分不同 Banner 实例,避免依赖 ID 或冗余类名;
- JS 层面:封装通用函数 activeBanner() 和 closeBanner(),接收 banner 元素、激活类名、关闭按钮选择器作为参数,实现逻辑复用。
? 重构后的 HTML 结构(简洁且语义化)
✅ 注意:两个 Banner 均使用 .exponea-banner 作为主样式类,关闭按钮统一为 .exponea-close —— 这是 CSS 复用的前提。
? 精简后的 CSS(无任何重复)
.exponea-banner {
font-family: Roboto, sans-serif;
position: fixed;
right: 20px;
bottom: 20px;
background-color: #2e364d;
color: #ebeef7;
padding: 30px 80px 30px 35px;
font-size: 16px;
line-height: 1;
border-radius: 5px;
box-shadow: 0 3px 30px rgba(116, 119, 176, 0.3);
opacity: 0;
visibility: hidden; /* 推荐用 visibility + opacity 替代 display: none,便于过渡动画 */
transition: opacity 0.4s ease, visibility 0.4s ease;
z-index: 999;
}
.exponea-banner .exponea-close {
position: absolute;
top: 0;
right: 0;
padding: 5px 10px;
font-size: 25px;
font-weight: 300;
cursor: pointer;
opacity: 0.75;
}
.exponea-banner .exponea-text {
margin-bottom: 8px;
}
.exponea-banner .exponea-count {
opacity: 0.7;
font-weight: 300;
display: flex;
align-items: center;
}
.exponea-banner .exponea-label {
position: absolute;
bottom: 10px;
right: 10px;
font-size: 12px;
opacity: 0.75;
text-align: left;
}
/* 激活状态:统一控制显隐与动画 */
.open {
visibility: visible;
opacity: 1;
}? 提示:移除了 .exponea-banner1 及所有 open1 相关样式;z-index 已统一设为 999,如需第二 Banner 层级更高,可在 JS 中动态设置 banner.style.zIndex = '9999',而非写死 CSS。
⚙️ 模块化 JavaScript(高内聚、低耦合)
// 获取 Banner 实例
const banner1 = document.querySelector('[data-banner="first"]');
const banner2 = document.querySelector('[data-banner="second"]');
// 通用关闭逻辑:为指定 Banner 的关闭按钮绑定点击事件
const closeBanner = (banner, activeClass, closeSelector) => {
const closeBtn = banner.querySelector(closeSelector);
if (closeBtn) {
closeBtn.addEventListener('click', () => banner.classList.remove(activeClass));
}
};
// 通用激活逻辑:添加类并绑定关闭行为
const activeBanner = (banner, activeClass, closeSelector) => {
banner.classList.add(activeClass);
closeBanner(banner, activeClass, closeSelector);
};
// Banner 1:页面加载完成即激活
const bannerOneHandler = (banner) => {
if (banner) activeBanner(banner, 'open', '.exponea-close');
};
// Banner 2:滚动至页面 90% 高度时激活(带执行防重)
const bannerTwoHandler = (banner) => {
if (!banner) return;
let executed = false;
const threshold = 0.9;
const handleScroll = () => {
const scrollBottom = window.innerHeight + window.scrollY;
const docHeight = document.body.offsetHeight;
if (scrollBottom >= docHeight * threshold && !executed) {
executed = true;
activeBanner(banner, 'open', '.exponea-close');
// 可选:移除监听提升性能
window.removeEventListener('scroll', handleScroll);
}
};
window.addEventListener('scroll', handleScroll);
};
// 启动两个 Banner
document.addEventListener('DOMContentLoaded', () => {
bannerOneHandler(banner1);
bannerTwoHandler(banner2);
});✅ 关键优势总结
| 维度 | 优化点 |
|---|---|
| CSS 维护性 | 所有样式集中于 .exponea-banner,新增 Banner 无需复制 CSS |
| JS 可扩展性 | activeBanner / closeBanner 支持任意数量 Banner 实例,只需传入对应元素与选择器 |
| HTML 语义性 | 使用 data-banner 属性替代冗余类名或 ID,结构更清晰、更利于自动化测试 |
| 性能与体验 | 使用 visibility + opacity 实现平滑过渡;滚动监听添加防抖/防重逻辑;支持动态卸载事件 |
⚠️ 注意事项:
- 确保每个 Banner 内部的 .exponea-close 结构一致;若需差异化关闭逻辑(如弹出确认框),可在 closeBanner 中扩展回调参数;
- 若 Banner 内容需服务端渲染或动态注入,建议将 data-banner 值作为数据源统一管理;
- 生产环境推荐将上述 JS 封装为 IIFE 或 ES 模块,避免全局污染。
通过以上重构,你不仅彻底消除了 CSS 重复,更构建了一套可复用、易测试、易扩展的 Banner 系统——这才是真正面向维护与协作的工程化实践。
立即学习“前端免费学习笔记(深入)”;










