在網頁內容很豐富的情境中,頁面上放置導覽列是很重要的功能,如下圖:
假設這是一個新聞或論壇網站,左邊 aside 部分就是我們的導覽列,上面有網站的 Brand logo,然後就是各主題的分類,包含 Home, News, Sport, Reel, Travel, Future 等各種主題。
那右邊 main 區塊,就是內容的主要部分,從圖中我們可以看到,因為內容很長,所以會需要有一個滾動條讓我們往下拉,才能夠看到下面的內容:
那當我們將頁面往下拉之後,會發現,左邊的 aside 導覽列其實就會看不到了。
假設一個情境,如果我們使用者在瀏覽網頁的時候,意猶未盡,很想要多看到更多不同類別的內容,但每次都需要把滾動條滑回最上面才能夠看到導覽列,這樣的操作會造成使用者在瀏覽的過程當中相當不便。特別是在使用者需要很頻繁的換頁找資料或瀏覽,並且每一頁的主要內容都很長,每次好不容易滑到最底部的時候,要換頁卻要再花好一番功夫才能滑到最上面看到導覽列。
為了解決這個困擾,我們就會想要讓左邊導覽列能夠「釘在畫面上」固定的位置,這樣不管滾動條滑到哪裡,導覽列也會跟著走,非常的方便。
我們來看看這樣的應用該怎麼實作。
首先來看一下主要的 html 結構:
<div class="wrapper">
<aside>...</aside>
<main>...</main>
</div>
那為了將導覽列 aside 跟主要區塊 main 做左右並排的排列,或許我們會考慮方便做網格佈局的 CSS Grid 來幫我們做到這件事,這麼想也是很合理的,以下是我們寫出來的 CSS:
.wrapper {
display: grid;
grid-template-columns: 250px minmax(10px, 1fr);
grid-gap: 20px;
}
從這個 CSS 可以看到,我們將外容器 wrapper 宣告為一個 grid 容器,然後對內元件的排列作規範,左邊的 aside 寬度為 250px
,右邊主要內容區塊則設置他的最小最大寬度 minmax(10px, 1fr)
,這樣能夠符合響應式的設計,最後給他一個 20px
的間隔。
接下來,我們要來實作釘選導覽列,我們可以利用 CSS 的 position
屬性。position 支援很多的功能,其中 sticky
可以幫助我們讓元件釘在最近的 scrolling ancestor
上,也就是最接近的可滾動元件。在 MDN 上的說明如下:
Sticky positioning is a hybrid of relative and fixed positioning. The element is treated as relative positioned until it crosses a specified threshold, at which point it is treated as fixed positioned.
既然如此,我們就可以把這屬性寫在導覽列的元件上:
aside {
position: sticky;
top: 0;
}
但是,神奇的事情發生了,我們此時會發現,導覽列並沒有我們預期的能夠釘在畫面上!!!
你一開始想必會懷疑是不是自己 CSS 抄錯了所以不會動,或是 html 結構不對。但是最後,如果順利的話,你會發現下面這個狀況,為了方便觀察,我們把導覽列 aside 也加上背景顏色:
我們發現,導覽列的高度居然跟主要內容區塊的高度是一樣的!這個應該就是造成他無法釘住的主要原因。
因為當我們對一個外容器宣告為 display: grid;
時,外容器當中的 align-items
屬性,預設值為 stretch
,意思是 將內容元素撐開至 grid 容器大小
。
我們換個情境來看,假設今天我們用有一組網格排列的卡片,你就不會覺得那麼奇怪了:
<div class="cards">
<div class="card">...</div>
<div class="card">...</div>
<div class="card">...</div>
</div>
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-gap: 20px;
}
一樣使用的是 CSS Grid,一樣外容器中 align-items
的預設值是 stretch
,但是在卡片的情境當中,我們就會覺得非常的整齊美觀。
既然如此,我們就來調整這個預設值就可以了。
調整的方法有兩種,一個是透過外容器來解決,我們將外容器的 align-items
設為 start
:
.wrapper {
display: grid;
grid-template-columns: 250px minmax(10px, 1fr);
grid-gap: 20px;
align-items: start; /* 覆蓋掉原本的預設值 stretch */
}
另一個方式,我們也可以針對內元件單獨做設置:
aside {
position: sticky;
top: 0;
align-self: start; /* 針對內元件單獨個別做設置 */
}
我們就能夠達到我們預期的「釘選導覽列」效果了:
這裡我們介紹了 CSS Grid 外容器當中,因為忽略預設值 align-items: stretch;
所造成的釘選導覽列失效問題。
這個預設值的效果,在卡片情境當中,我們從來不會覺得他奇怪,但是當他被放在釘選導覽列情境的時候,可能我們就會想不通為什麼會有這個奇怪的預設值,而容易忽略這個預設值而造成問題。
所以,適時的記憶一些容易被忽略的預設值,有助於幫助我們避免這些問題。