CSS錨點(diǎn)定位終于來(lái)了!
盼了好久,最近 Chrome 125終于迎來(lái)了CSS 錨點(diǎn)定位的正式支持。這是一個(gè)和 CSS 滾動(dòng)驅(qū)動(dòng)動(dòng)畫一樣,足以顛覆整個(gè) Web 開發(fā)領(lǐng)域的新特性。有了這個(gè)特性,很多以前強(qiáng)依賴 JS 的方式,都可以純 CSS解決,并且實(shí)現(xiàn)起來(lái)更加簡(jiǎn)單、更加靈活,一起看看吧!
一、快速了解 CSS 錨點(diǎn)定位
在過(guò)去,要實(shí)現(xiàn)一個(gè)元素定位,通常需要一個(gè)相對(duì)定位。比如這樣一個(gè) tooltip。
如果不借助 JS,讓這個(gè)氣泡位于按鈕的正上方,就只能約束HTML結(jié)構(gòu),讓這個(gè)氣泡位于按鈕內(nèi)部。
<button>
BUTTON
<tooltip>我是tooltip</tooltip>
</button>
并且設(shè)置按鈕為相對(duì)定位,才能通過(guò)絕對(duì)定位實(shí)現(xiàn)氣泡位于按鈕的正上方。
button{
position: relative;
}
tooltip{
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%)
}
雖然可以實(shí)現(xiàn),但是局限性很多。比如HTML要求嚴(yán)格,只能是嵌套結(jié)構(gòu),換種結(jié)構(gòu)就不行了,還有,如果父級(jí)有超出隱藏的樣式,這個(gè)氣泡也會(huì)被裁剪掉。因此,一般框架里不會(huì)采用這種 CSS實(shí)現(xiàn),都是通過(guò)JS動(dòng)態(tài)去獲取位置來(lái)實(shí)現(xiàn)的。
現(xiàn)在有了CSS錨點(diǎn)定位特性,一切都好辦了。
首先是對(duì)結(jié)構(gòu)無(wú)任何要求,可以是頁(yè)面上的任意地方的元素。
<button>BUTTON</button>
<tooltip>我是tooltip</tooltip>
由于沒有嵌套關(guān)系,所以我們要手動(dòng)的指定一下(不然誰(shuí)知道該怎么定位呢?),這里是通過(guò)anchor-name和position-anchor將兩個(gè)元素關(guān)聯(lián)(錨定)起來(lái),如下:
button{
anchor-name: --anchor-el;
}
tooltip{
position: absolute;
position-anchor: --anchor-el;
}
最后,再設(shè)置定位就行了,關(guān)鍵實(shí)現(xiàn)如下:
tooltip{
bottom: anchor(top);
left: anchor(center);
transform: translateX(-50%)
}
這樣就能實(shí)現(xiàn)任意兩個(gè)元素的錨定了。
你也可以訪問(wèn)以下在線鏈接(Chrome 125+)
- CSS anchor (codepen.io)[1]
是不是非常靈活呢?不過(guò)這里出現(xiàn)了一些從未見過(guò)的屬性和方法,下面再來(lái)具體介紹。
二、CSS 錨點(diǎn)定位語(yǔ)法詳解
為了實(shí)現(xiàn)這樣一個(gè)功能,CSS新推出了很多屬性和方法,如下:
1. 錨點(diǎn)的設(shè)置與引用 anchor-name、position-anchor
這個(gè)前面已經(jīng)用到了,主要是anchor-name和position-anchor兩個(gè)屬性,他們倆用一個(gè)唯一的標(biāo)識(shí)符鏈接起來(lái)。需要注意的是,這個(gè)標(biāo)識(shí)符必須要以雙短橫線開頭,和 CSS 變量名是一樣的,其他的則無(wú)效。
button{
anchor-name: anchor-el; /*屬性值無(wú)效*/
}
button{
anchor-name: --anchor-el;
}
tooltip{
position: absolute;
position-anchor: --anchor-el;
}
你可以理解為把設(shè)置anchor-name的元素當(dāng)做是以前的相對(duì)定位元素(錨點(diǎn)元素),而設(shè)置position-anchor的元素就當(dāng)成普通絕對(duì)定位元素就行了。
另外,如果標(biāo)識(shí)符有重復(fù),比如有多個(gè)button,都是相同的anchor-name,那么會(huì)以最后一個(gè)為準(zhǔn)。
2.錨點(diǎn)的位置表示 anchor()
前面說(shuō)了,設(shè)置position-anchor的元素可以當(dāng)做是普通的絕對(duì)定位。既然是定位,那就需要設(shè)置坐標(biāo),比如left和top值。由于不是固定的值,為了,這里又推出了一系列定位函數(shù),如下:
anchor(left)
anchor(center)
anchor(right)
anchor(top)
anchor(bottom)
比如anchor(left)表示錨定元素的最左側(cè),anchor(top)表示錨定元素的最上側(cè),依次類推,下面是一張示意圖。
值得注意的是,anchor(center)表示既可以表示水平居中,也可以表示垂直居中,這是由使用方式?jīng)Q定的。
top: anchor(center); /*垂直居中*/
left: anchor(center); /*水平居中*/
回到上一章的例子,我們要實(shí)現(xiàn)一個(gè)朝上居中的氣泡,所以定位元素的bottom要?jiǎng)偤锰幱阱^定元素的上方,然后水平方向上是常用的居中方式,先定位到中間,然后反向位移自身的一半,具體實(shí)現(xiàn)如下:
tooltip{
bottom: anchor(top);
left: anchor(center);
transform: translateX(-50%)
}
水平方向的居中看著不是特別優(yōu)雅,而且還占用了transform,可能不是特別靈活,下面來(lái)看另一個(gè)實(shí)現(xiàn)。
3. 錨定居中 anchor-center
上面水平居中用到了left和transform來(lái)實(shí)現(xiàn),其實(shí)還有新的實(shí)現(xiàn)方式,那就是anchor-center,不過(guò)這需要配合justify-self和align-self使用。
比如要水平居中,可以直接使用。
tooltip{
bottom: anchor(top);
justify-self: anchor-center;
}
如果要垂直居中,可以用align-self。
tooltip{
right: anchor(left);
align-self: anchor-center;
}
示意如下:
4. 更人性化的定位方式 inset-area
你可能在大部分組件庫(kù)都用過(guò)類似這樣的定位方式,例如Ant Design,一般是12個(gè)方位。
是不是比較好理解?一看就懂,比前面的left、top方式要簡(jiǎn)單的多了。
沒錯(cuò),錨點(diǎn)定位也支持類似的定位方式,引入了一種新型的定位系統(tǒng)叫做:inset-area。
這個(gè)方式比前面的實(shí)現(xiàn)更加便捷、更加靈活,它將錨定元素分成九宮格,并且考慮了各個(gè)位置的可擴(kuò)展性,一共有 20 種可能組合,如下:
inset-area: top; /* 居上,無(wú)尺寸限制 */
inset-area: top center; /* 居上并且不超過(guò)錨定元素尺寸 */
inset-area: top span-left; /* 居上并且左邊可以擴(kuò)展 */
inset-area: top span-right; /* 居上并且右邊可以擴(kuò)展 */
inset-area: left;
inset-area: left center;
inset-area: left span-top;
inset-area: left span-bottom;
inset-area: bottom center;
inset-area: bottom span-left;
inset-area: bottom span-right;
inset-area: bottom;
inset-area: right center;
inset-area: right span-top;
inset-area: right span-bottom;
inset-area: right;
inset-area: top left; /* 左上角 */
inset-area: top right; /* 右上角 */
inset-area: bottom left; /* 右下角 */
inset-area: bottom right; /* 右下角 */
看著是不是有點(diǎn)太多了,也有點(diǎn)暈,其實(shí)這里多了 8 種不常用的,下面做了一個(gè)示意圖,可以很清楚的看到每種方位的具體位置(虛線部分就是常見的12種方位)。
以上截圖修改來(lái)源于:https://anchor-tool.com。
回到前面第一章的例子,要實(shí)現(xiàn)居上垂直居中,其實(shí)可以一行代碼搞定。
tooltip{
inset-area: top;
}
是不是又精簡(jiǎn)了許多呢?
5. 錨點(diǎn)尺寸 anchor-size
有時(shí)候,我們可能還需要知道錨定元素的尺寸,比如這樣的場(chǎng)景
可以看到,在切換tab時(shí),底下的背景是可以無(wú)縫過(guò)渡的。在以前,我們要實(shí)現(xiàn)這樣的功能,必須要借助 JS來(lái)獲取當(dāng)前點(diǎn)擊元素的尺寸和位置,但現(xiàn)在,只需要借助 CSS 錨點(diǎn)定位就能輕松實(shí)現(xiàn)了。
位置信息前面以及提到了,用anchor(left)和anchor(top)就可以了,那尺寸呢,需要用到anchor-size。
anchor-size(width) /*錨點(diǎn)元素寬度*/
anchor-size(height) /*錨點(diǎn)元素高度*/
利用這個(gè)特性,我們可以很輕松的實(shí)現(xiàn)這樣一個(gè)效果,結(jié)構(gòu)如下:
<nav class="tab">
<a class="item" href="#HTML" name="HTML">HTML</a>
<a class="item" href="#CSS" name="CSS">CSS</a>
<a class="item" href="#JavaScript" name="JavaScript">JavaScript</a>
<a class="item" href="#React" name="React">React</a>
<a class="item" href="#Vue" name="Vue">Vue</a>
</nav>
我們用偽元素來(lái)當(dāng)做tab高亮背景,關(guān)鍵實(shí)現(xiàn)如下:
.tab::after{
content: '';
position: absolute;
border-radius: 100px;
background-color: rgba(65, 105, 225, 0.2);
position-anchor: --anchor-el;
width: anchor-size(width);
height: anchor-size(height);
left: anchor(left);
top: anchor(top);
transition: .3s;
pointer-events: none;
}
.item:target{
anchor-name: --anchor-el;
}
這樣就能輕松實(shí)現(xiàn)這個(gè)效果了,你也可以訪問(wèn)以下在線鏈接(Chrome 125+)
- CSS anchor nav (codepen.io)[2]
6. 動(dòng)態(tài)調(diào)整位置 position-try-options
有時(shí)候定位元素會(huì)處于屏幕邊緣,當(dāng)沒有足夠空間顯示時(shí),可以通過(guò)position-try-options來(lái)設(shè)置一個(gè)備用位置。
舉個(gè)例子,比如一個(gè)氣泡,默認(rèn)是朝上的,當(dāng)滾動(dòng)到屏幕邊緣時(shí)會(huì)自動(dòng)朝下,示意如下:
這種情況就可以用@position-try來(lái)實(shí)現(xiàn)了,具體做法是這樣的。
先通過(guò)position-try-options指定一個(gè)變量名,比如--bottom。
tooltip{
position: fixed;
position-anchor: --anchor-el;
inset-area: top;
position-try-options: --bottom;
}
然后通過(guò)@position-try來(lái)定義規(guī)則。
@position-try --bottom {
inset-area: bottom;
}
這樣就實(shí)現(xiàn)定位元素位置自動(dòng)調(diào)整了。
除此之外,還有一種便捷寫法,直接給position-try-options指定以下關(guān)鍵字。
position-try-options: flip-block; /*垂直翻轉(zhuǎn)*/
position-try-options: flip-inline; /*水平翻轉(zhuǎn)*/
這樣就無(wú)需@position-try定義了,實(shí)現(xiàn)更簡(jiǎn)單。
- CSS anchor position-try-options (codepen.io)[3]
當(dāng)然,我覺得這個(gè)功能還是稍顯不足的,比如當(dāng)氣泡帶有箭頭時(shí),雖然也能翻轉(zhuǎn),但是卻無(wú)法改變箭頭的位置,也就是無(wú)法查詢到當(dāng)前是否已經(jīng)翻轉(zhuǎn)了,就像這樣。
希望盡快解決吧~
三、和 popover 配合使用
畢竟popover只是解決了層級(jí)的問(wèn)題,而錨點(diǎn)定位解決了定位問(wèn)題。兩者結(jié)合,我們可以很輕松的實(shí)現(xiàn)各種常見的效果,已經(jīng)可以說(shuō)能夠完全替代主流框架中的popover組件了。
下面是一個(gè)功能完善的多級(jí)菜單,完全無(wú)需 JS即可實(shí)現(xiàn)。
首先是點(diǎn)擊出現(xiàn),這個(gè)就是popover的功能了,通過(guò)popovertarget和popover屬性,將兩者結(jié)合起來(lái),就能輕松實(shí)現(xiàn)點(diǎn)擊出現(xiàn)菜單的功能。
<button class="btn" popovertarget="more"></button>
<div class="menu" id="more" popover>
<button class="item">編輯</button>
<button class="item">刪除</button>
</div>
然后就定位,利用CSS錨點(diǎn)定位,將菜單定位到按鈕的右下方,也就兩三行代碼的事。
.btn{
anchor-name: --menu;
}
.menu{
position-anchor: --menu;
inset-area: bottom span-right;
}
這樣就能輕易實(shí)現(xiàn)懸浮菜單了,你也可以訪問(wèn)以下在線鏈接(Chrome 125+)
- CSS anchor menu (codepen.io)[4]
在codepen上找到了一個(gè)更完善的多級(jí)菜單案例。
https://codepen.io/jh3y/pen/dyLjbwG
四、總結(jié)和其他
介紹了這么多,一下子肯定難以接受,多回顧幾遍就明白了,至少可以知道錨點(diǎn)定位是干嘛的,如果以后有類似的需求也有一定的方向,下面總結(jié)一下本文要點(diǎn)
- CSS 錨點(diǎn)定位是一個(gè)顛覆性的新特性,一定要學(xué)會(huì)
- CSS 錨點(diǎn)定位可以設(shè)置任意元素相對(duì)任意元素做定位
- 主要是通過(guò)anchor-name和position-anchor兩個(gè)屬性關(guān)聯(lián)
- 錨點(diǎn)的位置用anchor()來(lái)定義,比如anchor(left)表示錨定元素的最左側(cè),anchor(top)表示錨定元素的最上側(cè)
- anchor-center可以實(shí)現(xiàn)居中定位,水平居中justify-self: anchor-center,垂直居中align-self: anchor-center
- inset-area是一種更人性化的定位方式,和常見的組件庫(kù)表示方位比較類似
- 還可以通過(guò) anchor-size來(lái)錨點(diǎn)元素的尺寸,anchor-size(width)表示寬度,anchor-size(height)表示高度
- position-try-options可以根據(jù)定位元素是否處于屏幕邊緣而自適應(yīng)定位方向
- 實(shí)際中更推薦和popover相互配合,可以輕松實(shí)現(xiàn)各類懸浮層效果
- 兼容性要求 Chrome 125+,期待早日使用吧
最近幾年CSS更新的確實(shí)有點(diǎn)太快了,很多以往的疑難雜癥都有了新的解決方式。但是很多時(shí)候?qū)W這些好像暫時(shí)沒啥用,畢竟可能 5 年以后才用得上。但是原生特性不像其他,一個(gè)框架兩三年就有可能被淘汰,或者有新的替代品出現(xiàn),原生的學(xué)到了就學(xué)到了,只要web存在的一天,就永遠(yuǎn)都不會(huì)過(guò)時(shí),所以也不虧是吧。
[1]CSS anchor (codepen.io): https://codepen.io/xboxyan/pen/dyEVVPb。
[2]CSS anchor nav (codepen.io): https://codepen.io/xboxyan/pen/zYQpvqg。
[3]CSS anchor position-try-options (codepen.io): https://codepen.io/xboxyan/pen/dyEJYRO。
[4]CSS anchor menu (codepen.io): https://codepen.io/xboxyan/pen/qBGpOKq。