[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`值,只能猜测大概值后进行调整,以达到满意的呈现效果。
![](https://box.kancloud.cn/aa51fceab3729a7b872c5cef331e26ad_680x465.png)
根据相似三角形的性质可计算出被前移的元素最终在屏幕上显示的实际大小
~~~
<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>
~~~
![](https://box.kancloud.cn/768ee0f34850f19806853704fe64fe6d_1762x859.png)
另外,关于`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](https://misc.aotu.io/JChehe/2016-8-24-css-3d-panorama/transform.jpg)
需要注意的是:transform 中的变换属性的顺序是有关系的,如 translateX(10px) rotate(30deg) 与 rotate(30deg) translateX(10px) 是不等价的。
另外,需要注意的是 scale 中如果有负值,则该方向会产生 180 度的翻转;
再另外,部分 transform 效果会导致元素(字体)模糊,如 translate 的数值存在小数、通过 translateZ 或 scale 放大元素等等。**每个浏览器都有其不同的表现**。
# 实现
想象一下,当我们站在十字路口中间,身体旋转 360°,这个过程中所看到的画面就是一幅以你为中心的全景图了。其实,当焦距不变时,我们就等同于站在一个圆柱体的中心。
<br>
但是,虚拟世界与现实世界的最大不同是:没有东西是连续的,即所有东西都是离散的。例如,你无法在屏幕上显示一个完美的圆。你只能以一个正多边形表示圆:边越多,圆就越“完美”。
<br>
同理,在三维空间中,每个 3D 模型都是一个多面体(即 3D 模型由不可弯曲的平面组成)。当我们讨论一个本身就是多面体(如立方体)的模型时并不足以为奇,但我们想展示其它模型时,如球体,就需要记住这个原理了。
![](https://box.kancloud.cn/669457659424c4edd7ce7ed673caa801_700x405.png)
[淘宝造物节的活动页](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)
![](https://box.kancloud.cn/f9dd8bfdfd8dd89f847ed59580515e43_540x402.png)
<br>
正九棱柱的俯视图
![](https://box.kancloud.cn/1372c2f3eb394b7ecce6b88ac0f70c66_329x383.png)
<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 等份):
![](https://box.kancloud.cn/04490c0c70c89fa2857babce0a1f07e8_1000x453.png)
基本要求:
1. 水平方向上需要首尾相连;
2. 因为效果图最终需要切成**N 等份**,所以尽可能让**设计图的宽度能被 N 整除**;
3. 图片尺寸不仅要考虑正视图的大小,还要考虑元素在上下旋转时依然能覆盖视野(可选)。
当然,上图只是作为背景,我们还可以添加一些小物体素材(与背景图的运动速度不同时,可形成视差效果,增强立体感),如:
![](https://box.kancloud.cn/89998bb314fcf600fa7aa0d70d937bc1_516x220.png)
![](https://box.kancloud.cn/e67903fa6d259689f6917fd69cbf9fc1_322x339.png)
小物体元素(虚线用于参考,造物节中共有 21 个小物体)
如上图所示,每个图片也被等分成 M 等份,而且 M 的宽度应该与 N(背景元素)的宽度相等。
对于顶部和底图图片,则无特殊要求。
##实景类
如果想制作实景的全景效果,可以看看 Google 街景:
[Google 街景](https://www.google.com/streetview/publish/)推荐的设备如下:
![](https://box.kancloud.cn/db3dc95620fd5ccc5efe428156a82d53_1000x1640.png)
如上图,最实惠的方式就是最后一个选项——[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算法
- 第八部分 工作代码总结
- 样式代码
- 框架代码
- 组件代码
- 功能代码
- 通用代码