上篇講完了基本組成動畫的 props ,透過三種 animate
、 initial
以及 transition
可以交織出不同的動畫組合,這些動畫都是網頁載入就觸發,接下來要討論跟使用者互動的 Gestures。
whileHover
whileFocus
whileTap
上篇講完了基本組成動畫的 props ,透過三種 animate
、 initial
以及 transition
可以交織出不同的動畫組合,這些動畫都是網頁載入就觸發,接下來要討論跟使用者互動的 Gestures。
想回想一下 CSS 中有哪些 偽類別 (pseudo-classes) 是有關使用者操作 ?
:hover
:focus
:active
:focus-visible
:focus-within
在 motion API 中提供多個 Animation helpers 的 props 讓元件可以擴展基本 React 監聽事件,目前支援的類型有 : hover 、 tap 、 pan 、 viewport 還有 drag 。
hover
是 desktop 上最常看見的滑鼠互動效果,但在 mobile 就沒什作用了 QQ 。而在 motion 元件加上 hover 動畫只要使用 whileHover
props,可以看做 當滑鼠滑入會觸發的 animate props。
motion 的 hover 保證只在裝置支援 hover 操作的時候觸發,解決 mobile 要額外處理 hover 的問題。
<motion.div
whileHover={{
scale: 1.2,
transition: { duration: 1 },
}}
/>
export default function WhileHover() {
return (
<motion.div
className="heart"
initial={{
x: 100,
rotate: 45,
}}
whileHover={{
scale: [0.9, 1.2, 1],
x: [100, 120, 100, 90, 100],
}}
transition={{
repeat: 3,
duration: 0.3,
type: "spring",
damping: 10,
}}
/>
);
}
效果 :
除此之外 motion 元件提供更細微控制的監聽事件,可以在剛進入 (start) 與離開 (end) 監聽事件,以往會透過 JavaScript 的 onmouseover
與 onmouseout
事件操作,motion API 也提供兩種 props。
onHoverStart
與 onHoverEnd
除了提供事件基本的 event
,還提供了第二個參數 info
<motion.div
onHoverStart={(e, info) => {
console.log("start", info);
}}
onHoverEnd={(e, info) => {
console.log("end", info);
}}
/>
info
印出來後裡面是一個物件,包含 pointer
物件有 x
、y
座標。
結果
站在雲林裡 : 是顯示可以在 onHoverStart
跟 onHoverEnd
操作 setState
可以,當 re-render 就會重新執行 motion 元件的 animate
。
翻牌動畫 : 子元素單個使用 hover 也可以解決,此範例只是展示有設定邊界值可以讓最外層的元素控制 state,進而影響到子元素的動畫,並且使用到 Hover 事件的第二個傳入的參數,拿到區塊對應的座標。另外是由於是同一個區塊,只會認大區塊的 hover 狀態。
以往如果想特別取消 hover 的寫法我們可能會在媒體查詢 @media
不同的裝置大小做區別,不過 CSS 提供了一個更方便的 hover
來偵測在是否支援 hover 的裝置,如此一來在 desktop 的 hover 互動轉換成 mobile 版面就更加容易了。
// 以往可能會個別在某區設定
@media(min-width: 996px){
.box:hover{
// ...
}
}
@media(hover :hover){
// hover 支援情況多用於 desktop 上
}
@media(hover :none){
// hover 不支援多用於 pad 與 phone 上
}
詳細可以參考 Kevin Powell 的影片 : Dealing with hover on mobile - YouTube
參考資料 :
foucs
焦點很常在 <input>
、 <a>
等元素點擊後顯示,或者使用 tab 讓元素被焦點。
<motion.input
type="text"
whileFocus={{
scale:1.1
}}
/>
暫時沒想到什麼好範例 XD
whileTap
點擊觸發,此外跟 Hover 一樣,提供不同的事件 :
onTap
: 完成整個點擊觸發,也就是點擊完後也在元素內,才是完成的。onTapStart
: 點擊一開始就觸發。onTapCancel
: 當點擊結束不在元素範圍內,等於取消點擊。<motion.div
className="tap"
whileHover={{
scale: 0.9,
}}
whileTap={{
scale: 1.1,
outline: "5px solid hsla(180, 50%, 50%,.6)",
}}
>
click me
</motion.div>
欲情故縱按讚
程式碼
export default function WhileTap() {
// 對應三種狀態
const [press, setPress] = useState(0);
return (
<>
<span
style={{
fontSize: "32px",
}}
>
{press === -1 && "無言臉"}
{press === 0 && "哭哭臉"}
{press === 1 && "愛心臉"}
</span>
<motion.div
className="tap"
whileHover={{
scale: [1.1, 1],
}}
whileTap={{
fontSize: "60px",
outline: "5px solid hsla(180, 50%, 50%,.6)",
}}
{/* 完成點擊且在還在範圍內 */}
onTap={() => setPress(1)}
{/* 點擊但還沒放開*/}
onTapStart={() => setPress(0)}
{/* 最後的疼愛是手放開~ */}
onTapCancel={() => setPress(-1)}
/>
</>
);
}
從 w3school 的範例
const cities = [
{
name: "London",
content: "London is the capital city of England.",
},
{
name: "Paris",
content: "Paris is the capital of France.",
},
{
name: "Tokyo",
content: "Tokyo is the capital of Japan.",
},
];
float
,我改用 positon:absolute
做const container = {
display: "flex",
border: "1px solid #aaa",
width: "80%",
margin: "0 auto",
borderRadius: "10px",
overflow: "hidden",
};
const tab = {
display: "flex",
flexDirection: "column",
border: "1px solid #ccc",
backgroundColor: "#f1f1f1",
width: "100px",
flex: "0 1 100px",
};
const tabContent = {
position: "absolute",
top: 0,
bottom: 0,
left: 0,
right: 0,
flex: "0 0 auto",
padding: "0 0 0 24px",
};
const tabBtn = {
padding: "20px 12px",
border: "none",
outline: "none",
textAlign: "left",
cursor: "pointer",
fontSize: "17px",
};
export { tab, tabContent, tabBtn, container };
const [city, setCity] = useState(null);
<div style={tab}>
{cities.map(({ name }) => (
<motion.button
key={name}
style={tabBtn}
whileHover={{
background: ["#ddd", "#ccc", "#ddd"],
}}
whileTap={{
fontSize: "24px",
}}
{/* 當不支援 hover 時也可以運作 */}
onTap={() => setCity(name)}
{/* 支援 hover 時可以運作 */}
onHoverStart={() => setCity(name)}
>
{name}
</motion.button>
))}
</div>
<div
style={{
position: "relative",
width: "100%",
}}
>
{cities.map(({ name, content }) => {
// 選到為 true
let show = city === name;
return (
<motion.div
key={name}
style={tabContent}
initial={{
{/* 初始化消失 */}
opacity: show ? 1 : 0,
}}
animate={{
{/* 符合的話顯示 */}
opacity: show ? 1 : 0,
{/* 讓上下層交換,這樣才能選到文字 */}
zIndex: show ? 2 : -1,
}}
transition={{
type: "tween",
ease: "easeInOut",
delay: 0.15,
}}
>
<h3>{name}</h3>
<p>{content}</p>
</motion.div>
);
})}
</div>
whileHover
: 等於 CSS 的 偽類別 :hover
,相關事件有 onHoverStart
和 onHoverEnd
。whileFocus
: 等於 CSS 的 偽類別 :focus
,用在 input
、textarea
或連結 a
。whileTap
: 當元素被點擊時。事件包含 onTap
、onTapStart
、onTapEnd
。明天會討論到 Gesture 的 whileInView
與 whileDrag
。