[React][TS]滾動視差:畫面橫移效果

張庭瑋
8 min readJun 28, 2020

--

最近逛網站時看到一個簡單卻不錯的效果

使用滾輪想讓網頁上下移動時,畫面卻是左右移動,這是怎麼做到的呢?

其實網頁還是確實有在上下移動,只是螢幕被當前畫面佔滿,此時畫面配合滑鼠滾動來平移,造成一種滑鼠上下滾畫面卻左右動的錯覺

今天來試著寫這個功能並且元件化

1.原理

2.props

3.scroll函式

4.position的改變

1.原理

先來個示意圖

滾動的過程

紅色的框框是螢幕的可視範圍,綠色的則是螢幕上會出現的畫面的全部,藍色的則是元件的最外層container(用途為製造滾動的空間)

當紅色區塊向下移動(滑鼠向下滾動)時綠色區塊也要漸漸左移,滾動到底部時綠色的區塊的末端要剛好抵住container的右邊

實際上如何計算往下滾動時畫面要左移多少?

移動距離示意圖

橫向的綠線螢幕上出現的畫面總共的寬度,縱向的藍線代表container的高度,橫向的紅線代表傳入的元件左右移動的距離,縱向的紅線代表可視範圍上下移動的距離

這裡可以看得出來幾點

1.左右移動的全部距離為綠色的寬度減去一個螢幕寬

2.上下移動的距離為container的高度減去一個螢幕高

3.兩條紅線的長度要大於0才有我們期待的效果

(也就是這個區塊內要能滾動,而且要有超過一個螢幕寬的畫面供平移)

4.綠色區塊的寬度是出現的畫面總共的寬度,那藍色區塊的高度是怎麼 決定的?根據2的條件,藍色區塊的高度其實只需大於一個螢幕高就可以了,也就是要多高是可以自己設定的,根據上面那張圖可以看得出來,起點跟終點綠色區塊的位置是固定的,所以藍色高度會影響的,其實是綠色區塊的移動速度,如果太高就會動得很慢,太低則反之。

2.props

如果可以的話當然希望可以這樣用

只要傳入想要橫著動的區塊進去就可以使用

想歸想,還是該來考慮一下預設條件

props1:橫著移動的區塊

畫面當然是一定要有的

這個錯覺的成立,就是看不到上下滾動的背景,所以首先要有一個高度是剛好一個螢幕高,寬度超過一個螢幕的畫面

由於要計算左右移動的幅度,所以需要獲取傳入的displayed區塊的寬度,所以在這裡可以在外面包覆一層wrapper並且設置ref

由於畫面必須是滿版所以先幫wrapper預設高度

這樣便可以抓取元素的寬度

props2:container的高度

剛剛提到過這個值會影響滾動速度,應該要可以被設定,不設定時也該有預設值

一般人看到height時,基於css的習慣,應該會比較習慣100px,100vh之類的,所以這裡預設高度props的型態為string

基於css的習慣,height常填入px.vh,所以這裡預設型態為string

預設值的話,我採取的作法是計算元素寬度時給予相對螢幕同比例的高度

例如說傳入的displayed區塊有2.5個螢幕寬高度就是2.5個螢幕高

設置container高度的state
使用useEffect檢查height是否傳入,沒傳入則計算預設值

並將container高度以style方式寫入

此時最外層要設置overflow:hidden(畢竟不是要讓使用者真的左右滑動)
先在container設置scroll事件時使用的ref

3.scroll事件

先設置一個state來儲存滾動進度,以及計算進度的函式

監聽scroll事件,目標是每次滾動時更新滾動進度

由於使用的區塊不一定會擺在網頁的頂端,所以progress=0的點應該要是容器的頂點,而抓取容器的頂點則需要使用方才設置的container的ref

基於使用方便,設定progress為0~100而非0~1

這裡由於傳入props的的height是個字串無法直接用來運算,所以用container的ref來抓取height

滾動的進度即為目前畫面的scrollTop距離container頂點的高度除以總共能滾動的長度,這裡要注意上下progress=0~100的滾動範圍為container高度減去一個螢幕高

因此計算式為(目前螢幕的頂點-container頂點)/(container高度-一個螢幕高)

有了滾動進度後就可以讓displayed區塊左右移動,移動的距離共為displayed區塊寬度減去一個螢幕寬

設置wrapper寬度的state
在檢查是否有傳入height的地方一併儲存wrapper的寬度

在wrapper的style上計算該移動的百分比

這裡要注意displayed區塊能移動的距離並非displayed的寬度,尚須減去一個螢幕寬,也就是translateX(…)中的最大值並非100%

移動到最後時displayed區塊的最右端要貼在螢幕最右邊

所以移動的百分比要乘上 (wrapperWidth — innerWidth) / wrapperWidth這個修正量

4.position的改變

在還沒完全滑進這區塊時,畫面應該是會隨著滾動條移動的狀態(static),直到完全滑入(即畫面佔滿螢幕後)後切換為fixed

progress=0~100時position為fixed

這裡要注意當往下滑出這個元件的區塊時,不能像原本滑入前設定為position:static,因為會讓displayed區塊彈回上面去

此時該讓displayed區塊置於container的底部,這裡使用position:absolute+bottom:0

這時別忘了在container還要加上position:relative

最後的結果如下

用codepen來跑跑看

觀念不難,主要是要搞清楚如何將滾動的距離轉換成畫面橫移的幅度

像筆者一開始寫的時候沒有畫圖,一直沒想到移動的距離需扣掉一個螢幕,導致寫出來的元件沒達到預期的效果(displayed畫面還沒屏移完就滾出這區塊之類的問題)

希望能給觀看的人當作參考!

--

--

No responses yet