JavaScript實(shí)現(xiàn)的圖片3D展示空間(3DRoom)
很久以前就看過一個(gè)3DRoom效果,是用復(fù)雜的計(jì)算實(shí)現(xiàn)的。
在上一篇圖片變換研究過css3的transform之后,就想到一個(gè)更簡單的方法來實(shí)現(xiàn)。
兼容:ie6/7/8, firefox 3.6.8, opera 10.6, safari 5.0.1, chrome 5.0
效果預(yù)覽

3DRoom

程序說明
【實(shí)現(xiàn)原理】
3D效果的關(guān)鍵,是深度的實(shí)現(xiàn)。
把3D容器看成一個(gè)由多個(gè)不同深度的層組成的空間,這些層的尺寸默認(rèn)跟容器一樣。
層里面放了該深度的圖片,并且各個(gè)層會(huì)根據(jù)深度的變化做縮放變換,從視覺上產(chǎn)生深度差。
縮放變換的比例按照最近點(diǎn)為1,最遠(yuǎn)點(diǎn)為0,逐漸變化。
關(guān)鍵的地方是層里面圖片的尺寸和坐標(biāo)必須跟著層同時(shí)變換,這個(gè)通過css3的transform很簡單就能實(shí)現(xiàn)。
這樣圖片只需設(shè)置好尺寸再相對(duì)層定好位就行了,避免了隨深度變化要不斷調(diào)整圖片尺寸和定位的麻煩。
【圖片加載】
在程序初始化之后,就可以調(diào)用add方法來添加圖片。
add方法有兩個(gè)參數(shù):圖片地址和參數(shù)對(duì)象,還會(huì)返回一個(gè)圖片操作對(duì)象。
操作對(duì)象包含以下屬性和方法,方便對(duì)圖片進(jìn)行操作:
img: 圖片元素
src: 圖片地址
options: 參數(shù)對(duì)象
show: 顯示圖片方法
remove: 移除圖片方法
其中options可以設(shè)置如下屬性:
屬性: 默認(rèn)值//說明
x: 0,//水平位移
y: 0,//垂直位移
z: 0,//深度
width: 0,//寬度
height: 0,//高度
scaleW: 1,//寬度縮放比例
scaleH: 1//高度縮放比例
其中x、y分別是水平和垂直坐標(biāo)的位移參數(shù),坐標(biāo)原點(diǎn)在容器底部中間,水平坐標(biāo)向右,縱坐標(biāo)向上,單位是px。
而z是深度,用于比例的計(jì)算,方向由近點(diǎn)到原點(diǎn)。
坐標(biāo)系如下圖:
圖片加載成功后,就會(huì)執(zhí)行_load圖片加載程序。
首先根據(jù)參數(shù)設(shè)置圖片樣式:
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) + "%;";
絕對(duì)定位是必須的,寬度和高度根據(jù)參數(shù)設(shè)置就行。
left和top根據(jù)坐標(biāo)參數(shù)計(jì)算,這里需要用百分比的形式表示,后面再詳細(xì)說明。
還要給圖片增加一個(gè)_z屬性記錄深度,方便調(diào)用。
最后插入對(duì)應(yīng)z的層,并重新顯示該層。
【層變換】
圖片加載后,會(huì)用_insertLayer程序把圖片插入到對(duì)應(yīng)的層中。
_insertLayer有兩個(gè)參數(shù):圖片元素和z深度。
程序用_layers對(duì)象,以z為關(guān)鍵字記錄對(duì)應(yīng)的層元素。
如果在該深度還沒有創(chuàng)建層,會(huì)自動(dòng)創(chuàng)建一個(gè):
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;";
層的坐標(biāo)和尺寸要跟容器一致,因?yàn)椴迦雸D片的坐標(biāo)是相對(duì)容器來定義的,這樣使用起來比較方便。
還會(huì)添加一個(gè)_count屬性,記錄層包含的圖片數(shù),最后插入到容器并記錄到_layers對(duì)象中。
獲取層對(duì)象后,就把圖片插入層中,并把_count計(jì)數(shù)加1。
接著就可以通過_showLayer程序根據(jù)深度顯示對(duì)應(yīng)的層。
程序包含三個(gè)坐標(biāo)屬性:_x、_y、_z,表示容器的三維坐標(biāo)的偏移量。
首先通過_getScale獲取比例方法得到z深度的縮放比例scale。
比例大于1,說明圖片在視覺深度的后面,理論上應(yīng)該看不到,所以隱藏;小于0,就是小到看不到了也隱藏。
而_x和_y偏移量也需要根據(jù)深度來重新計(jì)算,程序有兩種偏移方式:遠(yuǎn)點(diǎn)固定和近點(diǎn)固定。
遠(yuǎn)點(diǎn)固定的意思是平面位移偏移量隨著深度逐漸變小,產(chǎn)生以最遠(yuǎn)點(diǎn)為固定點(diǎn)移動(dòng)方向的效果,近點(diǎn)固定就剛好相反。
要實(shí)現(xiàn)這個(gè)效果,只要位移偏移量也跟著比例變化就行了,即遠(yuǎn)點(diǎn)固定時(shí)偏移量跟比例成正比,遠(yuǎn)點(diǎn)固定時(shí)是反比:
var moveScale = this.fixedFar ? scale : (1 - scale);
然后把這些參數(shù)交給_show程序來處理,并顯示效果。
為了最大限度地利用層元素,程序會(huì)在_remove圖片移除程序中,把沒有圖片的層放到_invalid廢棄層集合中,在需要插入層時(shí),優(yōu)先從_invalid中獲取。
【縮放比例】
上面已經(jīng)說了,縮放比例應(yīng)該按照最近點(diǎn)為1,最遠(yuǎn)點(diǎn)為0,逐漸變化。
程序默認(rèn)是通過下面的公式計(jì)算:
但用這個(gè)公式實(shí)現(xiàn)3DRoom效果的時(shí)候,會(huì)發(fā)現(xiàn)比例變化太急速,并不像這個(gè)3DRoom那樣平穩(wěn)。
研究代碼后發(fā)現(xiàn),原來它用的公式是這樣的:
其中FL和Z是一個(gè)常量來的,即公式可表示成:
那按照這個(gè)公式,深度為0時(shí)比例為1,深度為常量時(shí)比例為0.5,深度為無窮大時(shí)比例為0。
變化效果可以參考下面程序:
[Ctrl+A 全選 注:引入外部Js需再刷新一下頁面才能執(zhí)行]
可以看出,縮放比例在默認(rèn)公式是均勻變化的,而3DRoom公式是先快后慢,而且是逐漸變慢,所以有那種平穩(wěn)的感覺。
那按照實(shí)際,還可以自己設(shè)計(jì)適合的公式,只要符合在1到0之間變化就行。
【css3模式】
程序中有三種縮放變換方式:css3、zoom和base,模式的程序結(jié)構(gòu)跟上一篇類似。
縮放變換的目的是根據(jù)傳遞過來的比例和位置偏移量,把縮放效果顯示出來,實(shí)現(xiàn)最終的3D效果。
css3模式使用的是css3的transform,在上一篇已經(jīng)介紹過用transform的matrix做縮放和旋轉(zhuǎn),這次還需要后面兩個(gè)參數(shù)做位置變換。
后面兩個(gè)參數(shù)要注意單位的設(shè)置,在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.
意思是位移參數(shù)tx和ty,在Firefox需要帶單位,而WebKit和Opera只需要數(shù)字(不帶單位,默認(rèn)px)。
程序會(huì)根據(jù)瀏覽器設(shè)置單位。
使用css3模式,還可以通過修改_r弧度屬性進(jìn)行旋轉(zhuǎn)。
最后設(shè)置matrix實(shí)現(xiàn)變換:
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 + ")";
這里還要注意一個(gè)問題,計(jì)算得到的比例可能是一個(gè)很長的小數(shù),在拼字符時(shí)會(huì)出問題。
例如執(zhí)行:alert(0.0000001),會(huì)得到“1e-7”,js會(huì)用這個(gè)結(jié)果來拼字符,得到錯(cuò)誤的結(jié)果。
所以在做數(shù)字和字符的拼接時(shí),能用整數(shù)的應(yīng)該先轉(zhuǎn)成整數(shù),小數(shù)的話也要用toFixed轉(zhuǎn)換一下。
【zoom模式】
ie還不支持transform,但有一個(gè)zoom樣式能實(shí)現(xiàn)類似的效果。
由于zoom后,尺寸會(huì)發(fā)生變化,所以需要修正left和top移動(dòng)到正確的位置。
除了ie,webkit(chrome/safari)也支持zoom,不過ie6/7、ie8和webkit的實(shí)現(xiàn)并不完全相同。
測試以下代碼:
<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實(shí)現(xiàn)了想要的效果,但在webkit顯示的位置錯(cuò)了。
原因是使用zoom后,元素的left和top也會(huì)隨著縮放,那只要按比例重新計(jì)算就行。
像上面的例子,只要把left和top改成50/0.5,即100就正確了。
ie8就更麻煩,里面的內(nèi)容是按zoom縮放了,但left和top還是原來的大小。
被這個(gè)問題困擾了很久,最后發(fā)現(xiàn)通過用百分比定位就可以解決,在圖片加載時(shí)left和top要用百分比就是這個(gè)原因。
例如在例子中,修正left和top,并把最里面的div的left改成25%就可以了。
在ie8還看到一個(gè)問題,在zoom后,內(nèi)容是縮小了,容器和內(nèi)部元素的尺寸卻沒有變化,還好這不會(huì)影響到圖片的顯示,定位也要用left和top,免得麻煩。
還有,如果zoom的元素的尺寸用百分比設(shè)置,那元素的尺寸就不會(huì)根據(jù)zoom縮放了。
在計(jì)算時(shí)還要注意一個(gè)問題,上面提到在webkit和ie8,left和top都需要除以scale來修正,當(dāng)scale接近0到一定程度,結(jié)果會(huì)變成Infinity(無窮大)。
用Infinity進(jìn)行運(yùn)算會(huì)出錯(cuò),需要修正這個(gè)問題:
top = Math.min(MAX, Math.max( -MAX, top )) | 0;
其中MAX是Number.MAX_VALUE(js能表達(dá)的最大數(shù))。
【base模式】
還有兼容全部瀏覽器的base模式,用的是傳統(tǒng)的方法,即根據(jù)縮放比例,計(jì)算并設(shè)置每個(gè)圖片的尺寸和位置。
每次顯示時(shí),歷遍層里面的圖片,再逐個(gè)計(jì)算設(shè)置。
計(jì)算需要圖片的原始位置和尺寸,在第一次計(jì)算時(shí)會(huì)把數(shù)據(jù)保存在_original屬性中:
width: img.offsetWidth, height: img.offsetHeight,
left: img.offsetLeft, top: img.offsetTop
};
尺寸只要根據(jù)比例縮放就行,位置除了計(jì)算相對(duì)層的縮放還要加上相對(duì)容器的位移,這個(gè)跟zoom模式的計(jì)算是一樣的。
理解了層變換的方式后,再理解這個(gè)就不難了。
【zIndex】
深度除了要縮放和定位,還需要合理的前后遮蓋。
前后遮蓋需要用zIndex來實(shí)現(xiàn),可以在圖片或?qū)由显O(shè)置。
首先最簡單的方法是在層上設(shè)置:
<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>
實(shí)現(xiàn)一般的3D效果可以這樣設(shè)置。
但點(diǎn)擊測試,在ff和webkit前面的能觸發(fā)后面的不能觸發(fā),而ie和opera就前后都可以觸發(fā)。
ps:如果img換成div,那么ie和opera后面的元素也不能觸發(fā),原因還不清楚。
這樣要想像3DRoom那樣觸發(fā)圖片事件的話就不能在層設(shè)置zIndex。
還可以在圖片上設(shè)置:
<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>
這樣圖片在所有瀏覽器都能正常觸發(fā),但在ie6/7層疊的效果失效了,看來在ie6/7只能在層用zIndex。
還有一個(gè)問題,如果給div加上變換效果:
div{-moz-transform:scale(1);-webkit-transform:scale(1);-o-transform:scale(1);}
那圖片上的zIndex就會(huì)失效,那css3模式就只能在層設(shè)置zIndex了。
總結(jié)一下:
在css3模式肯定要在層設(shè)置zIndex,但圖片也不能觸發(fā)事件。
在zoom和base模式,應(yīng)該在圖片設(shè)置zIndex,但在ie6/7就要在層設(shè)置。
這樣至少在base模式層疊和圖片觸發(fā)事件都是正常的。
【msInterpolationMode】
開始做的時(shí)候,效果在ie8下會(huì)很卡,但這個(gè)3DRoom卻不會(huì)卡,最后發(fā)現(xiàn)是使用了-ms-interpolation-mode。
這個(gè)東西在aoao的文章中看過,但沒想到可以用在這里。
在MSDN有msInterpolationMode的介紹:
Gets or sets the interpolation (resampling) method used to stretch images.
即獲取或設(shè)置用于拉伸圖像的插值(重采樣)方法。
它有兩個(gè)值:
nearest-neighbor:使用近鄰插值模式。
bicubic:使用高品質(zhì)的雙三次插值模式。
這些名詞比較專業(yè),我們只要知道使用nearest-neighbor效率高但效果差,而bicubic效果好效率低就夠了。
程序把它設(shè)為nearest-neighbor提高效率,這樣在ie8中也不會(huì)卡了。
【拖動(dòng)方向變換/滾輪深度變換】
程序擴(kuò)展了拖動(dòng)視覺變換和滾輪深度變換。
拖動(dòng)和滾動(dòng)的做法跟上一個(gè)的做法差不多,這里拖動(dòng)是實(shí)現(xiàn)方向的變換,滾輪是實(shí)現(xiàn)深度的變換。
移動(dòng)是通過修改_x和_y屬性來實(shí)現(xiàn),縮放是通過修改_z來實(shí)現(xiàn)。
修改屬性之后再調(diào)用show方法顯示效果。
使用技巧
【3DRoom】
在3DRoom效果中,因?yàn)橐獙?shí)現(xiàn)圖片的觸發(fā)事件,所以不能用css3模式,原因是上面提到的層疊問題。
上面也提到在ie8被zoom的元素尺寸不會(huì)改變,導(dǎo)致觸發(fā)范圍錯(cuò)誤,所以也不用zoom模式。
使用base模式就不會(huì)有問題了。
在點(diǎn)擊圖片時(shí),視覺會(huì)移動(dòng)到圖片上面,這個(gè)通過點(diǎn)擊圖片后根據(jù)本身的三維參數(shù)修改_x/_y/_z來實(shí)現(xiàn):
i3D._z = -options.z | 0;
i3D._x = -options.x | 0;
i3D._y = options.y | 0;
i3D.show();
}
圖片在mouseover時(shí)會(huì)顯示一個(gè)邊框,為了讓圖片加邊框后不發(fā)生位移,加了一個(gè)"-1px"的margin,mouseout時(shí)再去掉。
這里3DRoom跟參考的效果還是有差距,本文主要還是對(duì)3D效果的實(shí)現(xiàn)和研究。
【模式選擇】
css3模式穩(wěn)定,大部分瀏覽器都支持,除了ie。
zoom模式兼容性不好,但ie支持。
base最慢,但兼容性好,而且沒有bug。
一般情況下應(yīng)優(yōu)先使用css3模式,然后是zoom,最后base,但像3DRoom那樣的情況就要按實(shí)際選擇了。
設(shè)計(jì)的時(shí)候,ie是打算用Matrix濾鏡的,但開發(fā)中發(fā)現(xiàn)一些問題,效率又太低,就不考慮了。
使用說明
實(shí)例化時(shí),必須有容器作為參數(shù):
然后調(diào)用i3D方法添加圖片:
可選參數(shù)用來設(shè)置系統(tǒng)的默認(rèn)屬性,包括:
屬性: 默認(rèn)值//說明
mode: "css3|zoom|base",//模式
x: 0,//水平偏移值
y: 0,//垂直偏移值
z: 0,//深度偏移值
r: 0,//旋轉(zhuǎn)角度(css3支持)
fixedFar: false,//是否遠(yuǎn)點(diǎn)固定
getScale: function(z){ return 1 - z / 1000; },//獲取比例方法
onError: function(err){}//出錯(cuò)時(shí)執(zhí)行
add方法的可選參數(shù)在圖片加載中已經(jīng)說明。
還提供了以下方法:
add:添加圖片;
show:顯示效果;
reset:重置默認(rèn)狀態(tài);
dispose:銷毀程序。
加入拖動(dòng)方向變換或滾輪深度變換擴(kuò)展后,可通過設(shè)置相關(guān)參數(shù)定義變換范圍。
打包下載地址 http://demo.jb51.net/js/Image3D/index.htm
演示地址 /201010/yuanma/Image3D.rar
相關(guān)文章
js實(shí)現(xiàn)增加數(shù)字顯示的環(huán)形進(jìn)度條效果
本文主要分享了js實(shí)現(xiàn)增加數(shù)字顯示的環(huán)形進(jìn)度條效果的示例代碼。具有一定的參考價(jià)值,下面跟著小編一起來看下吧2017-02-02JavaScript實(shí)現(xiàn)滑塊驗(yàn)證案例
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)滑塊驗(yàn)證案例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01在?localStorage?中上傳和檢索存儲(chǔ)圖像的示例詳解
這篇文章主要介紹了在?localStorage?中上傳和檢索存儲(chǔ)圖像,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06利用10行js代碼實(shí)現(xiàn)上下滾動(dòng)公告效果
這篇文章主要給大家介紹了關(guān)于利用10行js代碼實(shí)現(xiàn)滾動(dòng)公告效果的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起看看吧。2017-12-12查找頁面中所有類為test的結(jié)點(diǎn)的方法
這篇文章主要介紹了查找頁面中所有類為test結(jié)點(diǎn)的方法,需要的朋友可以參考下2014-03-03JavaScript使用shift方法移除素組第一個(gè)元素實(shí)例分析
這篇文章主要介紹了JavaScript使用shift方法移除素組第一個(gè)元素的用法,實(shí)例分析了javascript中shift方法的使用技巧,需要的朋友可以參考下2015-04-04Javascript實(shí)現(xiàn)秒表倒計(jì)時(shí)功能
這篇文章主要為大家詳細(xì)介紹了Javascript實(shí)現(xiàn)秒表倒計(jì)時(shí)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11