做一个 3D 图片画廊

有时候我会在深夜翻看自己硬盘里堆积的照片,从旅行的风景到日常的瞬间,它们零散地躺在文件夹里,显得有些杂乱。每次打开都要一张一张点开,不仅体验差,还很难有那种"沉浸感"。于是我萌生了一个想法:为什么不做一个 3D 图片画廊呢?让相册的浏览方式更有仪式感,不再是普通的平铺,而是像旋转的立体展示柜一样,既美观又有趣。这个想法听起来挺酷,但真正要实现却没那么简单。我决定从零开始搭建一个属于自己的 3D 图片画廊。

最初我脑海中想象的是一个可以用鼠标或触控去旋转的立方体,每一面都展示不同的照片,用户可以通过交互来切换视角。我也考虑过环形画廊的形式,类似一个旋转的圆盘,照片沿着圆周分布,随着操作而流动切换。最终我确定要先做一个环形画廊版本,后续再扩展成立方体或其他形状,这样循序渐进会更容易。

有了这样的整体逻辑,我心里就更清晰了。接下来要考虑的就是技术栈的选择。我的第一直觉是用 CSS3 的 transform 来实现,因为 CSS3 已经支持 rotateYtranslateZ 等属性,可以比较轻松地做出立体效果。如果后续想扩展更复杂的场景,再考虑用 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 的知识,更重要的是让我体会到了从无到有构建一个作品的乐趣。

相关推荐
繁依Fanyi3 小时前
用 Electron 做一个屏幕取色器
前端
某公司摸鱼前端3 小时前
一键 i18n 国际化神库!适配 Vue、React!
前端·vue.js·react.js·i18n
OEC小胖胖3 小时前
给你的应用穿上“外衣”:React中的CSS方案对比与实践
前端·前端框架·react·web
excel3 小时前
Nuxt 3 微前端:模块导入导出与路由跳转实战
前端
大家的林语冰3 小时前
Promise 再次进化,ES2025 新增 Promise.try() 静态方法
前端·javascript·ecmascript 6
大家的林语冰3 小时前
如何错误手写 ES2025 新增的 Promise.try() 静态方法
前端·javascript·ecmascript 6
繁依Fanyi3 小时前
做一个石头剪刀布小游戏
前端
用户21411832636024 小时前
dify插件开发-Dify 插件如何顺利上架应用市场?流程 + 常见问题一次讲透
前端
繁依Fanyi4 小时前
从零到一,制作一个项目展示平台
前端