今日任務:碰到選單會浮出子選單,子選單背景大小由內容決定
今天我們來學放在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>
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;
}
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');
}
偵測我們是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');
}
nav位子可能會變動,例如加了一個h2
nav移動dropdown位置也會跟著移動,所以背景(dropdownBackground)也要跟著移動,
讓背景(dropdownBackground)固定在nav相對位置上
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');
}
滑入後過200毫秒才會add('trigger-enter-active'),但這時滑鼠已經滑出
變成先remove('trigger-enter-active')>>200毫秒後又add('trigger-enter-active')
所以我們要先確認裡面div有class="trigger-enter",再繼續add('trigger-enter-active')
setTimeout(() => {
if (this.classList.contains('trigger-enter')) {
this.classList.add('trigger-enter-active');
}
}, 200);
AND&&
運算符執行以下操作:
返回從左到右求值時遇到的第一個false,
如果都是true,則返回最後一個操作數的值。
MDN: Logical AND (&&)
setTimeout(
() => this.classList.contains('trigger-enter') && this.classList.add('trigger-enter-active'),
200
);
今日學習到的:
setTimeout()
: 過一段時間後,執行一次,只執行一次。setInterval()
: 每過一段時間,執行一次,不斷循環。setTimeout()
方法可用箭頭函式綁住thisAND&&運算符
:從左到右求值時遇到的第一個false,如果都是true,則返回最後一個操作數的值。效果連結:連結
參考連結:
MDN: Logical AND (&&)
JS30