有時候我們會需要隱藏網頁上的元件。
舉幾個例子來說,例如我們為了使用者體驗的目的,我們希望在某某情境下,某些按鈕不要被使用者誤觸,或是為了畫面上排版的簡潔,我們希望並不是所有有關無關的元件都一股腦兒全部出現在畫面上。
以下圖為例,這是一個 MUI 的 Table 元件,當有任何一行資料被勾選時,Table 的頂部才會出現「垃圾桶」圖案的刪除按鈕,避免使用者在沒有勾選任何資料時按下刪除按鈕:
或者,在某些網站上,不同使用者有不同的權限,某些按鈕我們會希望擁有特定權限的人才能夠看到。例如下圖的 Admin Management
選單,我們希望只有管理者才能夠透過他進入後台頁面:
但無論如何,我們總會有許多不同的需求和理由想要隱藏或顯示某些網頁元件,我們今天要來討論實作的方法以及他會帶來的不同效果。
我們常用來隱藏網頁元件的 CSS 有 display, visiblity, opacity。下面是他們分別用來隱藏時的語法:
.element-a {
display: none;
}
.element-b {
visibility: hidden;
}
.element-c {
opacity: 0;
}
雖然好像都是隱藏元件,但是他們在各方面可能有一些細微的差異,如果用錯地方了,有可能會造成一些錯誤,甚至釀成災難。
當我們設置 display: none;
的時候,我們會發現該元件仍然會在 DOM 裡面,但是不會佔據頁面空間:
<div class="container">
<div class="group">
<div class="box">Top</div>
<div class="box">Bottom</div>
</div>
<div class="group">
<div class="box" style="display: none;">Top</div>
<div class="box">Bottom</div>
</div>
</div>
從上圖我們可以看到,我們分別有兩個 group,其中,右邊得這個 Top-Bottom group,我們將 Top 透過 display 設置為隱藏之後,雖然 DOM 元件還在,但是在畫面上,Top Box 不但不見了,而且畫面上不會為他保留空間,所以 Bottom Box 就順勢往上推了。
而如果我們使用 visibility: hidden;
或是 opacity: 0;
來隱藏元件,一樣 DOM 元件還在,但是我們可以看到 Top Box 原本佔據的空間被保留,因此 Bottom Box 不會往上推去佔據 Top Box 的空間:
<div class="container">
<div class="group">
<div class="box">Top</div>
<div class="box">Bottom</div>
</div>
<div class="group">
<div class="box" style="visibility: hidden;">Top</div>
<div class="box">Bottom</div>
</div>
<div class="group">
<div class="box" style="opacity: 0;">Top</div>
<div class="box">Bottom</div>
</div>
<div class="group">
<div class="box" style="display: none;">Top</div>
<div class="box">Bottom</div>
</div>
</div>
如果子元件沒有對應的設置的話,都會繼承父元件的樣式,也就是說會受到父元件設置的屬性所影響,換句話說,就是會跟父元件顯示的效果一樣。
因此我們要來看一下,如果子元件跟父元件設置「不同」的話,是否會有作用呢?
下面這是我們實驗的結構:
.container {
display: flex;
gap: 30px;
}
.center {
display: flex;
justify-content: center;
align-items: center;
}
.parent {
width: 200px;
height: 200px;
background: #1F7A8C;
}
.children {
width: 100px;
height: 100px;
background: #BFDBF7;
}
<div class="container">
<div class="center parent">
<div class="center children">Normal</div>
</div>
<div class="center parent" style="visibility: hidden;">
<div class="center children" style="visibility: visible;">visibility</div>
</div>
<div class="center parent" style="opacity: 0;">
<div class="center children" style="opacity: 1;">opacity</div>
</div>
<div class="center parent" style="display: none;">
<div class="center children" style="display: block;">display</div>
</div>
</div>
從左到右依序是:
visibility: hidden;
,子元件 visibility: visible;
opacity: 0;
,子元件 opacity: 1;
display: none;
,子元件 display: block;
我們可以發現,父子元件設置不同時,只有 visibility 在子元件的設置有起到作用,也就是父元件隱藏,但子元件顯示。其他兩個的子元件的設置,在父元件設置隱藏的前提之下,都沒有起到作用。
在上述的範例當中,我們在每一個被隱藏的元件上面綁定點擊事件,點擊之後跳出 alert
,來看看是否仍然能夠順利被觸發。
<div class="container">
<div class="center parent">
<div class="center children">Normal</div>
</div>
<div class="center parent" style="visibility: hidden;" onclick="alert('visibility')">
<div class="center children" style="visibility: visible;">visibility</div>
</div>
<div class="center parent" style="opacity: 0;" onclick="alert('opacity')">
<div class="center children" style="opacity: 1;">opacity</div>
</div>
<div class="center parent" style="display: none;" onclick="alert('display')">
<div class="center children" style="display: block;">display</div>
</div>
</div>
visibility
在 visibility 當中,因為父元件被設置為 visibility: hidden;
,因此,點擊父元件時,alert()
「不會」被觸發。
但很神奇的事是,因為我們子元件設置為 visibility: visible;
,所以點擊子元件時,會因為事件冒泡(從啟動事件的元件節點開始,逐層往上傳遞),而讓父元件的 alert()
事件被觸發。
opacity
在 opacity 當中,不管是點擊父元件還是子元件,alert()
事件都還是會被觸發,就好像是他一直在那邊,只是我們看不到而已。
display
設置 display 的元件,雖然元件在 DOM 當中仍然存在,但是因為在畫面上已經不佔據空間,所以我們連點擊他的機會都沒有,因此無法觸發 alert()
事件。
opacity
在 opacity 當中,因為他原意是設置元素的透明度,因此,透明度在 0 到 1 之間,能夠有過度的動畫也是很好理解的,整體的效果就是淡入淡出:
.block {
width: 200px;
height: 200px;
background: #1F7A8C;
opacity: 0;
transition: opacity 0.2s ease-in-out;
}
.block:hover {
opacity: 1;
}
<div class="container">
<div class="block"></div>
</div>
visibility
如果同樣的設置,只是改為 visibility
,我們會發現,初始狀態是 visibility: visible;
,經過 hover 之後要變成 visibility: hidden;
,他會經過一段延遲之後消失,並不會有淡入淡出的效果:
.block {
width: 200px;
height: 200px;
background: #1F7A8C;
visibility: visible;
transition: visibility 0.2s ease-in-out;
}
.block:hover {
visibility: hidden;
}
如果反過來,初始狀態是 visibility: hidden;
,經過 hover 之後要變成 visibility: visible;
,則是不起作用。
.block {
width: 200px;
height: 200px;
background: #1F7A8C;
visibility: hidden;
transition: visibility 0.2s ease-in-out;
}
.block:hover {
visibility: visible;
}
但是還有一個很神奇的地方,就是如果今天有 A, B 兩個 block,B 的初始狀態是 hidden
,A 是 visible
,如果要求 hover 在 A 上面,然後要讓 B 變成 visible
,卻是可以的喔!
.block {
width: 200px;
height: 200px;
}
.block__a {
background: #B5E2FA;
}
.block__b {
background: #0FA3B1;
visibility: hidden;
}
.block__a:hover + .block__b {
visibility: visible;
}
<div class="container">
<div class="block block__a">Block A</div>
<div class="block block__b">Block B</div>
</div>
display
display 他不僅不支持 transition 的效果,連帶跟他扯上關係的 transition 也會跟著失效!如下範例,我們在 Block B 上面同時設置 opacity: 0;
以及 display: none;
,連帶在 hover 的時候,就算把 display 設置為 block,其 opacity 的 transition 也會失效:
.block__a {
width: 200px;
height: 200px;
background: #B5E2FA;
}
.block__b {
width: 100px;
height: 100px;
background: #0FA3B1;
opacity: 0;
display: none;
transition: 1s
}
.block__a:hover .block__b {
opacity: 1;
display: block;
}
<div class='block__a'>
<div class='block__b'></div>
</div>
這是因為 display:none;
的元素,是不會渲染在頁面上的,而 transition 要起作用,元素必須是已經渲染在頁面上的元素。
雖然都是元件的隱藏,但是因為使用不同的屬性,會對頁面造成不同的效果。
如果我們要對元件做淡入淡出的 transition,那我們會比較建議選擇 opacity。如果我們元件消失之後,不會保留業面上的空間,能夠讓後面的元件遞補上來,做整齊的排列,那我們可能可以選擇 display: none;
。
那如果我們是要做權限控制,那我們應該選擇哪一個呢?假使我們要讓某個元件消失在 DOM 上面,那我們使用者三個屬性都不適合,因為我們想要隱藏的東西雖然在畫面上看不到,但是透過開發者模式來檢視原始碼的時候,還是會透過 DOM 來找到該元件,因此可能因為權限關係被隱藏的按鈕就會因此暴露了。所以此時,我們可以使用 JavaScript 來讓元件根本上從 DOM 消失,來確保權限的安全。
因此,因著不同的情境我們會需要不同的屬性和作法,希望瞭解每個元件的細節之後,我們在面對不同情境時,能夠做出正確的選擇,防止意外的發生。