做一个 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 的知识,更重要的是让我体会到了从无到有构建一个作品的乐趣。

相关推荐
wearegogog1234 小时前
基于 MATLAB 的卡尔曼滤波器实现,用于消除噪声并估算信号
前端·算法·matlab
Drawing stars4 小时前
JAVA后端 前端 大模型应用 学习路线
java·前端·学习
品克缤4 小时前
Element UI MessageBox 增加第三个按钮(DOM Hack 方案)
前端·javascript·vue.js
小二·4 小时前
Python Web 开发进阶实战:性能压测与调优 —— Locust + Prometheus + Grafana 构建高并发可观测系统
前端·python·prometheus
小沐°4 小时前
vue-设置不同环境的打包和运行
前端·javascript·vue.js
qq_419854055 小时前
CSS动效
前端·javascript·css
烛阴5 小时前
3D字体TextGeometry
前端·webgl·three.js
桜吹雪5 小时前
markstream-vue实战踩坑笔记
前端
C_心欲无痕6 小时前
nginx - 实现域名跳转的几种方式
运维·前端·nginx
花哥码天下6 小时前
恢复网站console.log的脚本
前端·javascript·vue.js