本文介绍如何使用JavaScript实现3D图片展示空间效果。通过模拟三维坐标,利用CSS3的transform属性进行缩放和位移,并针对IE6-8等旧版浏览器提供zoom和base兼容模式。文章详细解析了深度计算、层叠关系处理及性能优化技巧,确保在多种浏览器下实现平滑的3D视觉效果。
3D图片展示空间实现原理
该程序旨在模拟一个三维空间,其中的图片依据设定的三维坐标在此空间中显示。
此前曾见过一种3DRoom效果,其实现依赖于复杂的数学计算。
在上一篇研究CSS3的transform变换后,笔者构思出一种更为简便的实现方法。
兼容性覆盖:ie6/7/8, firefox 3.6.8, opera 10.6, safari 5.0.1, chrome 5.0
效果预览

3DRoom

程序说明
图片加载与层变换机制
【实现原理】
实现3D效果的核心在于深度的呈现。我们将3D容器视为由多个不同深度的层级构成的空间,这些层级的默认尺寸与容器一致。各层级内放置对应深度的图片,并通过随深度变化的缩放变换,在视觉上产生深度差异。缩放比例以最近点为1,最远点为0,呈渐变分布。关键在于,层级内的图片尺寸和坐标需随层级同步变换,借助css3的transform可轻松实现。如此,图片仅需设定好尺寸并相对于层级定位即可,避免了随深度变化不断调整图片尺寸和定位的繁琐过程。
【图片加载】
程序初始化后,可调用add方法添加图片。add方法接收两个参数:图片地址和参数对象,并返回一个图片操作对象。该操作对象包含以下属性和方法,便于对图片进行操作:
img: 图片元素
src: 图片地址
options: 参数对象
show: 显示图片方法
remove: 移除图片方法
其中options可设置如下属性:
属性: 默认值//说明
x: 0,//水平位移
y: 0,//垂直位移
z: 0,//深度
width: 0,//宽度
height: 0,//高度
scaleW: 1,//宽度缩放比例
scaleH: 1//高度缩放比例
其中x、y分别为水平和垂直坐标的位移参数,坐标原点位于容器底部中间,水平坐标向右,纵坐标向上,单位为px。z表示深度,用于比例计算,方向由近点指向原点。坐标系如下图所示:
图片加载成功后,将执行_load图片加载程序。首先根据参数设置图片样式:
代码如下:
img.style.cssText = "position:absolute;border:0;padding:0;margin:0;-ms-interpolation-mode:nearest-neighbor;"
+ "z-index:" + (99999 - z) + ";width:" + width + "px;height:" + height + "px;"
+ "left:" + (((clientWidth - width) / 2 + opt.x) / clientWidth * 100).toFixed(5) + "%;"
+ "top:" + ((clientHeight - height - opt.y) / clientHeight * 100).toFixed(5) + "%;"; 绝对定位是必须的,宽度和高度根据参数设置即可。left和top根据坐标参数计算,此处需采用百分比形式表示,后续将详细说明。此外,还需给图片增加一个_z属性以记录深度,方便调用。最后插入对应z的层,并重新显示该层。
【层变换】
图片加载后,会通过_insertLayer程序将图片插入到对应的层中。 _insertLayer包含两个参数:图片元素和z深度。 程序使用_layers对象,以z为关键字记录对应的层元素。 若在该深度尚未创建层,则自动创建一个:
代码如下:
layer = document.createElement("div");
layer.style.cssText = "position:absolute;border:0;padding:0;margin:0;left:0;top:0;visibility:hidden;background:transparent;width:" + this._clientWidth + "px;height:" + this._clientHeight + "px;";层的坐标和尺寸需与容器一致,因为插入图片的坐标是相对于容器定义的,这样使用更为便捷。
同时会添加一个_count属性,用于记录层包含的图片数量,最后将层插入到容器并记录到_layers对象中。
获取层对象后,将图片插入层中,并将_count计数加1。
接着,通过_showLayer程序根据深度显示对应的层。
程序包含三个坐标属性:_x、_y、_z,表示容器的三维坐标偏移量。
首先通过_getScale获取比例方法得到z深度的缩放比例scale。
若比例大于1,说明图片位于视觉深度的后方,理论上不可见,故隐藏;若小于0,即小到不可见,同样隐藏。
而_x和_y偏移量也需根据深度重新计算,程序提供两种偏移方式:远点固定和近点固定。
远点固定意味着平面位移偏移量随深度逐渐变小,产生以最远点为固定点移动方向的效果;近点固定则相反。
要实现此效果,只需让位移偏移量随比例变化,即远点固定时偏移量与比例成正比,近点固定时与比例成反比:
代码如下:
var moveScale = this.fixedFar ? scale : (1 - scale); 随后将这些参数交给_show程序处理,并显示效果。
为最大限度利用层元素,程序在_remove图片移除程序中,将不含图片的层放入_invalid废弃层集合中,在需要插入层时,优先从_invalid中获取。
缩放比例计算与模式选择
【缩放比例】
前文已述,缩放比例应按最近点为1,最远点为0,逐渐变化。 程序默认通过以下公式计算:
function(z){ return 1 - z / 1000; }但在使用此公式实现3DRoom效果时,会发现比例变化过于急速,不如该3DRoom效果平稳。
研究代码后发现,其使用的公式如下:
this.r = FL / (FL + (z * Z));其中FL和Z为常量,即公式可表示为:
function(z){ return 1/(1+z/常量); }按此公式,深度为0时比例为1,深度为常量时比例为0.5,深度为无穷大时比例为0。
变化效果可参考以下程序:
<div style="border-bottom: #000 1px solid; position: relative; border-left: #000 1px solid; width: 550px; height: 200px; border-top: #000 1px solid; border-right: #000 1px solid" id="idChart"></div>公式: <input id="idFormula" type="text" /> <input id="idFRun" value="执行" type="button" /> <input id="idFClear" value="清除" type="button" /> <input id="idFDefault" value="默认公式" type="button" /> <input id="idF3DRoom" value="3DRoom公式" type="button" /> [Ctrl+A 全选 注:引入外部Js需再刷新一下页面才能执行]
可以看出,默认公式下的缩放比例是均匀变化的,而3DRoom公式则是先快后慢,且逐渐变慢,因此呈现出平稳的感觉。
根据实际需求,还可自行设计合适的公式,只要符合在1到0之间变化即可。
【css3模式】
程序中包含三种缩放变换方式:css3、zoom和base,其程序结构与上一篇类似。 缩放变换的目的是根据传递过来的比例和位置偏移量,展示缩放效果,从而实现最终的3D效果。
css3模式使用css3的transform,在上一篇已介绍过用transform的matrix做缩放和旋转,此次还需利用后两个参数进行位置变换。 需注意单位设置,在MDC的-moz-transform中有说明: Gecko (Firefox) accepts a <length> value for tx and ty. Safari (WebKit) and Opera currently support a unitless <number> for tx and ty. 意思是位移参数tx和ty,在Firefox需带单位,而WebKit和Opera只需数字(不带单位,默认px)。 程序会根据浏览器类型设置单位。
使用css3模式时,还可通过修改_r弧度属性进行旋转。 最后设置matrix实现变换:
代码如下:
layer.style[ css3Transform ] = "matrix("
+ ( Cos * scale).toFixed(5) + "," + (Sin * scale).toFixed(5) + ","
+ (-Sin * scale).toFixed(5) + "," + (Cos * scale).toFixed(5) + ", "
+ Math.round(x) + unit + ", " + Math.round(y) + unit + ")";此处还需注意一个问题,计算得到的比例可能是一个很长的小数,在拼接字符串时会出现问题。
例如执行:alert(0.0000001),会得到“1e-7”,js会用此结果拼接字符串,导致错误结果。
因此,在数字和字符拼接时,若为整数应先转为整数,若为小数则需使用toFixed转换。
【zoom模式】
ie不支持transform,但有一个zoom样式能实现类似效果。 由于zoom后尺寸会发生变化,因此需修正left和top以移动到正确位置。
除ie外,webkit(chrome/safari)也支持zoom,但ie6/7、ie8和webkit的实现并不完全相同。 测试以下代码:
代码如下:
<style>
.inner{ width:100px; height:100px; position:absolute; background:#0CF; zoom:0.5; top:50px; left:50px;}
.inner div{ width:50px; height:50px;position:absolute; left:25px;background:#CCC;}
</style>
<div style="width:150px;height:150px; border:1px solid #000; position:relative;">
<div class="inner" id="t"><div>test</div></div>
</div>在ie6/7中实现了预期效果,但在webkit中显示位置错误。 原因是使用zoom后,元素的left和top也会随缩放变化,只需按比例重新计算即可。 如上述例子,将left和top改为50/0.5,即100即可修正。
ie8的情况更复杂,其内容按zoom缩放,但left和top仍保持原大小。 此问题困扰良久,最终发现通过百分比定位可解决,图片加载时left和top采用百分比的原因即在此。 例如在例子中,修正left和top,并将最内层div的left改为25%即可。 在ie8中还观察到一个问题,zoom后内容缩小,但容器和内部元素的尺寸未变,虽不影响图片显示,但定位仍需使用left和top,以免麻烦。 此外,若zoom元素的尺寸采用百分比设置,则元素尺寸不会随zoom缩放。
计算时还需注意一个问题,前文提到在webkit和ie8中,left和top均需除以scale修正,当scale接近0到一定程度时,结果将变为Infinity(无穷大)。 使用Infinity进行运算会出错,需修正此问题:
left = Math.min(MAX, Math.max( -MAX, left )) | 0;
top = Math.min(MAX, Math.max( -MAX, top )) | 0;其中MAX是Number.MAX_VALUE(js能表达的最大数)。
【base模式】
此外还有兼容全部浏览器的base模式,采用传统方法,即根据缩放比例,计算并设置每个图片的尺寸和位置。 每次显示时,遍历层内的图片,再逐个计算设置。 计算需图片的原始位置和尺寸,在第一次计算时将数据保存在_original属性中:
var original = img._original = img._original || {
width: img.offsetWidth, height: img.offsetHeight,
left: img.offsetLeft, top: img.offsetTop
};尺寸只需根据比例缩放,位置除计算相对层的缩放外,还需加上相对容器的位移,此计算与zoom模式相同。 理解层变换方式后,再理解此模式便不难。
zIndex层叠关系与性能优化
【zIndex】
深度除缩放和定位外,还需合理的前后遮盖。 前后遮盖需通过zIndex实现,可在图片或层上设置。 首先最简单的方法是在层上设置:
代码如下:
<style>
div,img{width:200px;height:200px;position:absolute;left:0;top:0;}
img{width:150px;height:150px;}
</style>
<div style="z-index:300;">
<img style="background:#0C9;" alt="300" onclick="alert(300)">
</div>
<div style="z-index:100;">
<img style="background:#396;left:50px;top:50px;" alt="100" onclick="alert(100)">
</div> 实现一般的3D效果可如此设置。
但点击测试发现,在ff和webkit中前面的能触发后面的不能触发,而ie和opera则前后均可触发。
ps:如果img换成div,那么ie和opera后面的元素也不能触发,原因尚不清楚。
因此,若要像3DRoom那样触发图片事件,便不能在层设置zIndex。
还可在图片上设置:
代码如下:
<style>
div,img{width:200px;height:200px;position:absolute;left:0;top:0;}
img{width:150px;height:150px;}
</style>
<div>
<img style="background:#0C9;z-index:300;" alt="300" onclick="alert(300)">
</div>
<div>
<img style="background:#396;left:50px;top:50px;z-index:100;" alt="100" onclick="alert(100)">
</div> 如此图片在所有浏览器中均可正常触发,但在ie6/7中层叠效果失效,看来在ie6/7只能在层用zIndex。
还有一个问题,若给div加上变换效果:
div{-moz-transform:scale(1);-webkit-transform:scale(1);-o-transform:scale(1);}
则图片上的zIndex将失效,因此css3模式只能在层设置zIndex。
总结如下: 在css3模式下肯定要在层设置zIndex,但图片也无法触发事件。 在zoom和base模式下,应在图片设置zIndex,但在ie6/7则需在层设置。 如此至少在base模式下层叠和图片触发事件均正常。
【msInterpolationMode】
开发初期,ie8下的效果会很卡顿,但该3DRoom却不会卡顿,最终发现是使用了-ms-interpolation-mode。 此属性在aoao的文章中见过,但未想到可用于此处。
在MSDN中有msInterpolationMode的介绍: Gets or sets the interpolation (resampling) method used to stretch images. 即获取或设置用于拉伸图像的插值(重采样)方法。 它有两个值: nearest-neighbor:使用近邻插值模式。 bicubic:使用高品质的双三次插值模式。 这些名词较为专业,我们只需知道使用nearest-neighbor效率高但效果差,而bicubic效果好效率低即可。 程序将其设为nearest-neighbor以提高效率,这样在ie8中也不会卡顿了。
交互扩展与使用说明
【拖动方向变换/滚轮深度变换】
程序扩展了拖动视觉变换和滚轮深度变换。 拖动和滚动的做法与上一个类似,此处拖动实现方向变换,滚轮实现深度变换。 移动通过修改_x和_y属性实现,缩放通过修改_z实现。 修改属性后再调用show方法显示效果。
使用技巧
【3DRoom】
在3DRoom效果中,因需实现图片触发事件,故不能使用css3模式,原因如前所述层叠问题。 前文亦提到,在ie8中被zoom的元素尺寸不会改变,导致触发范围错误,故也不使用zoom模式。 使用base模式则无此问题。
在点击图片时,视觉会移动到图片上方,这通过点击图片后根据本身的三维参数修改_x/_y/_z来实现:
img.onclick = function(){
i3D._z = -options.z | 0;
i3D._x = -options.x | 0;
i3D._y = options.y | 0;
i3D.show();
}图片在mouseover时会显示边框,为使图片加边框后不发生位移,加了一个"-1px"的margin,mouseout时再去除。 此处3DRoom与参考效果仍有差距,本文主要聚焦于3D效果的实现与研究。
【模式选择】
css3模式稳定,大部分浏览器均支持,除ie。 zoom模式兼容性不佳,但ie支持。 base最慢,但兼容性好,且无bug。 一般情况下应优先使用css3模式,其次是zoom,最后是base,但如3DRoom那般情况则需按实际选择。 设计时,ie原打算用Matrix滤镜,但开发中发现一些问题,效率又太低,故未予考虑。
使用说明
实例化时,必须有容器作为参数:
var i3D = new Image3D( container, options );然后调用i3D方法添加图片:
i3D.add( src, options );可选参数用于设置系统的默认属性,包括: 属性: 默认值//说明 mode: "css3|zoom|base",//模式 x: 0,//水平偏移值 y: 0,//垂直偏移值 z: 0,//深度偏移值 r: 0,//旋转角度(css3支持) fixedFar: false,//是否远点固定 getScale: function(z){ return 1 - z / 1000; },//获取比例方法 onError: function(err){}//出错时执行
add方法的可选参数在图片加载中已说明。
还提供了以下方法: add:添加图片; show:显示效果; reset:重置默认状态; dispose:销毁程序。
加入拖动方向变换或滚轮深度变换扩展后,可通过设置相关参数定义变换范围。 打包下载地址 http://demo.jb51.net/js/Image3D/index.htm 演示地址 /201010/yuanma/Image3D.rar