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

作者: cloudgamer  來源: 博客園  發布時間: 2010-09-21 21:44  閱讀: 1778 次  推薦: 1   原文鏈接   [收藏]  

  一般的平面效果,通過改變水平和垂直坐標就能實現,再加上深度,就能在視覺上的產生3D(三維)的效果。程序就是模擬這樣一個三維空間,里面的圖片會根據三維坐標顯示在這個空間。
很久以前就看過一個3DRoom效果,是用復雜的計算實現的。在上一篇圖片變換研究過css3的transform之后,就想到一個更簡單的方法來實現。
  兼容:ie6/7/8, firefox 3.6.8, opera 10.6, safari 5.0.1, chrome 5.0

  實現原理

  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圖片加載程序。首先根據參數設置圖片樣式:

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

  絕對定位是必須的,寬度和高度根據參數設置就行。eft和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。變化效果可以參考下面程序:

  公式:

  可以看出,縮放比例在默認公式是均勻變化的,而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來實現,可以在圖片或層上設置。首先最簡單的方法是在層上設置:

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

  加入拖動方向變換或滾輪深度變換擴展后,可通過設置相關參數定義變換范圍。

  完整實例下載

1
0
 
標簽:JavaScript 3D
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()