天天看點

JavaScript 圖檔3D展示空間(3DRoom)

一般的平面效果,通過改變水準和垂直坐标就能實作,再加上深度,就能在視覺上的産生3d(三維)的效果。 

程式就是模拟這樣一個三維空間,裡面的圖檔會根據三維坐标顯示在這個空間。 

很久以前就看過一個3droom 效果,是用複雜的計算實作的。 

在上一篇圖檔變換 研究過css3的transform之後,就想到一個更簡單的方法來實作。 

JavaScript 圖檔3D展示空間(3DRoom)
JavaScript 圖檔3D展示空間(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是深度,用于比例的計算,方向由近點到原點。 

坐标系如下圖: 

JavaScript 圖檔3D展示空間(3DRoom)

圖檔加載成功後,就會執行_load圖檔加載程式。 

首先根據參數設定圖檔樣式:

js代碼

JavaScript 圖檔3D展示空間(3DRoom)

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) + "%;";  

JavaScript 圖檔3D展示空間(3DRoom)

絕對定位是必須的,寬度和高度根據參數設定就行。 

left和top根據坐标參數計算,這裡需要用百分比的形式表示,後面再詳細說明。 

還要給圖檔增加一個_z屬性記錄深度,友善調用。 

最後插入對應z的層,并重新顯示該層。 

【層變換】 

圖檔加載後,會用_insertlayer程式把圖檔插入到對應的層中。 

_insertlayer有兩個參數:圖檔元素和z深度。 

程式用_layers對象,以z為關鍵字記錄對應的層元素。 

如果在該深度還沒有建立層,會自動建立一個:

JavaScript 圖檔3D展示空間(3DRoom)

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;";  

JavaScript 圖檔3D展示空間(3DRoom)

層的坐标和尺寸要跟容器一緻,因為插入圖檔的坐标是相對容器來定義的,這樣使用起來比較友善。 

還會添加一個_count屬性,記錄層包含的圖檔數,最後插入到容器并記錄到_layers對象中。 

擷取層對象後,就把圖檔插入層中,并把_count計數加1。 

接着就可以通過_showlayer程式根據深度顯示對應的層。 

程式包含三個坐标屬性:_x、_y、_z,表示容器的三維坐标的偏移量。 

首先通過_getscale擷取比例方法得到z深度的縮放比例scale。 

比例大于1,說明圖檔在視覺深度的後面,理論上應該看不到,是以隐藏;小于0,就是小到看不到了也隐藏。 

而_x和_y偏移量也需要根據深度來重新計算,程式有兩種偏移方式:遠點固定和近點固定。 

遠點固定的意思是平面位移偏移量随着深度逐漸變小,産生以最遠點為固定點移動方向的效果,近點固定就剛好相反。 

要實作這個效果,隻要位移偏移量也跟着比例變化就行了,即遠點固定時偏移量跟比例成正比,遠點固定時是反比:

JavaScript 圖檔3D展示空間(3DRoom)

var movescale = this.fixedfar ? scale : (1 - scale);  

JavaScript 圖檔3D展示空間(3DRoom)

然後把這些參數交給_show程式來處理,并顯示效果。 

為了最大限度地利用層元素,程式會在_remove圖檔移除程式中,把沒有圖檔的層放到_invalid廢棄層集合中,在需要插入層時,優先從_invalid中擷取。 

【縮放比例】 

上面已經說了,縮放比例應該按照最近點為1,最遠點為0,逐漸變化。 

程式預設是通過下面的公式計算:

JavaScript 圖檔3D展示空間(3DRoom)

function(z){ return 1 - z / 1000; }  

但用這個公式實作3droom效果的時候,會發現比例變化太急速,并不像這個3droom 那樣平穩。 

研究代碼後發現,原來它用的公式是這樣的:

JavaScript 圖檔3D展示空間(3DRoom)

this.r = fl / (fl + (z * z));  

 其中fl和z是一個常量來的,即公式可表示成:

JavaScript 圖檔3D展示空間(3DRoom)
JavaScript 圖檔3D展示空間(3DRoom)

function(z){ return 1/(1+z/常量); }  

那按照這個公式,深度為0時比例為1,深度為常量時比例為0.5,深度為無窮大時比例為0。 

變化效果可以參考這裡的公式示範程式 。 

可以看出,縮放比例在預設公式是均勻變化的,而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實作變換:

JavaScript 圖檔3D展示空間(3DRoom)

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 + ")";  

JavaScript 圖檔3D展示空間(3DRoom)

這裡還要注意一個問題,計算得到的比例可能是一個很長的小數,在拼字元時會出問題。 

例如執行:alert(0.0000001),會得到“1e-7”,js會用這個結果來拼字元,得到錯誤的結果。 

是以在做數字和字元的拼接時,能用整數的應該先轉成整數,小數的話也要用tofixed轉換一下。 

【zoom模式】 

ie還不支援transform,但有一個zoom樣式能實作類似的效果。 

由于zoom後,尺寸會發生變化,是以需要修正left和top移動到正确的位置。 

除了ie,webkit(chrome/safari)也支援zoom,不過ie6/7、ie8和webkit的實作并不完全相同。 

測試以下代碼:

JavaScript 圖檔3D展示空間(3DRoom)

<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進行運算會出錯,需要修正這個問題:

JavaScript 圖檔3D展示空間(3DRoom)

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屬性中:

JavaScript 圖檔3D展示空間(3DRoom)

var original = img._original = img._original || {  

    width: img.offsetwidth, height: img.offsetheight,  

    left: img.offsetleft,   top: img.offsettop  

};  

尺寸隻要根據比例縮放就行,位置除了計算相對層的縮放還要加上相對容器的位移,這個跟zoom模式的計算是一樣的。 

了解了層變換的方式後,再了解這個就不難了。 

【zindex】 

深度除了要縮放和定位,還需要合理的前後遮蓋。 

前後遮蓋需要用zindex 來實作,可以在圖檔或層上設定。 

首先最簡單的方法是在層上設定:

JavaScript 圖檔3D展示空間(3DRoom)

div,img{width:200px;height:200px;position:absolute;left:0;top:0;}  

img{width:150px;height:150px;}  

<div style="z-index:300;">  

    <img style="background:#0c9;" alt="300" onclick="alert(300)">  

<div style="z-index:100;">  

    <img style="background:#396;left:50px;top:50px;" alt="100" onclick="alert(100)">  

JavaScript 圖檔3D展示空間(3DRoom)

實作一般的3d效果可以這樣設定。 

但點選測試,在ff和webkit前面的能觸發後面的不能觸發,而ie和opera就前後都可以觸發。 

ps:如果img換成div,那麼ie和opera後面的元素也不能觸發,原因還不清楚。 

這樣要想像3droom那樣觸發圖檔事件的話就不能在層設定zindex。 

還可以在圖檔上設定:

JavaScript 圖檔3D展示空間(3DRoom)

<div>  

    <img style="background:#0c9;z-index:300;" alt="300" onclick="alert(300)">  

    <img style="background:#396;left:50px;top:50px;z-index:100;" alt="100" onclick="alert(100)">  

JavaScript 圖檔3D展示空間(3DRoom)

這樣圖檔在所有浏覽器都能正常觸發,但在ie6/7層疊的效果失效了,看來在ie6/7隻能在層用zindex。 

還有一個問題,如果給div加上變換效果:

JavaScript 圖檔3D展示空間(3DRoom)

div{-moz-transform:scale(1);-webkit-transform:scale(1);-o-transform:scale(1);}  

 那圖檔上的zindex就會失效,那css3模式就隻能在層設定zindex了。

JavaScript 圖檔3D展示空間(3DRoom)

總結一下: 

在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來實作:

JavaScript 圖檔3D展示空間(3DRoom)

img.onclick = function(){  

    i3d._z = -options.z | 0;  

    i3d._x = -options.x | 0;  

    i3d._y = options.y | 0;  

    i3d.show();  

}  

JavaScript 圖檔3D展示空間(3DRoom)

圖檔在mouseover時會顯示一個邊框,為了讓圖檔加邊框後不發生位移,加了一個"-1px"的margin,mouseout時再去掉。 

這裡3droom跟參考的效果還是有差距,本文主要還是對3d效果的實作和研究。 

【模式選擇】 

css3模式穩定,大部分浏覽器都支援,除了ie。 

zoom模式相容性不好,但ie支援。 

base最慢,但相容性好,而且沒有bug。 

一般情況下應優先使用css3模式,然後是zoom,最後base,但像3droom那樣的情況就要按實際選擇了。 

設計的時候,ie是打算用matrix濾鏡的,但開發中發現一些問題,效率又太低,就不考慮了。 

使用說明 

執行個體化時,必須有容器作為參數:

JavaScript 圖檔3D展示空間(3DRoom)

var i3d = new image3d( container, options );    

JavaScript 圖檔3D展示空間(3DRoom)

var i3d = new image3d( container, options );  

然後調用i3d方法添加圖檔:

js代

JavaScript 圖檔3D展示空間(3DRoom)

i3d.add( src, options );    

JavaScript 圖檔3D展示空間(3DRoom)

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:銷毀程式。 

加入拖動方向變換或滾輪深度變換擴充後,可通過設定相關參數定義變換範圍。