再續(xù)那些關于DOM的常見Hook封裝
本篇接著針對關于 DOM 的各個 Hook 封裝進行解讀。
useFullscreen
管理 DOM 全屏的 Hook。
該 hook 主要是依賴 screenfull[1] 這個 npm 包進行實現(xiàn)的。
選擇它的原因,估計有兩個:
- 它的兼容性好,兼容各個瀏覽器的全屏 API。
- 簡單,包體積小。壓縮后只要 1.1 k。
大概介紹幾個它的 API。
- .request(element, options?)。使一個元素全屏顯示。默認元素是<html>
- .exit()。退出全屏。
- .toggle(element, options?)。假如目前是全屏,則退出,否則進入全屏。
- .on(event, function)。添加一個監(jiān)聽器,用于當瀏覽器切換到全屏或切換出全屏或出現(xiàn)錯誤時。event 支持 'change' 或者 'error'。另外兩種寫法:.onchange(function)? 和.onerror(function)。
- .isFullscreen。判斷是否是全屏。
- .isEnabled。判斷當前環(huán)境是否支持全屏。
來看該 hook 的封裝:
首先是 onChange 事件中,判斷是否是全屏,從而觸發(fā)進入全屏的函數(shù)或者退出全屏的函數(shù)。當退出全屏的時候,卸載 ??change?
? 事件。
const { onExit, onEnter } = options || {};
// 退出全屏觸發(fā)
const onExitRef = useLatest(onExit);
// 全屏觸發(fā)
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);
}
};
手動進入全屏函數(shù),支持傳入 ref 設置需要全屏的元素。并通過 ??screenfull.request?
? 進行設置,并監(jiān)聽 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);
}
}
};
退出全屏方法,調(diào)用 screenfull.exit()。
// 退出全屏
const exitFullscreen = () => {
if (!state) {
return;
}
if (screenfull.isEnabled) {
screenfull.exit();
}
};
最后通過 toggleFullscreen,根據(jù)當前狀態(tài),調(diào)用上面兩個方法,達到切換全屏狀態(tài)的效果。
// 切換模式
const toggleFullscreen = () => {
if (state) {
exitFullscreen();
} else {
enterFullscreen();
}
};
?useHover
監(jiān)聽 DOM 元素是否有鼠標懸停。
主要實現(xiàn)原理是監(jiān)聽 mouseenter? 觸發(fā) onEnter 事件,切換狀態(tài)為 true,監(jiān)聽 mouseleave 觸發(fā) onLeave 事件,切換狀態(tài)為 false。代碼簡單,如下:
export default (target: BasicTarget, options?: Options): boolean => {
const { onEnter, onLeave } = options || {};
const [state, { setTrue, setFalse }] = useBoolean(false);
// 通過監(jiān)聽 mouseenter 判斷有鼠標懸停
useEventListener(
'mouseenter',
() => {
onEnter?.();
setTrue();
},
{
target,
},
);
// mouseleave 沒有鼠標懸停
useEventListener(
'mouseleave',
() => {
onLeave?.();
setFalse();
},
{
target,
},
);
return state;
};
useDocumentVisibility
監(jiān)聽頁面是否可見。
這個 hook 主要使用了 Document.visibilityState 這個 API。先簡單看下這個 API:
Document.visibilityState (只讀屬性), 返回document的可見性, 即當前可見元素的上下文環(huán)境。由此可以知道當前文檔 (即為頁面) 是在背后, 或是不可見的隱藏的標簽頁,或者 (正在) 預渲染。可用的值如下:
- 'visible' : 此時頁面內(nèi)容至少是部分可見. 即此頁面在前景標簽頁中,并且窗口沒有最小化。
- 'hidden' : 此時頁面對用戶不可見。即文檔處于背景標簽頁或者窗口處于最小化狀態(tài),或者操作系統(tǒng)正處于 '鎖屏狀態(tài)' 。
- 'prerender' : 頁面此時正在渲染中,因此是不可見的。文檔只能從此狀態(tài)開始,永遠不能從其他值變?yōu)榇藸顟B(tài)。
典型用法是防止當頁面正在渲染時加載資源,或者當頁面在背景中或窗口最小化時禁止某些活動。
最后看這個 hook 的實現(xiàn)就很簡單了:
- 通過 document.visibilityState 判斷是否可見。
- 通過 visibilitychange 事件,更新結果。
const getVisibility = () => {
if (!isBrowser) {
return 'visible';
}
// Document.visibilityState (只讀屬性), 返回document的可見性, 即當前可見元素的上下文環(huán)境。
return document.visibilityState;
};
function useDocumentVisibility(): VisibilityState {
const [documentVisibility, setDocumentVisibility] = useState(() => getVisibility());
useEventListener(
// 監(jiān)聽該事件
'visibilitychange',
() => {
setDocumentVisibility(getVisibility());
},
{
target: () => document,
},
);
return documentVisibility;
}
參考資料
[1]screenfull: https://www.npmjs.com/package/screenfull