iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 26
1

任務目標

滑鼠切換到導覽列的不同選項時,會顯示該選項的下拉式清單,此外,下拉式清單會以「移動」的方式切換。範例連結

作法

HTML 標籤如下。

<nav class="top">
  <!-- 下拉式選單的背景 -->
  <div class="dropdownBackground">
    <!-- 黏在背景上的箭頭 -->
    <span class="arrow"></span>
  </div>
  <!-- 導覽列的主體 -->
  <ul class="cool">
    <li>
      <a href="#">About Me</a>
      <div class="dropdown">
        <!-- 下拉選單內容 -->
      </div>
    </li>
    <li>
      <a href="#">Courses</a>
      <ul class="dropdown">
        <!-- 下拉選單內容 2 -->
      </ul>
    </li>
    <li>
      <a href="#">Other Links</a>
      <ul class="dropdown">
        <!-- 下拉選單內容 3 -->
      </ul>
    </li>
  </ul>
</nav>

下拉式清單會移動其實是我們利用 Day 22 的手法製造出來的錯覺。

我們額外製作出一個元素 dropdownBackground 假冒下拉式選單的背景,當滑鼠移動到導覽列的選項 <li> 時,讀取該選項的下拉式選單 dropdown 位置、內容的寬度等資訊,並利用這些資訊呈現假冒的下拉式選單背景,最後在假冒的選單背景上附加轉場效果,就會讓下拉選單在切換時有移動的效果了。

程式碼架構如下:

// 導覽列的清單的所有選項
const triggers = document.querySelectorAll('.cool > li');
// 假冒下拉背景的元素
const background = document.querySelector('.dropdownBackground');
// 整個導覽列元素
const nav = document.querySelector('.top');

function handleEnter() {
  // 處理移入效果
}

function handleLeave() {
  // 處理移出效果
}

// 滑鼠移入選項時處理移入效果
triggers.forEach(trigger => trigger.addEventListener('mouseenter', handleEnter));

// 滑鼠移出選項時處理移出效果
triggers.forEach(trigger => trigger.addEventListener('mouseleave', handleLeave));

重點在如何處理移入與移出效果。首先我們希望下拉選單 dropdown 的內容在滑鼠移入後可以出現。程式碼如下:

function handleEnter() {
  this.classList.add('trigger-enter');
  setTimeout(() => {
    console.log(this.classList);
    if(this.classList.contains('trigger-enter')) {
      this.classList.add('trigger-enter-active');
    }
  } ,150);
}

CSS設定如下:

.dropdown {
  /* 前略 */
  transition: all 0.5s;  
  opacity: 0;
  display: none;
}

.trigger-enter .dropdown {
  display: block;
}

.trigger-enter-active .dropdown {
  opacity: 1;
}

只要 dropdown 父層元素加上 trigger-entertrigger-enter-active 屬性,就能讓元素顯現。

但大家可能會發現,為了切換 opacitydisplay: block ,我們不只將其分開成兩個類別,還大費周章地讓 trigger-enter-activetrigger-enter 後才執行,而且還特意地等了 150 毫秒。為何要這麼做?

因為如果將其放在同個類別同時添加, opacity 的動作將不會顯現出來,原因是 當一個元素設定為 display: none ,瀏覽器根本不會將呈現出來。而 opacity 的切換對某個「確實存在」的元素才有效果。若將兩者放在同個類別同時切換,相當於對著 display: none 的元素進行 opacity 調整,是無效的。

了解這部分後,接下來只要運用 Day 22 的手法加入下拉式選單的背景就好。程式碼如下:

function handleEnter() {
  this.classList.add('trigger-enter');
  setTimeout(() => {
    if(this.classList.contains('trigger-enter')) {
      this.classList.add('trigger-enter-active');
    }
  } ,150);

  // 控制假冒背景顯示並跟著選單位置移動
  background.classList.add('open');
  const dropdown = this.querySelector('.dropdown');
  const dropdownCoords = dropdown.getBoundingClientRect();
  const navCoords = nav.getBoundingClientRect();

  // 計算相對於導覽列的座標位置
  const coords = {
    width: dropdownCoords.width,
    height: dropdownCoords.height,
    top: dropdownCoords.top - navCoords.top,
    left: dropdownCoords.left - navCoords.left,
  }

  // 將算出來的位置與寬高指定給下拉式選單背景
  background.style.setProperty('width', coords.width + 'px');
  background.style.setProperty('height', coords.height + 'px');
  background.style.setProperty('transform', `translate(${coords.left}px, ${coords.top}px)`);
}

利用 getBoundingClientRect() 取得該下拉式選單 dropdown 的座標位置跟寬高等資訊,然後與最外層元素 nav 的位置相減來得到該下拉式選單相對於整個導覽列的位置。用這些資訊拉設定假冒下拉式選單的位置與寬高即可。

以上就是 JS30 第二十六篇!

Reference

練習程式碼


上一篇
Day 25 - Event Flow - Bubble and Capture
下一篇
Day 27 - Click and Drag
系列文
JS30 錄30

尚未有邦友留言

立即登入留言