大家都能看得懂的原始碼

2022-08-30 15:04:54

本文是深入淺出 ahooks 原始碼系列文章的第十五篇,該系列已整理成檔案-地址。覺得還不錯,給個 star 支援一下哈,Thanks。

本篇接著針對關於 DOM 的各個 Hook 封裝進行解讀。

useFullscreen

管理 DOM 全螢幕的 Hook。

該 hook 主要是依賴 screenfull 這個 npm 包進行實現的。

選擇它的原因,估計有兩個:

  • 它的相容性好,相容各個瀏覽器的全螢幕 API。
  • 簡單,包體積小。壓縮後只要 1.1 k。

大概介紹幾個它的 API。

  • .request(element, options?)。使一個元素全螢幕顯示。預設元素是 <html>
  • .exit()。退出全螢幕。
  • .toggle(element, options?)。假如目前是全螢幕,則退出,否則進入全螢幕。
  • .on(event, function)。新增一個監聽器,用於當瀏覽器切換到全螢幕或切換出全螢幕或出現錯誤時。event 支援 'change' 或者 'error'。另外兩種寫法:.onchange(function).onerror(function)
  • .isFullscreen。判斷是否是全螢幕。
  • .isEnabled。判斷當前環境是否支援全螢幕。

來看該 hook 的封裝:

首先是 onChange 事件中,判斷是否是全螢幕,從而觸發進入全螢幕的函數或者退出全螢幕的函數。
當退出全螢幕的時候,解除安裝 change 事件。

const { onExit, onEnter } = options || {};
// 退出全螢幕觸發
const onExitRef = useLatest(onExit);
// 全螢幕觸發
const onEnterRef = useLatest(onEnter);
const [state, setState] = useState(false);

const onChange = () => {
  if (screenfull.isEnabled) {
    const { isFullscreen } = screenfull;
    if (isFullscreen) {
      onEnterRef.current?.();
    } else {
      screenfull.off('change', onChange);
      onExitRef.current?.();
    }
    setState(isFullscreen);
  }
};

手動進入全螢幕函數,支援傳入 ref 設定需要全螢幕的元素。並通過 screenfull.request 進行設定,並監聽 change 事件。

// 進入全螢幕
const enterFullscreen = () => {
  const el = getTargetElement(target);
  if (!el) {
    return;
  }

  if (screenfull.isEnabled) {
    try {
      screenfull.request(el);
      screenfull.on('change', onChange);
    } catch (error) {
      console.error(error);
    }
  }
};

退出全螢幕方法,呼叫 screenfull.exit()

// 退出全螢幕
const exitFullscreen = () => {
  if (!state) {
    return;
  }
  if (screenfull.isEnabled) {
    screenfull.exit();
  }
};

最後通過 toggleFullscreen,根據當前狀態,呼叫上面兩個方法,達到切換全螢幕狀態的效果。

// 切換模式
const toggleFullscreen = () => {
  if (state) {
    exitFullscreen();
  } else {
    enterFullscreen();
  }
};

useHover

監聽 DOM 元素是否有滑鼠懸停。

主要實現原理是監聽 mouseenter 觸發 onEnter 事件,切換狀態為 true,監聽 mouseleave 觸發 onLeave 事件,切換狀態為 false。程式碼簡單,如下:

export default (target: BasicTarget, options?: Options): boolean => {
  const { onEnter, onLeave } = options || {};
  const [state, { setTrue, setFalse }] = useBoolean(false);
  // 通過監聽 mouseenter 判斷有滑鼠懸停
  useEventListener(
    'mouseenter',
    () => {
      onEnter?.();
      setTrue();
    },
    {
      target,
    },
  );

  // mouseleave 沒有滑鼠懸停
  useEventListener(
    'mouseleave',
    () => {
      onLeave?.();
      setFalse();
    },
    {
      target,
    },
  );

  return state;
};

useDocumentVisibility

監聽頁面是否可見。

這個 hook 主要使用了 Document.visibilityState 這個 API。先簡單看下這個 API:

Document.visibilityState (唯讀屬性), 返回document的可見性, 即當前可見元素的上下文環境。由此可以知道當前檔案 (即為頁面) 是在背後, 或是不可見的隱藏的分頁,或者 (正在) 預渲染。可用的值如下:

  • 'visible' : 此時頁面內容至少是部分可見. 即此頁面在前景分頁中,並且視窗沒有最小化。
  • 'hidden' : 此時頁面對使用者不可見。即檔案處於背景分頁或者視窗處於最小化狀態,或者作業系統正處於 '鎖屏狀態' 。
  • 'prerender' : 頁面此時正在渲染中,因此是不可見的。檔案只能從此狀態開始,永遠不能從其他值變為此狀態。

典型用法是防止當頁面正在渲染時載入資源,或者當頁面在背景中或視窗最小化時禁止某些活動。

最後看這個 hook 的實現就很簡單了:

  • 通過 document.visibilityState 判斷是否可見。
  • 通過 visibilitychange 事件,更新結果。
const getVisibility = () => {
  if (!isBrowser) {
    return 'visible';
  }
  //  Document.visibilityState (唯讀屬性), 返回document的可見性, 即當前可見元素的上下文環境。
  return document.visibilityState;
};

function useDocumentVisibility(): VisibilityState {
  const [documentVisibility, setDocumentVisibility] = useState(() => getVisibility());

  useEventListener(
    // 監聽該事件
    'visibilitychange',
    () => {
      setDocumentVisibility(getVisibility());
    },
    {
      target: () => document,
    },
  );
  return documentVisibility;
}

本文已收錄到個人部落格中,歡迎關注~