iT邦幫忙

2024 iThome 鐵人賽

DAY 12
0

在上一篇文章中,我們介紹了如何使用 React Router 實現頁面導航,並展示了模組化頁面如何提升程式碼的結構性與可維護性。今天,我們將進一步學習如何從零開始打造一個功能完備的 React 導航欄元件,包括響應式設計、動態導航高亮、筆刷效果,以及通過 Sass 的 map-get 函數來管理多種主題樣式,確保在不同設備和主題模式下都能提供優秀的用戶體驗。

為什麼要打造導航欄元件?

導航欄是每個網站的核心部分。打造一個可重用、模組化的導航欄元件能夠讓你在開發中保持一致的設計風格和功能特性,這在大型應用或多頁網站中特別重要。重用導航欄元件不僅能減少重複的開發工作,還能讓你專注於其他複雜的業務邏輯,從而大幅提升開發效率和可維護性。以下是實際的業界案例:

  1. Airbnb:Airbnb 通過其設計系統 DLS(Design Language System)提供了統一的導航欄元件。這不僅縮短了 50% 的開發時間,還減少了 75% 的設計審查時間,確保設計與功能一致,極大提升了跨團隊協作的效率。
  2. IBM:IBM 的 Carbon Design System 包含了導航欄等常見元件,幫助開發者提升了約 40% 的開發速度。這套設計系統讓所有跨平台產品保持一致,降低了維護成本,並且顯著改善了用戶體驗。

這些案例表明,通過設計導航欄元件庫,能夠有效提高開發速度,確保產品一致性,並長期降低維護成本,最終帶來顯著的經濟效益。

設計目標

我們的目標是創建一個具有以下功能的 React 導航欄元件:

  1. 響應式設計:在桌面和移動設備上適應不同的佈局。
  2. 主題切換:使用 useTheme() 和 Sass 的 map-get 函數來管理多主題樣式,靈活應對深淺模式。
  3. 動態導航高亮:根據當前頁面,自動高亮對應的導航項目。
  4. 筆刷效果:為選中的導航項目添加視覺吸引力的筆刷背景效果。

這些功能確保導航欄在各種設備與主題模式下均具一致性和美觀性。

Day12

如果對 themeButton不熟悉,建議回顧 Day 7:進階模組化設計之 ThemeProvider 應用

實際演練

Step 1: 新增導航欄元件基本架構

首先,我們將新增導航欄元件 Navbar.jsx 的基礎結構,包含品牌區域和主題切換按鈕。我們使用 useTheme() 來管理主題切換,並根據當前主題顯示不同的品牌圖片。

//src/components/navBar/navBar.jsx 

const { isDarkMode } = useTheme();  // 獲取當前主題狀態

// 根據主題模式選擇 logo
const logo_light = require('@/assets/logo_light.svg');  // 淺色模式下的 Logo
const logo_dark = require('@/assets/logo_dark.svg'); // 深色模式下的 Logo

return (
    <div className={`${styles.navbar}
    ${isDarkMode ? styles.darkMode : styles.lightMode}`
    } >
        {/* 導航欄品牌區域 */}
        <div className={styles.navbar_brand}>
            <a href="/" className="navbar-brand">
                <img src={`${isDarkMode ? logo_dark : logo_light}`
                } alt="Luma Logo" />
            </a>
        </div>

        {/* 側邊菜單區域 */}
       
        {/* 主題切換按鈕 */}
        <ThemeButton className={styles.themeButton} />
        
        {/* 漢堡菜單按鈕 */}

    </div >
)

品牌的 Logo 固定在導航欄的左側,主題切換按鈕固定在右側。透過 Flexbox 的 justify-content: space-between,我們可以讓品牌區域和主題切換按鈕自動分佈在兩端。而 align-items: center 有兩個主要作用:

  1. navbar 中,它讓整個導航欄內的元素(如 Logo 和按鈕)在垂直方向上居中。
  2. navbar_brand 中,它確保品牌區域內的元素(如 Logo)也在垂直方向上對齊。

margin-left: auto 負責將主題切換按鈕推到最右側,最終達到導航欄整體平衡的佈局效果。

.navbar {
    display: flex;
    justify-content: space-between; // 左右對齊
    align-items: center; // 垂直方向居中
    padding: 10px 20px; // 內邊距,保持內容與邊框之間的距離
    height: 70%;
    max-width: 80%;
    margin: 5px auto;
}

.navbar_brand {
    display: flex;
    align-items: center; // 保證 Logo 與按鈕垂直居中
}

.themeButton {
    margin-left: auto; // 將主題按鈕推到右邊
}

這樣的設計確保了導航欄內元素的平衡佈局,無論在淺色模式還是深色模式下,都能夠保持一致的視覺效果。

Step 2: 使用 Sass map-get 進行主題設置

接下來,我們在 _theme.scss 使用 Sass 的 map-get 函數實現動態主題切換。map-get 讓我們能夠根據當前的主題模式,從預定義的主題地圖中提取對應的樣式。這樣我們就不需要硬編碼顏色值,減少了重複且更靈活地控制樣式。

$themes: (
    "light-mode": ("primary": #FFC0CB, // 淡粉色,用於Basic Stage按鈕
        "secondary": #E6E6FA, // 淡紫色,用於Advanced Stage按鈕
        "background-primary": #FFFFF0, // 淡黃色, 白色背景
        "highlight": #FFD700, // 金黃色,用於亮點或特定強調部分
        "text-primary": #1C1C1C, // 深灰色文字,用於標題和主要文字
        "text-secondary": #A9A9A9, // 中灰色,用於次要文字
        "button-text": #1C1C1C, // 按鈕文字黑色
    ),

    "dark-mode": ("primary": #4A90E2, // 亮藍色,用於Basic Stage按鈕
        "secondary": #F5A623, // 亮橙色,用於Advanced Stage按鈕
        "background-primary": #2C3E50, // 深藍色背景
        "highlight": #FFC107, // 淡黃色,用於月亮或亮點
        "text-primary": #FFFFFF, // 白色文字,用於標題和主要文字
        "text-secondary": #90A4AE, // 淡灰色文字,用於次要文字
        "button-text": #FFFFFF, // 按鈕文字白色
    )
);

Navbar.module.scss 文件中,為 navbar 元件定義了基於主題的樣式。這裡使用 @mixinmap-get 結合的方式,依據當前主題來自動應用樣式,例如動態設定邊框的顏色。

.navbar {
    //...其他實作
    
    // 根據主題應用樣式的 Mixin
    @mixin theme-container($theme) {
        border-bottom: 2px solid map-get(map-get($themes, $theme), "text-primary");
        border: 2px solid map-get(map-get($themes, $theme), "text-primary");
    }

    // light-mode 樣式
    &.lightMode {
        @include theme-container("light-mode");
    }

    // dark-mode 樣式
    &.darkMode {
        @include theme-container("dark-mode");
    }
}

Step 3: 新增響應式菜單

我們新增了菜單項目,並使用 menuItems 陣列來定義每個項目及其對應的鏈接。透過 React 的 useState 來管理當前選中的菜單項目,並通過 activeLink 動態地為選中的項目添加 active 樣式,從而實現當前頁面的高亮效果。此外,為了支援響應式設計,我們實現了漢堡菜單切換功能。

const Navbar = () => {
		const [activeLink, setActiveLink] = useState(0); // 控制當前選中的菜單項目
    const [isMenuOpen, setIsMenuOpen] = useState(false); // 控制漢堡菜單開關
    
    //...其他實作

    // 定義選單項目
    const menuItems = [
        { name: 'Home', link: '#home' },
        { name: 'About', link: '#about' },
        { name: 'Services', link: '#services' },
        { name: 'Contact', link: '#contact' }
    ];

    return (
        <div className={`${styles.navbar}
        ${isDarkMode ? styles.darkMode : styles.lightMode}`
        } >
            {/* ...其他實作 */}
                  
            {/* 側邊菜單區域 */}
            <div className={`${styles.sideMenu} ${isMenuOpen && styles.menuOpen}`}>
                <div className={styles.navbarLinks}>
                    {menuItems.map((item, index) => (
                        <li key={index} >
                            <a
                                href={item.link}
                                className={activeLink === index ? styles.active : ''}
                                onClick={() => setActiveLink(index)}
                            >
                                {item.name}
                            </a>
                        </li>
                    ))}
                </div>
                {/* 主題切換按鈕 */}
                <ThemeButton className={styles.themeButton} />
            </div>
            {/* 漢堡菜單按鈕 */}
            <button className={styles.hamburgerMenu} onClick={toggleMenu}>
                {isMenuOpen ? '✕' : '☰'}
            </button>
        </div>
    );
};

export default Navbar;

首先,我們使用 @media 查詢確保側邊選單在螢幕寬度小於 768px 時可以隱藏並在需要時顯示。當點擊漢堡菜單時,透過 menuOpen 顯示側邊選單,這樣可以在行動裝置上提供更佳的使用者體驗。

關鍵點:

  1. 響應式設計: 當螢幕寬度小於 768px 時,側邊選單 (.sideMenu) 將自動隱藏,並以漢堡菜單替代,用於控制選單的顯示與隱藏。這一功能主要通過在 @media (max-width: 768px) 中設置側邊選單的屬性 display: none 並配合 menuOpen 的狀態來實現。
  2. 使用 Flexbox 進行佈局: 在小螢幕裝置上,將側邊選單的佈局屬性設置為 flex-direction: column 並配合 align-items: center,將選單項目從原本的水平排列改為垂直排列,確保響應式設計在不同裝置上保持一致的佈局效果。

.sideMenu {
    display: flex;
    justify-content: space-between; // 預設為讓選單內容均勻分佈 
    align-items: center;
    flex-grow: 1;
}

.hamburgerMenu {
    display: none;
    font-size: 24px;
    background: none;
    border: none;
    cursor: pointer;
    color: inherit;
}

.themeButton {
    margin-left: auto; // 讓ThemeButton獨立靠右對齊 
}

@media (max-width: 768px) {

    .sideMenu {
        display: none;
        position: absolute;
        top: 55px;
        right: 0;
        margin-right: 5%;
        width: 30%;
        flex-direction: column;
        gap: 10px;
        padding: 20px;
        align-items: center;

    }
    
    .menuOpen {
        display: flex;
    }

    .hamburgerMenu {
        display: block;
        transition: transform 0.3s ease-in-out;

        &:active {
            transform: scale(0.9); // 點擊時縮小
        }
    }
}

Step 4: 添加筆刷效果

最後,我們將為導航欄的選中項目添加一個筆刷效果。這個效果是通過 CSS 偽元素 ::after 來實現的,當某個菜單項目被選中時,在其文字下方會出現一個動態的筆刷背景,增加視覺吸引力。

.navbarLinks li a {
    position: relative; // 使文字相對定位,偽元素能相對於它定位

    // 當菜單項目被選中時
	&.active::after {
      content: '';
      position: absolute;
      left: 0;
      bottom: 0; // 確保偽元素貼合文字底部
      width: 100%;
      height: 0.5em; // 根據需要調整高度,這裡使用字體高度的一半
      background-color: map-get(map-get($themes, $theme), "primary"); // 被選中時的顏色; 
      border-radius: 4px; // 使背景稍微圓潤,模仿筆刷效果
      opacity: 0.6; // 調整透明度,使背景不會過於明顯
      transition: width 0.3s ease; // 添加動畫效果
    }
}
  • ::after 偽元素:用於生成筆刷背景。利用position: absolute; 將偽元素定位在文字下方,height: 0.5em; 則控制了背景的高度,類似筆刷的效果。
  • 背景顏色和圓角:使用 map-get 從主題配置中動態獲取顏色,並通過 border-radius 來模仿筆刷的圓潤效果。
  • 動畫過渡transition: width 0.3s ease; 為偽元素的展開添加平滑動畫,使筆刷效果更加流暢。

這樣的筆刷效果可以為當前選中的菜單項目增加動態視覺效果,提升整體導航欄的互動性和美觀度。

結語

今天,我們成功地打造了一個功能齊全的 React 導航欄元件,包含響應式設計、動態導航高亮、筆刷效果以及主題切換等功能。通過當前的主題管理方式,我們能夠應對不同設備上的多種場景,為用戶提供一致的體驗。

然而,隨著應用的擴展,特別是在處理複雜的Figma 設計稿時,使用 Sass map-get 管理多主題變得難以維護。每次修改主題都需要處理大量重複的樣式代碼,這讓開發變得非常繁瑣。你是否也遇到過類似的挑戰?

https://ithelp.ithome.com.tw/upload/images/20240912/20168330SivtJOaep0.png

接下來,我們將採用 CSS 變數 替代 map-get,讓主題切換更靈活並更易於維護,以便能夠應對設計稿中的複雜需求,並在多個頁面中輕鬆實現動態主題切換。

此外,我們已將完整的代碼實作與更多練習題上傳至 GitHub,鼓勵大家前往查看,並回顧文章中的概念,挑戰更進階的優化練習。
👉 前往 GitHub 的 v0.12.0-responsive-navbar 查看完整程式碼


流光館Luma<∕> ✨ 期待與你繼續探索更多技術知識!



上一篇
Day 11: 用 React Router 實現頁面導航
下一篇
Day 13: 告別 Sass map-get,用 CSS 變數簡化主題管理
系列文
從PM到前端開發:我的React作品集之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言