useWindowSize 記錄與監視window的狀態 === ![image](https://hackmd.io/_uploads/rkxZSZNbR.png) ## *這不是插件! 這是我自己創的一個 customHook,因為很好用所以分享. 如有疑問歡迎分享想法讓這個hook更加精簡! --- ## 介紹: 這個 customHook 是用來監視: - current 視窗的**寬度**和**高度** - 是否已經Ready - scrollY目前高度 - bottomNavbar在指定條件出現或消失(客制) ## 用途: - 用視窗大小判斷特定JS去處理desktop web component 還是 mobile web 的component. - 用視窗高低來計算需要整個網頁高度 - 用來記錄scrollY,在特定的條件下去進行切換UI達到華麗的UX表現. - 用WindowReady 來判斷網頁render過程. - (客制)bottomNavbar的表現判斷 ## 程式: ```jsx interface WindowSizeProps { width: number; height: number; scrollY: number; isWindowReady: boolean; isAppear: boolean; } export const useWindowSize = () =>{ // 這裡可以做一些whitelist的事情 //例如某些page,bottomNavbar就不顯示。 const [windowSize, setWindowSize] = useState<WindowSizeProps>({ width: 0, height: 0, scrollY: 0, isWindowReady: false, isAppear: false, }); const lastScrollLocation = useRef<number>(0); const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null); useEffect(()=>{ const handleScroll = () =>{ if(lastScrollLocation.current >= window.scrollY || window.scrollY <= 0 ){ // scrolling up keep display setWindowSize((prevWindowSize)=>({ ...prevWindowSize, scrollY: window.scrollY, isAppear: true, })); }else { setWindowSize((prevWindowSize)=>({ ...prevWindowSize, scrollY:window.scrollY, isAppear:false, })); } lastScrollLocation.current = window.scrollY; clearTimeout(scrollTimeoutRef.current!); scrollTimeoutRef.current = setTimeout(()=>{ setWindowSize((prevWindowSize)=>({ ...prevWindowSize, isAppear:false, })); },200) }; const handleSize = () =>{ if(window.visualViewport){ setWindowSize((prevWindowSize)=>({ ...prevWindowSize, width: window?.visualViewport?.width ?? 0, height:window?.visualViewport?.height ?? 0, scrollY: window.scrollY, isWindowReady: true, })); } }; if(typeof window !== "undefined"){ window.addEventListener("resize", handleSize); window.addEventListener("scroll",handleScroll); handleSize(); return () =>{ window.removeEventListener("resize", handleSize); window.removeEventListener("scroll",handleScroll); clearTimeout(scrollTimeoutRef.current!); } } },[]); return windowSize; } ``` ## 講解: 1. 完成定義 windowSizeProps 類型。 2. 初始化狀態,如果有什麼想監聽window的狀態,都可以往裡面加。 3. 設置引用和定時器,置於為什麼用useRef呢: - **引用持久性**:useRef提供了一個容器,其.current屬性可用於儲存任何可變值。並且這個值在組件的所有渲染週期內保持不變。這對於跟踪不需要觸發渲染的數據來說是非常有用的。 - **避免不必要的渲染**:用useState來存儲定時器ID或非其他非狀態邏輯(如滾動位置)會在更新這些值時觸發組件的重新渲染。因為這個值的變化不需要去影響渲染,所以選用useRef在更新其current的時候才不會觸發重新渲染,對性能優化是有益的。 - **跨渲染週期的數據共享**: useRef 可以跨渲染週期保持數據,即使組件重新渲染,儲存在 useRef 中的數據也可以保持不變。 - **DOM引用**: useRef 通常用來引用組件內的DOM節點。當然不僅限於DOM引用,也適用於保持任何可變值。 4. handleScroll的第一個判斷是用來改變 isAppear 而誕生的,主要是在這裡紀錄和索取是最快的。如果 scroll up 的話,就讓套用 isAppear 的顯現出來,反則是消失。在這個專案裡我是搭配 transform:translateY來做變化。 5. 然後每次 lastScrollLocation 這個ref 來紀錄當下的scroll位置。 6. 然後就清楚定時器,這裡呢是要做如果 長期保持true,那就會自動藏起來。(optional,沒有一定要打開這個功能。) 7. 接下來呢,就定義handleScroll,很簡單就是把window的資料按照指定的名稱塞入其中就好了。 8. 功能寫好了,當然就是要運用了呀。打開 useEffect 然後判定如果window已經準備好了,那資料應該都有了吧?那就 run 吧!因為他們不是一直都需要執行的程式,所以記得 release 掉喔。 --- ## 總結: 這是連我家技術長都認為是有用的Hook,因為很多時候我們無法透過CSS去控制做切換的動作,而且很多地方會用得到,我就想說來做一個CustomHook 來監聽window目前的狀態。當然用最多的是 width 和 isAppear XDD。 如果要做navbar內容切換,我也推薦scrollY 去監聽到了某個高度就切換這樣。例如: ```jsx {scrollY > 1600 ? ( <div>....</div> ):null} ``` 這樣就可以做切換了。希望這篇文章能幫到你!再見啦!