iT邦幫忙

2024 iThome 鐵人賽

DAY 5
1

前幾天,我們從Playground的佈局下手,已經保證一個自適應的窗口來容納canvas,並獲得游標的位置,現在,讓我們延續昨天的基礎,設計更靈活的菜單收納方法。

需求

在後續的開發,canvas需要一個菜單面板來放置參數和控制鈕,內容也都不相同。那麼,重複的部分集中在CSS和UI邏輯,因此我們需要一個可重用的按鈕來實現菜單的收納和顯示。

Section 元件

function CanvasSection1({ratio, min}) => {
    //......
    const menu = useRef(null);
    return (
        <section className="section">
            <canvas width={min * ratio} height={ratio * min * ratio}></canvas>
            <div ref={menu} className="gamemenu">
                 {/* ...... */}
                <SlideMenuBtn menu={menu}></SlideMenuBtn>
            </div>
        </section>
    );
}

SlideMenuBtn 元件設計概念

按鈕點擊後,菜單就會收納,這個元件好用之處在於:

  1. 它會自動計算菜單的高度、寬度,實現收納功能
  2. 只需要傳入菜單元素( menu )
  3. 可以靈活指定收納方向

例如,設置收納方向為上方:
<SlideMenuBtn menu={menu} direction="top"></SlideMenuBtn>

佈局方式

我們透過設置inline-style,讓菜單往畫面外移動,復原的時候就用空字串,使用預設的css佈局:

// 向上收納
m.style.top = isOpen ? "" : "-" + positionOffset.top + "px";
// 向任意方向收納
m.style[direction] = isOpen ? "" : "-" + positionOffset[direction] + "px";

偏移量計算

利用 getBoundingClientRect 計算菜單和按鈕的偏移量,目標是把菜單隱藏起來,只露出按鈕,具體計算如下:

function SlideMenuBtn({menu, direction ="top"}){
    const [isOpen, setIsOpen] = useState(true);

    function handleClick(e){
        const m = menu.current; // 菜單元素
        const b = e.target; // 按鈕元素
        const menuRect  = m.getBoundingClientRect();
        const buttonRect  = b.getBoundingClientRect();
        const positionOffset = {
            "left": buttonRect.x - menuRect.x,
            "top": buttonRect.y - menuRect.y
        };
        m.style[direction] = !isOpen ? "" : "-" + positionOffset[direction] + "px";

        setIsOpen(!isOpen);
    }
    return <button onClick={handleClick} className="slideMenu">
        {isOpen ? "收起△" : "展開▽"}
    </button>;
}

在處理開關狀態時,我習慣用 !isOpen 來控制邏輯,因為我們的元件是先執行後渲染的策略,因此,用渲染後的值來判斷更加直覺。

可以把這個按鈕放在右下角,收納行為分成向左和向上:

/* CSS */
.gamemenu button.slideMenu{
    position: relative;
    float: inline-end;
}

相比之下,Vue對型別比較嚴格

<script setup>
import { ref } from 'vue';
const props = defineProps({
    menu: Object,
    direction: {
        type: String,
        default: 'left'
    }
});
const isOpen = ref(true);
const handleClick = (e) => {
    isOpen.value = !isOpen.value;

    const m = props.menu;
    const b = e.target;
    const menuRect = m.getBoundingClientRect();
    const buttonRect = b.getBoundingClientRect();
    const positionOffset = {
            "left": buttonRect.x - menuRect.x,
            "top": buttonRect.y - menuRect.y
        };
    m.style[props.direction] = isOpen.value ? "" : "-" + positionOffset[props.direction] + "px";
}
</script>

可以直接更新 isOpen.value 使用

<template>
    <button @click="handleClick" class="slideMenu">{{isOpen ? "收起△" : "展開▽"}}</button>
</template>

小細節:要用兩個大括號 {{}} 插值

結語

這樣,我們實現了一個簡單但靈活的菜單收納按鈕,既能自動計算並控制菜單的收納行為,也能夠通過 props 動態改變收納的方向,讓元件具備可重用性和靈活性。

如果感興趣,可以參考 Github 上的原始碼:
SlideMenuBtn.jsx
SlideMenuBtn.vue

最後,留給大家一個思考題:如果我們需要設計一個能夠在上下左右任意方向進行收納的元件,應該如何進行實現?希望這個問題能激發創意,實現更多可能性。


上一篇
A3 蹲馬步:掌握模板動態生成的導航欄元件
下一篇
A5 面板元件-一鍵搞定!錄影按鈕元件
系列文
讓演算法起舞:前端特效應用的探索之旅35
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言