接受前端挑戰:CSS 3D Cube
已發表: 2022-03-10你喜歡挑戰嗎? 你願意承擔你以前從未遇到過的任務,並在截止日期前完成嗎? 如果在執行任務的過程中遇到一個似乎無法解決的問題怎麼辦? 我想分享我在實際項目中第一次使用 CSS 3D 效果的經驗,並激勵您接受挑戰。
這是一個普通的日子,CreativePeople 的經理 Eugene 給我寫信。 他給我發了一段視頻,並解釋說他正在為一個新項目開發一個概念,並想知道我是否有可能開發出類似於視頻中的東西。
關於 SmashingMag 的進一步閱讀:
- Beercamp:CSS 3D 實驗
- 使用 Clip-Path 創建響應式形狀並打破常規
- 讓我們玩一下硬件加速的 CSS
它是一個圍繞其中一個軸旋轉的 3D 對象(準確地說是長方體)。 我已經有了一些使用 CSS 3D 的經驗,並且開始在我的腦海中形成一個解決方案。 我在 Google 上搜索了“CSS 3D 立方體”之類的關鍵字來確認我的想法,並回答 Eugene 說這是可能的。
尤金的下一個問題是我是否願意接手這個項目? 我喜歡棘手的任務,所以我無法拒絕。 當時,我並沒有意識到自己在做什麼,但我已經下定了決心。
磨礪你的斧頭
讓我們提醒自己關於軸——不是戰爭軸,而是數軸,與我們在學校學習的三維笛卡爾坐標系中的軸相同。 正如維基百科告訴我們的:
三維空間的笛卡爾坐標係是有序的三重線(軸),它們成對垂直,所有三個軸都有一個長度單位,每個軸都有一個方向。
下圖顯示了軸在 Web 瀏覽器中的方向。

x 軸是水平的,y 軸是垂直的,z 軸看起來像是從屏幕上朝你走來的。 z 軸的零值是屏幕的平面。 記住這一點。
清理視角
要創建一個 3D 對象,我需要一個帶有透視圖的元素(我們稱之為“場景”)。 透視是場景的深度,它取決於它包含的對象的大小。
.scene { perspective: 800px; }
如果視角太小,物體可能會變形。 如果太大,則 3D 效果將降低到零。
在 CodePen 上查看 Anna Selezniova (@askd) 的 Pen jqgMvL。
此外,場景中的所有對像只有一個視角。 3D 效果取決於視點的位置。
在 CodePen 上查看 Anna Selezniova (@askd) 的 Pen oxKzKv。
那麼,我們如何計算透視呢? 我發現它取決於旋轉軸。 對於 x 軸,高度值乘以 4 即可。 對於 y 軸,它將是寬度值乘以 4。這是我的神奇公式:
const perspective = dimension * 4;
全方位考慮
確定透視後,我開始創建一個 3D 對象。 我選擇了一個立方體,因為它簡單且可預測。 一個立方體元素被創建為一個相對定位的常規 div,並定義了寬度和高度(例如200px
)。 它通過值為preserve-3d
的transform-style
屬性轉換為 3D 對象。 它告訴瀏覽器根據 3D 世界的規則渲染所有嵌套元素。
在我的例子中,立方體有六個絕對定位的 div(或“邊”)。 類名對應於邊的初始位置( back
, left
, right
, top
, bottom
, front
)。 這是標記:
<div class="scene"> <div class="cube"> <div class="side back"></div> <div class="side left"></div> <div class="side right"></div> <div class="side top"></div> <div class="side bottom"></div> <div class="side front"></div> </div> </div>
默認情況下,所有邊都在一個平面上。 所以,我需要重新排列它們。 看起來是這樣的:
在 CodePen 上查看 Anna Selezniova (@askd) 的 Pen mPNwPx。
這是生成的 CSS:
.cube { position:relative; width: 200px; height: 200px; transform-style: preserve-3d; } .side { position: absolute; width: 200px; height: 200px; } .back { transform: translateZ(-100px); } .left { transform: translateX(-100px) rotateY(90deg); } .right { transform: translateX(100px) rotateY(90deg); } .top { transform: translateY(-100px) rotateX(90deg); } .bottom { transform: translateY(100px) rotateX(90deg); } .front { transform: translateZ(100px); }
為了旋轉立方體,我將立方體元素的transform
屬性設置為沿 x 軸的任意旋轉角度:
.cube { transform: rotateX(42deg); }
克服缺點
根據作業,我只是沿x軸旋轉立方體,所以我不需要左側或右側。 我添加了標題以與剩餘邊的初始位置對齊。
我開始旋轉立方體,發現底部和背面的字幕顯示是倒置的:

請參閱 CodePen 上 Anna Selezniova (@askd) 的 Pen GZVvMR。
為了解決這個問題,我將這些邊沿 x 軸旋轉了 180 度:
.back { transform: translateZ(-100px) rotateX(180deg); } .bottom { transform: translateY(100px) rotateX(270deg); }
超越屏幕
我開始用真實的內容填充兩邊,然後立即遇到了另一個問題。 我需要顯示 1 像素的虛線,但它們很模糊而且看起來很糟糕。
請參閱 CodePen 上 Anna Selezniova (@askd) 的 Pen VjeBPg。
我很快意識到問題出在哪裡。 你還記得那個圖像超出屏幕的 3D 電視廣告嗎? 我的立方體就是這樣。
如果您可以從左側或右側查看立方體,您會看到它的中心位於屏幕平面上(z 軸為零),並且正面超出屏幕。 因此,它在視覺上增加和模糊。
在 CodePen 上查看 Anna Selezniova (@askd) 的 Pen WwVEMR。
為了解決這個問題,我沿 z 軸移動了立方體以將正面與屏幕平面對齊:
.cube { transform:translateZ(-100px); }
現在是立方體,幾乎準備好了:
請參閱 CodePen 上 Anna Selezniova (@askd) 的 Pen Xdvery。
使用幻數
我想你已經註意到我正在使用幻數100
來沿軸移動邊。 值100
正好是我的測試立方體高度的一半。 為什麼是一半的高度? 因為那將是內接在立方體一側的圓的半徑(顯然是正方形)。
const offset = dimension / 2;
如果我需要旋轉一個三棱柱,這個圓將被內接在一個三角形中。 在這種情況下,偏移量的公式如下:
const offset = dimension / (2 * Math.sqrt(3));
吹走立方體
為了完成任務,我不得不在不同的瀏覽器中測試結果。
我在 Internet Explorer 中看到的圖片讓我陷入了沮喪。 要了解我在說什麼,請在您喜歡的瀏覽器中查看下面的演示。 我更改了一個屬性,導致多維數據集在 Internet Explorer 中顯示不正確。 但是,在您閱讀下面演示下的段落之前,請不要偷看源代碼。
請參閱 CodePen 上 Anna Selezniova (@askd) 的 Pen XKWMwV。
事實上,Internet Explorer 不支持值為preserve-3d
的transform-style
屬性。 我通過查看我的可信賴資源 Can I Use(見註 1)了解了這一點。 在上面的演示中,我將preserve-3d
替換為flat
。 你已經知道了嗎? 嘿,我告訴過你不要偷看!
我很沮喪,但我不打算放棄。 問題是學習新事物的機會。 此外,我已經接受了挑戰。
尋找支點
我一直在尋找一種不使用transform-style: preserve-3d
創建 3D 對象的方法,最終我發現了一個有用的屬性: transform-origin
。 它決定了元素變換的中心點。 我在下面創建了一個交互式演示,它將幫助您了解它的工作原理:
在 CodePen 上查看 Anna Selezniova (@askd) 的 Pen rLNmBp。
演示中元素的 3D 旋轉非常類似於立方體的正面,不是嗎? 這就是我用的。
(順便問一下,您是否嘗試選擇複選框backface-visibility: hidden
during the 3D 旋轉?此屬性用於在 3D 轉換期間隱藏元素的背面。)
重新開始
我開始重做立方體。 我不必與整個場景進行交互,因此我移除了scene
元素的perspective
屬性並將其添加到每個 3D 變換中,這樣現在每個元素都可以獨立變換。 此外,我為每一面設置了新屬性: transform-origin
的值等於立方體中心的位置,以及backface-visibility: hidden
。 以下是樣式的變化:
.scene { } .cube { position: relative; width: 200px; height: 200px; transform: perspective(800px) translateZ(-100px); } .side { position: absolute; transform-origin: 50% 50% -100px; backface-visibility: hidden; }
我必須把兩邊放在正確的地方。 由於transform-origin
屬性,我不需要移動它們,只需要圍繞軸旋轉它們。 就像魔術一樣! 讓我們看看它的外觀:
請參閱 CodePen 上 Anna Selezniova (@askd) 的 Pen zBYwEm。
這是側面放置的CSS:
.back { transform: perspective(800px) rotateY(180deg); } .top { transform: perspective(800px) rotateX(90deg); } .bottom { transform: perspective(800px) rotateX(-90deg); } .front { transform: perspective(800px); }
在這裡您可以看到正在運行的新立方體:
請參閱 CodePen 上 Anna Selezniova (@askd) 的 Pen wWvdXd。
還給凱撒什麼是凱撒的
第二個立方體的外觀和旋轉與第一個相同。 但在這種情況下,您需要單獨轉換每一面。 這可能不是很容易,特別是如果您想控制中間旋轉角度。
此外,如果您在 Chrome 中打開演示,您會看到旋轉過程中兩側會閃爍——非常令人沮喪。
最後,我使用transform-style: preserve-3d
的簡單測試應用了這兩種方法。 第一個立方體是默認的。 第二個多維數據集用於 Internet Explorer 和不支持preserve-3d
瀏覽器。
使用數學的力量
最後,我必須實現視差效果。 通常,此效果會響應用戶的操作,無論是鼠標光標的位置還是滾動條的位置。 在這種情況下,效果取決於旋轉角度。
請參閱 CodePen 上 Anna Selezniova (@askd) 的 Pen QENyqm。
那麼,我有什麼數據? 首先,我有字幕位置的起點和終點,或者簡單地說,就是它相對於邊中心的上下offset
。 其次,我有立方體的旋轉angle
。
我花了幾個小時試圖開發一個公式。 然後,我恍然大悟。 這就是我想到的:

在正弦和余弦的幫助下,我很容易根據角度計算出每個字幕的偏移量。 以下是我想出的公式:
const front_offset = offset * sin(angle) * -1; const bottom_offset = offset * cos(angle); const back_offset = offset * sin(angle); const top_offset = offset * cos(angle) * -1;
加起來
作業現已完成,我可以享受結果並與您分享。 親自看看它是如何工作的。 使用滾動或箭頭鍵來旋轉促銷塊。 另外,請嘗試上下拉動右側的黑色三角形以手動控制旋轉角度(不幸的是,此功能在 Internet Explorer 中不起作用)。 看起來很不錯,不是嗎? 並且性能相當高(大約每秒 60 幀)。
我很高興參與了這個網站的開發。 我在使用 CSS 3D 方面獲得了有用的經驗,並發現了許多有趣的屬性。 更重要的是,我知道一個人永遠不應該放棄; 您很可能會找到完成任務的方法。
我希望你喜歡我的故事,現在準備好迎接新的挑戰。