Try   HackMD

Day5:Flex Panel Gallery

竹白記事本,Javascript 30,紀錄。

tags: Javascript 30

實現效果

點擊任意一張圖片,圖片展開,同時從圖片上下兩方分別移入文字。點擊已經展開的圖片後,圖片被壓縮,同時該圖片上下兩端的文字被擠走。

重點

  1. 元件使用 flex 排版
  2. 定義兩段狀態
  3. 點擊時加上第一個狀態,當第一段狀態跑完後,接著第二個狀態

基礎語法

CSS

  • flex
  • transform

Event

說明

1. CSS 的部分

Day5 的範例 CSS 的比重比較多。基本上就是透過 Flex 排版完成頁面。透過 flex: ; 屬性控制元件的比例。

關於 CSS 的 Flex,之前有整理過一篇 Flex 教學整理,可以參考一下。

實作

1. 步驟

Step 1 改寫 HTML 與 CSS

<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 水平置中排列,就需要在容器加上:

.panel__list {
  display: flex;
  justify-content: center;
}

而要使元件等比寬,就需要在元件上加上 flex-grow 來控制擴展比例:

.panel__item {
  flex: 1;
}

flex 為簡寫屬性,當只有一個參數等同 flex-grow

接下來排序 <p> 元素,我要們使它水平垂直置中,並且垂直排列,就要在它的容器 .panel__item 加上:

.panel__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

接下來處理<p> 元素的大小與文字的排版:

.panel__item > p {
  display: flex;
  align-items: center;
  flex: 1;
}

Step 2 狀態

原始狀態:

.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%);
}

加上狀態:

.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

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__itemtransition0.7s 因此裡面內容的 <p> 可以加上 transition-delay: 0.7s 讓它跑完動畫後再執行。

.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%);
}

這樣就不用監聽動畫結束,直接延遲它的動畫觸發時間。另外在加上一點小功能,就是只允許一個畫面是展開狀態。

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. 實作連結