# Day5:Flex Panel Gallery [竹白記事本](https://chupainotebook.blogspot.com/),Javascript 30,紀錄。 ###### tags: `Javascript 30` ## 實現效果 點擊任意一張圖片,圖片展開,同時從圖片上下兩方分別移入文字。點擊已經展開的圖片後,圖片被壓縮,同時該圖片上下兩端的文字被擠走。 - [原始碼](https://github.com/chupai/JS30/tree/master/source_code/Day05) - [原始狀態](https://chupai.github.io/JS30/source_code/Day05/index-START.html) - [範例效果](https://chupai.github.io/JS30/source_code/Day05/index-FINISHED.html) ## 重點 1. 元件使用 `flex` 排版 2. 定義兩段狀態 3. 點擊時加上第一個狀態,當第一段狀態跑完後,接著第二個狀態 ## 基礎語法 ### CSS - `flex` - `transform` ### Event - [`addEventListener()`](https://developer.mozilla.org/zh-TW/docs/Web/API/EventTarget/addEventListener)事件監聽 - [`transitionend`](https://developer.mozilla.org/zh-CN/docs/Web/Events/transitionend) 事件 - [`TransitionEvent`](https://developer.mozilla.org/zh-CN/docs/Web/API/TransitionEvent) 介面 - [`propertyName`](https://developer.mozilla.org/zh-CN/docs/Web/API/TransitionEventt#属性) 屬性 ## 說明 ### 1. CSS 的部分 Day5 的範例 CSS 的比重比較多。基本上就是透過 `Flex` 排版完成頁面。透過 `flex: ;` 屬性控制元件的比例。 關於 CSS 的 Flex,之前有整理過一篇 **[Flex 教學整理](https://hackmd.io/@chupai/HyoyvZCqX)**,可以參考一下。 ## 實作 ### 1. 步驟 #### Step 1 改寫 HTML 與 CSS ```javascript <div class="panel"> <ul class="panel__list js-panel__list"> <li class="panel__item js-panel__item" style="background-image: url('img/bg1.jpg');" > <p>Hey</p> <p>Let's</p> <p>Dance</p> </li> <li class="panel__item js-panel__item" style="background-image: url('img/bg2.jpg');" > <p>Give</p> <p>Take</p> <p>Receive</p> </li> <li class="panel__item js-panel__item" style="background-image: url('img/bg3.jpg');" > <p>Experience</p> <p>It</p> <p>Today</p> </li> <li class="panel__item js-panel__item" style="background-image: url('img/bg4.jpg');" > <p>Give</p> <p>All</p> <p>You can</p> </li> <li class="panel__item js-panel__item" style="background-image: url('img/bg5.jpg');" > <p>Life</p> <p>In</p> <p>Motion</p> </li> </ul> </div> ``` 這裡改寫一下 HTML 與 CSS, 方便說明。 - `.panel__list` 為 `.panel__item` 元件的容器 - `.panel__item` 為 `<p>` 的容器 - `<p>` 為文字的容器 因此要將 `.panel__item` 水平置中排列,就需要在容器加上: ```css .panel__list { display: flex; justify-content: center; } ``` 而要使元件等比寬,就需要在元件上加上 `flex-grow` 來控制擴展比例: ```css .panel__item { flex: 1; } ``` `flex` 為簡寫屬性,當只有一個參數等同 `flex-grow`。 接下來排序 `<p>` 元素,我要們使它水平垂直置中,並且垂直排列,就要在它的容器 `.panel__item` 加上: ```css .panel__item { display: flex; flex-direction: column; align-items: center; justify-content: center; } ``` 接下來處理`<p>` 元素的大小與文字的排版: ```css .panel__item > p { display: flex; align-items: center; flex: 1; } ``` #### Step 2 狀態 原始狀態: ```css .panel__item { flex: 1; } .panel__item > p:nth-child(1) { transform: translateY(-100%); } .panel__item > p:nth-child(2) { font-size: 3em; } .panel__item > p:nth-child(3) { transform: translateY(100%); } ``` 加上狀態: ```css .panel__item.is-active { flex: 5; font-size: 2em; } .panel__item.is-open > p:nth-child(1) { transform: translateY(-100%); } .panel__item.is-open > p:nth-child(2) { font-size: 3em; } .panel__item.is-open > p:nth-child(3) { transform: translateY(100%); } ``` 這裡分成了兩段動畫,當 `.is-active` 狀態的動畫完成後,會接著執行 `.is-open`。當然這樣子做有點麻煩,之後會試著將其改寫再一起。 #### Step 3 END ```javascript const panelList = document.querySelector('.js-panel__list'); panelList.addEventListener('click', toggleOpen); panelList.addEventListener('transitionend', toggleActive); function toggleOpen(e) { if(!e.target.classList.contains('js-panel__item')){return;} e.target.classList.toggle('is-open'); } function toggleActive(e) { if(!e.target.classList.contains('js-panel__item')){return;} if (e.propertyName.includes('flex')) { e.target.classList.toggle('is-active'); } } ``` 這邊就直接使用事件委派來監聽底下元件,並使用 Day01 有用到的方式來監聽動畫完成後的要執行動作。 ### 2. 進階 #### 結合兩段狀態 使用 `transition-delay` 延遲第二段動畫執行。 `.panel__item` 的 `transition` 是 `0.7s` 因此裡面內容的 `<p>` 可以加上 `transition-delay: 0.7s` 讓它跑完動畫後再執行。 ```css .panel__item > p { transition-delay: 0.7s; } .panel__item.is-active { flex: 5; font-size: 2em; } .panel__item.is-active > p:nth-child(1) { transform: translateY(-100%); } .panel__item.is-active > p:nth-child(3) { transform: translateY(100%); } ``` 這樣就不用監聽動畫結束,直接延遲它的動畫觸發時間。另外在加上一點小功能,就是只允許一個畫面是展開狀態。 ```javascript const panelList = document.querySelector('.js-panel__list'); panelList.addEventListener('click', toggleActive); function resetActive() { const items = document.querySelectorAll('.js-panel__item'); items.forEach(function(key) { key.classList.remove('is-active'); }); } function toggleActive(e) { if (!e.target.classList.contains('js-panel__item')) { return; } resetActive(); e.target.classList.toggle('is-active'); } ``` 這邊一樣使用事件委派,並加上點選一個就重置其他 `.panel__item` 狀態的函式。 ### 3. 實作連結 - [兩段狀態](https://chupai.github.io/JS30/source_code/Day05/index1.html) - [實作連結](https://chupai.github.io/JS30/source_code/Day05/index.html)