最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

CSS與JS實戰(zhàn)之利用H5實現(xiàn)3D旋轉(zhuǎn)照片墻

 更新時間:2026年06月01日 09:55:57   作者:KY主創(chuàng)  
3D旋轉(zhuǎn)照片墻是一種網(wǎng)頁組件,允許用戶通過旋轉(zhuǎn)的方式查看多張圖片,用戶在用戶界面中能夠輕松地與各種圖片進(jìn)行交互,這篇文章主要介紹了CSS與JS實戰(zhàn)之利用H5實現(xiàn)3D旋轉(zhuǎn)照片墻的相關(guān)資料,需要的朋友可以參考下

前言

你有沒有想過,僅憑幾行 CSS 和一段 JavaScript,就能讓一組靜態(tài)圖片“懸浮”在空中,形成一個可以隨鼠標(biāo)拖拽自由旋轉(zhuǎn)的環(huán)形相冊?這聽起來像是某個高級前端框架或者 Three.js 這類 3D 庫的專屬領(lǐng)域,但實際上,現(xiàn)代瀏覽器原生支持的 CSS 3D 變換能力已經(jīng)足夠強大,完全可以實現(xiàn)這種視覺效果。

今天我們就來親手打造這樣一個 純原生實現(xiàn)的 3D 旋轉(zhuǎn)照片墻 —— 不依賴任何外部庫,不引入復(fù)雜構(gòu)建流程,只用最基礎(chǔ)的 transform、perspective 和 DOM 事件監(jiān)聽,帶你從零構(gòu)建一個流暢交互的立體相冊。

效果長什么樣?

想象一下:10 張技術(shù)主題圖片均勻分布在虛擬球體表面,圍繞中心點構(gòu)成一個完整的圓環(huán)。每張圖都有深度感(Z 軸偏移),初始時依次展開,像花瓣一樣緩緩打開。用戶按下鼠標(biāo)并拖動時,整個相冊會實時響應(yīng)手勢,在 X 和 Y 軸上平滑旋轉(zhuǎn),仿佛輕輕撥動了一個漂浮在黑暗中的發(fā)光魔盤。

關(guān)鍵特性包括:

  • ? 圖片呈圓形環(huán)繞排列,具備真實景深
  • ? 支持全向視角控制(上下左右均可旋轉(zhuǎn))
  • ? 鼠標(biāo)拖拽操作靈敏自然
  • ? 初始加載帶漸進(jìn)式動畫入場
  • ? 頁面極簡,兼容主流現(xiàn)代瀏覽器

這個項目不僅能作為個人作品集的亮點展示,也是理解 Web 中 3D 渲染機制的絕佳練手案例。

核心技術(shù)棧解析

要實現(xiàn)上述效果,我們需要掌握幾個核心的 CSS 與 JS 技術(shù)點,它們共同構(gòu)成了 Web 平臺原生 3D 動畫的基礎(chǔ)體系:

技術(shù)作用說明
perspective定義觀察者與 3D 元素之間的距離,決定透視強度
transform-style: preserve-3d確保子元素繼承父容器的 3D 上下文,避免被扁平化渲染
rotateX() / rotateY() / translateZ()控制元素繞軸旋轉(zhuǎn)及沿 Z 軸位移
transition添加過渡動畫,提升視覺流暢度
mousedown / mousemove / mouseup監(jiān)聽鼠標(biāo)事件,計算位移差以驅(qū)動旋轉(zhuǎn)

這些屬性看似簡單,但組合起來卻能創(chuàng)造出令人驚艷的空間感。尤其是 preserve-3d,它是實現(xiàn)真正立體結(jié)構(gòu)的關(guān)鍵開關(guān)——一旦缺失,所有子元素都會被壓平到二維平面,前功盡棄。

開發(fā)環(huán)境準(zhǔn)備

本項目完全基于原生前端技術(shù),無需 Node.js、Webpack 或任何構(gòu)建工具。你只需要:

  • 一個文本編輯器(推薦 VSCode)
  • 任意現(xiàn)代瀏覽器(Chrome/Firefox/Safari 均可)

如果你正在嘗試結(jié)合 AI 輔助開發(fā)工具鏈(比如 Qwen3-VL 的網(wǎng)頁推理功能),也可以先通過自然語言描述生成初步結(jié)構(gòu)代碼,再手動優(yōu)化交互邏輯和動畫細(xì)節(jié)。不過今天我們強調(diào)的是“親手寫出來”,因為只有深入每一行代碼,才能真正理解背后的運行原理。

實現(xiàn)步驟詳解

1. 構(gòu)建基礎(chǔ) HTML 結(jié)構(gòu)

我們從最簡單的結(jié)構(gòu)開始:一個外層容器用于定義 3D 視角,內(nèi)部包裹一個旋轉(zhuǎn)主體,里面放置多張圖片。

<div class="perspective">
  <div class="wrap" id="imgwrap">
    <img src="https://cdn.hackr.io/photos/featured-javascript-image.jpg" alt="JS" />
    <img src="https://cdn.hackr.io/photos/featured-react-image.jpg" alt="React" />
    <img src="https://cdn.hackr.io/photos/featured-vue-image.jpg" alt="Vue" />
    <img src="https://cdn.hackr.io/photos/featured-nodejs-image.jpg" alt="Node.js" />
    <img src="https://cdn.hackr.io/photos/featured-angular-image.jpg" alt="Angular" />
    <img src="https://cdn.hackr.io/photos/featured-tailwindcss-image.jpg" alt="Tailwind" />
    <img src="https://cdn.hackr.io/photos/featured-typescript-image.jpg" alt="TS" />
    <img src="https://cdn.hackr.io/photos/featured-docker-image.jpg" alt="Docker" />
    <img src="https://cdn.hackr.io/photos/featured-python-image.jpg" alt="Python" />
    <img src="https://cdn.hackr.io/photos/featured-git-image.jpg" alt="Git" />
  </div>
</div>

?? 幾個關(guān)鍵設(shè)計考量:

  • 所有 <img> 直接作為 .wrap 的子元素,不額外嵌套容器,減少層級干擾
  • 使用至少 8~10 張圖片,確保環(huán)形布局完整閉合
  • 圖片建議統(tǒng)一尺寸(如 240×140px),防止變形拉伸影響美觀

2. 設(shè)計 3D 空間的 CSS 樣式

接下來是重頭戲:如何用 CSS 構(gòu)建真實的三維空間感。

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  background: #111;
  overflow: hidden;
  user-select: none; /* 禁止文字選中 */
  transform: scale(0.7); /* 適配大屏顯示 */
  transform-origin: top center;
}

.perspective {
  perspective: 800px; /* 設(shè)置透視距離 */
}

.wrap {
  width: 240px;
  height: 140px;
  position: relative;
  margin: 150px auto;
  transform: rotateX(-20deg) rotateY(0deg);
  transform-style: preserve-3d; /* 關(guān)鍵!保持3D上下文 */
}

.wrap img {
  position: absolute;
  width: 100%;
  height: 100%;
  border-radius: 8px;
  box-shadow: 0 0 10px rgba(255, 255, 255, 0.6);
  background: #333;
  object-fit: cover;
}

?? 深入解讀幾個關(guān)鍵樣式:

  • perspective: 800px:值越小,透視感越強;過大則接近正交投影,失去立體感。800 是一個經(jīng)驗平衡點。
  • transform-style: preserve-3d:這是整個項目成敗的核心。如果沒有它,.wrap 內(nèi)部的所有 translateZ 都會被“拍扁”,無法形成空間分布。
  • rotateX(-20deg):讓整體略微抬頭,使底部圖片也能進(jìn)入視野,增強沉浸感。
  • position: absolute + width/height: 100%:確保每張圖占據(jù)相同區(qū)域,并能獨立進(jìn)行空間變換。

3. 使用 JavaScript 驅(qū)動交互動畫

現(xiàn)在讓這個靜態(tài)結(jié)構(gòu)“活”起來。

Step 1:頁面加載時初始化圖片位置

我們希望圖片在加載完成后依次“彈出”,形成動態(tài)入場效果。為此,利用 JavaScript 計算每張圖應(yīng)繞 Y 軸旋轉(zhuǎn)的角度,并沿 Z 軸推出一定距離。

window.onload = function () {
  const images = document.querySelectorAll(".wrap img");
  const len = images.length;
  const deg = 360 / len; // 均勻分配角度

  images.forEach((img, index) => {
    // 繞Y軸旋轉(zhuǎn) + 沿Z軸前移
    img.style.transform = `rotateY(${deg * index}deg) translateZ(565px)`;
    // 設(shè)置延遲過渡,營造逐個出現(xiàn)的節(jié)奏
    img.style.transition = `1s ease ${index * 0.1}s`;
  });
};

?? 參數(shù)說明:

  • translateZ(565px) 是經(jīng)驗值,需根據(jù) perspective 調(diào)整。太大會超出視野范圍,太小則無立體感。
  • transition-delay 利用 index * 0.1 實現(xiàn)錯峰動畫,視覺上更柔和自然。

Step 2:添加鼠標(biāo)拖拽控制

真正的交互來了!我們要監(jiān)聽鼠標(biāo)移動,動態(tài)更新 .wrap 的旋轉(zhuǎn)狀態(tài)。

let isDragging = false;
let lastX, lastY;
let rotX = -20, // 初始X軸傾斜角
  rotY = 0;     // 初始Y軸角度

document.addEventListener("mousedown", (e) => {
  isDragging = true;
  lastX = e.clientX;
  lastY = e.clientY;
  e.preventDefault(); // 阻止默認(rèn)行為(如選中文本)
});

document.addEventListener("mousemove", (e) => {
  if (!isDragging) return;

  const deltaX = e.clientX - lastX;
  const deltaY = e.clientY - lastY;

  // 更新旋轉(zhuǎn)角度(注意方向修正)
  rotX -= deltaY * 0.2;
  rotY += deltaX * 0.1;

  const wrap = document.getElementById("imgwrap");
  wrap.style.transform = `rotateX(${rotX}deg) rotateY(${rotY}deg)`;

  lastX = e.clientX;
  lastY = e.clientY;
});

document.addEventListener("mouseup", () => {
  isDragging = false;
});

?? 實現(xiàn)要點:

  • 使用全局事件監(jiān)聽,避免鼠標(biāo)移出圖片區(qū)域后中斷拖拽
  • rotXrotY 作為狀態(tài)變量持久保存當(dāng)前視角,保證連續(xù)性
  • 靈敏度系數(shù) 0.20.1 可調(diào)節(jié)操控手感,數(shù)值過大會導(dǎo)致抖動,過小則反應(yīng)遲鈍

?? 小技巧:你可以嘗試加入邊界限制(如 rotX = Math.max(-90, Math.min(90, rotX)))防止過度翻轉(zhuǎn)導(dǎo)致畫面倒置。

完整源碼整合

以下是可直接運行的完整 HTML 文件:

<!DOCTYPE html>
<html lang="zh" ondragstart="false">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>3D旋轉(zhuǎn)照片墻</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      background: #111;
      overflow: hidden;
      user-select: none;
      transform: scale(0.7);
      transform-origin: top center;
    }

    .perspective {
      perspective: 800px;
    }

    .wrap {
      width: 240px;
      height: 140px;
      position: relative;
      margin: 150px auto;
      transform: rotateX(-20deg) rotateY(0deg);
      transform-style: preserve-3d;
    }

    .wrap img {
      position: absolute;
      width: 100%;
      height: 100%;
      border-radius: 8px;
      box-shadow: 0 0 10px rgba(255, 255, 255, 0.6);
      background: #333;
      object-fit: cover;
    }
  </style>
</head>
<body>
  <div class="perspective">
    <div class="wrap" id="imgwrap">
      <img src="https://cdn.hackr.io/photos/featured-javascript-image.jpg" alt="JS" />
      <img src="https://cdn.hackr.io/photos/featured-react-image.jpg" alt="React" />
      <img src="https://cdn.hackr.io/photos/featured-vue-image.jpg" alt="Vue" />
      <img src="https://cdn.hackr.io/photos/featured-nodejs-image.jpg" alt="Node.js" />
      <img src="https://cdn.hackr.io/photos/featured-angular-image.jpg" alt="Angular" />
      <img src="https://cdn.hackr.io/photos/featured-tailwindcss-image.jpg" alt="Tailwind" />
      <img src="https://cdn.hackr.io/photos/featured-typescript-image.jpg" alt="TS" />
      <img src="https://cdn.hackr.io/photos/featured-docker-image.jpg" alt="Docker" />
      <img src="https://cdn.hackr.io/photos/featured-python-image.jpg" alt="Python" />
      <img src="https://cdn.hackr.io/photos/featured-git-image.jpg" alt="Git" />
    </div>
  </div>

  <script>
    window.onload = function () {
      const images = document.querySelectorAll(".wrap img");
      const len = images.length;
      const deg = 360 / len;

      images.forEach((img, index) => {
        img.style.transform = `rotateY(${deg * index}deg) translateZ(565px)`;
        img.style.transition = `1s ease ${index * 0.1}s`;
      });
    };

    let isDragging = false;
    let lastX, lastY;
    let rotX = -20, rotY = 0;

    document.addEventListener("mousedown", (e) => {
      isDragging = true;
      lastX = e.clientX;
      lastY = e.clientY;
      e.preventDefault();
    });

    document.addEventListener("mousemove", (e) => {
      if (!isDragging) return;

      const deltaX = e.clientX - lastX;
      const deltaY = e.clientY - lastY;

      rotX -= deltaY * 0.2;
      rotY += deltaX * 0.1;

      const wrap = document.getElementById("imgwrap");
      wrap.style.transform = `rotateX(${rotX}deg) rotateY(${rotY}deg)`;

      lastX = e.clientX;
      lastY = e.clientY;
    });

    document.addEventListener("mouseup", () => {
      isDragging = false;
    });
  </script>
</body>
</html>

? 運行方式

1. 將代碼保存為 index.html

2. 雙擊打開或部署到本地服務(wù)器

3. 鼠標(biāo)點擊并拖動即可體驗 3D 旋轉(zhuǎn)效果!

寫在最后:為什么還要手動實現(xiàn)?

如今,AI 已經(jīng)能夠根據(jù)一句話描述自動生成這類交互組件代碼。例如 Qwen3-VL 這樣的多模態(tài)模型,可以通過視覺編碼增強和空間感知能力,直接由截圖反推出 HTML/CSS/JS 結(jié)構(gòu)。未來,也許你只需說一句:“做個可以旋轉(zhuǎn)的照片墻”,AI 就能返回完整可運行代碼。

但我想說的是:能寫出代碼的人很多,理解它為何這樣工作的人才是少數(shù)。

當(dāng)你親手調(diào)整 perspective 數(shù)值、調(diào)試 preserve-3d 是否生效、計算 translateZ 的合理范圍時,你才真正掌握了 Web 3D 的底層邏輯。這種對原理的理解,是任何自動化工具都無法替代的。

所以,哪怕有一天 AI 能替我們寫完所有代碼,也請別停下學(xué)習(xí)的腳步。因為真正的創(chuàng)造力,永遠(yuǎn)來自于對“如何實現(xiàn)”的深刻洞察。

到此這篇關(guān)于CSS與JS實戰(zhàn)之利用H5實現(xiàn)3D旋轉(zhuǎn)照片墻的文章就介紹到這了,更多相關(guān)CSS與JS實現(xiàn)3D旋轉(zhuǎn)照片墻內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • JavaScript編寫推箱子游戲

    JavaScript編寫推箱子游戲

    本文給大家介紹的是使用javascript來實現(xiàn)一款經(jīng)典的老游戲--推箱子,主要側(cè)重于實現(xiàn)的思路,最后附上源碼給大家。
    2015-07-07
  • 詳解axios中封裝使用、攔截特定請求、判斷所有請求加載完畢)

    詳解axios中封裝使用、攔截特定請求、判斷所有請求加載完畢)

    這篇文章主要介紹了axios中封裝使用、攔截特定請求、判斷所有請求加載完畢的相關(guān)知識,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-04-04
  • 關(guān)于JS數(shù)組追加數(shù)組采用push.apply的問題

    關(guān)于JS數(shù)組追加數(shù)組采用push.apply的問題

    JS數(shù)組追加數(shù)組沒有現(xiàn)成的函數(shù),這么多年我已經(jīng)習(xí)慣了a.push.apply(a, b);這種自以為很酷的,不需要寫for循環(huán)的寫法,一直也沒遇到什么問題,直到今天我要append的b是個很大的數(shù)組時才遇到了坑。
    2014-06-06
  • 微信小程序 騰訊地圖SDK 獲取當(dāng)前地址實現(xiàn)解析

    微信小程序 騰訊地圖SDK 獲取當(dāng)前地址實現(xiàn)解析

    這篇文章主要介紹了微信小程序 騰訊地圖SDK 獲取當(dāng)前地址實現(xiàn)解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-08-08
  • 詳解基于Wepy開發(fā)小程序插件(推薦)

    詳解基于Wepy開發(fā)小程序插件(推薦)

    這篇文章主要介紹了詳解基于Wepy開發(fā)小程序插件(推薦),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-08-08
  • javascript數(shù)組includes、reduce的基本使用

    javascript數(shù)組includes、reduce的基本使用

    這篇文章主要給大家介紹了關(guān)于javascript數(shù)組includes、reduce的基本使用方法,includes方法是用于檢查特定元素是包含在數(shù)組還是字符串中的方法,而reduce用法則有很多,需要的朋友可以參考下
    2021-07-07
  • JS引用傳遞與值傳遞的區(qū)別與用法分析

    JS引用傳遞與值傳遞的區(qū)別與用法分析

    這篇文章主要介紹了JS引用傳遞與值傳遞的區(qū)別與用法,結(jié)合實例形式較為詳細(xì)的對比分析了javascript引用傳遞與值傳遞的原理、區(qū)別、使用方法及相關(guān)操作注意事項,需要的朋友可以參考下
    2018-06-06
  • TypeScript實現(xiàn)單鏈表的示例代碼

    TypeScript實現(xiàn)單鏈表的示例代碼

    鏈表是一種物理存儲單元上非連續(xù)、非順序的存儲結(jié)構(gòu),本文主要介紹了TypeScript實現(xiàn)單鏈表的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-08-08
  • JavaScript中的子窗口與父窗口的互相調(diào)用問題

    JavaScript中的子窗口與父窗口的互相調(diào)用問題

    本文給大家介紹了JavaScript中的子窗口與父窗口的互相調(diào)用問題,非常不錯,具有參考借鑒價值,需要的朋友參考下吧
    2017-02-02
  • JavaScript實現(xiàn)的SHA-1加密算法完整實例

    JavaScript實現(xiàn)的SHA-1加密算法完整實例

    這篇文章主要介紹了JavaScript實現(xiàn)的SHA-1加密算法,以完整實例形式分析了SHA-1加密算法的具體實現(xiàn)技巧,需要的朋友可以參考下
    2016-02-02

最新評論

富宁县| 广昌县| 浦东新区| 丰城市| 双辽市| 靖西县| 翁源县| 塔河县| 横峰县| 临沧市| 邯郸市| 芦山县| 建昌县| 集贤县| 扎囊县| 大化| 清远市| 油尖旺区| 沈阳市| 林甸县| 万全县| 鄂伦春自治旗| 新疆| 屏山县| 兴宁市| 黄山市| 巴彦淖尔市| 南川市| 肥西县| 平顶山市| 枣阳市| 隆尧县| 天柱县| 建德市| 延川县| 天峨县| 清水县| 韶山市| 柳江县| 拜泉县| 井冈山市|