- 問題: 使用 Next.js 不使用 Remix 原因: 不考慮發送 Sending form
- 問題: 使用 pages dir 不使用 app dir 原因: 不考慮 fetch data ( server side components )
- 問題: 使用 Chakra-UI 不使用 MUI 原因: Chakra-UI 上手快,且方便客製化
- 在 _document.tsx 中 Head組件包裹 從 cdnfonts 引入的 apercu 字體
- 在 src/theme 中 指定 body and heading 字體
基本上完成專案初始化設定
- 先創建 src/components/Header 資料夾 原因: 應為會有 單一Logic 可以抽出來 做成獨立hook 所以選擇用資料夾 而不是 Header.tsx
- 在該 資料夾 下創建 index.tsx 刻出頁面上的樣子 即 置中 與 文字上的樣式設定
- 創建 local custom hook 即 src/components/Header/hooks/useScrollHidden.tsx 原因: 先 local 其他地方用到 在拉到 global (即 src/hooks)
- 撰寫 useScrollHidden
- 這個 custom hook
- 有一個參數為 hiddenPx, type為number
- 回傳一個物件裡面有一個屬性 hidden, type為boolean
- 當 scroll往下 且 scroll > hiddenPx 時 hidden為true,否則 hidden為false
- 實作細節
- 先用 useState(false) 建立 是否隱藏狀態 ( 預設false )
- 使用 useEffect 來進行 document.addEventListener 原因: 直接操作真實DOM 是 component sideEffect的一種
- 先建立一個變數 lastScrollTop = 0 代表 這是上次的 ScrollTop
- 撰寫Scroll eventHandler
- 先算出當下的scrollTop
- 當 scroll > lastScrollTop 且 scroll > hiddenPx 時 hidden設為true,否則 hidden設為false
- 在 useEffect 中 增加scroll監聽事件 document.addEventListener
- 在 useEffect 中 回傳 cleanup effect function 即回傳 移除scroll監聽事件 document.removeEventListener
- useEffect 的 dependent array 中帶入 hiddenPx 以確保Scroll eventHandler中的hiddenPx是最新的
- 回到 src/components/Header/index.tsx
- 引入 useScrollHidden(300)
- 使用 framer-motion 的 AnimatePresence component, 確保元素消失時動畫能流暢運行
- 透過 流程控制 即 !hidden && element 來取消元素顯示
- 撰寫動畫
- 初始狀態與離開狀態: translateY: -96px, opacity: 0
- 進入狀態: translateY: 0px, opacity: 1
- 先分析一下 特效參考
- hover 單一 card 放大 1.1倍左右
- 左上角先在上層 ( figma是右下角在上層 )
- 卡片先打開,在收和 translateX + skew + z-index
- 重點1. 打開比較慢,收和比較快,打開來時要停頓一下
- 重點2. skew 看起來是X軸 爾且只有當下在上面那張卡片需要執行skew
- 創建檔案 src/components/CardsShuffleBlock/index.tsx
- 開始實作
- const [lastOnTop, setLastOnTop] = useState(true); 設定變數 表示那張在上面的狀態
- 最外面容器加上 { transformStyle: "preserve-3d" }(如下說明) 以及 relative 給 子元素使用absolute 因為會用到 translateZ ( Z-Index 無法作為關鍵影格使用)
- 裡面兩張圖片皆使用 absolute 且 Hover 時候 放大1.1倍
- 先設定關鍵影格 右下圖在上 切換到 左上圖在上
- 左上圖 { translateX: ["0px", "-250px", "-250px", "-160px", "0px"], translateZ: ["0px", "0px", "0px", "250px", "250px"], }
- 右下圖 { translateX: ["0px", "250px", "250px", "160px", "0px"], translateZ: ["30px", "30px", "30px", "0px", "0px"], skewX: ["0deg", "0deg", "-15deg", "-15deg", "0deg"], }
- 即先打開 停留一下切skew持續這個skew狀態直到收和到某個程度 再 歸位
- 再設定關鍵影格 左上圖在上 切換到 右下圖在上
- 左上圖 { translateX: ["0px", "-250px", "-250px", "-180px", "0px"], translateZ: ["30px", "30px", "30px", "0px", "0px"], skewX: ["0deg", "0deg", "10deg", "10deg", "0deg"], }
- 右上圖 { translateX: ["0px", "250px", "250px", "180px", "0px"], translateZ: ["0px", "0px", "0px", "30px", "30px"], }
- 同 3.4.3
- 先滿版且維持長寬比例 使用 Chakra-UI AspectRatio
- 撰寫到一個 custom hook -> useIntersectionObserver
- 需求是 當進入區塊顯示一定比例做一些事情,且當沒進入時候也可以做一些事情
- 規格是 參數一是 要監聽的ref, 參數二是 Tuple陣列 Tuple第一個值代表比例,第二個值代表要執行的function,參數三是沒進入時候要做的事情
- 先建立 thresholds 即 參數二陣列中每個項目第一個值即顯示比例所組成的
- 定義 IntersectionObserverCallback
- 判斷有沒有相交,沒相交則執行參數三的 function
- 相交則 先立下一個 flag ( isCalled ) 代表是否已經呼叫過函數 即處裡完該事件
- 先把 參數二 陣列讀一遍 直到 進入比例 大於等於 陣列中的比例 執行 對應的 function 並把 isCalled 設為 true 來阻止之後的 function被執行
- 最後保障每次 觸發 IntersectionObserverCallback 都可以 紀錄 是否相交 (isIntersecting) 以及 進入比率 (intersectionRatio) 狀態,並作為該hook的回傳
- 定義該 參數二陣列 與 參數三 的 function, 會使用到 useMemo 和 useCallback 因為 useIntersectionObserver 的 useEffect 會去偵測是否變動參考
- 定義 ResizeHandler 與 MINIMUM_PLAY_WIDTH ( 720px ),其中 ResizeHandler 會用到 isIntersecting 和 intersectionRatio 因為 縮放時候可能需會繼續播放
- 撰寫第二個 custom hook -> useResizeObserver
- 需求是 當 該元素改變大小 就執行特定事情
- 使用 ResizeObserver 去監聽元素 並在元素大小發生改變時 觸發 ResizeHandler
補充1: useIntersectionObserver 可以使用 react-intersection-observer 代替 但我不熟 補充2: useIntersectionObserver 可以使用 framer-motion 的 useInView 代替 但 會創建很多 IntersectionObserver 出來 補充3: video onResize 沒反應 我才使用 ResizeObserver
- 創建 img List 跑 map 渲染出來即可
- figma 上面圖片不完整所以抓兩個一樣照片來充當4與5
- 撰寫 useWheel hook 綁事件 滑鼠滑動可以水平滾動 需要用到useEffect 因為 必須使用原生事件綁定 會寫到 { passive: false }