[TOC]
# 準備
在實現 CSS3 3D 全景之前,我們先理清部分 CSS3 Transform 相關的屬性:
* [transform-origin](https://developer.mozilla.org/zh-CN/docs/Web/CSS/transform-origin):元素變形的原點(默認值為 50% 50% 0,該數值和后續提及的百分比默認均基于元素自身的寬高算出具體數值);
* [perspective](https://developer.mozilla.org/en-US/docs/Web/CSS/perspective): 指定了觀察者與`z=0`平面的距離,使具有三維變換的元素產生透視效果。(默認值:none,值只能是絕對長度,即負數是非法值);
* [transform-style](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-style):為子元素提供 2D 還是 3D 的場景。另外,該屬性是非繼承的;
* [transform](https://developer.mozilla.org/en-US/docs/Web/CSS/transform):修改 CSS 可視化模型的坐標空間,包括[平移(translate)](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translate3d)、[旋轉(rotate)](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotate)、[縮放(scale)](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scale)和[扭曲(skew)](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skew)。
<br>
下面我們對上述的一些點進行更深入的分析:
* 對于`perspective`,該屬性指定了“眼睛”與元素的`perspective-origin`(默認值是`50% 50% 0`)點的距離。那么問題來了:“當我們以`px`作為衡量單位時,它的實際距離該如何量化呢?”
答:當屏幕分辨率是 1080P(1920\*1080px)且該元素或其祖先元素的 `perspective`數值的值為`1920px`時,應用了 CSS3 3D Transform 的子元素的立體效果就相當于我們在距離一個屏幕寬度(1920px)的屏幕前觀看該元素時的真實效果。盡管如此,目前筆者也不知道如何準確地為元素設置一個合適的`perspective`值,只能猜測大概值后進行調整,以達到滿意的呈現效果。

根據相似三角形的性質可計算出被前移的元素最終在屏幕上顯示的實際大小
~~~
<style>
.container {
perspective: 1000px;
}
.box {
width: 600px;
height: 600px;
margin: 0 auto;
background: red;
transform: translateZ(300px);
}
</style>
<body>
<div class="container">
<div class="box"></div>
</div>
</body>
~~~

另外,關于`perspective`還有另外一個重要的點是:因為`perspective-origin`屬性的默認值是`50% 50% 0`,所以對哪個元素應用`perspective`屬性,就決定了“眼睛”的位置(即我們的“眼睛”是在哪個角度看物體)。一般來說,當我們需要正視物體時,就會將該屬性設置在與該元素中心重合的**某一祖先元素**上。
再另外,如果說:“如何讓一個元素(的背面)不可見?”,你可能會回答`backface-visibility:hidden;`。其實,對于在“眼睛”背后的元素(以元素的`transform-origin`為參考點),即**元素的`Z`軸坐標值大于`perspective`的值時,瀏覽器是不會將其渲染出來的**。
* 對于`transform-style`,該屬性指定了其**子元素**是處于 3D 場景還是 2D 場景。對于 2D 場景,元素的前后位置是按照平時的渲染方式(即若在普通文檔流中,同層級元素是按照代碼中元素的先后編寫順序,后面的元素會遮住在其前面的元素);對于 3D 場景,元素的前后位置則按照真實世界的規則排序(即靠近“眼睛”的元素,會遮住離“眼睛”遠的元素)。
另外,由于`transform-style`屬性是非繼承的,對于中間節點需要顯式設定。
* 對于`transform`屬性:下圖整理了 rotate3d、translate3d 的變換方向:

需要注意的是:transform 中的變換屬性的順序是有關系的,如 translateX(10px) rotate(30deg) 與 rotate(30deg) translateX(10px) 是不等價的。
另外,需要注意的是 scale 中如果有負值,則該方向會產生 180 度的翻轉;
再另外,部分 transform 效果會導致元素(字體)模糊,如 translate 的數值存在小數、通過 translateZ 或 scale 放大元素等等。**每個瀏覽器都有其不同的表現**。
# 實現
想象一下,當我們站在十字路口中間,身體旋轉 360°,這個過程中所看到的畫面就是一幅以你為中心的全景圖了。其實,當焦距不變時,我們就等同于站在一個圓柱體的中心。
<br>
但是,虛擬世界與現實世界的最大不同是:沒有東西是連續的,即所有東西都是離散的。例如,你無法在屏幕上顯示一個完美的圓。你只能以一個正多邊形表示圓:邊越多,圓就越“完美”。
<br>
同理,在三維空間中,每個 3D 模型都是一個多面體(即 3D 模型由不可彎曲的平面組成)。當我們討論一個本身就是多面體(如立方體)的模型時并不足以為奇,但我們想展示其它模型時,如球體,就需要記住這個原理了。

[淘寶造物節的活動頁](http://zwj360.im20.com.cn/)就是 CSS 3D 全景的一個很贊的頁面,它將全景圖分割成 20 等份,相鄰的元素構成的夾角 18°(360/20,相鄰兩側面相對于棱柱中心所構成的夾角)。需要注意的是:我們要確保**每個元素的正面是指向棱柱中心的**。所以要計算好每等份的旋轉角度值后,再將元素向外(即 Z 軸方向)平移`r`px。對于立方體的`r`就是`邊長/2`,而對于其它更復雜的正多面體呢?
<br>
舉例:對于正九棱柱,每個元素的寬為`210px`,對應的角度為`40°`,即如下圖:
圖片來自:[https://desandro.github.io/3dtransforms/docs/carousel.html](https://desandro.github.io/3dtransforms/docs/carousel.html)

<br>
正九棱柱的俯視圖

<br>
由此可得到一個公用函數,只需傳入含有**元素的寬度**和**元素數量**的對象,即可得到`r`值:
~~~
function calTranslateZ(opts) {
return Math.round(opts.width / (2 * Math.tan(Math.PI / opts.number)))
}
calTranlateZ({
width: 210,
number: 9
}); // 288
~~~
<br>
另外,為了讓下文易于理解,我們約定 HTML 的結構:
~~~
#view(perspective:1000px)
#stage(transform-style:preserve-3d)
#cube(transform-style:preserve-3d)
.div(width:600px;height:600px;) /*組成立方體的元素*/
~~~
正棱柱構建完成后,就需要將我們的“眼睛”放置在正棱柱內。由于在“眼睛”后的元素是不會被瀏覽器渲染的(與`.div元素`是否設置`backface-visibility:hidden;`無關),而且我們保證`.div元素`的**正面**都是指向正棱柱中心,這樣就形成 360° 被環繞的效果了。
<br>
那“眼睛”具體被放置在哪個位置呢?
答:通過設置`#stage`元素的`translateZ`值,讓不能被看到的`.div元素`在`Z`軸上的最終坐標值(即其自身`Z`坐標和祖先元素`Z`坐標相加)大于`#view`元素的`perspective`值即可。如:立方體的正面的`translateZ`是`-300px`(為了保證立方體的正面是指向立方體中心,正面元素需要以自身水平方向上的中線為軸,旋轉`180度`,即`rotateY(-180deg) translateZ(-300px)`,即正面元素向“眼球”方向平移了 300px),而`#view`的`perspective`值為`1000px`,那么`#stage`的`translateZ`值應該大于`700px`且小于`1300px`即可,具體數值則取決于你想要的呈現效果。
# 全景圖素材的制作
將全景圖制作分為設計類與實景類:
## 設計類
要制作類似[《淘寶造物節》](http://zwj360.im20.com.cn/)的全景頁面,設計稿需要有以下這些要求。
注:下面提及的具體數據均基于《造物節》,可根據自身要求進行調整(若發現欠缺,歡迎作出補充)。
整體背景設計圖如下(2580*1170px,被分成 20 等份):

基本要求:
1. 水平方向上需要首尾相連;
2. 因為效果圖最終需要切成**N 等份**,所以盡可能讓**設計圖的寬度能被 N 整除**;
3. 圖片尺寸不僅要考慮正視圖的大小,還要考慮元素在上下旋轉時依然能覆蓋視野(可選)。
當然,上圖只是作為背景,我們還可以添加一些小物體素材(與背景圖的運動速度不同時,可形成視差效果,增強立體感),如:


小物體元素(虛線用于參考,造物節中共有 21 個小物體)
如上圖所示,每個圖片也被等分成 M 等份,而且 M 的寬度應該與 N(背景元素)的寬度相等。
對于頂部和底圖圖片,則無特殊要求。
##實景類
如果想制作實景的全景效果,可以看看 Google 街景:
[Google 街景](https://www.google.com/streetview/publish/)推薦的設備如下:

如上圖,最實惠的方式就是最后一個選項——[Google 街景 APP](https://www.google.com/streetview/apps/),該應用提供了全景相機功能,但正如圖片介紹所說,這是需要練習的,因此對操作要求比較高。
補充:
上周六(2016.8.20)參加了 TGDC 的分享會,嘉賓分享了他們處理全景的方式:
1. 利用 RICOH THETA S 等專業設備拍出全景圖
2. 導出靜態圖像
3. 利用設備專門提供的 APP 或 krpamo tools、pano2vr、Glsky box 等工具將靜態圖像轉為 6 張圖
4. 利用 Web 技術制作可交互的全景圖
其中 Web 技術有以下 3 種可選方式(當然,還有其它):
* CSS3(本文所提及的方式)
* Three.js
* krpano(為全景而生,低級瀏覽器則回退到 Flash),[查看教程](http://krpano.com/docu/tutorials/quickstart/?from=groupmessage&isappinstalled=0#top)
# 參考資料
[CSS 3D Panorama - 淘寶造物節技術剖析]([https://aotu.io/notes/2016/08/24/2016-8-24-css-3d-panorama/](https://aotu.io/notes/2016/08/24/2016-8-24-css-3d-panorama/))
[淘寶造物節邀請函]([http://show.im20.com.cn/zwj/](http://show.im20.com.cn/zwj/))
[橫豎屏重力感應的易用組件]([https://github.com/shrekshrek/orienter](https://github.com/shrekshrek/orienter))
- 第一部分 HTML
- meta
- meta標簽
- HTML5
- 2.1 語義
- 2.2 通信
- 2.3 離線&存儲
- 2.4 多媒體
- 2.5 3D,圖像&效果
- 2.6 性能&集成
- 2.7 設備訪問
- SEO
- Canvas
- 壓縮圖片
- 制作圓角矩形
- 全局屬性
- 第二部分 CSS
- CSS原理
- 層疊上下文(stacking context)
- 外邊距合并
- 塊狀格式化上下文(BFC)
- 盒模型
- important
- 樣式繼承
- 層疊
- 屬性值處理流程
- 分辨率
- 視口
- CSS API
- grid(未完成)
- flex
- 選擇器
- 3D
- Matrix
- AT規則
- line-height 和 vertical-align
- CSS技術
- 居中
- 響應式布局
- 兼容性
- 移動端適配方案
- CSS應用
- CSS Modules(未完成)
- 分層
- 面向對象CSS(未完成)
- 布局
- 三列布局
- 單列等寬,其他多列自適應均勻
- 多列等高
- 圣杯布局
- 雙飛翼布局
- 瀑布流
- 1px問題
- 適配iPhoneX
- 橫屏適配
- 圖片模糊問題
- stylelint
- 第三部分 JavaScript
- JavaScript原理
- 內存空間
- 作用域
- 執行上下文棧
- 變量對象
- 作用域鏈
- this
- 類型轉換
- 閉包(未完成)
- 原型、面向對象
- class和extend
- 繼承
- new
- DOM
- Event Loop
- 垃圾回收機制
- 內存泄漏
- 數值存儲
- 連等賦值
- 基本類型
- 堆棧溢出
- JavaScriptAPI
- document.referrer
- Promise(未完成)
- Object.create
- 遍歷對象屬性
- 寬度、高度
- performance
- 位運算
- tostring( ) 與 valueOf( )方法
- JavaScript技術
- 錯誤
- 異常處理
- 存儲
- Cookie與Session
- ES6(未完成)
- Babel轉碼
- let和const命令
- 變量的解構賦值
- 字符串的擴展
- 正則的擴展
- 數值的擴展
- 數組的擴展
- 函數的擴展
- 對象的擴展
- Symbol
- Set 和 Map 數據結構
- proxy
- Reflect
- module
- AJAX
- ES5
- 嚴格模式
- JSON
- 數組方法
- 對象方法
- 函數方法
- 服務端推送(未完成)
- JavaScript應用
- 復雜判斷
- 3D 全景圖
- 重載
- 上傳(未完成)
- 上傳方式
- 文件格式
- 渲染大量數據
- 圖片裁剪
- 斐波那契數列
- 編碼
- 數組去重
- 淺拷貝、深拷貝
- instanceof
- 模擬 new
- 防抖
- 節流
- 數組扁平化
- sleep函數
- 模擬bind
- 柯里化
- 零碎知識點
- 第四部分 進階
- 計算機原理
- 數據結構(未完成)
- 算法(未完成)
- 排序算法
- 冒泡排序
- 選擇排序
- 插入排序
- 快速排序
- 搜索算法
- 動態規劃
- 二叉樹
- 瀏覽器
- 瀏覽器結構
- 瀏覽器工作原理
- HTML解析
- CSS解析
- 渲染樹構建
- 布局(Layout)
- 渲染
- 瀏覽器輸入 URL 后發生了什么
- 跨域
- 緩存機制
- reflow(回流)和repaint(重繪)
- 渲染層合并
- 編譯(未完成)
- Babel
- 設計模式(未完成)
- 函數式編程(未完成)
- 正則表達式(未完成)
- 性能
- 性能分析
- 性能指標
- 首屏加載
- 優化
- 瀏覽器層面
- HTTP層面
- 代碼層面
- 構建層面
- 移動端首屏優化
- 服務器層面
- bigpipe
- 構建工具
- Gulp
- webpack
- Webpack概念
- Webpack工具
- Webpack優化
- Webpack原理
- 實現loader
- 實現plugin
- tapable
- Webpack打包后代碼
- rollup.js
- parcel
- 模塊化
- ESM
- 安全
- XSS
- CSRF
- 點擊劫持
- 中間人攻擊
- 密碼存儲
- 測試(未完成)
- 單元測試
- E2E測試
- 框架測試
- 樣式回歸測試
- 異步測試
- 自動化測試
- PWA
- PWA官網
- web app manifest
- service worker
- app install banners
- 調試PWA
- PWA教程
- 框架
- MVVM原理
- Vue
- Vue 餓了么整理
- 樣式
- 技巧
- Vue音樂播放器
- Vue源碼
- Virtual Dom
- computed原理
- 數組綁定原理
- 雙向綁定
- nextTick
- keep-alive
- 導航守衛
- 組件通信
- React
- Diff 算法
- Fiber 原理
- batchUpdate
- React 生命周期
- Redux
- 動畫(未完成)
- 異常監控、收集(未完成)
- 數據采集
- Sentry
- 貝塞爾曲線
- 視頻
- 服務端渲染
- 服務端渲染的利與弊
- Vue SSR
- React SSR
- 客戶端
- 離線包
- 第五部分 網絡
- 五層協議
- TCP
- UDP
- HTTP
- 方法
- 首部
- 狀態碼
- 持久連接
- TLS
- content-type
- Redirect
- CSP
- 請求流程
- HTTP/2 及 HTTP/3
- CDN
- DNS
- HTTPDNS
- 第六部分 服務端
- Linux
- Linux命令
- 權限
- XAMPP
- Node.js
- 安裝
- Node模塊化
- 設置環境變量
- Node的event loop
- 進程
- 全局對象
- 異步IO與事件驅動
- 文件系統
- Node錯誤處理
- koa
- koa-compose
- koa-router
- Nginx
- Nginx配置文件
- 代理服務
- 負載均衡
- 獲取用戶IP
- 解決跨域
- 適配PC與移動環境
- 簡單的訪問限制
- 頁面內容修改
- 圖片處理
- 合并請求
- PM2
- MongoDB
- MySQL
- 常用MySql命令
- 自動化(未完成)
- docker
- 創建CLI
- 持續集成
- 持續交付
- 持續部署
- Jenkins
- 部署與發布
- 遠程登錄服務器
- 增強服務器安全等級
- 搭建 Nodejs 生產環境
- 配置 Nginx 實現反向代理
- 管理域名解析
- 配置 PM2 一鍵部署
- 發布上線
- 部署HTTPS
- Node 應用
- 爬蟲(未完成)
- 例子
- 反爬蟲
- 中間件
- body-parser
- connect-redis
- cookie-parser
- cors
- csurf
- express-session
- helmet
- ioredis
- log4js(未完成)
- uuid
- errorhandler
- nodeclub源碼
- app.js
- config.js
- 消息隊列
- RPC
- 性能優化
- 第七部分 總結
- Web服務器
- 目錄結構
- 依賴
- 功能
- 代碼片段
- 整理
- 知識清單、博客
- 項目、組件、庫
- Node代碼
- 面試必考
- 91算法
- 第八部分 工作代碼總結
- 樣式代碼
- 框架代碼
- 組件代碼
- 功能代碼
- 通用代碼