前言
在这篇技术博客中,我将详细解析一个流行的卡片展开效果实现方案,这个效果在GitHub最受欢迎的50个项目中占有一席之地。我们将从布局、CSS样式到JavaScript交互进行全面讲解。
让我们先来瞅瞅大概的动画效果吧🚀🚀🚀

项目概述
这个项目展示了一组卡片,默认状态下所有卡片均匀分布,当用户点击某个卡片时,该卡片会展开显示更多内容,同时其他卡片会收缩。这种交互方式在图片展示、产品特性介绍等场景非常实用。
HTML结构分析
构建一个初始的框架可以用一行代码解决:.container>(.qq-panel>h3.qq-panel__title)*5
,然后其他的背景属性什么的慢慢加
html
<div class="container">
<div class="qq-panel qq-panel_active" style="background-image: url('https://images.unsplash.com/photo-1558979158-65a1eaa08691?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80')">
<h3 class="qq-panel__title">Explore The World</h3>
</div>
<div class="qq-panel" style="background-image: url('https://images.unsplash.com/photo-1572276596237-5db2c3e16c5d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80')">
<h3 class="qq-panel__title">Wild Forest</h3>
</div>
<div class="qq-panel" style="background-image: url('https://images.unsplash.com/photo-1507525428034-b723cf961d3e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1353&q=80')">
<h3 class="qq-panel__title">Sunny Beach</h3>
</div>
<div class="qq-panel" style="background-image: url('https://images.unsplash.com/photo-1551009175-8a68da93d5f9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1351&q=80')">
<h3 class="qq-panel__title">City on Winter</h3>
</div>
<div class="qq-panel" style="background-image: url('https://images.unsplash.com/photo-1549880338-65ddcdfd017b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80')">
<h3 class="qq-panel__title">Mountains - Clouds</h3>
</div>
</div>
- 使用BEM命名规范(Block Element Modifier)命名类名
- 卡片背景图片通过内联样式设置,便于动态更改
- 初始状态下第一个卡片有
qq-panel_active
类,这个类是用来区分有没有点击的,初始状态下,只有第一张卡片是被点击的
CSS样式详解
全局重置与基础设置
css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
*
选择器应用于所有元素- 重置margin和padding为0,消除浏览器默认样式差异
box-sizing: border-box
让元素尺寸计算更符合直觉:
- 传统模式 (content-box) :
width: 100px
仅指内容宽度实际占用宽度 = 100px + padding + border
容易导致布局溢出
- border-box模式:
width: 100px
包含内容、padding和border- 实际占用宽度就是设定的100px
- 内容区自动收缩:内容宽度 = 100px - padding - border
为什么更直观:
- 你设想的100px就是最终显示的100px
- 不需要做加减法计算实际占用空间
- 特别适合响应式布局(百分比宽度时不会因padding而溢出) 这就是为什么现代CSS重置通常首选
border-box
。
弹性布局与居中
css
body {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
overflow: hidden;
}
display: flex
将body设置为弹性容器align-items: center
垂直居中(交叉轴方向)justify-content: center
水平居中(主轴方向)height: 100vh
使body高度等于视窗高度overflow: hidden
隐藏溢出内容,防止滚动条出现
注意:
- vh单位:1vh等于视窗高度的1%,100vh就是整个视窗高度。这是响应式设计中常用的相对单位。
- justify-content :现在是水平居中,但其实是主轴的方向居中,
align-items
就是另一个方向的居中(这两个方向相互垂直),通过flex-direction
属性可以改变主轴的方向。(可以参考博客:告别浮动!Flexbox弹性布局终极指南引言)
容器样式
css
.container {
display: flex; /* 弹性布局 */
width: 90vw; /* 宽度 90% 视窗宽度 */
}
- 再次使用flex布局,使子元素排列在一行
width: 90vw
容器宽度为视窗宽度的90%,留出边距
卡片基础样式
css
.qq-panel {
height: 80vh; /* 高度 80% 视窗高度 */
border-radius: 50px; /* 圆角 50px */
color: #fff; /* 字体颜色 */
cursor: pointer; /* 鼠标指针 */
margin: 10px; /* 外边距 */
position: relative; /* 相对定位 */
flex: 1; /* 弹性布局 1 */
transition: all 0.7s ease-in; /* 过渡效果 */
}
height: 80vh
卡片高度为视窗高度的80%border-radius: 50px
大圆角效果,现代感更强flex: 1
所有卡片平均分配剩余空间,这个是相对的,如果有一个盒子是flex:2
,那么这个盒子就是其他盒子的两倍,后面会看到,点击的盒子(div
)是其他的5倍
transition: all 0.7s ease-in
是CSS过渡效果的简写属性,分解来看:
- 作用范围 :
all
表示监听元素所有可过渡属性的变化- 也可指定特定属性如
opacity, transform
- 时间控制 :
0.7s
表示过渡持续700毫秒- 时间长短影响动画节奏感(0.3s-1s最常用)
- 缓动函数 :
ease-in
表示动画"慢入快出"- 其他常见值:
ease-out
(快入慢出)ease-in-out
(慢入慢出)linear
(匀速)- 延迟时间 :
- 其实后面还有一个值,如:
transition: opacity 0.3s ease-in 0.4s;
所示,这里的0.4s
表示动画不会立即执行,而是等待 0.4 秒后才开始。提示:过渡属性应写在元素的默认状态,而非:hover等伪类中
卡片标题样式
css
.qq-panel__title {
font-size: 24px; /* 字体大小 */
position: absolute; /* 绝对定位 */
bottom: 20px; /* 底部 20px */
left: 20px; /* 左边 20px */
opacity: 0; /* 不透明度 */
}
- 使用绝对定位将标题固定在卡片左下角
- 初始
opacity: 0
使标题不可见
激活状态卡片样式
css
.qq-panel_active {
flex: 5; /* 弹性布局 5 */
}
.qq-panel_active .qq-panel__title {
opacity: 1; /* 不透明度 */
transition: opacity 0.3s ease-in 0.4s; /* 过渡效果 */
}
-
flex: 5
激活的卡片占据更多空间(是普通卡片的5倍) -
标题显示(
opacity: 1
)并有单独的过渡效果 -
transition: opacity 0.3s ease-in 0.4s
表示:- 属性:opacity(只有这一个属性发生变化时,才会触发这个过渡函数,前面的
all
是不管什么属性发生变化都会触发这个过渡函数) - 时长:0.3秒
- 缓动函数:ease-in
- 延迟:0.4秒(让卡片展开动画先进行)
- 属性:opacity(只有这一个属性发生变化时,才会触发这个过渡函数,前面的
JavaScript交互逻辑
javascript
//获取所有卡片元素
const panels = document.querySelectorAll('.qq-panel');
panels.forEach(panel => {
// JS 是事件机制的语言
panel.addEventListener('click', () => {
// 移除所有的 active 类
removeActiveClasses(); // 模块化
panel.classList.toggle('qq-panel_active');
});
});
function removeActiveClasses() {
panels.forEach(panel => {
panel.classList.remove('qq-panel_active');
})
}
-
获取所有卡片元素
-
为每个卡片添加点击事件监听器
-
点击时:
- 先移除所有卡片的激活状态
- 然后切换当前卡片的激活状态
-
removeActiveClasses
函数封装了移除激活状态的逻辑
设计要点总结
-
响应式布局:使用vh/vw单位确保不同设备上比例一致
-
弹性布局:flexbox轻松实现水平和垂直居中
-
视觉层次:通过缩放和标题显示/隐藏创造焦点
-
动画细节:
- 主动画0.7秒确保效果明显但不拖沓
- 标题延迟0.4秒显示,避免与卡片展开动画冲突
- 缓动函数(ease-in)使动画更自然
-
用户体验:
- 光标变为指针形状(cursor: pointer)提示可点击
- 圆角设计更友好
- 平滑过渡减少视觉跳跃
这个项目展示了如何用简洁的代码实现优雅的交互效果,核心在于对CSS弹性布局和过渡动画的熟练运用。通过分析这个案例,我们可以学习到现代前端开发中许多实用的技巧和设计理念。