您知道那些完全扁平的瓦楞紙箱嗎?您可以將它們折疊起來並用膠帶粘好,製成一個實用的盒子。當需要回收時,再將它們剪開壓平。最近,有人向我諮詢了這個概念的3D動畫版本,我認為用純CSS實現它會是一個有趣的教程,所以我們開始了!
這種動畫會是什麼樣子?我們如何創建包裝時間線?尺寸可以靈活調整嗎?讓我們創建一個純CSS包裹切換效果。
最終效果如下:點擊即可打包和解包紙箱。
從哪裡開始呢?最好提前做好規劃。我們知道我們需要一個包裹模板,並且需要將其在三維空間中折疊。如果您不熟悉CSS 3D,我建議您閱讀這篇文章入門。
如果您熟悉3D CSS,您可能會傾向於構建一個長方體然後開始。但是,這會帶來一些問題。我們需要考慮包裹如何從2D轉換為3D。
讓我們從創建模板開始。我們需要提前規劃好標記,並思考我們希望包裝動畫如何運作。讓我們從一些HTML開始。
<div> <div> <div> <div> <div></div> <div></div> <div> <div></div> <div></div> </div> <div> <div></div> <div></div> <div> <div></div> <div></div> </div> </div> </div> </div> </div> </div>
這裡有很多內容。有很多div。我經常喜歡使用Pug來生成標記,這樣可以將內容分成可重用的塊。例如,每一面都有兩個蓋板。我們可以為側面創建一個Pug mixin,並使用屬性應用修飾符類名,使所有這些標記更容易編寫。
mixin flaps() .package__flap.package__flap--top .package__flap.package__flap--bottom mixin side(class) .package__side(class=`package__side--${class || 'side'}`) flaps() block .scene .package__wrapper .package side('main') side('extra') side('flipped') side('tabbed')
我們使用了兩個mixin。一個為盒子的每一面創建蓋板。另一個創建盒子的側面。請注意,在side mixin中,我們使用了block。 mixin用法的子元素就在此處呈現,這特別有用,因為我們稍後需要嵌套一些側面以簡化我們的工作。
生成的標記:
<div class="scene"> <div class="package__wrapper"> <div class="package"> <div class="package__side package__side--main"> <div class="package__flap package__flap--top"></div> <div class="package__flap package__flap--bottom"></div> <div class="package__side package__side--extra"> <div class="package__flap package__flap--top"></div> <div class="package__flap package__flap--bottom"></div> </div> </div> <div class="package__side package__side--flipped"> <div class="package__flap package__flap--top"></div> <div class="package__flap package__flap--bottom"></div> <div class="package__side package__side--tabbed"> <div class="package__flap package__flap--top"></div> <div class="package__flap package__flap--bottom"></div> </div> </div> </div> </div> </div>
嵌套側面使折疊包裹更容易。就像每一面都有兩個蓋板一樣。側面的子元素可以繼承側面的變換,然後應用它們自己的變換。如果我們從長方體開始,就很難利用這一點。
查看這個演示,它在嵌套和非嵌套元素之間切換,以查看實際效果。
每個盒子都有一個transform-origin設置為右下角,值為100% 100%。選中“Transform”切換會將每個盒子旋轉90度。但是,請注意,如果我們嵌套元素,該變換的行為會發生變化。
我們切換了兩個版本的標記,但沒有更改其他任何內容。
嵌套:
<div> <div> <div> <div> <div></div> </div> </div> </div> </div>
非嵌套:
<div> <div></div> <div></div> <div></div> <div></div> </div>
應用一些樣式到我們的HTML之後,我們就有了我們的包裹模板。
樣式指定了不同的顏色,並將側面定位到包裹上。每個側面的位置相對於“主”側面。 (您很快就會明白為什麼嵌套如此有用。)
需要注意一些事情。就像使用長方體一樣,我們使用--height
、 --width
和--depth
變量來確定尺寸。這將使我們以後更容易更改包裹尺寸。
.package { height: calc(var(--height, 20) * 1vmin); width: calc(var(--width, 20) * 1vmin); }
為什麼這樣定義尺寸?我們使用了一個無單位的默認尺寸20,這個想法我從Lea Verou的2016年CSS ConfAsia演講中獲得的(從52:44開始)。將自定義屬性用作“數據”而不是“值”,我們可以使用calc()
隨意使用它們。此外,JavaScript不必關心值單位,我們可以更改為像素、百分比等,而無需在其他地方進行更改。您可以將其重構為--root
中的係數,但這很快也會變得過於復雜。
每一側的蓋板的尺寸也需要比它們所屬的側面略小。這樣,當我們折疊它們時,就不會出現z-index衝突。
.package__flap { width: 99.5%; height: 49.5%; background: var(--flap-bg, var(--face-4)); position: absolute; left: 50%; transform: translate(-50%, 0); } .package__flap--top { transform-origin: 50% 100%; bottom: 100%; } .package__flap--bottom { top: 100%; transform-origin: 50% 0%; } .package__side--extra > .package__flap--bottom, .package__side--tabbed > .package__flap--bottom { top: 99%; } .package__side--extra > .package__flap--top, .package__side--tabbed > .package__flap--top { bottom: 99%; }
我們還開始考慮各個部件的transform-origin
。頂部蓋板將從其底部邊緣旋轉,底部蓋板將從其頂部邊緣旋轉。
我們可以使用偽元素來創建右側的標籤。我們使用clip-path
來獲得所需的形狀。
.package__side--tabbed:after { content: ''; position: absolute; left: 99.5%; height: 100%; width: 10%; background: var(--face-3); -webkit-clip-path: polygon(0 0%, 100% 20%, 100% 80%, 0 100%); clip-path: polygon(0 0%, 100% 20%, 100% 80%, 0 100%); transform-origin: 0% 50%; }
讓我們開始在3D平面上使用我們的模板。我們可以首先在X軸和Y軸上旋轉.scene
。
.scene { transform: rotateX(-24deg) rotateY(-32deg) rotateX(90deg); }
我們準備開始折疊我們的模板了!我們的模板將根據自定義屬性--packaged
進行折疊。如果值為1,則可以折疊模板。例如,讓我們折疊一些側面和偽元素標籤。
.package__side--tabbed, .package__side--tabbed:after { transform: rotateY(calc(var(--packaged, 0) * -90deg)); } .package__side--extra { transform: rotateY(calc(var(--packaged, 0) * 90deg)); }
或者,我們可以為所有不是“主”側面的側面編寫一個規則。
.package__side:not(.package__side--main), .package__side:not(.package__side--main):after { transform: rotateY(calc((var(--packaged, 0) * var(--rotation, 90)) * 1deg)); } .package__side--tabbed { --rotation: -90; }
這將涵蓋所有側面。
還記得我說過嵌套側面允許我們繼承父元素的變換嗎?如果我們更新我們的演示,以便我們可以更改--packaged
的值,我們可以看到該值如何影響變換。嘗試將--packaged
的值在1和0之間滑動,您就會明白我的意思。
現在我們有了切換模板折疊狀態的方法,我們可以開始處理一些運動了。我們之前的演示在兩種狀態之間切換。我們可以為此使用transition
。最快的方法?向.scene
中的每個子元素的transform
添加一個transition
。
.scene *, .scene *::after { transition: transform calc(var(--speed, 0.2) * 1s); }
但是我們不會一次性折疊整個模板——在現實生活中,折疊過程是有順序的,我們會先折疊一側及其蓋板,然後繼續下一個,依此類推。作用域自定義屬性非常適合此目的。
.scene *, .scene *::after { transition: transform calc(var(--speed, 0.2) * 1s) calc((var(--step, 1) * var(--delay, 0.2)) * 1s); }
在這裡,我們說,對於每個轉換,使用transition-delay
為--step
乘以--delay
。 --delay
值不會改變,但每個元素可以定義它在序列中的“步驟”。然後我們可以明確地說明事情發生的順序。
.package__side--extra { --step: 1; } .package__side--tabbed { --step: 2; } .package__side--flipped, .package__side--tabbed::after { --step: 3; }
查看以下演示,以更好地了解其工作原理。更改滑塊值以更新事情發生的順序。您可以更改哪輛車獲勝嗎?
同樣的技術是我們接下來要做的關鍵。我們甚至可以引入一個--initial-delay
,為所有內容添加一個輕微的暫停,以獲得更逼真的效果。
.race__light--animated, .race__light--animated:after, .car { animation-delay: calc((var(--step, 0) * var(--delay-step, 0)) * 1s); }
如果我們回顧我們的包裹,我們可以更進一步,並將“步驟”應用於所有將要轉換的元素。這相當冗長,但它確實有效。或者,您可以在標記中內聯這些值。
.package__side--extra > .package__flap--bottom { --step: 4; } .package__side--tabbed > .package__flap--bottom { --step: 5; } .package__side--main > .package__flap--bottom { --step: 6; } .package__side--flipped > .package__flap--bottom { --step: 7; } .package__side--extra > .package__flap--top { --step: 8; } .package__side--tabbed > .package__flap--top { --step: 9; } .package__side--main > .package__flap--top { --step: 10; } .package__side--flipped > .package__flap--top { --step: 11; }
但是,它感覺不太現實。
如果我在現實生活中折疊盒子,我可能會先將盒子翻轉過來,然後再折疊頂部蓋板。我們該如何做到這一點呢?嗯,那些目光敏銳的人可能已經註意到了.package__wrapper
元素。我們將使用它來滑動包裹。然後我們將包裹在x軸上旋轉。這將給人一種將包裹翻到側面的印象。
.package { transform-origin: 50% 100%; transform: rotateX(calc(var(--packaged, 0) * -90deg)); } .package__wrapper { transform: translate(0, calc(var(--packaged, 0) * -100%)); }
相應地調整--step
聲明,我們可以得到類似這樣的效果。
如果您在折疊和未折疊狀態之間切換,您會注意到展開看起來不對。展開順序應該是折疊順序的完全反向。我們可以根據--packaged
和步驟數來翻轉--step
。我們最新的步驟是15。我們可以將我們的轉換更新為此:
.scene *, .scene *:after { --no-of-steps: 15; --step-delay: calc(var(--step, 1) - ((1 - var(--packaged, 0)) * (var(--step) - ((var(--no-of-steps) 1) - var(--step))))); transition: transform calc(var(--speed, 0.2) * 1s) calc((var(--step-delay) * var(--delay, 0.2)) * 1s); }
這確實是一大堆calc
來反轉transition-delay
。但是,它確實有效!我們必須提醒自己要保持--no-of-steps
值是最新的!
我們還有另一個選擇。當我們繼續走“純CSS”路線時,我們最終將使用複選框技巧來切換折疊狀態。我們可以有兩組定義的“步驟”,當我們的複選框被選中時,其中一組處於活動狀態。這當然是一個更冗長的解決方案。但是,它確實讓我們能夠更精細地控制。
/* 折疊*/ :checked ~ .scene .package__side--extra { --step: 1; } /* 展開*/ .package__side--extra { --step: 15; }
在我們放棄在演示中使用dat.gui之前,讓我們來調整一下包裹的尺寸。我們要檢查一下我們的包裹在折疊和翻轉時是否保持居中。在這個演示中,包裹有一個更大的--height
,而.scene
有一個虛線邊框。
我們不妨順便調整一下我們的變換,以便更好地居中包裹:
/* 通過在z軸上平移來考慮包裹高度*/ .scene { transform: rotateX(calc(var(--rotate-x, -24) * 1deg)) rotateY(calc(var(--rotate-y, -32) * 1deg)) rotateX(90deg) translate3d(0, 0, calc(var(--height, 20) * -0.5vmin)); } /* 通過在翻轉之前滑動深度來考慮包裹深度*/ .package__wrapper { transform: translate(0, calc((var(--packaged, 0) * var(--depth, 20)) * -1vmin)); }
這使我們在場景中獲得了可靠的居中效果。但這完全取決於個人喜好!
現在讓我們擺脫dat.gui,使之成為“純”CSS。為此,我們需要在HTML中引入一堆控件。我們將使用一個複選框來折疊和展開我們的包裹。然後我們將使用一個單選按鈕來選擇包裹尺寸。
<label for="one">S</label> <label for="two">M</label> <label for="three">L</label> <label for="four">XL</label> <input type="checkbox" id="package"> <label for="package" class="open">打開包裹</label> <label for="package" class="close">關閉包裹</label>
在最終演示中,我們將隱藏輸入並使用標籤元素。但是,現在,讓我們先讓它們都可見。訣竅是在某些控件被選中時使用同級組合器(~)。然後,我們可以設置.scene
上的自定義屬性值。
#package:checked ~ .scene { --packaged: 1; } #one:checked ~ .scene { --height: 10; --width: 20; --depth: 20; } #two:checked ~ .scene { --height: 20; --width: 20; --depth: 20; } #three:checked ~ .scene { --height: 20; --width: 30; --depth: 20; } #four:checked ~ .scene { --height: 30; --width: 20; --depth: 30; }
這是一個包含該功能的演示!
現在我們可以讓事情看起來“漂亮”,並添加一些額外的細節。讓我們首先隱藏所有輸入。
input { position: fixed; top: 0; left: 0; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0; }
我們可以將尺寸選項設置為圓形按鈕:
.size-label { position: fixed; top: var(--top); right: 1rem; z-index: 3; font-family: sans-serif; font-weight: bold; color: #262626; height: 44px; width: 44px; display: grid; place-items: center; background: #fcfcfc; border-radius: 50%; cursor: pointer; border: 4px solid #8bb1b1; transform: translate(0, calc(var(--y, 0) * 1%)) scale(var(--scale, 1)); transition: transform 0.1s; } .size-label:hover { --y: -5; } .size-label:active { --y: 2; --scale: 0.9; }
我們希望能夠點擊任何地方來切換包裹的折疊和展開。因此,我們的.open
和.close
標籤將佔據整個屏幕。想知道為什麼我們有兩個標籤嗎?這是一個小技巧。如果我們使用transition-delay
並放大相應的標籤,我們可以在包裹轉換時隱藏兩個標籤。這就是我們對抗垃圾點擊的方式(儘管它不會阻止用戶在鍵盤上按空格鍵)。
.close, .open { position: fixed; height: 100vh; width: 100vw; z-index: 2; transform: scale(var(--scale, 1)) translate3d(0, 0, 50vmin); transition: transform 0s var(--reveal-delay, calc(((var(--no-of-steps, 15) 1) * var(--delay, 0.2)) * 1s)); } #package:checked ~ .close, .open { --scale: 0; --reveal-delay: 0s; } #package:checked ~ .open { --scale: 1; --reveal-delay: calc(((var(--no-of-steps, 15) 1) * var(--delay, 0.2)) * 1s); }
查看此演示,了解我們在.open
和.close
中添加了background-color
的位置。在轉換期間,兩個標籤都不可見。
我們已經擁有了完整的功能!但是,我們的包裹目前有點平淡無奇。讓我們添加額外的細節,使其看起來更像“盒子”,例如包裹膠帶和包裝標籤。
像這樣的細節僅受我們想像力的限制!我們可以使用我們的--packaged
自定義屬性來影響任何內容。例如, .package__tape
正在轉換scaleY
變換:
.package__tape { transform: translate3d(-50%, var(--offset-y), -2px) scaleX(var(--packaged, 0)); }
需要注意的是,每當我們添加影響序列的新功能時,都需要更新我們的步驟。不僅是--step
值,還有--no-of-steps
值。
這就是創建純CSS 3D包裹切換的方法。你會把它放到你的網站上嗎?不太可能!但是,看到你可以用CSS實現這些事情很有趣。自定義屬性非常強大。
為什麼不超級慶祝一下,送出CSS的禮物呢!
保持出色! ʕ •ᴥ•ʔ
以上是如何製作純CSS 3D軟件包切換的詳細內容。更多資訊請關注PHP中文網其他相關文章!