一篇帶你學習 CSS 實現卷軸滾動效果
「慶余年2」 終于開播了~最近起點讀書APP內上架了慶余年典藏書,最大的特色是里面新加入了全新的閱讀皮膚,一個擬物化的卷軸滾動效果,效果如下:
就是在拖動頁面時,卷軸會隨著頁面的滾動而展開或卷起,就像在拖動真的畫布一樣,非常舒適,錄屏可能看著不是很清晰,強烈建議去端內自行體驗。
當時看到這個效果時就在思考,如何在 web 中也實現這樣一個效果呢?
經過一番琢磨,發現僅使用 CSS 就能完成這樣的效果。下面是我復刻的效果。
這是如何實現呢?一起看看吧
一、CSS卷軸滾動的原理
首先 CSS 中并沒有真正的 3d 滾動,立方體還可以勉強拼接,這種圓形的不行,因此我們需要用其他方式來實現。
這里其實是一個最簡單的平移動畫,只需要將紋理上下無縫平移,結合漸變和陰影,就能得到看似滾動的效果了。
先簡單布局一下。
<div class="reel">
<div class="reel-bg"></div>
</div>
這里要用到卷軸的素材圖片,是這樣的。
兩邊保留,中間拉伸的一個自適應結構,可以用到border-image,劃分出需要拉伸的地方即可。
有關border-image的詳細教程可以自行搜索,這里就不展開了,具體實現如下:
.reel{
position: relative;
height: 28px;
margin: 0 15px;
border-image: url("卷軸.png") fill 42 36/14px 12px/0 12px;
}
這樣就能得到卷軸的結構了。
接下來就是紋理了,素材是這個。
我們把這個素材放在卷軸容器了,做上下無限平移動畫。
.reel-bg{
position: absolute;
left: 0;
width: 100%;
height: 368px;
background: url("紋理.jpg") 50% 0/auto 50%;
animation: scroll 3s linear forwards infinite;
}
@keyframes scroll {
0%{
transform: translateY(-50%);
}
100% {
transform: translateY(-0%);
}
}
效果如下:
現在看著非常扁平,沒有立體感,主要是紋理沒有融入到背景之中。
如何將紋理完美的融合到后面的背景呢?沒錯,需要用到混合模式,這里用正片疊底就行了。
.reel-bg{
/**/
mix-blend-mode: multiply;
}
效果就好很多了。
最后給卷軸加點陰影,超出隱藏。
.reel{
/**/
overflow: hidden;
box-shadow: 0 5px 10px 5px rgba(0,0,0,.3), 0 10px 20px 10px rgba(0,0,0,.5);
}
這樣就比較真實了。
滾動效果出來了,如何和頁面滾動關聯起來呢?接著往下看。
二、CSS滾動驅動動畫
回到這里,先把整個布局完善一下。
<body>
<div class="reel">
<div class="reel-bg"></div>
</div>
<article>
<p>范慎很困難地撐著上眼皮,看著指頭算自己這輩子做過些什么有意義的事情,結果右手五根瘦成筷子一樣的指頭還沒有數完,他就嘆了一口氣,很傷心地放棄了這個工作。病房里的藥水味總是這么刺鼻,旁邊那床的老爺子前兩天已經去地藏王菩薩那里報道了,大概再過幾天就輪到自己吧。他得了某種怪病,重癥肌無力,就是特別適合言情小說男主角的那種病。據說沒得醫,將來嗝屁的那天什么都動不了,只有眼淚可以流下來。痛!</p>
<p>范慎很困難地撐著上眼皮,看著指頭算自己這輩子做過些什么有意義的事情,結果右手五根瘦成筷子一樣的指頭還沒有數完,他就嘆了一口氣,很傷心地放棄了這個工作。病房里的藥水味總是這么刺鼻,旁邊那床的老爺子前兩天已經去地藏王菩薩那里報道了,大概再過幾天就輪到自己吧。他得了某種怪病,重癥肌無力,就是特別適合言情小說男主角的那種病。據說沒得醫,將來嗝屁的那天什么都動不了,只有眼淚可以流下來。痛!</p>
<!--很多文本-->
</article>
</body>
簡單修飾一下,由于卷軸要固定到頂部,可以采用sticky布局。
html{
background-color: #22312D;
font-family: cursive;
}
body{
margin: 0;
}
article{
background-color: #F5EBD4;
padding: 1em 0.5em;
border-left: 10px solid #405C53;
border-right: 10px solid #405C53;
margin: 0 15px;
}
p{
margin: 0;
padding: 0.2em 0;
color: #2C402E;
line-height: 150%;
text-indent: 2em;
}
h1{
text-align: center;
color: #F5EBD4;
}
效果如下:
不過這里還有點問題,由于是整個頁面在滾動,內容滾到頂部會漏出來,如下:
所以還需要找個東西遮擋一下,這里我們直接用偽元素實現,設置相同的背景色就行了。
html::before{
content: '';
display: block;
height: 30px;
background: inherit;
position: sticky;
top: 0;
}
由于是sticky
,也不用關注層級問題,效果如下:
現在就讓卷軸滾動和頁面滾動聯動起來, 非常簡單,只需要添加animation-timeline屬性就行了,設置滾動時間線為root,如下:
.reel-bg{
animation: scroll 1s linear forwards;
animation-timeline: scroll(root);
}
這樣在頁面滾動時卷軸也跟著“轉動”了。
但看著卷軸轉地是不是有點慢了?
確實是這樣,這個時候表示頁面從頭滾到到底部,執行一次動畫,也就是滾動一圈,所以頁面內容越多,滾動距離越長,那么卷軸轉的也就越慢。
下面來修復這個問題
三、優化卷軸滾動速度
簡單來處理,可以給個合適的動畫次數,比如:
.reel-bg{
animation: scroll 1s linear forwards 5;
animation-timeline: scroll(root);
}
這樣表示頁面從頭滾到到底部,會執行5次動畫,也就是相當于會滾動5圈,所以看著速度就變快了。
現在就舒服多了。
不過這種處理方式有個問題,動畫次數是跟內容長度強相關的,如果在向下滾動時動態加載內容,就需要更新這個值,稍顯麻煩。
那么,有沒有辦法讓滾動速度保持均衡呢?也就是無論內容多少,速度都是一致的。
這就需要用到 CSS 動畫范圍區間了,也就是animation-range,簡單來講就是設置滾動區間。
默認情況下,滾動區間就是從頁面頂部滾動到底部,我們可以手動改變這個范圍,比如我們設置一個比較大的值。
.reel-bg{
--s: 999999;
animation: scroll 1s linear forwards;
animation-timeline: scroll(root);
animation-range: 0 calc(var(--s) * 1px);
}
這個表示頁面滾動從0滾動到999999px時,上面的卷軸會滾動一圈。這顯然不行,我們需要動態去計算滾動的圈數,就是動畫播放次數。
.reel-bg{
animation-iteration-count: calc(var(--s)/184/3.14);
}
為啥是這個值呢?簡單解釋一下,184是這個卷軸上下的平移距離,然后再除以圓周率3.14,這樣看著會更加自然。
這樣就能完美實現卷軸滾動了,無論內容長短,滾動速度都是一致的。
核心實現其實就這幾行,是不是非常簡單。
.reel-bg{
--s: 999999;
animation: scroll 1s linear forwards calc(var(--s)/184/3.14);
animation-timeline: scroll(root);
animation-range: 0 calc(var(--s) * 1px);
}
四、不支持瀏覽器兼容
雖然 CSS
實現很簡單,但是兼容性還不行,截至目前(2024年4月27日)僅支持Chrome 115+。
所以實際生成中不能直接使用,需要降級處理。
簡單的降級是不支持的不執行動畫,這個只需要用@supports查詢一下就行了。
@supports (animation-timeline: scroll()) {
.reel-bg{
--s: 999999;
animation: scroll 1s linear forwards calc(var(--s)/184/3.14);
animation-timeline: scroll(root);
animation-range: 0 calc(var(--s) * 1px);
}
}
這樣在不支持的瀏覽器,比如 Safari 下就不會自動播放動畫了。
如果也想實現卷軸滾動效果,那就需要借助JS的力量,其實實現也不難,就是找到頁面的scrollTop和紋理的上下平移關系,做個映射就好了,具體實現如下:
if (!CSS.supports('animation-timeline: scroll()')){
console.log('不支持animation-timeline')
const bg = document.querySelector('.reel-bg')
window.addEventListener('scroll',function(){
console.log(this.scrollY)
bg.style.transform = `translateY(${this.scrollY / Math.PI % 184 / 368 * 100 - 80}%)`
})
}
下面是在Safari中的效果,也能完美支持了。
你也可以訪問以下鏈接查看真實效果。
- CSS QYN book (juejin.cn)[1]
- CSS QYN book (codepen.io)[2]
五、總結一下
以上就是本文的全部內容了,一個有趣的交互效果,你學到了嗎,下面總結一下本文重點。
- CSS卷軸滾動的原理其實是一個最簡單的平移動畫,只需要將紋理上下無縫平移,結合漸變和陰影,就能得到看似滾動的效果了。
- 簡單的平移動畫看著非常扁平,沒有立體感,主要是紋理沒有融入到背景之中。
- 利用混合模式正片疊底,可以讓平移動畫看著像滾動動畫了。
- CSS 滾動驅動動畫可以讓頁面滾動和卷軸滾動聯動起來。
- 默認情況下,頁面從頭滾到到底部,執行一次動畫,也就是滾動一圈,所以頁面內容越多,滾動距離越長,卷軸轉的也就越慢。
- 可以通過改變動畫重復次數來調整卷軸滾動速度,局限是需要根據頁面滾動長度來計算合適的數值。
- 用足夠大的 CSS 動畫范圍區間(animation-range)配合動畫重復次數可以實現卷軸滾動速度保持均衡。
- 目前兼容性還有些差,可以用CSS.supports做兼容處理。
[1]CSS QYN book (juejin.cn): https://code.juejin.cn/pen/7362400098958966793。
[2]CSS QYN book (codepen.io): https://codepen.io/xboxyan/pen/VwNqEoE。