製作簡單的側邊欄side bar

更新 發佈閱讀 34 分鐘
這次的side bar不一樣了,上一次我好像做的太簡單了,所以完讀率只有14%,我好難過啊(;´д`)ゞ,所以我這準備的內容有多一點。



這次的目標

  • 按鈕的排版
  • 按鈕滑過去會有顏色變化
  • Side bar能夠展開
  • 箭頭能夠移動至被選擇的物件上


宣告

這次我創了兩個檔案 SideBar.tsx 跟 SideBarItem.tsx 並放在SideBar的資料夾

SideBarItem.tsx

const SideBarItem = ({ icon, handleClick, name, }: { icon: React.ReactNode, handleClick: Function, name: string}) => {

return (
<div className={`grid grid-cols-[80px-auto] bg-slate-900 rounded-lg`} onClick={() => { handleClick() }}>

<div className="col-start-1 flex justify-center items-center size-20">
{icon}
</div>
<p className={`col-start-2 hidden text-4xl`}>{name}</p>
</div>
)
}
export default SideBarItem

SideBarItem會有圖片以及展開後的文字,所以我用grid切出兩個區域,接著設定這些tag的顏色、大小

P.S. 展開後的文字等等再處理,所以我目前先讓他hidden

SideBar.tsx

import { IoIosMenu, IoIosCalendar, IoIosHome, IoIosSettings, IoIosHelp } from "react-icons/io";
import { MdOutlineTask } from "react-icons/md";
import SideBarItem from "./SideBarItem";
const SideBar = () => {
return (
<div className={`bg-black text-white p-3`}>
<SideBarItem handleClick={() => { }} icon={<IoIosMenu size="40" />} name=" " />

<SideBarItem handleClick={() => { }} icon={<IoIosHome size="40" />} name='Home' />
<SideBarItem handleClick={() => { }} icon={<MdOutlineTask size="40" />} name='Task' />
<SideBarItem handleClick={() => { }} icon={<IoIosCalendar size="40" />} name='Calendar' />
<SideBarItem handleClick={() => { }} icon={<IoIosHelp size="70" />} name='Help' />
<SideBarItem handleClick={() => { }} icon={<IoIosSettings size="40" />} name='Settings' />


<div className={` h-[3px] bg-slate-200 rounded-lg`}></div>

</div>
)
}
export default SideBar

接著在SideBar裡,把所需要的Icon import進來,並將資料都丟進去SideBarItem裡,

最後加入分隔線

<div className={` h-[3px] bg-slate-200 rounded-sm`}></div>

完成後會長這樣

raw-image


排版

按鈕我分成三種

raw-image

所以我讓最外層是grid,分出四格

<div className={`grid grid-rows-[auto_auto_1fr_auto] gap-5 bg-black text-white p-3`}>
<div className="row-start-1">
{/* 第一種 */}
<SideBarItem handleClick={() => { }} icon={<IoIosMenu size="40" />} name=" " />
</div>

<div className="row-start-4">
{/* 分隔線 */}
<div className={` h-[3px] bg-slate-200 rounded-lg`}></div>
</div>

<div className={`row-start-3 space-y-3`}>
{/* 第二種 */}
<SideBarItem handleClick={() => { }} icon={<IoIosHome size="40" />} name='Home' />
<SideBarItem handleClick={() => { }} icon={<MdOutlineTask size="40" />} name='Task' />
<SideBarItem handleClick={() => { }} icon={<IoIosCalendar size="40" />} name='Calendar' />
</div>

<div className="row-start-4 space-y-3">
{/* 第三種 */}
<SideBarItem handleClick={() => { }} icon={<IoIosHelp size="70" />} name='Help' />
<SideBarItem handleClick={() => { }} icon={<IoIosSettings size="40" />} name='Settings' />
</div>

</div>

:hover

為了讓滑鼠滑過去要有反應,我加了:hover跟cursor-pointer

在SideBarItem最外層的div上

hover:bg-slate-300 hover:text-black  //顏色(背景、圖片)
hover:rounded-[25px]                 //邊框      
cursor-pointer // 滑鼠鼠標

但這樣一瞬間變化太大,螢幕閃爍會害眼睛很不舒服,所以要在多加這一行

transition-all duration-200 ease-linear

Side Bar 展開

這是整篇最麻煩的,首先先創一個變數isOpen以及func控制Side Bar 的開關。

const [isOpen, setisOpen] = useState<boolean>(false)
function toggleIsOpen(): void{
setisOpen(!isOpen);
}

將toggleIsOpen()丟進去handleClicked

<SideBarItem handleClick={() => { toggleIsOpen() }} icon={<IoIosMenu size="40" />} name=" " />


接著把IsOpen傳入SideBarItem的props

並加入判斷是裡控制寬度

transition-all duration-200 ease-linear //這個也順便丟進去
<p className={`col-start-2 flex items-center text-white text-4xl ${isOpen ? 'w-52 ' : 'w-0'}
overflow-hidden
            transition-all duration-200 ease-linear`}>{name}</p>


<p> overflow

歐對了,如果沒加overflow-hidden,SideBar會變這樣

raw-image

menu也展開

但這展開時,menu也伸長了

raw-image


為了避免這個情況

要給裝menu的div flex屬性之外,還要讓SideBarItem判斷是否需要<p>

SideBarItem.tsx

{name === '' ? <></> :  <p className={`col-start-2 flex items-center text-4xl ${isOpen ? 'w-52 ' : 'w-0'} 
overflow-hidden
transition-all duration-200 ease-linear`}>{name}</p>}


目前的程式

SideBarItem.tsx

const SideBarItem = ({ icon, handleClicked, name, isOpen}: { icon: React.ReactNode, handleClicked: Function, name: string, isOpen: boolean}) => {

return (
<div className={`grid grid-cols-[80px-auto] bg-slate-900 rounded-lg
hover:bg-slate-300 hover:text-black hover:rounded-[25px]
transition-all duration-200 ease-linear cursor-pointer`} onClick={() => { handleClicked() }}>

<div className="col-start-1 flex justify-center items-center size-20">
{icon}
</div>
{name === '' ? <></> : <p className={`col-start-2 flex items-center text-4xl ${isOpen ? 'w-52 ' : 'w-0'}
overflow-hidden
transition-all duration-200 ease-linear`}>{name}</p>}

</div>
)
}
export default SideBarItem

SideBar.tsx

import { IoIosMenu, IoIosCalendar, IoIosHome, IoIosSettings, IoIosHelp } from "react-icons/io";
import { MdOutlineTask } from "react-icons/md";
import SideBarItem from "./SideBarItem";
import { useState } from "react";

const SideBar = () => {
const [isOpen, setisOpen] = useState<boolean>(false)
function toggleIsOpen(): void {
setisOpen(!isOpen);
}

return (
<div className={`grid grid-rows-[auto_auto_1fr_auto] gap-5 bg-black text-white p-3`}>
<div className="row-start-1 flex">
{/* 第一種 */}
<SideBarItem handleClicked={() => { toggleIsOpen();}} icon={<IoIosMenu size="40" />} name="" isOpen={isOpen}/>

</div>

<div>
{/* 分隔線 */}
<div className={` h-[3px] bg-slate-200 rounded-lg`}></div>
</div>


<div className={`row-start-3 space-y-3`}>
{/* 第二種 */}
<SideBarItem handleClicked={() => { }} icon={<IoIosHome size="40" />} name='Home' isOpen={isOpen}/>
<SideBarItem handleClicked={() => { }} icon={<MdOutlineTask size="40" />} name='Task' isOpen={isOpen}/>
<SideBarItem handleClicked={() => { }} icon={<IoIosCalendar size="40" />} name='Calendar' isOpen={isOpen}/>
</div>

<div className="row-start-4 space-y-3">
{/* 第三種 */}
<SideBarItem handleClicked={() => { }} icon={<IoIosHelp size="70" />} name='Help' isOpen={isOpen}/>
<SideBarItem handleClicked={() => { }} icon={<IoIosSettings size="40" />} name='Settings' isOpen={isOpen}/>
</div>

</div>
)
}
export default SideBar

箭頭

這個部份我是用position: absolute去做到的,所以我需要一個變數去儲存箭頭目的地的y座標

為此,我用了useState

const [arrowPosition, setArrowPosition] = useState<string>('[8px]')

接著

import箭頭到SideBar.tsx裡

import { FaCaretRight } from "react-icons/fa";

加進去div裡

<div className={`row-start-3 space-y-3 relative`}>

{/* 第二種 */}

<SideBarItem handleClicked={() => { }} icon={<IoIosHome size="40" />} name='Home' isOpen={isOpen}/>

<SideBarItem handleClicked={() => { }} icon={<MdOutlineTask size="40" />} name='Task' isOpen={isOpen}/>

<SideBarItem handleClicked={() => { }} icon={<IoIosCalendar size="40" />} name='Calendar' isOpen={isOpen}/>
<div className={`absolute top-${arrowPosition} left-[-20px] transition-all duration-200 ease-linear`}>
{/* 箭頭 */}
<FaCaretRight size="40"/>
</div>

</div>

P.S.怕會有人不知道,如果子項用了position: absolute且想以父項作為基準點的話,則父項也要有position: relative 或 absolute 或...


目前應該會做到這

raw-image


function onNavItemClicked(position: string): void {
if(arrowPosition === position){
toggleIsOpen() //順便加的功能
return
}
setArrowPosition(position)
//打開某個頁面,還沒寫
}

把func加進去

<div className={`row-start-3 space-y-3 relative`}>
{/* 第二種 */}
<SideBarItem handleClicked={() => { onNavItemClicked('[8px]') }} icon={<IoIosHome size="40" />} name='Home' isOpen={isOpen}/>
<SideBarItem handleClicked={() => { onNavItemClicked('[100px]') }} icon={<MdOutlineTask size="40" />} name='Task' isOpen={isOpen}/>
<SideBarItem handleClicked={() => { onNavItemClicked('[192px]') }} icon={<IoIosCalendar size="40" />} name='Calendar' isOpen={isOpen}/>

<div className={`absolute top-${arrowPosition} left-[-20px] transition-all duration-200 ease-linear`}>
{/* 箭頭 */}
<FaCaretRight size="40"/>
</div>
</div>


後記

我知道top-[ooopx]這東西其實可以變成數學算式,好像廢話,畢竟我每個Item的大小以及padding都是固定的。但我好懶歐。可能以後多一些功能像是能自由更改icon大小,或是動態增減SideBarItem的時候,在更改好了。


我也知道我一開始的圖的menu是置中的,但後來的是在旁邊,這是因為,這個程式是我重寫的(我一開始寫的時候寫的很亂),結果我後來比較喜歡這種放邊邊的樣式,反正如果想要置中,就多打一個 justify-center在menu的div的flex旁邊

反正就先這樣了,希望你們會喜歡。

P.S. 喜歡的話拜託拜託按個愛心,我會很開心

這是我這次整個的程式

SideBarItem.tsx

const SideBarItem = ({ icon, handleClicked, name, isOpen}: { icon: React.ReactNode, handleClicked: Function, name: string, isOpen: boolean}) => {

return (
<div className={`grid grid-cols-[80px-auto] bg-slate-900 rounded-lg
hover:bg-slate-300 hover:text-black hover:rounded-[25px]
transition-all duration-200 ease-linear cursor-pointer`} onClick={() => { handleClicked() }}>

<div className="col-start-1 flex justify-center items-center size-20">
{icon}
</div>
{name === '' ? <></> : <p className={`col-start-2 flex items-center text-4xl ${isOpen ? 'w-52 ' : 'w-0'}
overflow-hidden
transition-all duration-200 ease-linear`}>{name}</p>}

</div>
)
}
export default SideBarItem


SideBar.tsx

import { IoIosMenu, IoIosCalendar, IoIosHome, IoIosSettings, IoIosHelp } from "react-icons/io";
import { MdOutlineTask } from "react-icons/md";
import SideBarItem from "./SideBarItem";
import { useState } from "react";
import { FaCaretRight } from "react-icons/fa";

const SideBar = () => {
const [isOpen, setisOpen] = useState<boolean>(false)
function toggleIsOpen(): void {
setisOpen(!isOpen);
}

const [arrowPosition, setArrowPosition] = useState<string>('[8px]')
function onNavItemClicked(position: string): void {
if (arrowPosition === position) {
toggleIsOpen()
return
}
setArrowPosition(position)
}

return (
<div className={`grid grid-rows-[auto_auto_1fr_auto] gap-5 bg-black text-white p-3`}>
<div className="row-start-1 flex">
{/* 第一種 */}
<SideBarItem handleClicked={() => { toggleIsOpen(); }} icon={<IoIosMenu size="40" />} name="" isOpen={isOpen} />
</div>

<div>
{/* 分隔線 */}
<div className={` h-[3px] bg-slate-200 rounded-lg`}></div>
</div>

<div className={`row-start-3 space-y-3 relative`}>
{/* 第二種 */}
<SideBarItem handleClicked={() => { onNavItemClicked('[8px]') }} icon={<IoIosHome size="40" />} name='Home' isOpen={isOpen} />
<SideBarItem handleClicked={() => { onNavItemClicked('[100px]') }} icon={<MdOutlineTask size="40" />} name='Task' isOpen={isOpen} />
<SideBarItem handleClicked={() => { onNavItemClicked('[192px]') }} icon={<IoIosCalendar size="40" />} name='Calendar' isOpen={isOpen} />

<div className={`absolute top-${arrowPosition} left-[-20px] transition-all duration-200 ease-linear`}>
{/* 箭頭 */}
<FaCaretRight size="40" />
</div>
</div>

<div className="row-start-4 space-y-3">
{/* 第三種 */}
<SideBarItem handleClicked={() => { }} icon={<IoIosHelp size="70" />} name='Help' isOpen={isOpen} />
<SideBarItem handleClicked={() => { }} icon={<IoIosSettings size="40" />} name='Settings' isOpen={isOpen} />
</div>

</div>
)
}
export default SideBar


留言
avatar-img
Web開發日誌
3會員
5內容數
這是一篇帶新手快速入門的Web教學。會帶你製作第一個todo-list,雖然看起來很簡單,但其實蘊含很多觀念。
你可能也想看
Thumbnail
在 vocus 與你一起探索內容、發掘靈感的路上,我們又將啟動新的冒險——vocus App 正式推出! 現在起,你可以在 iOS App Store 下載全新上架的 vocus App。 無論是在通勤路上、日常空檔,或一天結束後的放鬆時刻,都能自在沈浸在內容宇宙中。
Thumbnail
在 vocus 與你一起探索內容、發掘靈感的路上,我們又將啟動新的冒險——vocus App 正式推出! 現在起,你可以在 iOS App Store 下載全新上架的 vocus App。 無論是在通勤路上、日常空檔,或一天結束後的放鬆時刻,都能自在沈浸在內容宇宙中。
Thumbnail
市場經驗拉長之後,很多投資人都會遇到同一個問題:不是方向看錯,而是部位太集中個股,常常跟大趨勢脫節。 早年的台股環境,中小股非常吃香,反而權值股不動,但QE量化寬鬆後,特別是疫情之後,後疫情時代,鈔票大量在股市走動,這些大資金只能往權值股走,因此早年小P的策略偏向中小型個股,但近年AI興起,高技術
Thumbnail
市場經驗拉長之後,很多投資人都會遇到同一個問題:不是方向看錯,而是部位太集中個股,常常跟大趨勢脫節。 早年的台股環境,中小股非常吃香,反而權值股不動,但QE量化寬鬆後,特別是疫情之後,後疫情時代,鈔票大量在股市走動,這些大資金只能往權值股走,因此早年小P的策略偏向中小型個股,但近年AI興起,高技術
Thumbnail
vocus 慶祝推出 App,舉辦 2026 全站慶。推出精選內容與數位商品折扣,訂單免費與紅包抽獎、新註冊會員專屬活動、Boba Boost 贊助抽紅包,以及全站徵文,並邀請你一起來回顧過去的一年, vocus 與創作者共同留下了哪些精彩創作。
Thumbnail
vocus 慶祝推出 App,舉辦 2026 全站慶。推出精選內容與數位商品折扣,訂單免費與紅包抽獎、新註冊會員專屬活動、Boba Boost 贊助抽紅包,以及全站徵文,並邀請你一起來回顧過去的一年, vocus 與創作者共同留下了哪些精彩創作。
Thumbnail
有時候回顧我部落格的文章,發現往往自己嘔心瀝血之作點閱數少的可憐... 反而隨意寫、在我眼中跟個版廢文沒兩樣的作品點閱率很高,一直感到很疑惑。我想知道怎樣的文章受讀者喜愛? 搜尋引擎又會推薦給讀者什麼樣子的文章?
Thumbnail
有時候回顧我部落格的文章,發現往往自己嘔心瀝血之作點閱數少的可憐... 反而隨意寫、在我眼中跟個版廢文沒兩樣的作品點閱率很高,一直感到很疑惑。我想知道怎樣的文章受讀者喜愛? 搜尋引擎又會推薦給讀者什麼樣子的文章?
Thumbnail
前言與本文適合對象 本篇電子書是在幫目前已經有在經營的自媒體在更往上提升,所以不會教如何架設網站、主機操作與設定等。 如果真的想要自己架設網站的可以參考:網站帶路姬 想要找部落格主機代管的可以找:金城事務所 我自己的建議是架站跟主機選擇都不是創作者需要學習的,可以交給專業的服務商幫你
Thumbnail
前言與本文適合對象 本篇電子書是在幫目前已經有在經營的自媒體在更往上提升,所以不會教如何架設網站、主機操作與設定等。 如果真的想要自己架設網站的可以參考:網站帶路姬 想要找部落格主機代管的可以找:金城事務所 我自己的建議是架站跟主機選擇都不是創作者需要學習的,可以交給專業的服務商幫你
Thumbnail
這次的side bar不一樣了,上一次我好像做的太簡單了,所以完讀率只有14%,我好難過啊(;´д`)ゞ,所以我這準備的內容有多一點。 這次的目標 按鈕的排版 按鈕滑過去會有顏色變化 Side bar能夠展開 箭頭能夠移動至被選擇的物件上 宣告 這次我創了兩個檔案 SideBa
Thumbnail
這次的side bar不一樣了,上一次我好像做的太簡單了,所以完讀率只有14%,我好難過啊(;´д`)ゞ,所以我這準備的內容有多一點。 這次的目標 按鈕的排版 按鈕滑過去會有顏色變化 Side bar能夠展開 箭頭能夠移動至被選擇的物件上 宣告 這次我創了兩個檔案 SideBa
Thumbnail
這篇文章教你如何製作側邊欄,包括準備工作、HTML和CSS的部分,還有一些互動效果。文章涵蓋了連結、圖片、超連結、大小、顏色、排版、flex和滑鼠互動等內容。
Thumbnail
這篇文章教你如何製作側邊欄,包括準備工作、HTML和CSS的部分,還有一些互動效果。文章涵蓋了連結、圖片、超連結、大小、顏色、排版、flex和滑鼠互動等內容。
Thumbnail
本文介紹如何使用UINavigationBarAppearance調整四種場景下的UI外觀,並探討客製化返回鍵UI又保留返回手勢的做法,可以有效地客製化NavigationBar的外觀,並避免一些NG作法。
Thumbnail
本文介紹如何使用UINavigationBarAppearance調整四種場景下的UI外觀,並探討客製化返回鍵UI又保留返回手勢的做法,可以有效地客製化NavigationBar的外觀,並避免一些NG作法。
Thumbnail
這幾天我一直沒有發布新文章,是因為我一直在研究電子報訂閱的事情。其實架設一個網站不難! 佈置好首頁的版面也不難! 對我來說反而是[電子報訂閱]難倒我了。 光是找一個外掛看人家推薦一堆電子報程式,我光選就消耗掉我2天時間了! 總算選好要使用的程式下在完畢,才是接下來困難的地方。 我好死不死選到了
Thumbnail
這幾天我一直沒有發布新文章,是因為我一直在研究電子報訂閱的事情。其實架設一個網站不難! 佈置好首頁的版面也不難! 對我來說反而是[電子報訂閱]難倒我了。 光是找一個外掛看人家推薦一堆電子報程式,我光選就消耗掉我2天時間了! 總算選好要使用的程式下在完畢,才是接下來困難的地方。 我好死不死選到了
Thumbnail
礙於我的個人網站二月跟三月都讓我去跟主機商詢問問題,外加未來等過了優惠價,年度的支出會增加,還有每次看到旅遊、美食部落客都有使用,上次還有店家問我怎麼沒有想用pixnet,讓我覺得有點無法回嘴。
Thumbnail
礙於我的個人網站二月跟三月都讓我去跟主機商詢問問題,外加未來等過了優惠價,年度的支出會增加,還有每次看到旅遊、美食部落客都有使用,上次還有店家問我怎麼沒有想用pixnet,讓我覺得有點無法回嘴。
Thumbnail
剛剛檢視我的文章 追蹤人數 好難看 沒有打中大眾心 反而是交友軟體 點擊率最高 那我接下來一系列 狂分享交友軟體 骯髒事? 為了流量? 好的交友軟體 是個趨勢 因為現代人 太寂寞 但太多交友軟體 充滿情色 亂 愛滋? 軟體只是個媒介 識人 需要時間 觀
Thumbnail
剛剛檢視我的文章 追蹤人數 好難看 沒有打中大眾心 反而是交友軟體 點擊率最高 那我接下來一系列 狂分享交友軟體 骯髒事? 為了流量? 好的交友軟體 是個趨勢 因為現代人 太寂寞 但太多交友軟體 充滿情色 亂 愛滋? 軟體只是個媒介 識人 需要時間 觀
Thumbnail
產出一篇 SEO 文章時應該要注意什麼?如何下筆? 此篇文章以我過去的經驗,分享有關 SEO 文章內容產出的架構與邏輯。
Thumbnail
產出一篇 SEO 文章時應該要注意什麼?如何下筆? 此篇文章以我過去的經驗,分享有關 SEO 文章內容產出的架構與邏輯。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News