CSS 實現(xiàn)帶tooltip的slider
現(xiàn)代 CSS 強大的令人難以置信。
這次我們來用 CSS 實現(xiàn)一個全功能的滑動輸入器,也就是各大組件庫都有的slider,效果如下:
還可以改變一下樣式,像這樣。
特別是在拖動時,tooltip還能跟隨拖動的方向和速度呈現(xiàn)不同的傾斜角度,這些是如何通過CSS實現(xiàn)的呢?一起來看看吧~
一、自定義input range
首先來看滑動輸入器的最原始形態(tài)。
<input type="range">
效果如下:
要自定義樣式,一般要修改這幾個偽元素。
::-webkit-slider-container{
/*容器*/
}
::-webkit-slider-runnable-track{
/*軌道*/
}
::-webkit-slider-thumb{
/*手柄*/
}
這里可以很輕松的改變軌道的寬高,拖拽手柄的大小等等。
[type="range"] {
-webkit-appearance: none;
appearance: none;
margin: 0;
outline: 0;
background-color: transparent;
width: 400px;
overflow: hidden;
height: 20px;
}
[type="range"]::-webkit-slider-runnable-track {
height: 4px;
background: #eee;
border-radius: 4px;
}
[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background-color: #9747FF;
transform: translateY(-50%);
margin-top: 2px;
}
效果如下:
相信大家很容易做到這一步,因為只需要自定義這幾個偽元素就行了。
這里還有一個難點,就是左邊滑過的區(qū)域,如何也自定義顏色呢?畢竟沒有專門的選擇器(Firefox有,這里主要討論Chrome),接下來請繼續(xù)看。
二、自定義滑過區(qū)域的顏色
在之前,曾經(jīng)通過border-image實現(xiàn)過類似的效果,主要原理是border-image可以在繪制元素之外,在拖拽手柄左側(cè)繪制一個足夠長的條條就行了。
不過這種實現(xiàn)有一個局限,由于是通過超出隱藏的方式裁剪掉多出的部分,使得滑動條邊緣是“一刀切”的,無法實現(xiàn)圓角
回到這里,可以想想,要實現(xiàn)自定義左邊滑過區(qū)域的樣式,本質(zhì)是需要知道當(dāng)前的滑動進度,假設(shè)進度是--progress,那么軌道滑過區(qū)域的背景色可以這樣來表示。
[type="range"]::-webkit-slider-runnable-track {
height: 4px;
background: linear-gradient(#9747FF 0 0) 0 0/calc(var(--progress) * 1%) 100% no-repeat #eee;
border-radius: 4px;
}
那么,如何實時獲取這個進度呢?
在以往,可以借助 JS實時更新這樣一個自定義變量,這也是目前最好的實現(xiàn)方式。
而現(xiàn)在,有了更好的方式來徹底實現(xiàn)這樣一個功能,那就是滾動驅(qū)動動畫。
有些同學(xué)可能無法理解,這里又沒有滾動,怎么會和這個特性有關(guān)呢?別急,滾動驅(qū)動有兩種類型,一個是 scroll()、還有一個是view(),我們這里要用到的就是view(),其實也就是利用這一點來監(jiān)聽元素在視區(qū)的位置。
具體怎么做呢?
首先,通過@property定義一個CSS變量,整數(shù)類型。
@property --progress {
syntax: "<integer>";
initial-value: 0;
inherits: true;
}
然后定一個動畫,從0到100就行了,表示進度。
@keyframes slider {
to {
--progress: 100;
}
}
由于是需要監(jiān)聽拖拽手柄,也就是::-webkit-slider-thumb 的位置,所以要給這個偽元素添加view-timeline,但是我們需要通過::-webkit-slider-thumb改變父級軌道::-webkit-slider-runnable-track的樣式,所以需要用到time-scope(可以跨層級關(guān)聯(lián)),具體實現(xiàn)如下:
[type="range"]{
timeline-scope: --slider;
animation: slider linear 3s reverse;
animation-timeline: --slider;
}
[type="range"]::-webkit-slider-thumb {
/**/
view-timeline: --slider inline;
}
這樣一來,拖拽手柄的位置就通過動畫實時映射到了input 上,效果如下:
這樣就是實現(xiàn)了自定義滑動區(qū)域的樣式,是不是非常神奇?
三、實時顯示滑動進度
由于前面實現(xiàn)了--progress的實時變化,現(xiàn)在展示出來就比較容易了,需要用CSS計數(shù)器。
為了方便表示,我們可以單獨用一個標(biāo)簽來展示進度,結(jié)構(gòu)如下:
<label class="slider">
<input type="range">
<output class="tooltip"></output>
</label>
然后將--progress通過偽元素呈現(xiàn)出來。
.tooltip::before{
content: counter(num);
counter-reset: num var(--progress);
}
效果如下:
數(shù)字已經(jīng)可以正常顯示了,但是有個問題,起始點不對,不是0和100,我們把拖拽手柄透明度降低,可以看到進度其實是這樣的。
這是由于默認的animation-range是cover,除了這種方式,還有其他幾種方式。
很明顯,我們這里應(yīng)該需要contain,因為滑塊是始終在軌道內(nèi)的。
.slider{
position: relative;
timeline-scope: --slider;
animation: slider linear 3s reverse;
animation-timeline: --slider;
animation-range: contain; /*設(shè)置animation-range*/
}
效果如下:
這樣進度就正常了。
四、tooltip自動跟隨滑塊
首先美化一下,小三角可以用另一個偽元素實現(xiàn)。
.tooltip{
position: absolute;
margin: 0 0 20px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
font-size: 14px;
border-radius: 4px;
padding: 10px;
color: #f0f0f0;
background-color: #333;
transform-origin: center bottom;
filter: drop-shadow(4px 4px 4px rgba(50, 50, 50, 0.3));
}
.tooltip::after{
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: black transparent transparent transparent;
}
效果如下:
那么,如何讓這個tooltip自動跟隨滑塊呢?
一種方式是直接通過--progress來計算left值。
.tooltip{
position: absolute;
left: calc(var(--progress) * 1%);
}
不過這種計算是有偏差的,這是因為定位是相對于軌道位置的,而不是滑塊中心。
為了修復(fù)這個問題,我們可以給input一個負的margin。
[type="range"] {
/* */
margin: 0 -10px;
}
這樣就可以了。
不過這樣還有有尺寸方面的問題的,如下:
實現(xiàn)非常簡單,只需要給滑塊一個anchor-name。
[type="range"]::-webkit-slider-thumb {
/**/
anchor-name: --thumb;
}
然后給tooltip設(shè)置錨點定位。
.tooltip{
position: absolute;
position-anchor: --thumb;
inset-area: top;
}
這樣就完美實現(xiàn)了,也不用擔(dān)心定位偏差的問題。
五、自動跟隨拖拽方向
還有最后一個效果,可以自動跟隨拖拽方向,這是如何實現(xiàn)的呢?
重新定義一個CSS變量。
@property --progress2 {
syntax: "<integer>";
initial-value: 0;
inherits: true;
}
然后給這個變量設(shè)置為--progress,但是要給一個過渡時間。
.tooltip{
--progress2: var(--progress);
transition: --progress2 .1s ease-out;
}
由于有過渡時間,所以這兩個變量就會有一個差值,類似于這樣。
根據(jù)這個差值,我們不就可以知道拖動方向和速度了嗎,然后給一個旋轉(zhuǎn)角度。
.tooltip{
transform-origin: center bottom;
rotate: calc((var(--progress2) - var(--progress))*2deg);
}
這樣就實現(xiàn)了文章開頭所示效果:
完整代碼可以查看以下鏈接
- CSS slider (juejin.cn)[1]
- CSS slider (codepen.io)[2]
還可以改變一下樣式。
[type="range"]::-webkit-slider-runnable-track {
height: 20px;
background: linear-gradient(#9747FF 0 0) 0 0/calc(var(--progress) * 1% + 20px * (50 - var(--progress))/100) 100% no-repeat #eee;
border-radius: 24px;
}
[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background-color: #fff;
border: 3px solid #9747FF;
view-timeline: --slider inline;
anchor-name: --thumb;
}
這樣可以得到下面的效果:
完整代碼可以查看以下鏈接
- CSS custom slider (juejin.cn)[3]
- CSS custom slider (codepen.io)[4]
六、兼容性和總結(jié)
這個實現(xiàn)主要依賴于滾動驅(qū)動動畫,所以兼容性要求Chrome 115+,如果需要tooltip的錨點定位,則需要Chrome 125+,下面總結(jié)一下。
- Chrome 沒有專門的選擇器來自定義滑過區(qū)域的樣式。
- 通過border-image可以自定義滑過區(qū)域的樣式,但是不能實現(xiàn)圓角。
- 滾動驅(qū)動動畫的視圖滾動可以監(jiān)聽滑塊的位置。
- 通過CSS變量和滾動驅(qū)動動畫可以將實時進度傳遞給軌道,從而通過線性漸變繪制滑過區(qū)域顏色。
- 借助CSS計數(shù)器和偽元素可以將CSS變量顯示在頁面。
當(dāng)然,不兼容的瀏覽器也可以采用回退措施,比如不支持滾動驅(qū)動動畫,我們可以用JS來動態(tài)賦值--progress變量,不支持錨定定位,我們可以用絕對定位,配合left也能實現(xiàn),雖然復(fù)雜一點,但也是現(xiàn)階段比較好的處理方式了,剩下的就交給時間吧。
[1]CSS slider (juejin.cn): https://code.juejin.cn/pen/7409224431194603574。
[2]CSS slider (codepen.io): https://codepen.io/xboxyan/pen/eYwxxdQ。
[3]CSS custom slider (juejin.cn): https://code.juejin.cn/pen/7415566353493000219。
[4]CSS custom slider (codepen.io): https://codepen.io/xboxyan/pen/OJeddWm。