如何用Bootstrap做左導覽列

Bootstrap上有個navbar的功能,裡面提供了一個External content的功能讓我們可以將內容先隱藏起來,等到使用者點擊按鈕觸發後才會以動畫的方式拉開內容,這樣的動畫呈現是使用了Bootstrap它自己的Collapse元件,但是Collapse只提供上下的展開,並沒有左右伸縮的功能,這讓我很苦惱,因為需求是要做從左邊展開的導覽列,自己刻一個是沒有問題,但既然Bootstrap都有提供Collapse以及跟按鈕的整合了,不用一下說不過去,這篇就來看看要怎麼使用Bootstrap做出一個可以有轉換動畫做隱藏及顯示的左清單列吧。

2018-02-08: add new content: 使用transform的轉場動畫

先來做個一般的隱藏內容。

新增一個隱藏內容的元素:

1
2
3
4
5
6
<div class="collapse" id="navbarToggleExternalContent">
<div class="bg-dark p-4">
<h4 class="text-white">Collapsed content</h4>
<span class="text-muted">Toggleable via the navbar brand.</span>
</div>
</div>
  • class要加上collaspe

新增一個觸發按鈕:

1
2
3
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarToggleExternalContent">
<span class="navbar-toggler-icon"></span>
</button>
  • data-toggle要設為collapse
  • data-target設定選擇器,指向你要隱藏的元素(這裡是#navbarToggleExternalContent)。

這樣我們就完成了一個簡單的隱藏/顯示內容的功能了,下面是完整的程式碼:

See the Pen bootstrap 4 simple collapse with navbar by Peter Chen (@peterhpchen) on CodePen.

Collapse

要動手前我們要先知道關於Collapse這個元件的用法,它是一個可以讓對象在動作時有慢慢展開的效果,主要是由下面三個class來控制:

  • collapse: 初始化時設定的class,會將元素隱藏
  • collapsing: 觸發展開/縮回時開始到完成前會有的class,這上面設定了轉換時的動畫
  • collapse.show: 展開後的元素會多一個show的class,將元素顯示出來。

小小的原碼探險

我們由文件知道了上面三個類別會做的動作,從我們的需求上看,collapsing是我們應該深入的地方,因為這裡是在處理動畫呈現的部分,現在來看Bootstrap是怎麼實作出來的,我們可以看到_transitions.scss這個檔案,把焦點聚在Collapsing這邊:

1
2
3
4
5
6
7
8
9
10
/* _transitions.scss */
.collapsing {
position: relative;
height: 0;
overflow: hidden;
@include transition($transition-collapse);
}

/* _variables.scss */
$transition-collapse: height .35s ease !default;

我們可以注意到transition-property是設定height,所以Collapse是不會有橫向的動畫的,所以之後再覆寫時應該要改動這個設置。

上面collapsing的樣式會看到它將height設為0,而transition會在height改變的時候動作,所以一定有個地方改變了元素的height,接著找到了collapse.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...

const dimension = this._getDimension()

...

const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)
const scrollSize = `scroll${capitalizedDimension}`

...

this._element.style[dimension] = `${this._element[scrollSize]}px` // 用元素的scrollWidth(scrollHeight)來決定展開的大小

...

_getDimension() {
const hasWidth = $(this._element).hasClass(Dimension.WIDTH) // 判斷有沒有width class來決定要抓高度還是寬度
return hasWidth ? Dimension.WIDTH : Dimension.HEIGHT
}

...

這邊有兩個重點:

  • _getDimension()會決定要抓的是Width還是Height,如果有設定Width類別的話就會抓Width
  • 展開的大小是由scrollwidth(scrollHeight)決定。

這樣就萬事具備了,接著我們來試著開發吧。

將隱藏內容由左邊展開

接著進入本篇的重點,我們想要把隱藏的內容由左邊展開。

加上width類別讓_getDimension()取到的是Width:

1
2
3
<div class="collapse width" id="navbarToggleExternalContent">
...
</div>

覆寫.collapsing:

1
2
3
4
5
.collapsing {
width: 0;
transition-property: width;
height: 100%;
}

覆寫的原因說明在下面:

  • width設為0: 起始寬度設置。
  • transition-property設為width: 轉換的目標變為width
  • height設為100%: 因高度不再是轉換的目標,所以原本的height: 0要改為原來的高度,要不然會因為長寬都是0造成在轉換期間會看不到元素的錯誤。

下面是依照上面設置後的演示:

See the Pen bootstrap 4 simple collapse with navbar(have scrollWidth Problem) by Peter Chen (@peterhpchen) on CodePen.

上面這樣成功讓隱藏內容由右邊展開了,但有兩個問題:

  • 展開後的元素會擠掉其他元素
  • 在展開過程中內文的文字會依照目前的寬度折行

第一個問題可以用position:fixed的設置來解決,我們用Bootstrap的position-fixed類別來實作。

1
2
3
<div class="collapse width position-fixed" id="navbarToggleExternalContent">
...
</div>

第二個問題可以white-space設為nowrap來實現(參考自StackOverflow)。

解決這兩個問題後,現在會像是下面這樣。

See the Pen bootstrap 4 left navbar(have scrollWidth Problem) by Peter Chen (@peterhpchen) on CodePen.

如果觸發的話我們又會看到另一個問題,在轉換的最後會突然的顯示一節,有不平滑的感覺。

經過觀察發現scrollwidth的長度並不是真正的長度,它並不包含right padding,從這篇StackOverflow我們可以知道將padding改為用margin實作就好,因此我們將內容改為下面這樣:

1
2
3
4
<div class="bg-dark py-4">
<h4 class="text-white mx-4">Collapsed content</h4>
<span class="text-muted mx-4">Toggleable via the navbar brand.</span>
</div>

程式如下:

See the Pen bootstrap v4 left menu bar(not height 100%) by Peter Chen (@peterhpchen) on CodePen.

使用transform的轉場動畫

上面的例子所使用的轉場對象是width,看到的是清單內容慢慢隱沒的效果,還有另一種是直接把內容向左推,推動的是整個區塊,我們來看兩者的效果差別:

transform-vs-width-transition

上面的比較圖我們可以看到左邊的是我們目前使用width做轉換的版本,可以看到它會慢慢的把內容文字從又變捲掉,而右邊會是整個區塊往左推,所以可以看到是從左邊的內容開始消失。

兩種不同的版本會因設計需求不同而被採用,現在來示範用translate的方式實作。

首先因為bootstrap的collapse是對長度(寬度或高度)做轉換,而translate的效果是不需要動到長度的,因此我們先把原本設定寬度dimensionwidth類別拿掉:

1
<div class="bg-dark collapse position-fixed" id="navbarToggleExternalContent">...</div>

拿掉width後,bootstrap變回抓預設的dimension: Height了,所以我們現在要固定高度:

1
2
3
4
5
#navbarToggleExternalContent{
display: block;
height: 100%;
min-height: 100%;
}

在固定高度後,就要來修改動畫的部分,前面有說因為collapse是以長度為動畫依據,所以全部的預設動畫都沒有用處了,我們需要自己加上動畫。

為此我們需要定義一個class,在show的時候加到元素上,藉這個class來設定開啟時候的狀態,為了達到這個目的我們用bootstrap提供的js事件來實作:

1
2
3
4
5
6
7
8
9
const $menu = $('#navbarToggleExternalContent');

$menu.on('show.bs.collapse', function () {
$menu.addClass('menu-show');
});

$menu.on('hide.bs.collapse', function () {
$menu.removeClass('menu-show');
});

使用menu-show class,在show的時候加上,在hidden的時候清掉,現在我們在show的時候元素上會有menu-show這個類別了。

最後我們來加上動畫吧,在初始的狀態下我們是要隱藏menu的,所以我們用translateX(-100%)來隱藏清單:

1
2
3
4
5
#navbarToggleExternalContent{
transform: translateX(-100%);
transition: transform .35s ease;
...
}

接著在顯示的狀態下,清單的起始位置應該要在0px(靠左)的地方:

1
2
3
#navbarToggleExternalContent.menu-show{
transform: translateX(0%);
}

如此一來,我們就可以得到一個translate的左導覽列了:

See the Pen bootstrap v4 left menu transform bar by Peter Chen (@peterhpchen) on CodePen.

結語

Bootstrap是個很易用的元件庫,我們可以用它來節省不少的時間及精力,像這次的覆寫也是因為Bootstrap本身就已經有開好入口讓我們可以輕鬆地修改,會卡住的應該就是對Transition還不太了解的人(就是我),理解CSS的Transition後就很簡單了。

備註

  • 本文使用的是Bootstrap 4

參考