最近我很荣幸的组织了今年(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了?恩,我也不记得了! |