這篇文章跟大家分享CSS 高階技巧,介紹如何使用CSS達到波浪進度條效果,希望對大家有幫助!
本文是 CSS Houdini 之 CSS Painting API 系列第三篇。
在上兩篇中,我們詳細介紹了 CSS Painting API 是如何一步一步,實現自訂圖案甚至實現動畫效果的!
在這篇文章中,我們將繼續探索,嘗試使用 CSS Painting API,去實現一些過往純 CSS 無法實現的效果。 【推薦學習:css影片教學】
再簡單快速的過一下,什麼是 CSS Painting API。
CSS Painting API 是 CSS Houdini 的一部分。而 Houdini 是一組底層 API,它們公開了 CSS 引擎的各個部分,從而使開發人員能夠透過加入瀏覽器渲染引擎的樣式和佈局流程來擴展 CSS。 Houdini 是一組API,它們使開發人員可以直接存取CSS 物件模型 (CSSOM),使開發人員可以編寫瀏覽器可以解析為CSS 的程式碼,從而創建新的CSS 功能,而無需等待它們在瀏覽器中本地實作。
CSS Paint API 目前的版本是 CSS Painting API Level 1。它也被稱為 CSS Custom Paint 或 Houdini's Paint Worklet。
我們可以把它理解為 JS In CSS,利用 JavaScript Canvas 畫布的強大能力,實現過往 CSS 無法實現的功能。
CSS 實現波浪效果,一直是 CSS 的一個困難之一。在過往,雖然我們有很多方式利用Hack 出一些波浪效果,我在之前的多篇文章中有反复提及過:
是的,大部分時候,我們都是利用一些奇技淫巧實現波浪效果,像是這樣:
如今,有了CSS Painting API,我們已經可以繪製真實的波浪效果了。看看程式碼:
<div></div> <script> if (CSS.paintWorklet) { CSS.paintWorklet.addModule('/CSSHoudini.js'); } </script>
div { position: relative; width: 300px; height: 300px; background: paint(waveDraw); border-radius: 50%; border: 2px solid rgba(255, 0, 0, 0.5); }
我們定義了一個 waveDraw
方法,接下來,就透過利用 registerPaint 來實作這個方法即可。
// 文件名为 CSSHoudini.js registerPaint( "waveDraw", class { static get inputProperties() { return []; } paint(ctx, size, properties) { const { width, height } = size; const initY = height * 0.5; ctx.beginPath(); for (let i = 0; i <= width; i++) { ctx.lineTo(i, initY + Math.sin((i) / 20) * 10); } ctx.lineTo(width, height); ctx.lineTo(0, height); ctx.lineTo(0, initY); ctx.closePath(); ctx.fillStyle = 'rgba(255, 0, 0, 0.9)'; ctx.fill(); } } );
這樣,我們就得到了這樣一個波浪效果:
#上面的程式碼其實很好理解,簡單解釋一下,我們核心就是利用路徑繪製,基於Math.sin()
三角函數,繪製了一段sin(x) 三角函數的圖形。
整個圖形從ctx.beginPath()
開始,第一個點是ctx.lineTo(0, initY Math.sin((i) / 20) * 10)
,不過Math.sin(0) = 0
,所以等於ctx.lineTo(0, initY)
#initY
在這的作用是控制從什麼高度開始繪製波浪圖形,我們這裡的取值是initY = height * 0.5
,也就是定義成了圖形的中間位置
利用 for (let i = 0; i <= width; i++)
循环,配合 ctx.lineTo(i, initY + Math.sin((i) / 20) * 10)
,也就是在每一个 x 轴上的点,都绘制一个点
随后三个在循环体外的 ctx.lineTo
的作用是让整个图形形成一个闭环
最后 ctx.closePath()
完成整个路径,ctx.fill()
进行上色
如果不 ctx.fill()
上色,利用 ctx.stroke()
绘制边框,也是可以的,其实我们得到是这样一个图形:
上图是同时去掉了 CSS 代码里面的 border-radius: 50%
,方便大家理解。
当然,上面的图形,有个很大的问题,没法动起来,所以,我们需要借助一个 CSS @Property 自定义变量,让它拥有一些动画效果。
我们需要改造一下代码,首先,添加一个 CSS @Property 自定义变量:
@property --animation-tick { syntax: '<number>'; inherits: false; initial-value: 1000; } div { // ... 代码与上述保持一致 animation: move 20s infinite linear; --animation-tick: 1000; } @keyframes move { 100% { --animation-tick: 0; } }
我们添加了一个 --animation-tick
变量,并且利用 CSS 动画,让它从 1000 减至 0。
下一步,利用这个不断在变化的 CSS 自定义变量,我们在 waveDraw
方法中,把它利用上:
// 文件名为 CSSHoudini.js registerPaint( "waveDraw", class { static get inputProperties() { return ["--animation-tick"]; } paint(ctx, size, properties) { let tick = Number(properties.get("--animation-tick")); const { width, height } = size; const initY = height * 0.5; ctx.beginPath(); for (let i = 0; i <= width; i++) { ctx.lineTo(i, initY + Math.sin((i + tick) / 20) * 10); } ctx.lineTo(width, height); ctx.lineTo(0, height); ctx.lineTo(0, initY); ctx.closePath(); ctx.fillStyle = 'rgba(255, 0, 0, 0.9)'; ctx.fill(); } } );
仔细看,和上述的代码变化不大,核心在于,利用三角函数绘制图形的时候,我们把这个变量加入进去。
从原来的 ctx.lineTo(i, initY + Math.sin((i) / 20) * 10)
,变成了 ctx.lineTo(i, initY + Math.sin((i + tick) / 20) * 10)
。
这样,在这个不断变化的变量的作用下,我们的波浪图形就能运动起来了:
CodePen Demo -- CSS Houdini Wave
虽然能动了,但是总是感觉还少了些什么。如果我们把这个波浪效果应用与进度条之类的效果上,我们可以需要可以快速定义波浪的振幅、每个波峰之间的间距、效果的颜色、百分比等等。
因此,我们需要再通过一个 CSS 变量,让它成为一个实际可用的封装良好的波浪进度条。我们再简单改造一下:
@property --animation-tick { syntax: '<number>'; inherits: false; initial-value: 1000; } @property --height { syntax: '<number>'; inherits: false; initial-value: .7; } div { position: relative; width: 300px; height: 300px; background: paint(waveDraw); animation: move 20s infinite linear; border-radius: 50%; border: 2px solid var(--color1); --amplitude: 15; --gap: 28; --animation-tick: 700; --height: 0.7; --color1: rgba(255, 0, 0, 0.5); --color2: rgba(255, 0, 0, 0.4); --color3: rgba(255, 0, 0, 0.3); transition: --height 8s; }
可以看到,我们定义了非常多个 CSS 变量,每次,它们都是有意义的:
--animation-tick
表示波浪运动的速率--amplitude
波浪的振幅--gap
波峰间距--initHeight
初始高度--color1
、--color2
、--color3
我们会叠加 3 层波浪效果,显得更真实一点,这里 3 个颜色表示 3 层波浪的颜色定义好这些 CSS 变量后,我们就可以把它们运用在实际的waveDraw
方法中。看看代码:
registerPaint( "waveDraw", class { static get inputProperties() { return [ "--animation-tick", "--height", "--gap", "--amplitude", "--color1", "--color2", "--color3" ]; } paint(ctx, size, properties) { let tick = Number(properties.get("--animation-tick")); let initHeight = Number(properties.get("--height")); let gap = Number(properties.get("--gap")); let amplitude = Number(properties.get("--amplitude")); let color1 = properties.get("--color1"); let color2 = properties.get("--color2"); let color3 = properties.get("--color3"); this.drawWave(ctx, size, tick, amplitude, gap, initHeight, color1); this.drawWave(ctx, size, tick * 1.21, amplitude / 0.82, gap + 2, initHeight + 0.02, color2); this.drawWave(ctx, size, tick * 0.79, amplitude / 1.19, gap - 2, initHeight - 0.02, color3); } /** * ctx * size * tick 速率 * amplitude 振幅 * gap 波峰间距 * initHeight 初始高度 * color 颜色 */ drawWave(ctx, size, tick, amplitude, gap, initHeight, color) { const { width, height } = size; const initY = height * initHeight; tick = tick * 2; ctx.beginPath(); for (let i = 0; i <= width; i++) { ctx.lineTo(i, initY + Math.sin((i + tick) / gap) * amplitude); } ctx.lineTo(width, height); ctx.lineTo(0, height); ctx.lineTo(0, initY); ctx.closePath(); ctx.fillStyle = color; ctx.fill(); } } );
可以看到,我们在 paint()
方法中,调用了 this.drawWave()
。每次调用 this.drawWave()
都会生成一个波浪图形,通过 3 层的叠加效果,生成 3 层波浪。并且,把我们在 CSS 中定义的变量全部的应用了起来,分别控制波浪效果的不同参数。
这样,我们就得到了这样一个波浪效果:
通过控制 CSS 中的 --height
变量,还可以实现高度的变化,从而完成真实的百分比,实现一种进度条效果。
div:hover { --height: 0; }
效果如下:
很好,非常不错的效果。有了上述一些 CSS 自定义变量的帮助,我们就可以通过封装好的 waveDraw
方法,实现不同颜色,不同大小,不同速率的波浪进度条效果了。
我们只需要简单的改变一下传入的 CSS 变量参数即可:
<div></div> <div></div> <div></div>
div { position: relative; width: 300px; height: 300px; background: paint(waveDraw); animation: move 20s infinite linear; border-radius: 50%; border: 2px solid var(--color1); --amplitude: 15; --gap: 28; --animation-tick: 700; --height: 0.7; --color1: rgba(255, 0, 0, 0.5); --color2: rgba(255, 0, 0, 0.4); --color3: rgba(255, 0, 0, 0.3); transition: --height 8s; } div:nth-child(2) { --amplitude: 6; --gap: 25; --animation-tick: 300; --height: 0.5; --color1: rgba(28, 90, 199, 0.5); --color2: rgba(28, 90, 199, 0.4); --color3: rgba(28, 90, 199, 0.3); } div:nth-child(3) { --amplitude: 3; --gap: 30; --animation-tick: 1200; --height: 0.3; --color1: rgba(178, 120, 33, 0.5); --color2: rgba(178, 120, 33, 0.4); --color3: rgba(178, 120, 33, 0.3); }
看看效果如何:
CodePen Demo -- CSS Hudini Custom Wave Effects !
這樣,借助 CSS Painting API,我們完美的實現了波浪圖形,並且藉助它,實現了波浪進度條效果。透過傳入不同的 CSS 變量,我們有了快速批量產生不同效果的能力。彌補了過往 CSS 在波浪效果上的缺陷!
當然,就基於上述的程式碼,還是有一些可以優化的空間的:
在上述的CSS 程式碼中,可以看到,我們是傳入了3 個關於顏色的CSS 變量,--color1
、--color2
、--color3
,正常而言,這裡傳入1 個顏色即可,轉換成HSL 顏色表示法,替換L 色值,得到近似的另外兩個色值即可。當然,這樣做的話會增添非常多的JavaScript 程式碼,所以,本文為了方便大家理解,偷懶直接傳入了3 個CSS 顏色變數值;
整個波浪效果單輪的動畫持續時間我設定為了20s,但是在本文中,沒有去適配動畫的手尾銜接,也就是可能會出現每20s,波浪效果有一個明顯的跳動的感覺。解決這個問題,有兩個想法
--animation- tick
的值設定的非常的大,然後把對應的單輪動畫時間設定的非常長,這樣,基本上也感受不到動畫的跳幀第三個問題可能就在於相容性了
好吧,其實上一篇文章也談到了相容問題,因為可能有很多看到這篇文章並沒有去翻閱前兩篇文章的同學。那麼,CSS Painting API 的兼容性到底如何呢?
CanIUse - registerPaint 資料如下(截止至2022-11-23):
Chrome 和Edge 基於Chromium 核心的瀏覽器很早就已經支持,而主流瀏覽器中,Firefox 和Safari 目前還不支援。
CSS Houdini 雖然強大,目前看來要大規模上生產環境,仍需一段時間的等待。讓我們給時間一點時間!
原文網址:https://juejin.cn/post/7170868201645932551
作者:ChokCoco
#(學習影片分享:web前端)
以上是聊聊怎麼利用CSS實現波浪進度條效果的詳細內容。更多資訊請關注PHP中文網其他相關文章!