」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > 使用 React Hooks 建立可存取的導覽功能表欄

使用 React Hooks 建立可存取的導覽功能表欄

發佈於2024-08-25
瀏覽:756

Building an Accessible Navigation Menubar with React Hooks

不小心发表了!请稍后回来了解更多!

介绍

创建可访问的 Web 应用程序不仅是一种好的做法,而且现在是一种必要。最近,我有机会构建一个专注于 a11y 的导航菜单栏。当我进行研究时,我意识到大多数菜单栏都不符合 ARIA 模式。例如,您是否知道应该使用箭头键导航菜单栏并管理其自己的焦点,而不是通过选项卡浏览菜单项?

虽然我确实找到了一些教程,但我最终没有完全遵循它们。我写这篇文章是因为我认为我最终构建的内容值得分享 - 如果您也对小型组件和自定义挂钩有兴趣。

虽然我将通过一些开发步骤来构建此博客,但我的目标不是编写分步指南。我相信您了解 React 基础知识以及自定义钩子的工作原理。

我现在只分享关键的实现细节,但我计划将来当我有更多时间时用代码沙箱示例更新本文。

我们正在建设什么?

对于这个博客,我们正在构建一个导航菜单栏,就像您在许多网络应用程序的顶部或侧面看到的那样。在此菜单栏中,某些菜单项可能有子菜单,这些子菜单将在鼠标进入/离开时打开/关闭。

HTML 标记

首先,语义 HTML 和适当的角色以及 ARIA 属性对于可访问性至关重要。对于菜单栏模式,您可以从此处的官方文档中阅读更多内容。

以下是适当 HTML 标记的示例:

请注意,我们正在使用语义 HTML 的按钮标签。该按钮还应该有 aria-haspopup 来提醒屏幕阅读器。最后,应根据菜单状态分配适当的 aria-expanded 属性。

成分

让我们看看我们需要的组件。显然,我们需要一个整体菜单组件,以及一个菜单项组件。

有些菜单项有子菜单,有些则没有。带有子菜单的菜单项需要管理其状态,以便在悬停和键盘事件时打开/关闭子菜单。所以它需要有自己的组件。

子菜单也需要是它自己的组件。尽管子菜单也只是菜单项的容器,但它们不管理其状态或处理键盘事件。这将它们与顶级导航菜单区分开来。

我最终编写了这些组件:

  • NavMenu 用于菜单栏的最外层。
  • MenuItem 用于单个菜单项。
    • 菜单项链接
    • MenuItemWithSubMenu
  • SubMenu 用于展开的子菜单。 MenuItem 可以递归嵌套在子菜单中。

焦点管理

用非常简单的话说,“焦点管理”只是意味着组件需要知道哪个孩子拥有焦点。因此,当用户的焦点离开并返回时,先前聚焦的子级将重新聚焦。

焦点管理的常用技术是“Roving Tab Index”,其中组中焦点元素的 Tab 索引为 0,其他元素的 Tab 索引为 -1。这样,当用户返回焦点组时,选项卡索引为 0 的元素将自动获得焦点。

NavMenu 的第一个实现可能如下所示:

export function NavMenu ({ menuItems }) {
  // state for the currently focused index
  const [focusedIndex, setFocusedIndex] = useState(0);

  // functions to update focused index
  const goToStart = () => setCurrentIndex(0);
  const goToEnd = () => setCurrentIndex(menuItems.length - 1);
  const goToPrev = () => {
    const index = currentIndex === 0 ? menuItems.length - 1 : currentIndex - 1;
    setCurrentIndex(index);
  };
  const goToNext = () => {
    const index = currentIndex === menuItems.length - 1 ? 0 : currentIndex   1;
    setCurrentIndex(index);
  };

  // key down handler according to aria specification
  const handleKeyDown = (e) => {
    e.stopPropagation();
    switch (e.code) {
      case "ArrowLeft":
      case "ArrowUp":
        e.preventDefault();
        goToPrev();
        break;
      case "ArrowRight":
      case "ArrowDown": 
        e.preventDefault();
        goToNext();
        break;
      case "End":
        e.preventDefault();
        goToEnd();
        break;
      case "Home":
        e.preventDefault();
        goToStart();
        break;
      default:
        break;
    }
  }

  return (
    
  );
}

e.preventDefault() 是为了防止 ArrowDown 滚动页面之类的事情。

这是 MenuItem 组件。让我们暂时忽略带有子菜单的项目。当 focusIndex 发生变化时,我们使用 useEffect、usePrevious 和 element.focus() 来聚焦于元素:

export function MenuItem ({ item, index, focusedIndex, setFocusedIndex }) {
  const linkRef = useRef(null);
  const prevFocusedIndex = usePrevious(focusedIndex);
  const isFocused = index === focusedIndex;

  useEffect(() => {
    if (linkRef.current 
      && prevFocusedIndex !== currentIndex 
      && isFocused) {
      linkRef.current.focus()
    }
  }, [isFocused, prevFocusedIndex, focusedIndex]);

  const handleFocus = () => {
    if (focusedIndex !== index) {
      setFocusedIndex(index);
    }
  };

  return (
    
  • {item.label}
  • ); }

    请注意,a 标签应该具有 ref (带有子菜单的菜单项的按钮),因此当它们被聚焦时,默认键盘行为将按预期启动,例如 Enter 上的导航。此外,根据焦点元素正确分配选项卡索引。

    我们正在为焦点事件添加一个事件处理程序,以防焦点事件不是来自键/鼠标事件。这是网络文档的引用:

    不要假设所有焦点更改都将通过按键和鼠标事件实现:屏幕阅读器等辅助技术可以将焦点设置到任何可聚焦元素。

    调整#1

    如果您遵循上述 useEffect,您会发现即使用户没有使用键盘进行导航,第一个元素也会获得焦点。为了解决这个问题,我们可以检查活动元素,并且仅在用户启动某些键盘事件时调用 focus() ,这会将焦点从 body 上移开。

      useEffect(() => {
        if (linkRef.current 
          && document.activeElement !== document.body // only call focus when user uses keyboard navigation
          && prevFocusedIndex !== focusedIndex
          && isCurrent) {
          linkRef.current.focus();
        }
      }, [isCurrent, focusedIndex, prevFocusedIndex]);
    

    逻辑重用和自定义 Hook

    到目前为止,我们已经有了功能性的 NavMenu 和 MenuItemLink 组件。让我们继续讨论带有子菜单的菜单项。

    当我快速构建它时,我意识到这个菜单项将共享大部分逻辑

    版本聲明 本文轉載於:https://dev.to/godsamit/building-an-accessible-navigation-menubar-with-react-hooks-blh?1如有侵犯,請聯絡[email protected]刪除
    最新教學 更多>
    • 增強您的 Web 動畫:像專業人士一樣最佳化 requestAnimationFrame
      增強您的 Web 動畫:像專業人士一樣最佳化 requestAnimationFrame
      流畅且高性能的动画在现代 Web 应用程序中至关重要。然而,管理不当可能会使浏览器的主线程过载,导致性能不佳和动画卡顿。 requestAnimationFrame (rAF) 是一种浏览器 API,旨在将动画与显示器的刷新率同步,从而确保与 setTimeout 等替代方案相比更流畅的运动。但有效...
      程式設計 發佈於2024-11-06
    • 為什麼MySQL伺服器在60秒內就消失了?
      為什麼MySQL伺服器在60秒內就消失了?
      MySQL 伺服器已消失- 恰好在60 秒內在此場景中,之前成功運行的MySQL 查詢現在遇到了60 秒後逾時,顯示錯誤「MySQL 伺服器已消失」。即使調整了 wait_timeout 變量,問題仍然存在。 分析:超時正好發生在 60 秒,這表明是設置而不是資源限制是原因。直接從 MySQL 客戶...
      程式設計 發佈於2024-11-06
    • 為什麼帶有“display: block”和“width: auto”的按鈕無法拉伸以填充其容器?
      為什麼帶有“display: block”和“width: auto”的按鈕無法拉伸以填充其容器?
      了解具有“display: block”和“width: auto”的按鈕的行為當您設定“display: block”時一個按鈕,它會調整其佈局以佔據可用的整個寬度。但是,如果將其與“width: auto”結合使用,則按鈕會出現意外行為,並且無法拉伸以填充其容器。此行為源自於按鈕作為替換元素的基...
      程式設計 發佈於2024-11-06
    • 為 Bluesky Social 創作機器人
      為 Bluesky Social 創作機器人
      How the bot will work We will develop a bot for the social network Bluesky, we will use Golang for this, this bot will monitor some hashtags ...
      程式設計 發佈於2024-11-06
    • 為什麼 PHP 的浮點運算會產生意外的結果?
      為什麼 PHP 的浮點運算會產生意外的結果?
      PHP 中的浮點數計算精度:為什麼它很棘手以及如何克服它在PHP 中處理浮點數時,這一點至關重要了解其固有的準確性限制。如程式片段所示:echo("success");} else {echo("error");} 您可能會驚訝地發現,儘管值之間的差異小於0....
      程式設計 發佈於2024-11-06
    • Python中可以透過變數ID逆向取得物件嗎?
      Python中可以透過變數ID逆向取得物件嗎?
      從 Python 中的變數 ID 擷取物件參考Python 中的 id() 函數傳回物件的唯一識別。人們很容易想知道是否可以反轉此過程並從其 ID 取得物件。 具體來說,我們想要檢查取消引用變數的ID 是否會擷取原始物件:dereference(id(a)) == a瞭解引用的概念及其在Python...
      程式設計 發佈於2024-11-06
    • Go 的 Defer 關鍵字如何在函數執行順序中發揮作用?
      Go 的 Defer 關鍵字如何在函數執行順序中發揮作用?
      了解 Go 的 Defer 關鍵字的功能使用 Go 時,了解 defer 關鍵字的行為至關重要。此關鍵字允許開發人員推遲函數的執行,直到周圍的函數返回。但是,需要注意的是,函數的值和參數在執行 defer 語句時進行評估。 範例:評估 Defer Order為了說明這一點,請考慮以下內容代碼:pac...
      程式設計 發佈於2024-11-06
    • WordPress Gutenberg 全域狀態管理初學者指南
      WordPress Gutenberg 全域狀態管理初學者指南
      构建复杂的 WordPress 块编辑器 (Gutenberg) 应用程序时,有效管理状态变得至关重要。这就是 @wordpress/data 发挥作用的地方。它允许您跨 WordPress 应用程序中的不同块和组件管理和共享全局状态。 如果您不熟悉管理全局状态或使用@wordpress/data,...
      程式設計 發佈於2024-11-06
    • 亞馬遜解析簡單且完全由您自己完成
      亞馬遜解析簡單且完全由您自己完成
      I came across a script on the Internet that allows you to parse product cards from Amazon. And I just needed a solution to a problem like that. I wrac...
      程式設計 發佈於2024-11-06
    • React JSX 如何在幕後轉換為 JavaScript
      React JSX 如何在幕後轉換為 JavaScript
      當您編寫 React 時,您會經常看到 JSX – 在 JavaScript 程式碼中看起來像 HTML 的語法。但你有沒有想過這段程式碼在瀏覽器中是如何運作的呢? 神奇之處在於:JSX 不是有效的 JavaScript!瀏覽器無法直接理解它。在幕後,像 Babel 這樣的工具介入將 JSX 轉換...
      程式設計 發佈於2024-11-06
    • 如何透過 CSS 變換實現傾斜:兩側傾斜
      如何透過 CSS 變換實現傾斜:兩側傾斜
      使用CSS 變換實現傾斜:傾斜兩側提供的圖像展示了一種有趣的傾斜效果,該效果使元素的兩個角都形成角度。若要使用 CSS 轉換重新建立此效果,請按照下列步驟操作:應用透視傾斜:若要新增透視,請使用下列 CSS屬性:transform: perspective(distance) rotateY(ang...
      程式設計 發佈於2024-11-06
    • Express.js 基礎:初學者指南 - Node.js 教學系列 - 第 10 部分
      Express.js 基礎:初學者指南 - Node.js 教學系列 - 第 10 部分
      介紹: 嘿!如果您是 Node.js 新手,您可能聽說過 Express.js——一個用於建立 Web 伺服器和 API 的輕量級、快速且靈活的框架。在本指南中,我將引導您了解 Express 的基礎知識,並向您展示入門是多麼容易。 準備好?讓我們開始吧! 1....
      程式設計 發佈於2024-11-06
    • Python:未來的語言
      Python:未來的語言
      在不断发展的技术领域,某些编程语言已经占据主导地位,并塑造了我们构建软件和与软件交互的方式。其中,Python 脱颖而出,它不仅获得了巨大的普及,而且还将自己定位为未来技术的关键工具。其简单性、多功能性和强大的库使 Python 成为从 Web 开发到数据科学、人工智能、自动化等各种应用程序的首选语...
      程式設計 發佈於2024-11-06
    • 如何在 PHP 中將 PDF 檔案儲存為 MySQL BLOB(帶有程式碼範例)?
      如何在 PHP 中將 PDF 檔案儲存為 MySQL BLOB(帶有程式碼範例)?
      使用PHP 將PDF 檔案儲存為MySQL BLOB使用PHP 在MySQL 中將PDF 檔案儲存為BLOB(二進位大物件)時,建議考慮在資料庫中儲存二進位資料的潛在缺點。但是,如果您選擇這樣做,可以採用以下方法:首先,定義一個包含整數 ID 欄位和名為 DATA 的 BLOB 欄位的資料表。 用於...
      程式設計 發佈於2024-11-06
    • 使用 React Router v6 在 React 中實作麵包屑
      使用 React Router v6 在 React 中實作麵包屑
      面包屑在网页开发中非常重要,因为它们为用户提供了一种方法来跟踪他们在我们网页中的当前位置,并帮助我们的网页导航。 在本指南中,我们将使用 React-router v6 和 Bootstrap 在 React 中实现面包屑。 React-router v6 是 React 和 React Nati...
      程式設計 發佈於2024-11-06

    免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

    Copyright© 2022 湘ICP备2022001581号-3