iT邦幫忙

2022 iThome 鐵人賽

DAY 3
0
自我挑戰組

向網頁施點魔法粉 framer-motion 系列 第 3

#03 Put your Gestures up - Hover, Focus & Tap

  • 分享至 

  • xImage
  •  

上篇講完了基本組成動畫的 props ,透過三種 animateinitial 以及 transition 可以交織出不同的動畫組合,這些動畫都是網頁載入就觸發,接下來要討論跟使用者互動的 Gestures。

目錄

  1. 我進去了我又出來了 : whileHover
  2. Watch me nae nae : whileFocus
  3. 啪打碰 : whileTap
  4. 小實作 : Hover Tab

本節資源 :
程式碼 | 網頁展示

上篇講完了基本組成動畫的 props ,透過三種 animateinitial 以及 transition 可以交織出不同的動畫組合,這些動畫都是網頁載入就觸發,接下來要討論跟使用者互動的 Gestures。

想回想一下 CSS 中有哪些 偽類別 (pseudo-classes) 是有關使用者操作 ?

  • :hover
  • :focus
  • :active
  • :focus-visible
  • :focus-within

在 motion API 中提供多個 Animation helpers 的 props 讓元件可以擴展基本 React 監聽事件,目前支援的類型有 : hover 、 tap 、 pan 、 viewport 還有 drag 。

我進去了我又出來了 : whileHover

hover 是 desktop 上最常看見的滑鼠互動效果,但在 mobile 就沒什作用了 QQ 。而在 motion 元件加上 hover 動畫只要使用 whileHover props,可以看做 當滑鼠滑入會觸發的 animate props

motion 的 hover 保證只在裝置支援 hover 操作的時候觸發,解決 mobile 要額外處理 hover 的問題。

<motion.div
  whileHover={{
    scale: 1.2,
    transition: { duration: 1 },
  }}
/>
  • 來點 Doki Doki 心跳
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 的 onmouseoveronmouseout 事件操作,motion API 也提供兩種 props。

onHoverStart & onHoverEnd

onHoverStartonHoverEnd 除了提供事件基本的 event,還提供了第二個參數 info

<motion.div
    onHoverStart={(e, info) => {
        console.log("start", info);
    }}
    onHoverEnd={(e, info) => {
        console.log("end", info);
    }}
/>

info 印出來後裡面是一個物件,包含 pointer 物件有 xy 座標。

  • 結果

  • 站在雲林裡 : 是顯示可以在 onHoverStartonHoverEnd 操作 setState 可以,當 re-render 就會重新執行 motion 元件的 animate

  • 翻牌動畫 : 子元素單個使用 hover 也可以解決,此範例只是展示有設定邊界值可以讓最外層的元素控制 state,進而影響到子元素的動畫,並且使用到 Hover 事件的第二個傳入的參數,拿到區塊對應的座標。另外是由於是同一個區塊,只會認大區塊的 hover 狀態。

補充 : @media 的 hover :hover 只在 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

參考資料 :

  1. caniuse
  2. hover MDN

Watch me nae nae : whileFocus

foucs 焦點很常在 <input><a> 等元素點擊後顯示,或者使用 tab 讓元素被焦點。

 <motion.input
    type="text"
    whileFocus={{
        scale:1.1
    }}
/>

暫時沒想到什麼好範例 XD

啪打碰 : whileTap

whileTap 點擊觸發,此外跟 Hover 一樣,提供不同的事件 :

  1. onTap : 完成整個點擊觸發,也就是點擊完後也在元素內,才是完成的。
  2. onTapStart : 點擊一開始就觸發。
  3. 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)}
            />
        </>
    );
}


小實作 : Hover Tab

從 w3school 的範例

  • 把城市資訊拉出來,tab 跟內容都可以用陣列來顯示
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.",
    },
];
  • 把原先的 style 改造一下,原範例是用 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 };
  • 透過 state 來控制目前的城市名
const [city, setCity] = useState(null);
  • Tab 按鈕
<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>
  • desktop 效果 :
  • 模擬 mobile 效果,hover 不能作用的情況下,讓 tap 也能做同樣的事

總結

  • whileHover : 等於 CSS 的 偽類別 :hover,相關事件有 onHoverStartonHoverEnd
  • whileFocus : 等於 CSS 的 偽類別 :focus,用在 inputtextarea 或連結 a
  • whileTap : 當元素被點擊時。事件包含 onTaponTapStartonTapEnd
  • motion 元件會在 hover 不支援的裝置會使用 Tap ,hover 的操作則會自動取消。

明天會討論到 Gesture 的 whileInViewwhileDrag

參考資料

  1. 官方文件 : Gestures | Framer for Developers
  2. 實作範例 : W3Schools Tryit Editor

上一篇
#02 Dancing with Animation and Transition
下一篇
#04 Put your Gestures up II - Pan, whileDrag, whileInView
系列文
向網頁施點魔法粉 framer-motion 15
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言