有时候我会在深夜翻看自己硬盘里堆积的照片,从旅行的风景到日常的瞬间,它们零散地躺在文件夹里,显得有些杂乱。每次打开都要一张一张点开,不仅体验差,还很难有那种"沉浸感"。于是我萌生了一个想法:为什么不做一个 3D 图片画廊呢?让相册的浏览方式更有仪式感,不再是普通的平铺,而是像旋转的立体展示柜一样,既美观又有趣。这个想法听起来挺酷,但真正要实现却没那么简单。我决定从零开始搭建一个属于自己的 3D 图片画廊。
最初我脑海中想象的是一个可以用鼠标或触控去旋转的立方体,每一面都展示不同的照片,用户可以通过交互来切换视角。我也考虑过环形画廊的形式,类似一个旋转的圆盘,照片沿着圆周分布,随着操作而流动切换。最终我确定要先做一个环形画廊版本,后续再扩展成立方体或其他形状,这样循序渐进会更容易。

有了这样的整体逻辑,我心里就更清晰了。接下来要考虑的就是技术栈的选择。我的第一直觉是用 CSS3 的 transform 来实现,因为 CSS3 已经支持 rotateY
、translateZ
等属性,可以比较轻松地做出立体效果。如果后续想扩展更复杂的场景,再考虑用 Three.js 这样的专业 3D 库。于是我的第一版决定完全依赖 HTML + CSS + JavaScript 来完成。
我先搭了一个基本的 HTML 结构:一个容器 div 作为画廊主体,里面放若干张图片。每张图片都用一个 figure
元素包裹,这样更语义化一些。代码大致是这样的:
html
<div class="gallery">
<figure><img src="img1.jpg" alt="pic1"></figure>
<figure><img src="img2.jpg" alt="pic2"></figure>
<figure><img src="img3.jpg" alt="pic3"></figure>
<figure><img src="img4.jpg" alt="pic4"></figure>
<figure><img src="img5.jpg" alt="pic5"></figure>
</div>
接下来重点就是 CSS。我给 .gallery
添加了 transform-style: preserve-3d;
和 perspective
,让其子元素能够在三维空间里呈现。我希望这些图片分布在一个圆环上,于是用了一个小技巧:假设有 N 张图片,那么每张图沿着圆周平均分布,每个的角度就是 360 / N
。例如 5 张图片,角度间隔就是 72°。于是我给每个 figure 设置类似 rotateY(72deg * i) translateZ(300px)
的 transform,就能让它们围绕 Y 轴排成一个圆。
代码如下:
css
.gallery {
width: 600px;
height: 400px;
margin: 100px auto;
position: relative;
transform-style: preserve-3d;
perspective: 1200px;
}
.gallery figure {
position: absolute;
width: 260px;
height: 180px;
left: 170px;
top: 110px;
transform-origin: 50% 50% 300px;
transition: transform 1s;
}
.gallery figure img {
width: 100%;
height: 100%;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(0,0,0,0.3);
}
不过单靠 CSS 还不够,我需要用 JavaScript 来动态控制旋转。于是我加了一段逻辑:当用户点击左右按钮时,整个 gallery 容器绕 Y 轴旋转一个角度,从而展示下一个或上一个图片。角度的增量和数量相关,比如 angle = 360 / N
。
js
const gallery = document.querySelector('.gallery');
const figures = document.querySelectorAll('.gallery figure');
let angle = 360 / figures.length;
let curr = 0;
document.getElementById('next').onclick = () => {
curr++;
gallery.style.transform = `rotateY(${-angle * curr}deg)`;
}
document.getElementById('prev').onclick = () => {
curr--;
gallery.style.transform = `rotateY(${-angle * curr}deg)`;
}
运行之后,画廊真的能转起来了!当我点击"下一张",整个环形顺时针旋转 72°,新的图片就正对用户了。那一刻的成就感特别强烈。不过问题也随之出现:图片之间的切换显得有点生硬,没有过渡动画。我仔细检查,发现我把 transition 写在了 figure
元素上,但其实旋转的是 gallery
容器,所以动画没有生效。把 transition
移到 .gallery
的 transform 上,就自然顺滑了。
再进一步,我希望这个画廊不仅能用按钮控制,还能用拖拽来旋转。于是我监听了鼠标按下、移动和松开的事件,根据拖动的距离来计算旋转角度。这样用户就能用手势滑动的方式去浏览,体验更自然。我在实现过程中遇到一个小坑:如果直接用 mousemove
的距离来控制旋转,很容易出现旋转过快或过慢的问题。最后我通过一个比例系数来调整,类似于 rotateY(totalX / 5)
,才让手感合适。
随着功能一点点完善,我还给画廊加上了缩放与景深效果。比如当某张图片正对用户时,可以稍微放大一点,并且加重阴影,让它成为视觉焦点。这是通过在旋转结束时计算当前角度来实现的。我写了一个小函数,根据 curr
的值判断哪张图片处于正中间,然后给它添加一个 active
类名,CSS 再去控制样式。
css
.gallery figure.active img {
transform: scale(1.2);
box-shadow: 0 20px 40px rgba(0,0,0,0.5);
transition: all 0.6s;
}
那一刻整个画廊终于有了"灵魂",不再只是单纯的旋转,而是真的有层次感和焦点。我甚至忍不住放上了几张旅行的照片,配合着 3D 的效果,就像重新走进了当时的场景。
当然,过程中还有很多小问题,比如不同分辨率下如何保持居中,图片大小不一致时如何裁剪适配,如何在移动端保持流畅度等等。我慢慢一点点解决,比如用 object-fit: cover
来保证图片比例不变形,或者用媒体查询去适配不同的屏幕。
写到这里,我发现这篇文章已经写了不少内容,不过这还只是开始。我还想讲讲我如何给画廊加上自动旋转、背景音乐、灯光特效,甚至用 Three.js 做进阶版的立体空间效果。
基本的旋转功能做出来之后,我就开始思考如何让这个画廊更加生动。最直接的一个想法就是增加自动旋转。毕竟,如果用户不操作,整个画廊停在那里未免显得有些死板。于是我在 JavaScript 里写了一个 setInterval
定时器,每隔三秒自动执行一次 curr++
并触发旋转。这样画廊就会缓慢自转,照片一张张轮流呈现在正中央。
不过实际运行后,我发现自动旋转和手动拖拽之间存在冲突。当我在拖动时,如果定时器也在运行,就会出现画廊突然"抢走"控制权的情况。我想了一个办法:当检测到用户在按下鼠标或手指触摸时,就 clearInterval
停止自动旋转,等松开之后再重新启动定时器。这样逻辑就顺畅多了。
js
let autoRotate = setInterval(() => {
curr++;
gallery.style.transform = `rotateY(${-angle * curr}deg)`;
}, 3000);
gallery.addEventListener('mousedown', () => clearInterval(autoRotate));
gallery.addEventListener('mouseup', () => {
autoRotate = setInterval(() => {
curr++;
gallery.style.transform = `rotateY(${-angle * curr}deg)`;
}, 3000);
});
当我第一次看到画廊自己慢慢转动的时候,那种观感真的很舒服,就像商场里旋转展示的橱窗一样,静静地展示着每张照片。
不过在优化体验时,我也注意到一个问题:旋转的速度不能太快,否则眼睛会跟不上;也不能太慢,否则就没什么意思。我测试了几次,发现三秒一张是比较理想的节奏。同时我在 CSS 动画里调了 transition
的时间,让旋转的过渡在 1 秒左右完成,这样既不会显得突兀,也不会太拖沓。
我还想给画廊增加一点"舞台感"。光有旋转还不够,我希望背景也能有层次,于是我给画廊容器外层加了一个渐变背景,类似舞台灯光的感觉。
css
body {
background: radial-gradient(circle at center, #222, #000);
overflow: hidden;
}
效果一出来,画廊就像悬浮在舞台中央,被一束聚光灯照亮。我还给每张图片加了一点反射效果,用 ::after
做了一个渐变透明的镜像,这让照片看起来像摆在玻璃台面上。
css
.gallery figure img::after {
content: '';
position: absolute;
top: 100%;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to bottom, rgba(255,255,255,0.3), transparent);
transform: scaleY(-1);
}
虽然只是一个小小的装饰,但视觉效果立刻丰富了很多。
在交互上,我想让用户能够用键盘操作。比如按左右方向键可以旋转,按空格键暂停或恢复自动播放。实现起来很简单,只需要监听 keydown
事件:
js
document.addEventListener('keydown', (e) => {
if(e.key === 'ArrowRight') {
curr++;
gallery.style.transform = `rotateY(${-angle * curr}deg)`;
}
if(e.key === 'ArrowLeft') {
curr--;
gallery.style.transform = `rotateY(${-angle * curr}deg)`;
}
if(e.key === ' ') {
if(autoRotate) {
clearInterval(autoRotate);
autoRotate = null;
} else {
autoRotate = setInterval(() => {
curr++;
gallery.style.transform = `rotateY(${-angle * curr}deg)`;
}, 3000);
}
}
});
这样一来,用户可以完全不用鼠标,只靠键盘就能操控整个画廊。
到这里,功能算是比较完整了,但我还不满意。我想要更"沉浸"的感觉。于是我尝试给画廊加上背景音乐。进入页面时,音乐自动播放,伴随着图片旋转,整个人的心境会进入一种"回忆"的氛围。
我用 HTML5 <audio>
标签加载一首轻音乐:
html
<audio id="bgm" autoplay loop>
<source src="music.mp3" type="audio/mpeg">
</audio>
不过自动播放在很多浏览器上默认是禁止的,除非用户有过交互。我就加了一个"播放音乐"的按钮,用户点一下就能开启背景音乐。顺便我还给音乐播放加了一个小小的旋转唱片动画,摆在画廊右下角,既是装饰,也是控制按钮。

这次的 3D 画廊不仅让我学到了很多 CSS3 和 JavaScript 的知识,更重要的是让我体会到了从无到有构建一个作品的乐趣。