大家好,這篇文來記錄一下幾天前在 Medium 看到的一個 CSS 展開收合的更佳做法。
以往在實作時,我自己都會很直覺地使用 CSS 的 height
屬性去作出展開、收合的效果,例如以下例子:
HTML
<!-- another.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
/>
<link rel="stylesheet" href="./another.css" />
</head>
<body>
<div class="portlet">
<input type="checkbox" />
<div class="portlet-header">
<span>Portlet Header</span>
<a href="javascript:;"
><i class="fa fa-chevron-down" aria-hidden="true"></i
></a>
</div>
<div class="portlet-content">
<article>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo
repudiandae animi minus atque suscipit placeat voluptas nisi, illum
nesciunt quas expedita accusamus hic unde exercitationem nulla
neque. Nulla, tempora repellendus. Vero dignissimos consequuntur
quis, tenetur sit illum labore maiores quod, aliquam nam debitis
quaerat et earum temporibus architecto odio nemo odit id. Magnam ex
doloremque vero nam error voluptate eum. Blanditiis ipsa nobis
laborum voluptatem mollitia unde facilis repellendus rerum numquam,
autem, quasi minus amet. Reprehenderit maxime sint culpa aut amet
porro, veritatis aspernatur officia ad adipisci maiores reiciendis
quasi? Facilis tempora officiis eaque praesentium quasi, laboriosam
fugiat est dolor eveniet id soluta cupiditate vel, consequatur
repellat deleniti facere placeat, ipsam distinctio perspiciatis
aspernatur! Ipsam fuga minus soluta labore quas! Totam explicabo,
illo eum aspernatur omnis dicta dolore. Ipsa, optio mollitia eum
iure voluptates vitae deleniti cum nemo animi quaerat ipsum. Atque
nemo laboriosam reprehenderit. Voluptate perferendis repellat
eveniet facere? Asperiores porro fugiat praesentium nobis voluptate
quaerat nihil numquam similique labore, excepturi ipsa minus
repudiandae, provident aliquam? Nisi consequuntur exercitationem
aliquam? Sint libero dignissimos laudantium modi praesentium
exercitationem eveniet illo. Ullam reprehenderit quod est quis?
Expedita in, impedit perspiciatis doloribus quasi, eaque ad
asperiores maxime facilis voluptate rem ipsum odit reiciendis
molestiae itaque adipisci, harum facere at quo explicabo dolore!
Quas corporis iusto accusamus voluptatibus quidem eligendi,
dignissimos officia sed modi eum dolorem, aut, voluptas incidunt
mollitia quod quia delectus sit ab laboriosam saepe accusantium
voluptatem. Esse incidunt vero quos! Perferendis fugit nulla tempora
doloribus quos numquam, neque nisi, est delectus soluta recusandae
blanditiis iusto voluptatum fuga. Animi magnam aliquam inventore
ipsa esse cupiditate quo consequuntur! Magnam blanditiis sed
corporis! Id possimus, sunt suscipit ipsam assumenda libero error
dolorem ab recusandae! Ipsam vero ipsa doloribus molestiae voluptate
pariatur repellat esse, ad accusantium, odit, necessitatibus at
omnis consequuntur? Provident, fugit? Nulla.
</p>
</article>
</div>
</div>
</body>
</html>
CSS
* {
font-family: Arial, Helvetica, sans-serif;
box-sizing: border-box;
}
article {
overflow: hidden;
}
article p {
margin: 10px;
}
a {
color: unset;
transition: 400ms transform ease;
}
input[type="checkbox"] {
position: absolute;
margin: 0;
width: 16px;
height: 16px;
right: 10px;
top: 10px;
z-index: 1;
opacity: 0;
cursor: pointer;
}
input[type="checkbox"]:checked ~ .portlet-header a {
transform: rotate(180deg);
}
input[type="checkbox"]:checked ~ .portlet-content {
height: 560px;
}
.portlet {
position: relative;
border: 1px solid #0abab5;
border-radius: 8px;
max-width: 600px;
overflow: hidden;
}
.portlet-header {
background-color: #0abab5;
color: #fff;
padding: 10px;
display: flex;
justify-content: space-between;
}
.portlet-content {
height: 0;
transition: 400ms height ease;
}
這邊實作出來的效果會和最上方的 gif 動畫範例相同,原理也很簡單,運用 <article>
元素的 overflow 效果搭配父層 height
為 0 的情況下,子層 <p>
的內容就會因為溢出而被隱藏;在展開(這邊使用 checkbox
的 checked
屬性搭配 Subsequent-sibling combinator 去作出類似 toggle 的效果)時將高度寫死成元素在沒有溢出時的高度 560px
,搭配 transition 就能做出很柔順的展開收合效果。
以上在元素高度固定時就能完美達成需求。然而,當內容撐起的高度無法預測時,以上做法就會存在問題。以往我會使用 max-height
去處理高度不一定的問題:
/* 其他內容 ... */
input[type="checkbox"]:checked ~ .portlet-content {
max-height: 999vh;
transition: 400ms max-height ease-in;
}
/* 其他內容 ... */
.portlet-content {
max-height: 0;
transition: 400ms max-height ease-out;
}
/* 其他內容 ... */
這樣做雖然可以達到適應內容,一方面 999vh
也無法預防內容超過 1000vh
的狀況;另一方面則是展開收合的動畫其實是很不滑順的。
運用 grid-template-rows 解決上述問題
grid-template-rows
是 CSS grid 排版中可使用的 CSS 屬性,用來定義 grid 排版中水平軌道的寬度。我們將其定義為 1fr
,表示占用最大的可用空間,而由於容器的大小是由內容決定的,1fr
就會讓其把寬度撐到最開。我們將其設定為:
/* 其他內容 ... */
input[type="checkbox"]:checked ~ .portlet-content {
grid-template-rows: 1fr;
}
/* 其他內容 ... */
.portlet-content {
display: grid;
grid-template-rows: 0fr;
transition: 400ms grid-template-rows ease;
}
/* 其他內容 ... */
便能達成所要的展開收合效果,且無論內容多寡,效果都相當好。
以上就是 CSS 展開收合動畫我記錄到的更好做法。若內文有任何錯誤也歡迎提出!
References: