CSS 滾動(dòng)驅(qū)動(dòng)動(dòng)畫實(shí)現(xiàn)圓弧滾動(dòng)條
前不久看到這樣一個(gè)很有趣的效果,它的滾動(dòng)條是沿著圓角邊緣滾動(dòng)的,效果如下
你可以查看原鏈接來(lái)體驗(yàn)一下
https://codepen.io/jh3y/pen/gOEgxbd。
這是如何實(shí)現(xiàn)的呢?
原效果中由于為了兼容不支持CSS滾動(dòng)驅(qū)動(dòng)的瀏覽器,特意用 JS做了兼容,所以看著比較復(fù)雜,其實(shí)核心非常簡(jiǎn)單,下面我將用最簡(jiǎn)短的 CSS 來(lái)復(fù)刻這一效果,一起看看吧!
一、SVG 路徑動(dòng)畫
從本質(zhì)上來(lái)講,其實(shí)是一個(gè) SVG 路徑動(dòng)畫。
具體如何實(shí)現(xiàn)呢?
首先,我們通過設(shè)計(jì)軟件繪制一個(gè)這樣的路徑。
注意設(shè)置描邊的大小還有端點(diǎn)的類型,比如下面是round效果。
然后導(dǎo)出SVG,可以得到這樣一段代碼。
<svg viewBox="0 0 31 433" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 4C9.96737 4 15.6903 6.37053 19.9099 10.5901C24.1295 14.8097 26.5 20.5326 26.5 26.5V406.5C26.5 412.467 24.1295 418.19 19.9099 422.41C15.6903 426.629 9.96737 429 4 429" stroke="black" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
然后,如何讓這段SVG動(dòng)起來(lái)呢?
很簡(jiǎn)單,現(xiàn)在SVG是一段實(shí)線,我們可以通過stroke-dasharray設(shè)置成虛線,比如:
path{
stroke-dasharray: 80
}
這樣會(huì)得到一個(gè)實(shí)線和虛線間隔都是80的虛線。
如果希望虛線空白的地方更大一點(diǎn),該怎么設(shè)置呢?很簡(jiǎn)單,繼續(xù)往后加。
path{
stroke-dasharray: 80 120
}
效果如下:
所以,這種寫法其實(shí)相當(dāng)于把當(dāng)前的值無(wú)限重復(fù),示意如下:
當(dāng)然,我們這里不需要設(shè)置的這么復(fù)雜,只需要一小段實(shí)線就夠了,所以是實(shí)現(xiàn)加上一段足夠長(zhǎng)的虛線(超過路徑本身就行),實(shí)現(xiàn)如下:
path{
stroke-dasharray: 80 1000
}
這樣就得到了一小段實(shí)線。
那么,如何讓他動(dòng)起來(lái)呢?很簡(jiǎn)單,改變一下偏移就可以,這個(gè)可以用stroke-dashoffset來(lái)實(shí)現(xiàn)。
比如:
@keyframes scroll {
to {
stroke-dashoffset: -370
}
}
path{
stroke-dasharray: 80 1000;
animation: scroll 3s alternate-reverse infinite;
}
效果如下:
是不是有點(diǎn)像呢?
我們?cè)僬{(diào)整一下起始偏移量,讓它出去一點(diǎn)。
@keyframes scroll {
0% { stroke-dashoffset: 75; }
100% { stroke-dashoffset: -445; }
}
這樣就更接近我們想要的效果了。
整個(gè)運(yùn)動(dòng)原理就是這樣了,接著往下看
二、CSS 滾動(dòng)驅(qū)動(dòng)動(dòng)畫
接下來(lái)需要通過滾動(dòng)驅(qū)動(dòng)動(dòng)畫將容器滾動(dòng)與CSS動(dòng)畫「聯(lián)動(dòng)」起來(lái)。
簡(jiǎn)單來(lái)講,「CSS 滾動(dòng)驅(qū)動(dòng)動(dòng)畫」指的是將「動(dòng)畫的執(zhí)行過程由頁(yè)面滾動(dòng)」進(jìn)行接管,也就是這種情況下,「動(dòng)畫只會(huì)跟隨頁(yè)面滾動(dòng)的變化而變化」,也就是滾動(dòng)多少,動(dòng)畫就執(zhí)行多少,「時(shí)間不再起作用」。
先簡(jiǎn)單布局一下:
<div class="list">
<div class="item" id="item_1">1</div>
<div class="item" id="item_2">2</div>
<div class="item" id="item_3">3</div>
<div class="item" id="item_4">4</div>
<div class="item" id="item_5">5</div>
<div class="item" id="item_6">6</div>
<div class="item" id="item_7">7</div>
</div>
美化一下:
然后,我們將默認(rèn)的滾動(dòng)條隱藏,用我們這個(gè) SVG
路徑來(lái)代替,由于需要絕對(duì)定位,我們?cè)偬滓粚痈讣?jí)。
<div class="wrap">
<div class="list">
<div class="item" id="item_1">1</div>
<div class="item" id="item_2">2</div>
<div class="item" id="item_3">3</div>
<div class="item" id="item_4">4</div>
<div class="item" id="item_5">5</div>
<div class="item" id="item_6">6</div>
<div class="item" id="item_7">7</div>
<!--滾動(dòng)條-->
<svg class="scroller" viewBox="0 0 31 433" fill="none" xmlns="http://www.w3.org/2000/svg">
<path class="scroller_thumb" d="M4 4C9.96737 4 15.6903 6.37053 19.9099 10.5901C24.1295 14.8097 26.5 20.5326 26.5 26.5V406.5C26.5 412.467 24.1295 418.19 19.9099 422.41C15.6903 426.629 9.96737 429 4 429" stroke="black" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
</div>
相關(guān)CSS如下:
.wrap{
position: relative;
}
.scroller {
position: absolute;
top: 0;
bottom: 0;
right: 0;
pointer-events: none;
height: -webkit-fill-available;
margin: 5px;
}
.scroller_thumb{
stroke: hsl(0 0% 100% / 0.5);
stroke-dasharray: 80 450;
stroke-width: 8px;
animation: scroll both 5s linear;
}
這樣結(jié)構(gòu)就搭好了,只是滾動(dòng)條會(huì)自動(dòng)播放。
接下來(lái)就是最關(guān)鍵的一步,加上滾動(dòng)驅(qū)動(dòng)動(dòng)畫。
.scroller_thumb{
animation: scroll both 5s linear;
animation-timeline: scroll();
}
但是這樣是不起作用的,直接使用scroll()會(huì)自動(dòng)尋找它的相對(duì)父級(jí),也就是.wrap,但實(shí)際滾動(dòng)的其實(shí)是.list,所以這種情況下我們需要具名的滾動(dòng)時(shí)間線,實(shí)現(xiàn)如下:
.list{
scroll-timeline: --scroller;
}
.scroller_thumb{
animation: scroll both 5s linear;
animation-timeline: --scroller;
}
這樣SVG路徑動(dòng)畫就能跟隨容器滾動(dòng)而運(yùn)動(dòng)了。
三、CSS 滾動(dòng)吸附
原效果中還有一個(gè)滾動(dòng)回彈的效果,當(dāng)滾動(dòng)到容器邊緣時(shí),會(huì)自動(dòng)回彈到起始位置。
其實(shí)只需要用到 CSS scroll snap 就可以了。
https://developer.mozilla.org/zh-CN/docs/Web/CSS/scroll-snap-type。
實(shí)現(xiàn)很簡(jiǎn)單,給滾動(dòng)容器添加scroll-snap-type屬性,表示這是個(gè)允許滾動(dòng)吸附的容器。
.list{
scroll-snap-type: y mandatory;
}
然后就指定需要吸附的點(diǎn)了,由于需要回彈的效果,所以滾動(dòng)容器的首尾需要一個(gè)空白的容器,這里直接用兩個(gè)偽元素來(lái)生成
.list::before,
.list::after{
content: '';
height: 50px;
flex-shrink: 0;
}
效果如下:
然后我們?cè)O(shè)置滾動(dòng)吸附點(diǎn)就行了,設(shè)置第一個(gè)元素頂部和最后一個(gè)元素底部,其他元素居中就行了。
.item{
scroll-snap-align: center;
}
.item:first-child{
scroll-snap-align: start;
}
/*最后一個(gè)元素是 SVG,所以這里用倒數(shù)第二個(gè)元素*/
.item:nth-last-child(2){
scroll-snap-align: end;
}
這樣就實(shí)現(xiàn)了文章開頭的效果了。
完整代碼可以查看以下鏈接(無(wú)任何 JS)
- CSS round scroll (juejin.cn)[1]
- CSS round scroll (codepen.io)[2]
四、總結(jié)一下
總的來(lái)說,CSS滾動(dòng)驅(qū)動(dòng)在滾動(dòng)交互上帶來(lái)了無(wú)限可能,很多以前必須借助 JS來(lái)實(shí)現(xiàn)的都可以輕易實(shí)現(xiàn),下面總結(jié)一下。
- 從本質(zhì)上來(lái)講,右側(cè)的滾動(dòng)條其實(shí)是一個(gè) SVG 路徑動(dòng)畫。
- SVG路徑可以通過stroke-dasharray設(shè)置虛實(shí)間隔。
- 當(dāng)虛線間隔足夠長(zhǎng)時(shí),超過路徑本身,就能得到一小塊實(shí)線。
- 通過改變stroke-dashoffset偏移能夠?qū)崿F(xiàn)路徑描邊動(dòng)畫。
- 借助 CSS滾動(dòng)驅(qū)動(dòng)動(dòng)畫可以將SVG路徑動(dòng)畫跟隨容器滾動(dòng)而運(yùn)動(dòng)。
- 滾動(dòng)回彈效果其實(shí)就是CSS scroll snap實(shí)現(xiàn)的。
[1]CSS round scroll (juejin.cn): https://code.juejin.cn/pen/7326425332964130856。
[2]CSS round scroll (codepen.io): https://codepen.io/xboxyan/pen/WNmjZLo。