iT邦幫忙

2024 iThome 鐵人賽

DAY 26
0

Stripe Follow Along Dropdown 伸縮自如的下拉選單

這次的練習是網頁中很常見到的頂部導覽列,當你滑鼠移動進入其中一個導覽項目時,會跑出下拉的選單,再次移動到下一個導覽項目,這個選單會跟著移動,並且寬度會跟隨內容大小改變。

JS操作上不困難,比較困難的應該是html和css的結構規劃。因為我一直被自己的爛css雷到哈哈

https://ithelp.ithome.com.tw/upload/images/20241005/20169174WM03wSIaHw.png

個人codepen

技巧點

先來看一下html結構的部分


<div class="dropdown-background">
  <span class="arrow"></span>
</div>
<nav class="top-nav">
  <ul class="list">
    <li>
      <a href="">about me</a>
      <div class="dropdown dropdown1">
        <div class="intro">
          <img src="https://picsum.photos/200/200" alt="">
          <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Aperiam deserunt atque dignissimos illum quia minus doloribus ab, harum magni fugit quam perferendis nulla, aspernatur neque suscipit omnis ratione corporis doloremque.</p>
        </div>
      </div>
    </li>
    <li>
      <a href="">course</a>
      <ul class="dropdown dropdown2">
        <li>Vue Tutorial</li>
        <li>React Tutorail</li>
        <li>Angular Tutorial</li>
        <li>Basic ES6</li>
      </ul>
    </li>
    <li>
      <a href="">other links</a>
      <ul class="dropdown dropdown3">
        <li>twitter</li>
        <li>facebook</li>
        <li>instagram</li>
        <li>line</li>
      </ul>
    </li>
  </ul>
</nav>

底下都會用元素的class名稱說明。

上面中的dropdown-background,就是會跟著移動的背景,他的擺放位置很重要!!!,目前是擺放在最外層的位置,或者將他移動進入到.top-nav的內層,這時會定位在top-nav身上。因為這個dropdown-background會做絕對定位,定位在內層或外層,會影響到你移動的問題。

dropdown-background的寬度高度,會動態改變。所以每次當你滑鼠移動到li元素時,就取得dropdown的getBoundingClientRect(),再將dropdown的寬高,和相對於視窗口的left、top距離賦予給dropdown-background。

因為是相對視窗口的位置,假設dropdown-background是定位在top-nav身上,此時外層多了一個h1元素,就會發現dropdown-background的位置沒辦法對準在dropdown身上。都是因為上面多了一個h1元素所佔據的高度。

https://ithelp.ithome.com.tw/upload/images/20241005/20169174VqksmAJUUj.png

要解決上面的問題,你要先知道dropdown-background是定位在top-nav身上,但你取得的dropdown的getBoundingClientRect(),是相對於視窗口。所以定位在top-nav時,就多了上面的h1高度。這時候,就要計算top-nav相對於視窗最上緣的高度為多少,再將要移動的top距離,減掉top-nav的offsetTop。

可以參考程式碼如下:

const triggers = document.querySelectorAll(".list > li");
const dropdownBackGround = document.querySelector(".dropdown-background");
const nav = document.querySelector(".top-nav");

function openDropdown() {
  this.querySelector(".dropdown").classList.add("dropdown-show");
  setTimeout(() => {
    this.querySelector(".dropdown").classList.contains("dropdown-show") &&
      this.querySelector(".dropdown").classList.add("dropdown-show-active");
  }, 100);

  const { width, height, top, left } = this.querySelector(
    ".dropdown"
  ).getBoundingClientRect();
  dropdownBackGround.style.width = `${width}px`;
  dropdownBackGround.style.height = `${height}px`;
  dropdownBackGround.style.transform = `translate(${left - nav.offsetLeft}px, ${
    top - nav.offsetTop
  }px)`;
  dropdownBackGround.style.opacity = 1;
}

function closeDropdown() {
  this.querySelector(".dropdown").classList.remove(
    "dropdown-show",
    "dropdown-show-active"
  );
  dropdownBackGround.style.opacity = 0;
}

// 每一個.list底下的li元素都綁定滑鼠移動事件
triggers.forEach((item) => item.addEventListener("mouseenter", openDropdown));
triggers.forEach((item) => item.addEventListener("mouseleave", closeDropdown));

所以dropdown-background元素的擺放位置很重要,如果拉去最外層定位在body身上時,你就不用管top-nav相對於視窗口的top、left距離是不是0。

其實不管放哪裡,只要清楚他目前定位在哪一個元素身上,並且理解getBoundingClientRect的概念,應該隨便你放都不是問題。

另外這邊的位移使用transform,而不是用position:absolute top left的方式。你要用也是可以。只是css的這兩個元素差別在於畫面需不需要重新繪製,對效能上可能有些許的影響。

不論用transform或position:absolute top left的方式,你的元素定位才是影響到你JS計算的問題。

function openDropdown() {

  // 利用定時器,當容器已經變成block的時候,再去加上opacity的變化,並且防止背景還沒出現時,dropdown的內容已經出現
  this.querySelector(".dropdown").classList.add("dropdown-show");
  setTimeout(() => {
    this.querySelector(".dropdown").classList.contains("dropdown-show") &&
      this.querySelector(".dropdown").classList.add("dropdown-show-active");
  }, 100);
    
    // 解構賦值
  const { width, height, top, left } = this.querySelector(
    ".dropdown"
  ).getBoundingClientRect();
  
  // 動態設定寬高和位置
  dropdownBackGround.style.width = `${width}px`;
  dropdownBackGround.style.height = `${height}px`;
  // dropdownBackGround.style.transform = `translate(${left - nav.offsetLeft}px, ${
  //   top - nav.offsetTop
  // }px)`;
  dropdownBackGround.style.transform = `translate(${left}px, ${top}px)`;
  // dropdownBackGround.style.left = `${left - nav.offsetLeft}px`;
  // dropdownBackGround.style.top = `${top - nav.offsetTop}px`;
  // dropdownBackGround.style.left = `${left}px`;
  // dropdownBackGround.style.top = `${top}px`;
  dropdownBackGround.style.opacity = 1;
}

心得

體會到html和css寫得好,再操作JS會比較輕鬆。不然在做dom元素移動時,根本沒有照我想的方式在移動...QQ


上一篇
Event Capture, Propagation, Bubbling and Once
下一篇
Click and Drag to Scroll
系列文
鱷魚帶我練習JavaScript之個人練功坊29
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言