前幾天,我們從Playground的佈局下手,已經保證一個自適應的窗口來容納canvas,並獲得游標的位置,現在,讓我們延續昨天的基礎,設計更靈活的菜單收納方法。
在後續的開發,canvas需要一個菜單面板來放置參數和控制鈕,內容也都不相同。那麼,重複的部分集中在CSS和UI邏輯,因此我們需要一個可重用的按鈕來實現菜單的收納和顯示。
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 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;
}
<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
最後,留給大家一個思考題:如果我們需要設計一個能夠在上下左右任意方向進行收納的元件,應該如何進行實現?希望這個問題能激發創意,實現更多可能性。