復刻調皮的按紐
偶然看到,沒填資料就不給按的按紐,覺得很有趣,就跟著做了一個
HTML的部分很單純: 表單裡面有usernam和password input,以及送出按紐
送出按紐其實有兩個,疉在一起,底下的按紐負賚佔位的任務,上方的會移動
<!-- submit box 包了兩個送出按紐 -->
<div class="form-item submit-box">
<button type="button" value="placeholder" class="placeholder-btn" disabled></button>
<button type="submit" value="submit" class="submit-btn" disabled>Submit</button>
</div>
和使用者的互動有三個 (三種事件監聽),依照對象可大致分為兩組:
- 資料輸入欄位: username、password
- input event
- 送出按鈕
- submit event
- mouseenter event
// Event Listeners
elements.form.addEventListener('input', handleFormInput);
elements.submitBtn.addEventListener('click', handleFormSubmit);
elements.submitBtn.addEventListener('mouseenter', moveButton);
輸入欄位決定了表單是不是可以送出,表單依據可否送出的狀態,判斷送出按鈕要移動或是送出。可以準備一個變數isReadyToSubmit
用來記錄這個會共享的狀態。
輸入欄位監聽: 如果沒填入使用者名稱和密碼,就讓按扭無法點擊 (disabled)
isReadyToSubmit = enteredUsername && enteredPassword;
submitBtn.disabled = !isReadyToSubmit;
比較不同的是動畫: 在滑鼠踫到按鈕時,移動上層的那個按鈕,讓它看起來像有「逃跑」的效果。
需要準備兩個項目:
- 目標位置
- 動畫效果
目標位置
以按紐本身長寬為基礎,乘上隨機產生的距離和方向,讓它只會在原本的位置附近 (原來的做法是一路往邊界移動,直到踫到邊再回原位)
const basemovex = 84;
const baseemovey = 36;
const xMove = Math.random() * basemovex * (Math.random() < 0.5 ? -1 : 1)
const yMove = Math.random() * baseemovey * (Math.random() < 0.5 ? -1 : 1)
e.target.style.setProperty('--x-move', `${xMove}px`);
e.target.style.setProperty('--y-move', `${yMove}px`);
e.target.classList.add('moving');
算出來後設定到submit button的property,讓CSS可以用來移到新的位置 (css custom properties)
.submit-btn {
background-color: #f4a261;
color: #ffffff;
border: none;
cursor: pointer;
position: absolute;
left: 0;
transition: all 0.6s;
will-change: transform;
transform: translate(var(--x-move, 0), var(--y-move, 0));
}
移動時像是在逃跑的動畫
一開始就是被像果凍般的動畫吸引。用scale做,看起來就有縮小變大的效果
動畫顯示的條件是:
- 表單還不能送出時 (缺username或是password)
- 游標踫到按鈕時 才可以動
JS在符合條件時,在按紐加上moving
class,而CSS在發現有.moving
class的候播放動畫
// e.target是 submit button
// 監聽submit button mouseenter事件
e.target.classList.add('moving');
CSS custom properties 命名規則: 兩個dash (--
) 做為prefix
.submit-btn.moving {
cursor: not-allowed;
animation: jellyBounce .6s cubic-bezier(.04,.43,.025,1.07);
transform: scale(1)
}
@keyframes jellyBounce {
0%, 100% {
transform: scale(1) translate(var(--x-move), var(--y-move));
}
50% {
transform: scale(1.2, .8) translate(var(--x-move), var(--y-move));
}
80% {
transform: scale(.9, 1.1) translate(var(--x-move), var(--y-move));
}
}
如果是採用讓按鈕一直往外「逃跑」的做法,可以用IntersectionObserver監控submit button,讓它在踫到或是靠近邊緣的時候回到原點從新開始
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
// If button is not fully visible and form isn't ready, reset position
if (!entry.isIntersecting && !isReadyToSubmit) {
submitBtn.style.setProperty('--x-move', '0px');
submitBtn.style.setProperty('--y-move', '0px');
submitBtn.classList.remove('moving');
}
});
},
{
root: document.querySelector('.form-container'),
// Reset when less than 80% of button is visible
threshold: 0.8,
}
);
observer.observe(submitBtn);