iT邦幫忙

2022 iThome 鐵人賽

DAY 26
0
自我挑戰組

JavaScript 30天挑戰 自學筆記系列 第 26

JS30 自學筆記 Day26_Stripe Follow Along Dropdown

  • 分享至 

  • xImage
  •  

今日任務:碰到選單會浮出子選單,子選單背景大小由內容決定

HTML部分

今天我們來學放在nav裡面,怎麼處理子元素的位置

<nav class="top">
    <div class="dropdownBackground">
        <span class="arrow"></span>
    </div>
    <ul class="cool">
        <li>
            <a href="#">About Me</a>
            <div class="dropdown dropdownBio">
                ...
            </div>
        </li>
        <li>
            <a href="#">Courses</a>
            <ul class="dropdown courses">
                ...
            </ul>
        </li>
        <li>
            <a href="#">Other Links</a>
            <ul class="dropdown dropdownLinks">
                ...
            </ul>
        </li>
    </ul>
</nav>

CSS部分

display:none,不會有動畫
opcticy:0~1,才會有動畫
所以我們可以分開兩階段來寫,就可以有動畫效果

.dropdown {
    opacity: 0;
    position: absolute;
    overflow: hidden;
    padding: 20px;
    top: -20px;
    border-radius: 2px;
    transition: all 0.5s;
    transform: translateY(100px);
    will-change: opacity;
    display: none;
}

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

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

背景部分

.dropdownBackground {
    ...
    transition: all 0.3s, opacity 0.1s, transform 0.2s;
    transform-origin: 50% 0;
    display: flex;
    justify-content: center;
    opacity: 0;
}

.dropdownBackground.open {
    opacity: 1;
}

JS部分

獲取元素+監聽滑鼠移入移出事件

Day22:滑鼠事件

  • mouseenter event: 滑鼠進入元素邊界時觸發,事件不會 bubble。
  • mouseleave event: 滑鼠完全離開元素時觸發,事件不會 bubble。
const triggers = document.querySelectorAll('.cool > li');
const background = document.querySelector('.dropdownBackground');
const nav = document.querySelector('.top');

function arrowEnter() {
    console.log('Enter');
}
function arrowLeave() {
    console.log('Leave');
}

triggers.forEach((trigger) => {
    trigger.addEventListener('mouseenter', arrowEnter);
    trigger.addEventListener('mouseleave', arrowLeave);
});

當移入/移出的時候

1.將div顯示

function arrowEnter() {
    console.log(this);
    this.classList.add('trigger-enter');
    this.classList.add('trigger-enter-active');
}

2.想要過一段時間後內容再浮現

  • setTimeout(): 過一段時間後,執行一次,只執行一次
  • setInterval(): 每過一段時間,執行一次,不斷循環

所以我們這邊使用setTimeout(),setTimeout()方法看this會發現是window。

function arrowEnter() {
    console.log(this);
    this.classList.add('trigger-enter');
    setTimeout(function () {
        console.log(this);
        this.classList.add('trigger-enter-active');
    }, 200);
}


setTimeout() 方法改用箭頭函式綁住this

function arrowEnter() {
    console.log(this);
    this.classList.add('trigger-enter');
    setTimeout(() => {
        console.log(this);
        this.classList.add('trigger-enter-active');
    }, 200);
}

內容移出

function arrowLeave() {
    this.classList.remove('trigger-enter');
    this.classList.remove('trigger-enter-active');
}

加上背景

function arrowEnter() {
    this.classList.add('trigger-enter');
    setTimeout(() => {
        this.classList.add('trigger-enter-active');
    }, 200);
    background.classList.add('open');
}
function arrowLeave() {
    this.classList.remove('trigger-enter');
    this.classList.remove('trigger-enter-active');
    background.classList.remove('open');
}

設定背景寬高與位置

偵測dropdown位置

偵測我們是hover到哪個dropdown
並使用getBoundingClientRect()找到dropdown在頁面上的位置和寬高

function arrowEnter() {
    const dropdown = this.querySelector('.dropdown');
    const dropdownCoords = dropdown.getBoundingClientRect();
    console.log(dropdownCoords);
   ...
}

設定背景寬高與位置

因為我們兩段式 add class,所以當div顯示(display:block)但opcticy:0時
畫面還沒顯示但就可以抓到寬高,
將dropdown寬高和位置設定到背景(dropdownBackground)上

function arrowEnter() {
    ...
    const dropdown = this.querySelector('.dropdown');
    const dropdownCoords = dropdown.getBoundingClientRect();
    const Coords = {
        width: dropdownCoords.width,
        height: dropdownCoords.height,
        x: dropdownCoords.x,
        y: dropdownCoords.y,
    };
    background.style.setProperty('width', `${Coords.width}px`);
    background.style.setProperty('height', `${Coords.height}px`);
    background.style.setProperty('transform', `translate(${Coords.x}px,${Coords.y}px)`);
    background.classList.add('open');
}

dropdown固定在nav相對位置上,背景也要跟著移動

nav位子可能會變動,例如加了一個h2

nav移動dropdown位置也會跟著移動,所以背景(dropdownBackground)也要跟著移動,
讓背景(dropdownBackground)固定在nav相對位置上

偵測nav位置,要減掉nav的top和left

function arrowEnter() {
    ...
    const dropdown = this.querySelector('.dropdown');
    const dropdownCoords = dropdown.getBoundingClientRect();
    const navCoords = nav.getBoundingClientRect();//偵測nav位置
    const Coords = {
        width: dropdownCoords.width,
        height: dropdownCoords.height,
        x: dropdownCoords.x - navCoords.x,
        y: dropdownCoords.y - navCoords.y,
    };
    console.log(dropdownCoords.top);
    console.log(dropdownCoords.left);
    background.style.setProperty('width', `${Coords.width}px`);
    background.style.setProperty('height', `${Coords.height}px`);
    background.style.setProperty('transform', `translate(${Coords.x}px,${Coords.y}px)`);
    background.classList.add('open');
}

解決setTimeout()問題

滑入後過200毫秒才會add('trigger-enter-active'),但這時滑鼠已經滑出
變成先remove('trigger-enter-active')>>200毫秒後又add('trigger-enter-active')
所以我們要先確認裡面div有class="trigger-enter",再繼續add('trigger-enter-active')

方法1:if

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

方法2:&&

AND&&運算符執行以下操作:
返回從左到右求值時遇到的第一個false,
如果都是true,則返回最後一個操作數的值。
MDN: Logical AND (&&)

setTimeout(
        () => this.classList.contains('trigger-enter') && this.classList.add('trigger-enter-active'),
        200
    );

今日學習到的:

  • CSS可以透過分開display和opcticy兩階段來寫,就可以有動畫效果。
  • setTimeout(): 過一段時間後,執行一次,只執行一次。
  • setInterval(): 每過一段時間,執行一次,不斷循環。
  • setTimeout() 方法可用箭頭函式綁住this
  • nav移動dropdown位置也會跟著移動,所以背景(dropdownBackground)也要跟著移動。
  • AND&&運算符:從左到右求值時遇到的第一個false,如果都是true,則返回最後一個操作數的值。

效果連結:連結

參考連結:
MDN: Logical AND (&&)
JS30


上一篇
JS30 自學筆記 Day25_Event Capture, Propagation, Bubbling and Once
下一篇
JS30 自學筆記 Day27_Click and Drag to Scroll
系列文
JavaScript 30天挑戰 自學筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言