求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
Beercamp: CSS 3D的一次尝试
 

发布于2013-1-7,来源:爱构图

 

最近我很荣幸的组织了今年(2012年)的Beercamp网站。如果你还不是很熟悉,那我给你介绍一下:Beercamp是设计师和开发者们的一次聚会,同时它也是进行前端技术实验的游乐园。每一年的Beercamp,我们都会把浏览器兼容性抛诸脑后,并且向“语义学”说一句“去你的”,没有那些“羁绊”,我们就能够更好地利用高级浏览器的新特性,开发出一些有趣的东西。

今年的实验是:苏斯博士(Dr. Seuss,美国著名的儿童文学作家)的3D弹出式书籍。如果你还没有看到过,那你应该去Beercamp网站看一下。这个网站展示了我们能够利用SVG和CSS的3D转换做出什么样的效果。在这个过程中我学到了很多东西,并且愿意与大家分享在3D空间内开展工作时一些非常有用的技术。

Beercamp2012: 淘气鬼的故事(A Tale of International Mischief)

在进入主题之前,首先要声明的是,如果我把网站涉及到的所有内容全都讲一遍,而没有把你烦死,这基本上是不可能的。为你着想,当然,也为了我自己,我只会给出一些要点。另外需要注意的是,我们使用了jQuery,而且为了简单,很多代码进行了删减(包括浏览器前缀),这也是你快速扫描代码片段时需要注意的。

最后,请记住,这只是一个实验,它并不能在所有浏览器上良好的运作,也不能出色的向后兼容,并且语言标记的使用可能也并不优雅。

好了,暂时先把你的定罪问责放在一边吧,我们要做一些有意思的事情了。

要点1:畅游3D,乐趣多多

在我开始创建这个Beercamp网站之前,我进行了一些关于如何让弹出式书籍更加有趣的调研。当我翻看纸质版的《那些你想去的地方》(Oh, the Places You’ll Go. 作者:苏斯博士)时,我发现自己会从不同的角度去观察书的每一页。“横看成岭侧成峰,远近高低各不同”,从不同的角度观察事物得到不同的效果,这件事情本身是非常有趣的,如果能再加上与环境的交互,那就更加迷人了。

Beercamp的灵感来源:苏斯博士的《那些你想去的地方》一书

我希望在我的数字版图书中,通过直观可见而且细致入微的控制,也能创建出那样吸引人的效果。为了实现此效果,场景需要根据鼠标的坐标位置来旋转,使得用户能够简单的移动和翻看书籍。

实现这一效果实际上是非常简单的:

1. 设置一个事件监听函数

为mousemove事件添加一个事件监听函数:

$document.mousemove(rotateScene);

2. 计算旋转的角度

我希望书本能够以鼠标的x轴位置为基准,在Y轴方向进行-15°到15°的旋转:

rotationY = -15 + (30 * e.pageX / $body.width());

3. 执行旋转

$scene.css(‘transform’: ‘rotateY(‘ + rotationY + ‘deg)’);

非常简单,对吧?但是有一个问题,iPhone和iPad的用户并没有鼠标坐标。嗯,对!他们有陀螺仪。旋转手持设备就类似于旋转一本书,因此根据移动设备的方向来调整场景,将会是直观而且令人愉悦的交互效果。实现这个效果的步骤与上面的步骤大致相同,稍有差异。

1. 设置一个事件监听函数

window.addEventListener(‘deviceorientation’, rotateScene, false);

2. 识别设备的方向

在计算旋转角度之前,我们先要知道移动设备是横向还是纵向。这个判断可以通过获取window.orientation的属性值来进行:

Landscape(横向)

Math.abs(window.orientation) == 90

Portrait(纵向)

window.orientation == 0

通过window.orientation属性值判断移动设备的方向

3. 计算旋转角度

现在有了方向,我们就可以从陀螺仪获取正确的数值。如果设备是横向的,就获取beta属性值,如果是纵向的,就获取gamma属性值。

var theta = (Math.abs(window.orientation) == 90) ? e.beta : e.gamma;

rotationY = 0 + (15 * (theta / -45));

deviceorientation事件使我们可以获取alpha,beta和gamma三个方向数据。注意到这些方向数据都是相对于移动设备的当前方向。上图显示了移动设备在纵向模式下垂直于设备平面的坐标轴。

4. 执行旋转

$scene.css(‘transform’: ‘rotateY(‘ + rotationY + ‘deg)’);

要点2:深度排序,问题多多

支持3D转换的浏览器有很多,但是很少有浏览器能够做得非常出色。除去性能问题不说,最大的阻碍就是不正确的深度排序。

深度排序是两个平面在三维空间中相交时必须要考虑的问题。渲染引擎必须首先确定哪一个平面(或者更准确一些,平面的哪一块区域)应该被完全渲染,哪一个平面应该被裁剪。

深度排序在不同浏览器的实现差异

不幸的是,不同浏览器实现了不同的深度排序,并且每一种实现都有各自尚未解决的问题。因此,为了防止在书籍的弹出过程中“平面穿透下层元素”带来的兼容性问题,最好的解决办法是:保持每一个平面的独立。

Beercamp网站包含大量的平面相交的情况。最初,我让所有的书页在3D空间中以相同的原点(0,0,0)进行旋转。这就意味着书中的每一个平面都希望显示在最上面,从而导致兼容性问题。为了解决这个问题,我们可以参考一本实体书籍的书背,所有的书页沿着书背逐个排列即可。我通过让书页按照一条弧线排列来实现这个效果,并且让打开的书页在弧线的最顶端。

按照弧线旋转书页,可以避免裁剪带来的兼容性问题

function updateDrag(e) {
        …
        // operate on each spread
        $(‘.spreads li’).each(function(i) {
               // calculate the angle increment
               var ANGLE_PER_PAGE = 20;
               
               // determine which slot this page should be turned to
               var offsetIndex = per < 0 ? 5 + curPageIndex – i : 5 + curPageIndex – i – 2;
               
               // calculate the angle on the arc this page should be turned to
               var offsetAngle = per < 0 ? offsetIndex – per – 1 : offsetIndex – per + 1;
               
               // calculate the x coordinate based on the offsetAngle
               var tarX = 5 * Math.cos(degToRad(offsetAngle * ANGLE_PER_PAGE + 10));
               
               // calculate the z coordinate based on the offsetAngle
               var tarZ = 5 * Math.sin(degToRad(offsetAngle * ANGLE_PER_PAGE + 10));
               
               // position the page
               $(this).css(‘transform’, ‘translateX(‘ + tarX.toFixed(3) + ‘px)
 translateZ(‘ + tarZ.toFixed(3) + ‘px)’);
        });
 }

这一个技术可以解决大部分深度排序导致的问题,但是并不能解决全部。进一步的优化需要依赖于浏览器供应商。Safari似乎可以让程序运行在桌面应用和移动应用中;Chrome的稳定版本稍显逊色,但是最新的Canary版已经工作的非常出色了;Firefox做得非常好,但是低“帧刷新频率”依然是它的软肋。

综上所述,要取得战争的全面胜利,任重道远哪!

要点3:向量空间非常复杂,但是极其有用。

构建弹出效果是这个项目到目前为止最难的一部分,但是也是我最满意的一部分。其他的弹出式互联网书籍,都没有使用逼真的、模拟真实书籍翻阅过程的“弹出式机械学”(pop-up mechanics)来实现效果。这是因为:实现起来确实极其复杂。

编程实现“弹出式机械学”,依赖于向量空间的计算。一个向量本质上就是一条已知长度和方向的线。知道了这条线的长度和方向,我们就能够进行一些运算。在构建弹出效果的过程中,我们用到了向量积(两个向量的叉积),向量积就是在3D空间中同时垂直于两个向量的向量。

向量积的重要性体现在:它决定了每一个弹出片段([译注]例如书中的插画)向上旋转的角度。我并不打算向你介绍让人头昏眼花的运算过程(如果感兴趣可以看一下下面的数学运算),相反,下面给出了一个具体的视觉展示。

运动过程中的向量积

刚开始我们定义了两个点,这两个点是每一个弹出片段与书页的接触点,用于为每一个弹出片段定义向量(图中红色的线)。使用这些向量,我们能够计算它们的向量积(图中蓝色的线),这条线实际上就是弹出元素的对折线。让一个需要弹出的片段沿着向量积的方向旋转,就得到了一个完美的与书页连接在一起的弹出效果。

在我看来,这确实不是简单的数学计算。如果你对向量的计算感兴趣,我强烈推荐你使用Sylvester,它确实能让向量计算简化许多。

  function setFold() {
      var points = [];
      // origin
      points[0] = [0, 0, 0];
        
      var adj = Math.sqrt(Math.pow(POPUP_WIDTH, 2) – Math.pow(POPUP_WIDTH * Math.sin(degToRad(-15)), 2));
        
      // left piece: bottom outside
      points[1] = [-adj * Math.cos(degToRad(-180 * fold)), adj * Math.sin(degToRad(-180 * fold)), 
POPUP_WIDTH * Math.sin(degToRad(-15))];
        
      // right piece: bottom outside
      points[2] = [adj * Math.cos(degToRad(-180 * 0)), POPUP_WIDTH * Math.sin(degToRad(-180 * 0)), 
POPUP_WIDTH * Math.sin(degToRad(-15))];       
       // left piece: top inside
      points[3] = [-POPUP_WIDTH * Math.cos(degToRad((-180 * fold) - 90)), POPUP_WIDTH * Math.sin(degToRad
((-180 * fold) - 90)), 0];       
       var len = Math.sqrt(Math.pow(points[1][0], 2) + Math.pow(points[1][1], 2) + Math.pow(points[1][2], 2));
 
     // normalize the vectors
      var normV1 = $V([points[1][0] / len, points[1][1] / len, points[1][2] / len]);
      var normV2 = $V([points[2][0] / len, points[2][1] / len, points[2][2] / len]);
      var normV3 = $V([points[3][0] / len, points[3][1] / len, points[3][2] / len]);
 
     // calculate the cross vector
      var cross = normV1.cross(normV2);
 
     // calculate the cross vector’s angle from vector 3
      var crossAngle = -radToDeg(cross.angleFrom(normV3)) – 90;
 
     // transform the shape
      graphic.css(‘transform’, ‘translateY(‘ + depth + ‘px) rotateZ(‘ + zRot + ‘deg)
 rotateX(‘ + crossAngle + ‘deg)’);
  }

要点4:SVG,赞了又赞!

淡定!淡定!我知道你们以前听人讲过SVG。好吧,你们还是要再听一遍。SVG是一种能够在3D空间内良好运作的令人难以置信的技术!Beercamp网站上所有的插画都是用Illustrator软件制作并导出成SVG进行显示的,这种实现方式带来了众多的好处。

好处1:文件大小

因为弹出的片段包含很多大的透明区域,所以SVG节省的文件大小是巨大的。用PNG实现相同效果,文件大小会比没有经过压缩的SVG文件大2倍至3倍。而且,如果将插画导出成SVGZ格式,我们就能节省更多。

SVGZ是压缩版的SVG,文件小得令人难以相信。实际上,Beercamp中使用的SVGZ文件,比实现相同效果的PNG文件小9倍!

当然,实现这些效果需要服务器进行相关的配置,只需在.htaccess文件中进行如下配置即可:

AddType image/svg+xml svg svgz

AddEncoding gzip svgz

好处2:灵活性

SVG的灵活性可以说是它带来的最显著的好处了。Beercamp网站上的图形都能在3D空间中进行拉伸,以适应浏览器窗口的大小,同时每个书页上都有热点区域,允许用户放大以获取更多信息。由于所有的内容都是用SVG实现的,插画保持了纯粹和干净,而不用担心自己如何在3D空间中被操纵。

SVG文件天生就有很强的可交互性(天生丽质哇~)

好处3:自包含的动画效果

Bearcamp网站上所有的SVG文件都是当成背景图片使用的。这样做不仅使得标记更加干净,而且允许图片在多处复用,比如在弹出片段中。但是,这就意味着我们失去了对这些SVG节点的DOM操作能力。如果我们要在背景SVG中实现某些动画效果,该怎么办呢?

SVG允许将动画效果定义在SVG文件内部。最终Beercamp网站的所有的弹出图片都是静态的,但是早期版本中,我们用动画实现了啤酒泡泡的效果。后来考虑到“低能浏览器”(less-capable browsers)的性能问题,我们把这部分效果去掉了。话说回来,SVG的动画效果在WebKit中运行得那个流畅啊!

SVG动画没有它的“亲戚”CSS那么赞,但是它还是能够实现动画的。在一个元素内,我们能够添加一个animate节点来指定一些典型的动画属性,比如:属性properties,属性值values,开始时间,持续事件,重复次数等。下面就是Beercamp的啤酒泡泡效果的动画效果代码片段。

   <circle fill=”#fff” opacity=”.4″ clip-path=”url(#right-mug-clip)” cx=”896″ cy=”381″ r=”5″>
      <animate attributeType=”XML” attributeName=”cx” from=”890″ to=”881″ begin=”7s” dur=”5s”
repeatCount=”indefinite” />
      <animate attributeType=”XML” attributeName=”cy” from=”381″ to=”100″ begin=”7s” dur=”5s”
repeatCount=”indefinite” />
   </circle>

要点5:实验虽凌乱,但是很重要

我们暂且把实战中的各种花絮放到一边,先谈谈实验相关的话题。谈到开发网站的实战,我们就很容易能够想到可交互性、跨平台、跨浏览器、完美的向后兼容、语义优美、渐进增强、_______, _______ 和 _______(选几个最时髦的口号填上去吧)。这些技术用来保证实际生产的网站能够可用并且稳定,但是也限制了我们的创造性。

首先,我要承认:Beercamp网站还有很多问题,浏览器支持有限,可用性还需要提高。但是,这个网站就是一个实验品,它的作用就是探索“什么是可能的”,而不是“满足生产实际需要”。

在业内,一种教条主义正崭露头角,而上面说的流行词汇就是它的一些原则。技术实验,使得我们能够脱离教条进行思考,它是一种非常出色的,能够激发人们的好奇心,爆发人们的才能,并最终推动行业进步的练习方式。

如果你现在没有在某些方面开展技术实验,我认为,你应该马上开始!

CSS 3D的现状

CSS 3D尚未迎来自己的辉煌,目前浏览器并没有很好的支持它。但是在不远的将来,浏览器一定会很好的支持。比如:移动端的Safari,在硬件加速器的辅助之上,能够极快的执行3D变换并且很少出现深度排序的问题。其他浏览器厂商发布稳定的支持CSS 3D的浏览器版本,只是时间问题。到时候看CSS 3D与其他新兴的技术(例如WebGL)掐架,将会是一件很有意思的事情。

啊?你这么快就不记得Flash了?恩,我也不记得了!

相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程
 
分享到
 
 


十天学会DIV+CSS(WEB标准)
HTML 5的革新:结构之美
介绍27款经典的CSS框架
35个有创意的404错误页面
最容易犯的13个JavaScript错误
设计易理解和操作的网站
更多...   


设计模式原理与应用
从需求过渡到设计
软件设计原理与实践
如何编写高质量代码
单元测试、重构及持续集成
软件开发过程指南


东软集团 代码重构
某金融软件服务商 技术文档
中达电通 设计模式原理与实践
法国电信 技术文档编写与管理
西门子 嵌入式设计模式
中新大东方人寿 技术文档编写
更多...