讓路徑動畫更好用!CSS offset-path現(xiàn)在也支持基本形狀了
分享一個 offset-path 新特性
大家有使用過offset-path嗎?沒用過不要緊,相信大家都見過這種酷炫的路徑動畫,這種就可以用offset-path來實現(xiàn)。
隨著 CSS的不斷發(fā)展,最近在Chrome 116中,offset-path也支持基本形狀了,也就是常見的inset、circle、polygon等等,有了這些形狀的支持,路徑動畫寫起來更加方便了,一起了解一下吧。
一、過去僅支持 path
先簡單介紹一下offset-path的用法。offset-path是用來實現(xiàn)路徑動畫的,所以前提是需要準備好路徑。這里的路徑可以在支持 SVG的設(shè)計軟件中繪制,比如Figma。
這是我用鋼筆工具隨便勾勒的一條路徑,先準備好放在一邊。
現(xiàn)在來一點布局。
<div class="con path">
</div>
我們用偽元素來作為偏移路徑的元素。
.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)在效果如下:
沒什么特別的,我們現(xiàn)在加上offset-path,如何添加呢?我們需要一段path路徑,剛才我們在Figma上繪制的圖形,可以直接導(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屬性值拿出來就行了,就像這樣。
.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)在效果如下:
為啥是歪的呢?這是因為路徑的起始位置就是這樣,我們可以把這個 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>
剛好位于起點處。
現(xiàn)在我們給個動畫,讓它從起點運動到終點,只需要改變offset-distance就行了。
.con::before{
/* */
animation: offset 3s linear infinite;
}
@keyframes offset {
to {
offset-distance: 100%;
}
}
這樣就得到了一個最基礎(chǔ)的路徑動畫了。
是不是非常簡單呢?
二、path 的局限性
前面的path雖然靈活,但是不好維護,而且一些基本形狀也必須要轉(zhuǎn)成path才行。
比如,要沿著一個圓形來運動,我們可以在Figma中繪制一個圓。
如果我們直接復(fù)制這個SVG會得到這樣一段代碼。
<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還是挺聰明的,自動識別到了這是一個圓,所以得到了circle這個標簽。但這種結(jié)構(gòu)之前是無法使用的,我們需要的是path,因此要轉(zhuǎn)一下。
在Figma中,可以用Flatten將圖形“扁平化”,也就是讓這個形狀失去基本形狀特性,變成一個普通的路徑。
這樣我們就能得到帶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")
}
雖然也能實現(xiàn),但是一眼看上去,完全不知道是什么形狀。
其次,path還有一個問題,就是不支持自適應(yīng)尺寸,因為里面的值都是固定的,無法動態(tài)去改變,比如我們希望這個圓能盡可能大的撐滿整個容器,path就無法實現(xiàn)這樣的效果。
因此,為了解決這樣的問題,現(xiàn)在也支持基本形狀了。
三、現(xiàn)在支持基本形狀了
所謂基本形狀,就是一種表現(xiàn)基礎(chǔ)圖形的 CSS 數(shù)據(jù)類型,適用于clip-path、shape-outside和offset-path。
https://developer.mozilla.org/en-US/docs/Web/CSS/basic-shape
其實就是這幾類
- 圓 circle()
- 橢圓 ellipse()
- 矩形 inset()、rect()、xywh()
- 多邊形 polygon()
這里面有些大家可能已經(jīng)在clip-path中用到過了,所以這里不會詳細介紹每個語法的詳細用法,有興趣可以在官網(wǎng)自行查看。
1. circle
首先來看圓。語法很簡單。
circle( <shape-radius>? [ at <position> ]? )
前面的shape-radius是圓的半徑,可以是長度單位或者百分比,還支持closest-side和farthest-side關(guān)鍵詞,后面的position表示圓心位置(默認居中)。
offset-path: circle(50px);
offset-path: circle(20% at right center);
offset-path: circle(closest-side);
offset-path: circle(farthest-side);
其中,closest-side表示距離邊緣「最近」時的半徑,farthest-side表示距離邊緣「最遠」時的半徑,這一點和徑向漸變中是類似的。
實際使用來看看,還是上面的結(jié)構(gòu)。
<div class="con circle">
給偽元素一個offset-path。
.circle::before{
offset-path: circle(); /*默認 closest-side */
animation: offset 3s linear infinite;
}
效果如下:
而且這個路徑是自適應(yīng)的,可以自動跟隨外部容器的變化而變化,比如將這個高度改小一些。
是不是比path實現(xiàn)要靈活很多呢?
2. ellipse
橢圓和圓比較類似,只是多了一個半徑,就不贅述了。
ellipse( [ <shape-radius>{2} ]? [ at <position> ]? )
我們直接看代碼。
<div class="con ellipse">
.ellipse::before{
offset-path: ellipse(); /*默認 closest-side closest-side */
animation: offset 3s linear infinite;
}
效果如下:
3.inset
inset表示矩形,并且支持圓角。
inset( <length-percentage>{1,4} [ round <`border-radius`> ]? )
前面有 4 個值,分別表示距離上、右、下、左的距離,如下:
并且支持圓角,這樣要實現(xiàn)一個圓角矩形的路徑動畫就很方便了。
<div class="con inset"></div>
.inset::before{
offset-path: inset(20px round 16px);
}
效果如下:
還有兩個函數(shù),rect()和xywh()也能實現(xiàn)矩形,只是方式不一樣,這個以后再做介紹。
4. polygon
這個相信大家都很熟悉了,用來繪制多邊形的。
比如我們要繪制一個三角形,只需要指定三個點就行了,如下:
在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.其實也還支持 url
可以直接用一段 svg作為路徑偏移,更加直觀。比如在 Figma 中繪制一個五角星。
我們直接復(fù)制出SVG放到頁面上。
<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 一個 id為svgPath,然后可以直接這么使用。
.url::before{
offset-path: url('#svgPath');
}
這樣一來,SVG既可以用于展示,又可以用于offset-path了,效果如下:
以上所有demo可以查看以下鏈接
- CSS offset-path (codepen.io)[1]
- CSS offset-path (juejin.cn)[2]
幾個基本形狀就這些了,接下來看一個實際應(yīng)用
四、圓弧菜單展開效果
有了基本形狀的支持,可以很方便的實現(xiàn)一些有意思的效果,比如這樣的菜單展開
其實就是一個圓形路徑動畫。首先來看結(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>
這里是通過 popover 來控制打開和收起的,不清楚popover的可以參考之前這篇文章:原生 popover 終于來了!
我們給每個子菜單加上路徑偏移 offset-path。
.item {
offset-path: circle(80px);
}
效果如下:
為啥只有一個子菜單呢?這是因為所有的菜單都重疊在了一起,我們需要分散開來。
由于圓形路徑動畫是順時針方向的,就像這樣。
所以我們需要將 5 個子元素平均分配到半個圓弧上,如下:
用代碼實現(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%;
}
給每個元素分別設(shè)置不同的offset-distance后,就變成了這樣。
最后,只要在打開菜單時設(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;
}
就能得到我們想要的展開效果了。
完整代碼可以查看:Radial Menu Popover remix using offset-path: circle() (codepen.io)[3]。
五、兼容性和總結(jié)
這個是 Chrome 116推出的新特性,目前還不是特別好,尤其是Safari拖了后腿,兼容性如下:
所以大規(guī)模使用還是需要等待一段時間,下面總結(jié)一下本文要點。
- 酷炫的路徑動畫可以用offset-path來實現(xiàn)。
- 之前僅支持 path(),雖然靈活,但是不好維護,不直觀,而且一些基本形狀也必須要轉(zhuǎn)成path才行。
- path()不支持自適應(yīng)尺寸。
- 基本形狀是一種表現(xiàn)基礎(chǔ)圖形的 CSS 數(shù)據(jù)類型,適用于clip-path、shape-outside和offset-path,語法都是通用的。
- 基本形狀主要有 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。