一、雨夜中的回忆盒子
窗外的雨点击打在玻璃上,我蜷在沙发里翻着旧硬盘。光标停在 "2020 年夏" 的文件夹上,指尖突然触到一丝凉意 ------ 那是巴厘岛海水的温度,是东京居酒屋的烟火气,是布拉格广场的玫瑰香。我突然想做一个「时光展柜」,让这些瞬间能在点击间绽放成立体的故事。
🌊 场景还原:当点击遇见记忆
html
<div class="container">
<!-- 初始状态:五张卡片像被压缩的时间胶囊 -->
<div class="panel active" style="background-image:url('https://picsum.photos/id/1018/1200/800')">
<h3>Explore the World</h3>
</div>
<div class="panel" style="background-image:url('https://picsum.photos/id/292/1200/800')">
<h3>Have a good dinner</h3>
</div>
<!-- 更多卡片... -->
</div>
当鼠标点击那张海洋主题的卡片时,它突然从窄条膨胀成巨幕 ------ 浪花声仿佛透过屏幕传来,2019 年在巴厘岛潜水的记忆瞬间被点亮:珊瑚群在阳光下折射出蓝绿色光斑,教练打手势让我看游过的海龟... 其他卡片则谦逊地退到两侧,像博物馆里等待观赏的展品。
二、技术拆解:让静态图片拥有呼吸感
这个交互效果的核心,是 CSS 弹性布局与过渡动画的默契配合:
1. 卡片的「呼吸」机制
css
.panel {
flex: 0.5; /* 初始宽度占比 */
transition: flex 0.7s ease-in; /* 0.7秒缓动展开 */
}
.panel.active {
flex: 5; /* 激活后宽度扩大10倍 */
}
就像老式相机的暗箱,卡片在flex: 0.5到flex: 5的变化中,从「缩略图」变为「巨幕」。ease-in缓动函数让展开过程像拉开窗帘般自然 ------ 开始缓慢,逐渐加速,最后轻柔定格。
2. 文字的「浮现」魔法
css
.panel h3 {
opacity: 0; /* 初始隐藏 */
}
.panel.active h3 {
opacity: 1;
transition: opacity 0.3s ease-in 0.4s; /* 0.4秒后淡入 */
}
标题文字的出现藏着小心机:在卡片展开 0.4 秒后才开始淡入。这个延迟让视觉焦点先被图片抓住,再自然过渡到文字叙事,就像电影镜头从全景慢慢推到字幕。
三、适配不同屏幕的「时光机」
当我在手机上打开这个页面时,发现最后两张卡片自动隐藏了 ------ 这是代码里的「屏幕魔法」:
css
@media(max-width: 480px) {
.panel:nth-of-type(4),
.panel:nth-of-type(5) {
display: none; /* 小屏幕隐藏后两张卡片 */
}
}
就像老式相册会根据页数自动调整厚度,响应式设计让展柜在不同设备上都能完美呈现。在地铁上用手机浏览时,手指点击卡片的瞬间,狭窄的屏幕会突然「膨胀」出整个世界。
四、从技术到故事:交互设计的灵魂
这个看似简单的效果,藏着交互设计的核心逻辑:让操作成为故事的入口。当用户点击卡片时,不是在触发代码,而是在「打开」一段记忆 ------ 就像小时候打开奶奶的木箱,每一次掀开盖子,都有樟脑丸香气和旧毛衣里掉出的糖纸。
你可以用它做:
- 旅行博客的「目的地展厅」,点击城市卡片展开行程故事
- 美食公众号的「菜谱墙」,点击菜品图片弹出做法
- 个人简历的「项目展示」,点击卡片展开作品详情
javascript
const panels = document.querySelectorAll('.panel');
panels.forEach(panel => {
panel.addEventListener('click', () => {
removeActiveClasses(); // 先关闭所有卡片
panel.classList.add('active'); // 再打开当前卡片
});
});
这段 JavaScript 像极了博物馆的管理员:当你走进一个展厅,它会轻轻关上其他展室的门,让你能专注欣赏眼前的故事。
尾声:让代码成为记忆的容器
雨还在下,但硬盘里的旧照片已经有了新的生命。这个用 CSS 和 JavaScript 搭建的「时光展柜」,让我明白技术最美的样子不是炫技,而是成为情感的载体。就像此刻,当我点击那张海洋图片,2019 年的阳光依然能穿过代码,照亮现实里的雨夜。
五、完整代码
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Expanding Card Effect</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Muli&display=swap');
*{
box-sizing: border-box;
}
body {
font-family: 'Muli', sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
overflow: hidden;
margin: 0;
}
.container {
display: flex;
width: 90vw;
}
.panel {
background-size: auto 100%;
background-position: center;
background-repeat: no-repeat;
height: 80vh;
border-radius: 50px;
color: #fff;
cursor: pointer;
flex:0.5;
margin: 10px;
position: relative;
transition: flex 0.7s ease-in;
}
.panel h3{
font-size: 24px;
position: absolute;
bottom: 20px;
left: 20px;
margin: 0;
opacity: 0;
}
.panel.active {
flex: 5;
}
.panel.active h3 {
opacity: 1;
transition: opacity 0.3s ease-in 0.4s;
}
@media(max-width: 480px) {
.container {
width:100vw;
}
.panel:nth-of-type(4),
.panel:nth-of-type(5) {
display: none;
}
}
</style>
</head>
<body>
<div class="container">
<!-- 使用在线图片替代本地图片 -->
<div class="panel active" style="background-image:url('https://picsum.photos/id/1018/1200/800')">
<h3>Explore the World</h3>
</div>
<div class="panel" style="background-image:url('https://picsum.photos/id/292/1200/800')">
<h3>Have a good dinner</h3>
</div>
<div class="panel" style="background-image:url('https://picsum.photos/id/431/1200/800')">
<h3>Drink some tea for happy!</h3>
</div>
<div class="panel" style="background-image:url('https://picsum.photos/id/1025/1200/800')">
<h3>See the beautiful things!</h3>
</div>
<div class="panel" style="background-image:url('https://picsum.photos/id/325/1200/800')">
<h3>Let's Play!</h3>
</div>
</div>
<script>
const panels = document.querySelectorAll('.panel');
panels.forEach((panel)=>{
panel.addEventListener('click',()=>{
removeActiveClasses();
panel.classList.add('active')
})
})
function removeActiveClasses(){
panels.forEach(panel=>{
panel.classList.remove('active');
})
}
</script>
</body>
</html>