---
# System prepended metadata

title: useWindowSize 記錄與監視window的狀態

---

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}
```

這樣就可以做切換了。希望這篇文章能幫到你！再見啦！