今天進入JS30的第五天,今天將透過非常酷炫的畫面特效,來認識CSS3中的Flex屬性。[1]
實作連結
首先在作者給的範例當中,我們可以看到頁面被分成了五個區塊,分別包含不同背景以及文字。當我們按下其中的任一區塊,該區塊的寬度會增加,並且裡面的字體會放大,再次按下則會恢復原本的樣貌。而轉換時的動畫特效就是使用第二天所學到的transform
及transition
的CSS屬性。
要精準的使用flex屬性,我們就需要了解其兩個重要的特性:Flexible Box Layout Module以及Flex Property。
根據w3schools的參考資料,Flex的Flexible Box特性,是一種新的Layout模型。如同其字面意思,它可以讓我們靈活的安排頁面的排版結構,取代原本舊CSS中的float
以及position
屬性。[2]
接下來將透過幾個參考網站中的gif圖檔,來介紹幾個常用的Flexible Box Layout Module屬性。[3]
display
修改為flex
屬性,內部元素的排版方式,將被更改為類似inline-block
屬性的樣貌,並且可以使用flex專屬的CSS屬性。#container {
display: flex;
}
display:flex
屬性,會將頁面上的方向,分成Main-Axis(主要軸)以及Cross-Axis(交叉軸),而flex-direction
指定的方向,將成為主要軸的方向,其垂直的方向則成為交叉軸。row(行)
、column(列)
、reverse-row(反向行)
及reverse-column(反向列)
,若無特別指定,預設值為flex-direction:row
。#container {
display: flex;
flex-direction: column;
}
Justify-content
屬性,為控制主要軸上各元素對齊方式的屬性,其中總共包含了五種條件,flex-start
、flex-end
、center
、space-between
、space-around
。#container {
display: flex;
flex-direction: row;
justify-content: flex-start;
}
Align-items
屬性,則是控制交叉軸上各元素對齊方式的屬性,其中也包含了五種條件,flex-start
、flex-end
、center
、stretch
、baseline
。#container {
display: flex;
flex-direction: row;
justify-content: flex-start;
}
透過justify-content
以及align-items
屬性,可以做出輕鬆做出絕對置中,但是當flex-direction
的不同時,所呈現的置中行為也會有所不同。
align-self
與align-items
屬性類似,都是控制交叉軸上的對齊方式,但前者是單一元素自己,後者則是控制容器中的所有元素。#one {
align-Self: flex-start;
}
#two {
align-Self: flex-start;
}
flex property為使用flex時最重要的屬性,該屬性由flex-grow
、flex-shrink
、flex-basis
三個子屬性組合而成,只填一個數值的話將default給定為flex-growth
屬性值:
{
flex:flex-grow flex-shrink flex-basis;
}
flex property最特別的地方,就是他能讓flex容器內的元素,利用此flex屬性,讓元素隨著容器的大小變化也跟著變化。[4][5]
當flex容器的主要軸長度,大於所有元素的長度總和,也就是說當剩餘空間 > 0,裡面元素就會隨著其flex-grow設定延伸,來補滿整個容器。而當flex容器的主要軸長度,小於所有元素的長度總和,也就是說剩餘空間 < 0時,裡面元素就會隨著其flex-shrink設定壓縮,避免元素突出容器。
因此我們能利用其以上的特點,來做出響應式的頁面效果,以下就來介紹各個子屬性:
flex-grow
:flex-shrink
:flex-basis
:flex-grow
:延伸空間 = (該元素flex-grow/所有元素flex-grow總和)*剩餘空間
原本每個元素的寬為100px,但因為flex容器的大小為1000px,所以還有600px的剩餘空間,而每個元素的flex-grow都為1,因此每個元素的延伸空間都為 1 / 4 x 600 = 150px,因此延伸後每個元素的寬度都為100+150=250px。
container{
width:1000px;
display:flex;
}
item{
flex-basis: 100px;
}
container{
width:1000px;
display:flex;
}
item{
flex-grow: 1;
flex-basis: 100px;
}
flex-shrink
:壓縮空間 = -(該元素flex-shrink*flex-basis/所有元素flex-shrink*flex-basis總和)*剩餘空間
原本每個元素的寬為200px,但因為flex容器的大小為1000px,所以剩餘空間為1000-8x200 = -600px,而每個元素flex-shrink預設值都為1,且flex-basis = 100px,因此每個元素的壓縮空間都為 (1x100/8x1x100)x600,因此壓縮後每個元素的大小都為200-75=125px。
container{
width:1000px;
display:flex;
}
item{
flex-basis: 200px;
}
container{
width:1000px;
display:flex;
}
item{
flex-shrink: 1;
flex-basis: 200px;
}
了解以上關於flex的特性之後,我們可以開始著手今天的課題。第一件事就是將元素們加上flex屬性,可以看到草稿中的DOM以及目前頁面如下:
<div class="panels">
<div class="panel panel1">
<p>Hey</p>
<p>Let's</p>
<p>Dance</p>
</div>
<div class="panel panel2">
<p>Give</p>
<p>Take</p>
<p>Receive</p>
</div>
<div class="panel panel3">
<p>Experience</p>
<p>It</p>
<p>Today</p>
</div>
<div class="panel panel4">
<p>Give</p>
<p>All</p>
<p>You can</p>
</div>
<div class="panel panel5">
<p>Life</p>
<p>In</p>
<p>Motion</p>
</div>
</div>
首先我們需要先將五個.panel
元素轉成與作者相同的排列方式,因此我們在其容器元素.panels
的CSS屬性中,加上display:flex;
並指定加上flex-direction:row
。但.panel
元素並沒有被給定寬度,因此其寬度目前是由內容物的大小所撐出來的,因此會看到以下這樣的情況。
.panels {
min-height:100vh;
overflow: hidden;
display:flex;
flex-direction: row;
}
可以看到黃色的區域,就是前面提到的剩餘空間,因此我們在.panel
元素中加入flex-grow = 1
的屬性,讓每個元素均勻分布並將容器填滿。
但我們的.panel
元素中的<p>
元素依然擠在畫面上方,因此我們也要將其容器.panel
元素,加上display:flex;
屬性,並指定方向flex-direction:column
。
除此之外,<p>
元素也須加上幾個flex的相關屬性:
flex-grow:1
display:flex
justify-content: center
align-items: center
//新增的CSS屬性
.panel {
flex-grow:1;
display: flex;
flex-direction: column;
}
.panel p {
flex-grow:1;
display:flex;
justify-content: center;
align-items: center;
}
完成新增這些的CSS屬性設定之後,就可以看到我們的頁面呈現完整的均勻分配。
但是在作者範例中,頁面剛開啟的時候,最上面跟最下面的<p>
元素,並沒有顯示在頁面上,而是點下滑鼠之後,才從上方與下方飛入。這邊我們就要用到第二天中所使用到的CSS transform
屬性,透過其子屬性translate()
,將這兩個<p>
元素移出畫面之外。
這邊透過.panel > *:first-child
以及.panel > *:last-child
的方法,取得.panel
中的第一及最後一個元素,並加上translateY()
屬性,來使這兩個元素被移出畫面之外
.panel > *:first-child { transform: translateY(-100%); }
.panel > *:last-child { transform: translateY(100%); }
當.panel
被按下的時候,其所佔的範圍會變大,最上層與最下層的<p>
元素會移入頁面中,且該.panel
元素中的字體都放大。
再次按下的時候,所佔的範圍會變小,最上層與最下層的<p>
元素會移回原本的位置。
如同JS30第一天學到的內容,透過按下按鍵,觸發函式來轉換選定元素的CSS屬性。首先我們需要先設定觸發函式後的元素,需要加上哪些屬性:
flex-grow
的分配比例,來達到變大的效果。<p>
元素都不在頁面中,因此需更改此屬性,將這兩個元素移回頁面中。.open
的class,而最上層與最下層的<p>
元素會被加上.open-active
的class。如此一來,我們就能完成透過class轉化,做出這次課題中的特效。.panel.open {
flex-grow:5;
font-size:40px;
}
.panel.open-active > *:first-child { transform: translateY(0); }
.panel.open-active > *:last-child { transform: translateY(0); }
首先我們將每個.panel
元素加上監聽事件,透過選折器將每一個元素選起來,並將選取器得到的陣列,利用前一章所學到的forEach()
方法,將每個元素都加上監聽事件click
,而這邊作者為了讓特效完全表現出來,所以將最上層與最下層的<p>
元素移動的時間移到完成字體放大縮小之後,因此這邊需額外監聽transitionend
這個事件:
const allPanel = document.querySelectorAll('.panel');
allPanel.forEach(function (panel) { panel.addEventListener('click', toggleOpen) });
allPanel.forEach(function (panel) { panel.addEventListener('transitionend', toggleOpenActive) });
click
後觸發class的轉換,因此這邊使用了classList.toggle()
,這個方法可以判斷元素當中是否有指定的class,如果沒有則新增上去,若有則移除該class:function toggleOpen(event) {
//event.target為觸發事件的目標元素
event.target.classList.toggle('open');
};
open
修改為open-active
,會發現我們的<p>
元素並不會進入畫面,這是為什麼呢?console.log(event)
,我們可以從開發者模式中,觀察到有兩個transitionend事件被觸發,因此第一個transitionend事件結束後會將class加上,但是第二個transitionend事件結束後又把class改回來,因此我們的<p>
元素便無法成功加上open-active
這個class。open-active
這個class的動作。function toggleOpenActive(event) {
console.log(event);
if (event.propertyName.includes('flex')) {
this.classList.toggle('open-active');
}
};
在今天的課題當中,我們學到以下的技能:
Flex是一個非常強大的屬性,不管是其安排子元素排列方式的屬性,或是剩餘空間的運用,都能讓網頁的響應式互動提升,讓使用者在瀏覽的同時,能有更生動的體驗。以上是JS30第五天的分享心得,歡迎大家交流討論,感謝您的閱讀。
HELLO
2. Flex property中的
1.flex-frow:
應該是grow?
另外 好文推推!
謝謝你幫忙抓出錯誤!!