blog icon indicating copy to clipboard operation
blog copied to clipboard

CSS 3D Panorama - 淘宝造物节技术剖析

Open JChehe opened this issue 7 years ago • 5 comments

本文首发于 凹凸实验室

封面

前言

3D 全景并不是什么新鲜事物了,但以前我们在 Web 上看到的 3D 全景一般是通过 Flash 实现的。若我们能将 CSS3 Transform 的相关知识运用得当,也是能实现类似的效果。

准备

在实现 CSS3 3D 全景之前,我们先理清部分 CSS3 Transform 相关的属性:

  • perspective: 指定观察者与 z=0 平面的距离,使具有三维变换的元素产生透视效果。(默认值:none,值只能是绝对长度,即负数是非法值)。
  • perspective-origin:指定观察者在哪个位置查看物体(基于同时指定了 perspective 的元素,默认值:50% 50%,即正视物体。值为 50% 100% 时,即仰视物体)。
  • transform-style:为当前元素的子元素提供 2D 还是 3D 的空间。另外,该属性是非继承的。
  • transform:修改 CSS 可视化模型的坐标空间,包括 平移(translate)旋转(rotate)缩放(scale)扭曲(skew)
  • transform-origin:元素 transform 的原点(默认值为 50% 50% 0)。

下面我们对上述的一些点进行更深入的分析:

  • 对于 perspective 对元素大小的影响: perspective
    根据 相似三角形 的性质可计算出被前移的元素最终在屏幕上显示的实际大小

    另外,对于在“眼睛”背后的元素(即元素的 z 轴坐标值大于 perspective 的值),浏览器是不会将其渲染出来的。

  • 对于 transform-style,该属性指定其子元素是处于 3D 场景还是 2D 场景。对于 2D 场景,子元素位于元素本身的平面内,即使旋转也不会穿透其他子元素;对于 3D 场景,子元素以真实 3D 空间进行定位展示。

    另外,由于 transform-style 是非继承属性,对于中间节点需再次指定。

  • 对于 transform 属性:下图整理了 rotate3d、translate3d 的变换方向:
    transform
    需要注意的是:transform 中的变换属性是有顺序的,如 translateX(10px) rotate(30deg)rotate(30deg) translateX(10px) 是不等价的。

    另外,如果 scale 值为负数,则该方向会 180 度翻转;

    再另外,transform 可能会导致元素(字体)模糊,如 translate 的数值存在小数、通过 translateZ 或 scale 放大元素等等。每个浏览器都有其不同的表现

实现

理清了 CSS Transform 相关的知识点后,下面就讲讲如何实现 CSS 3D 全景 :

想象一下,当我们站在十字路口中间,身体旋转 360°,这个过程中所看到的画面就是一幅以你为中心的全景图。其实,当焦距不变时,我们就等同于站在一个圆柱体的中心。

但是,虚拟世界与现实世界最大的不同点是:没有东西是连续的,即所有东西都是离散的。例如,你无法在屏幕上显示一个完美的圆。你只能以一个正多边形表示圆:边越多,圆就越“完美”。

同理,在三维空间中,每个 3D 模型都是一个多面体(即 3D 模型由不可弯曲的平面组成)。当我们讨论一个本身就是多面体(如立方体)的模型时并不足以为奇,但对于其它模型,如球体,就需要意识这个原理。 三维环境的球体

淘宝造物节的活动页 是一个基于 CSS 3D 实现的全景页面。它将全景图分割成 20 等份,为一个正 20 棱柱。需要注意的是:我们要确保每个元素的正面是指向棱柱中心。所以要计算好每等份的旋转角度值后,再将元素向外(即 Z 轴方向)平移 r px。对于立方体的 r 就是 边长/2,而对于其他正棱柱呢?

举例:对于正九棱柱,每个元素的宽为 210px,对应的圆心角为 40°,即如下图:
图片来自:https://desandro.github.io/3dtransforms/docs/carousel.html
正九棱柱的俯视图
正九棱柱的俯视图

计算过程
计算过程

由此可得到一个通用函数,只需传入含有元素的宽度元素数量的对象,即可得到 r 值:

function calTranslateZ(opts) {
  return Math.round(opts.width / (2 * Math.tan(Math.PI / opts.number)))
}

calTranlateZ({
    width: 210,
    number: 9
});  // 288

俯视时所看到的元素外移动画
俯视所看到的元素外移动画

另外,为了让下文易于理解,我们约定 HTML 的结构:

#view(perspective: 420px)
    #cube(transform-style: preserve-3d)
        .face // 组成立方体的面

正棱柱构建完成后,就需要将我们的“眼睛”放置在正棱柱内。由于浏览器不会渲染在“眼睛”后的元素(与 .face 元素 是否设置 backface-visibility: hidden; 无关),且保证 .face 元素正面均指向正棱柱中心,这样就形成 360° 被环绕的效果了。

根据上述知识,笔者粗略地模仿了“造物节”的效果:https://css3dpanorama-1251477229.cos.ap-guangzhou.myqcloud.com/index.html

另外,只需 6 幅图即可实现一个常见的无死角全景图效果:https://css3dpanorama-1251477229.cos.ap-guangzhou.myqcloud.com/street.html

可由下图看出,将水平的 4 张图片合成后就是一张全景图:
此处输入图片的描述

以上就是我们通过 CSS3 Transform 相关属性实现的可交互全景效果了。当然,交互效果可以是拖拽,也可以是重力感应等。

全景图素材的制作

将全景图制作分为设计类与实景类:

设计类

要制作类似 《淘宝造物节》 的全景页面,设计稿有以下要求。

注:下面提及的具体数据均基于《造物节》,可根据自身要求进行调整(若发现欠缺,欢迎作出补充)。

整体背景设计图如下(2580*1170px,被分成 20 等份):
淘宝造物节整体效果图

基本要求:

  1. 水平方向上需要首尾相连;
  2. 因为效果图最终需要切成 N 等份,所以设计图的宽度要能被 N 整除
  3. 不要遗漏上下两面。

为了增强立体感,可添加能形成视差效果的小物体(与背景图不同的运动速度、延迟时间),如:
物体小元素1
物体小元素2
小物体元素(虚线用于参考,造物节中共有 21 个小物体)

如上图虚线所示,每个小物体也会被等分成 M 份,且每份宽度应与背景元素宽度相等。

实景类

如果想制作实景的全景效果,可以看看 Google 街景:

Google 街景 推荐的设备如下: Google 街景推荐的设备

如上图,最实惠的方式就是最后一个选项——Google 街景 APP,该应用提供了全景相机功能。但正如图片介绍所说,这是需要练习的,因此对操作要求比较高。

补充: 上周六(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),查看教程

现场快速制作的 会议现场全景

可见,优秀硬件设备的出现,大大减少了后期处理的时间,而 Web 则提供了一个优秀的展现平台。


最后

随着终端设备的软硬件不断完善与提高,Web 在 3D 领域也不甘落后,如果你玩腻了 2D 的 H5 或者想为用户提供更加新颖真实的体验,全景也许是一种选择。

最后,如有不清晰或不明白的地方,可以留言,我会尽可能解决的。谢谢谢~

JChehe avatar Dec 03 '16 13:12 JChehe

你好 有源码可以参考下吗

zjp123 avatar Nov 11 '19 07:11 zjp123

你好 有源码可以参考下吗

@zjp123 没有哦,弄丢了。网上好像有插件实现。

JChehe avatar Nov 12 '19 03:11 JChehe

全景图载入的时候有种路径动画的效果那部分是怎么实现的呢?

xiao252 avatar Apr 11 '20 09:04 xiao252

全景图载入的时候有种路径动画的效果那部分是怎么实现的呢?

@xiao252 是指加载进度条完成后,组成棱柱的每片方块从小到大的效果吗?

  1. 棱柱作为一个整体进行旋转
  2. 同时,组成棱柱的每片方块按序错开一定间隔时间,执行从 scale(0) -> scale(1) 的过渡

JChehe avatar Apr 11 '20 10:04 JChehe

全景图载入的时候有种路径动画的效果那部分是怎么实现的呢?

@xiao252 是指加载进度条完成后,组成棱柱的每片方块从小到大的效果吗?

  1. 棱柱作为一个整体进行旋转
  2. 同时,组成棱柱的每片方块按序错开一定间隔时间,执行从 scale(0) -> scale(1) 的过渡

哦 ,明白了 :)

xiao252 avatar Apr 11 '20 11:04 xiao252