讓路徑動(dòng)畫(huà)更好用!CSS offset-path現(xiàn)在也支持基本形狀了

分享一個(gè) offset-path 新特性
大家有使用過(guò)offset-path嗎?沒(méi)用過(guò)不要緊,相信大家都見(jiàn)過(guò)這種酷炫的路徑動(dòng)畫(huà),這種就可以用offset-path來(lái)實(shí)現(xiàn)。

demo 來(lái)源:https://codepen.io/ahmadbassamemran/pen/bXByBv
隨著 CSS的不斷發(fā)展,最近在Chrome 116中,offset-path也支持基本形狀了,也就是常見(jiàn)的inset、circle、polygon等等,有了這些形狀的支持,路徑動(dòng)畫(huà)寫(xiě)起來(lái)更加方便了,一起了解一下吧。
一、過(guò)去僅支持 path
先簡(jiǎn)單介紹一下offset-path的用法。offset-path是用來(lái)實(shí)現(xiàn)路徑動(dòng)畫(huà)的,所以前提是需要準(zhǔn)備好路徑。這里的路徑可以在支持 SVG的設(shè)計(jì)軟件中繪制,比如Figma。

這是我用鋼筆工具隨便勾勒的一條路徑,先準(zhǔn)備好放在一邊。
現(xiàn)在來(lái)一點(diǎn)布局。
<div class="con path">
</div>我們用偽元素來(lái)作為偏移路徑的元素。
.con{
position: relative;
width: 300px;
height: 200px;
background-color: #FFEFC5;
border-radius: 8px;
flex-shrink: 0;
}
.con::before{
position: absolute;
content: '??';
width: 40px;
height: 40px;
border-radius: 8px;
display: grid;
place-content: center;
background-color: #3E65FF;
color: #fff;
z-index: 2;
}現(xiàn)在效果如下:

沒(méi)什么特別的,我們現(xiàn)在加上offset-path,如何添加呢?我們需要一段path路徑,剛才我們?cè)贔igma上繪制的圖形,可以直接導(dǎo)出SVG。

可以得到這樣一段代碼。
<svg width="300" height="200" viewBox="0 0 300 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 39.567C36.479 34.0777 82.9341 33.7783 104.922 76.4954C132.407 129.892 117.317 157.339 192.766 154.844C253.126 152.848 280.072 87.1417 286 54.5383" stroke="black" stroke-width="7"/>
</svg>我們只需要將path里面的d屬性值拿出來(lái)就行了,就像這樣。
.path::before{
offset-path: path("M16 39.567C36.479 34.0777 82.9341 33.7783 104.922 76.4954C132.407 129.892 117.317 157.339 192.766 154.844C253.126 152.848 280.072 87.1417 286 54.5383")
}現(xiàn)在效果如下:

為啥是歪的呢?這是因?yàn)槁窂降钠鹗嘉恢镁褪沁@樣,我們可以把這個(gè) SVG 也放到 html 中,順便改一下描邊。
<div class="con path">
<svg width="300" height="200" viewBox="0 0 300 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 39.567C36.479 34.0777 82.9341 33.7783 104.922 76.4954C132.407 129.892 117.317 157.339 192.766 154.844C253.126 152.848 280.072 87.1417 286 54.5383" stroke="#FF336F" stroke-dasharray="2 2"/>
</svg>
</div>剛好位于起點(diǎn)處。

現(xiàn)在我們給個(gè)動(dòng)畫(huà),讓它從起點(diǎn)運(yùn)動(dòng)到終點(diǎn),只需要改變offset-distance就行了。
.con::before{
/* */
animation: offset 3s linear infinite;
}
@keyframes offset {
to {
offset-distance: 100%;
}
}這樣就得到了一個(gè)最基礎(chǔ)的路徑動(dòng)畫(huà)了。

是不是非常簡(jiǎn)單呢?
二、path 的局限性
前面的path雖然靈活,但是不好維護(hù),而且一些基本形狀也必須要轉(zhuǎn)成path才行。
比如,要沿著一個(gè)圓形來(lái)運(yùn)動(dòng),我們可以在Figma中繪制一個(gè)圓。

如果我們直接復(fù)制這個(gè)SVG會(huì)得到這樣一段代碼。
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="100" fill="#FFD75A"/>
</svg>Figma還是挺聰明的,自動(dòng)識(shí)別到了這是一個(gè)圓,所以得到了circle這個(gè)標(biāo)簽。但這種結(jié)構(gòu)之前是無(wú)法使用的,我們需要的是path,因此要轉(zhuǎn)一下。
在Figma中,可以用Flatten將圖形“扁平化”,也就是讓這個(gè)形狀失去基本形狀特性,變成一個(gè)普通的路徑。

這樣我們就能得到帶path的SVG代碼了。
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M200 100C200 155.228 155.228 200 100 200C44.7715 200 0 155.228 0 100C0 44.7715 44.7715 0 100 0C155.228 0 200 44.7715 200 100Z" fill="#FFD75A"/>
</svg>然后用在offset-path中。
.path::before{
offset-path: path("M200 100C200 155.228 155.228 200 100 200C44.7715 200 0 155.228 0 100C0 44.7715 44.7715 0 100 0C155.228 0 200 44.7715 200 100Z")
}雖然也能實(shí)現(xiàn),但是一眼看上去,完全不知道是什么形狀。
其次,path還有一個(gè)問(wèn)題,就是不支持自適應(yīng)尺寸,因?yàn)槔锩娴闹刀际枪潭ǖ模瑹o(wú)法動(dòng)態(tài)去改變,比如我們希望這個(gè)圓能盡可能大的撐滿整個(gè)容器,path就無(wú)法實(shí)現(xiàn)這樣的效果。

因此,為了解決這樣的問(wèn)題,現(xiàn)在也支持基本形狀了。
三、現(xiàn)在支持基本形狀了
所謂基本形狀,就是一種表現(xiàn)基礎(chǔ)圖形的 CSS 數(shù)據(jù)類(lèi)型,適用于clip-path、shape-outside和offset-path。
https://developer.mozilla.org/en-US/docs/Web/CSS/basic-shape
其實(shí)就是這幾類(lèi)
- 圓 circle()
- 橢圓 ellipse()
- 矩形 inset()、rect()、xywh()
- 多邊形 polygon()
這里面有些大家可能已經(jīng)在clip-path中用到過(guò)了,所以這里不會(huì)詳細(xì)介紹每個(gè)語(yǔ)法的詳細(xì)用法,有興趣可以在官網(wǎng)自行查看。
1. circle
首先來(lái)看圓。語(yǔ)法很簡(jiǎn)單。
circle( <shape-radius>? [ at <position> ]? )前面的shape-radius是圓的半徑,可以是長(zhǎng)度單位或者百分比,還支持closest-side和farthest-side關(guān)鍵詞,后面的position表示圓心位置(默認(rèn)居中)。
offset-path: circle(50px);
offset-path: circle(20% at right center);
offset-path: circle(closest-side);
offset-path: circle(farthest-side);其中,closest-side表示距離邊緣「最近」時(shí)的半徑,farthest-side表示距離邊緣「最遠(yuǎn)」時(shí)的半徑,這一點(diǎn)和徑向漸變中是類(lèi)似的。

實(shí)際使用來(lái)看看,還是上面的結(jié)構(gòu)。
<div class="con circle">給偽元素一個(gè)offset-path。
.circle::before{
offset-path: circle(); /*默認(rèn) closest-side */
animation: offset 3s linear infinite;
}效果如下:

而且這個(gè)路徑是自適應(yīng)的,可以自動(dòng)跟隨外部容器的變化而變化,比如將這個(gè)高度改小一些。

是不是比path實(shí)現(xiàn)要靈活很多呢?
2. ellipse
橢圓和圓比較類(lèi)似,只是多了一個(gè)半徑,就不贅述了。
ellipse( [ <shape-radius>{2} ]? [ at <position> ]? )我們直接看代碼。
<div class="con ellipse">.ellipse::before{
offset-path: ellipse(); /*默認(rèn) closest-side closest-side */
animation: offset 3s linear infinite;
}效果如下:

3.inset
inset表示矩形,并且支持圓角。
inset( <length-percentage>{1,4} [ round <`border-radius`> ]? )前面有 4 個(gè)值,分別表示距離上、右、下、左的距離,如下:

并且支持圓角,這樣要實(shí)現(xiàn)一個(gè)圓角矩形的路徑動(dòng)畫(huà)就很方便了。
<div class="con inset"></div>.inset::before{
offset-path: inset(20px round 16px);
}效果如下:

還有兩個(gè)函數(shù),rect()和xywh()也能實(shí)現(xiàn)矩形,只是方式不一樣,這個(gè)以后再做介紹。
4. polygon
這個(gè)相信大家都很熟悉了,用來(lái)繪制多邊形的。
比如我們要繪制一個(gè)三角形,只需要指定三個(gè)點(diǎn)就行了,如下:

在offset-path中也是如此。
<div class="con polygon">
<svg width="300" height="200" viewBox="0 0 300 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<polygon points="150,0 300,200 0,200" stroke="#FF336F" stroke-dasharray="2 2"/>
</svg>
</div>.polygon::before{
offset-path: polygon( 50% 0,100% 100%, 0 100%);
}效果如下:

5.其實(shí)也還支持 url
可以直接用一段 svg作為路徑偏移,更加直觀。比如在 Figma 中繪制一個(gè)五角星。

我們直接復(fù)制出SVG放到頁(yè)面上。
<div class="con url">
<svg width="300" height="200" viewBox="0 0 300 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="svgPath" d="M64.8437 68.8473C65.2788 68.5288 65.7911 68.2949 66.3695 68.1793L66.4675 68.6697C66.6996 68.6233 66.9456 68.5983 67.2051..." fill="#C1A4FF" fill-opacity="0.76" stroke="#FF336F" stroke-dasharray="2 2"/>
</svg>
</div>給這段 path 一個(gè) id為svgPath,然后可以直接這么使用。
.url::before{
offset-path: url('#svgPath');
}這樣一來(lái),SVG既可以用于展示,又可以用于offset-path了,效果如下:

以上所有demo可以查看以下鏈接
- CSS offset-path (codepen.io)[1]
- CSS offset-path (juejin.cn)[2]
幾個(gè)基本形狀就這些了,接下來(lái)看一個(gè)實(shí)際應(yīng)用
四、圓弧菜單展開(kāi)效果
有了基本形狀的支持,可以很方便的實(shí)現(xiàn)一些有意思的效果,比如這樣的菜單展開(kāi)

其實(shí)就是一個(gè)圓形路徑動(dòng)畫(huà)。首先來(lái)看結(jié)構(gòu)。
<button class="menu-toggle" id="menu-toggle" popovertarget="menu-items">
?
</button>
<menu class="menu-items" id="menu-items" popover anchor="menu-toggle">
<li class="item">
<button>??</button>
</li>
<li class="item">
<button>??</button>
</li>
<li class="item">
<button>??</button>
</li>
<li class="item">
<button>??</button>
</li>
<li class="item">
<button>??</button>
</li>
</menu>這里是通過(guò) popover 來(lái)控制打開(kāi)和收起的,不清楚popover的可以參考之前這篇文章:原生 popover 終于來(lái)了!
我們給每個(gè)子菜單加上路徑偏移 offset-path。
.item {
offset-path: circle(80px);
}效果如下:

為啥只有一個(gè)子菜單呢?這是因?yàn)樗械牟藛味贾丿B在了一起,我們需要分散開(kāi)來(lái)。
由于圓形路徑動(dòng)畫(huà)是順時(shí)針?lè)较虻模拖襁@樣。

所以我們需要將 5 個(gè)子元素平均分配到半個(gè)圓弧上,如下:

用代碼實(shí)現(xiàn)就是。
.item:nth-child(1) {
offset-distance: 100%;
}
.item:nth-child(2) {
offset-distance: 87.5%;
}
.item:nth-child(3) {
offset-distance: 75%;
}
.item:nth-child(4) {
offset-distance: 62.5%;
}
.item:nth-child(5) {
offset-distance: 50%;
}給每個(gè)元素分別設(shè)置不同的offset-distance后,就變成了這樣。

最后,只要在打開(kāi)菜單時(shí)設(shè)置不同的延時(shí),如下:
.menu-items:not(:popover-open) {
.item:nth-child(1) {
--delay: 0s;
}
.item:nth-child(2) {
--delay: 0.1s;
}
.item:nth-child(3) {
--delay: 0.2s;
}
.item:nth-child(4) {
--delay: 0.3s;
}
.item:nth-child(5) {
--delay: 0.4s;
}就能得到我們想要的展開(kāi)效果了。

完整代碼可以查看:Radial Menu Popover remix using offset-path: circle() (codepen.io)[3]。
五、兼容性和總結(jié)
這個(gè)是 Chrome 116推出的新特性,目前還不是特別好,尤其是Safari拖了后腿,兼容性如下:

所以大規(guī)模使用還是需要等待一段時(shí)間,下面總結(jié)一下本文要點(diǎn)。
- 酷炫的路徑動(dòng)畫(huà)可以用offset-path來(lái)實(shí)現(xiàn)。
- 之前僅支持 path(),雖然靈活,但是不好維護(hù),不直觀,而且一些基本形狀也必須要轉(zhuǎn)成path才行。
- path()不支持自適應(yīng)尺寸。
- 基本形狀是一種表現(xiàn)基礎(chǔ)圖形的 CSS 數(shù)據(jù)類(lèi)型,適用于clip-path、shape-outside和offset-path,語(yǔ)法都是通用的。
- 基本形狀主要有 circle()、ellipse()、inset()、polygon()。
- 現(xiàn)在還在支持 url(),可以直接用一段 svg作為路徑偏移,更加直觀。
[1]CSS offset-path (codepen.io): https://codepen.io/xboxyan/pen/rNgaxNR。
[2]CSS offset-path (juejin.cn): https://code.juejin.cn/pen/7367709756003516457。
[3]Radial Menu Popover remix using offset-path: circle() (codepen.io): https://codepen.io/cssgrid/pen/zYmeLam。
































