前端網頁動效
-如何用 CSS 和 JavaScript 製作音樂波浪 waveform 背景特效?
前幾天幫公司專案做了一個簡單的 Landing Page,客戶希望後面有 waveform 的樣式,並隨著滑鼠移動變化波浪。像這樣 👇
今天就來看看怎麼做出這樣的特效吧!
Waveform - HTML 部分
首先,我們來看 HTML
:
<div class="bg">
<div class="bg__bars bg__bars--back"></div>
<div class="bg__bars bg__bars--front"></div>
</div>
這個 HTML
結構相對簡單,整個背景動畫的容器是 <div class="bg">
,其中有兩個子元素,分別用來表示背景的前後兩層條紋:
.bg__bars--back
是背景層(後面顏色為藍色的條紋)。.bg__bars--front
是前景層(前面顏色為綠色的條紋)。
等等會用 JavaScript
塞很多 bar
在 bg__bars
裡面。
Waveform - CSS 部分
:root {
--cr-bg: #2a3330;
--cr-green: #7cf5bd;
--cr-blue: #010024;
}
首先 :root
定義了三個顏色變數,方便在程式中管理顏色:
--cr-bg
為背景顏色。--cr-green
為前條紋的顏色。--cr-blue
為背景條紋的顏色。
.bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
overflow: hidden;
pointer-events: none;
background-color: var(--cr-bg);
}
.bg
設置為全螢幕顯示(利用 inset: 0;
和 absolute
定位),背景色使用了 --cr-bg
的深綠色,並且禁止點擊事件(pointer-events: none
),這樣條紋就不會影響到其他互動操作。
.bg__bars {
position: absolute;
inset: 0;
display: flex;
align-items: flex-end;
}
.bg__bars
設置了條紋容器的位置與排列,用 flex
就能讓條紋從左到右排好。並使用align-items: flex-end
讓條紋從下方開始。
.bg__bar {
position: relative;
flex: 1 1 0;
height: 50%;
transform-origin: bottom;
}
.bg__bar
是單個條紋的樣式,每個條紋的高度都為 50%,也就是螢幕的一半高度,等等直接控制 transform: scale
就能調整 bar 的高度。使用 scale 控制高度的原因是 scale 的效能會比直接控制 Height 來的好。
最後記得用 transform-origin: bottom;
設置動畫變形的原點為條紋的底部,這樣縮放效果才會從下往上展開,而不是從中間開始。如果沒有設定就會像這樣。
.bg__bar__inner {
position: absolute;
inset: 0;
width: calc(100% + 1px);
}
而需要 inner__bar
的原因是,我們有兩個部分需要控制高度:
.bg__bar
: 外層控制整體的波浪.bg__bar__inner
: 內層控制自身的脈動效果
.bg__bar__inner
寬度多出 1px 來避免邊緣的視覺縫隙。沒有設置的話,有時候就會出現像這樣的縫隙。
.bg__bars--front .bg__bar__inner {
background-color: var(--cr-green);
}
.bg__bars--back .bg__bar__inner {
background-color: var(--cr-blue);
}
最後為前景和後景設置不同顏色。
Waveform - JavaScript 部分
接著來解釋 JavaScript
,JS 主要負責動態生成條紋並根據視窗大小與滑鼠位置製作波浪效果。
我們先簡單看一下 JS 的架構。
barCounts
、mousePosition
是很多function
會用的全局變數。initBars
、createBars
、createBar
用來製作 DOM。getScale
、updateBarScale
、updateBars
負責製作波浪效果?handleResize
、handleMouseMove
、onload
則是處理各自的事件。
全域變數
let barCounts = 0;
let mousePosition = { x: 0, y: 0 };
這些變數用來儲存條紋數量、滑鼠位置。
我們等等會根據螢幕寬度調整 barCounts 的數量,animation 也必須做好 RWD 啊!
initBars 函數
function initBars() {
const frontBars = document.querySelector('.bg__bars--front');
const backBars = document.querySelector('.bg__bars--back');
frontBars.innerHTML = '';
backBars.innerHTML = '';
}
這個函數不難在每次調整大小時清除現有條紋,為下一步生成新的條紋做好準備。
createBars 和 createBar 函數
function createBars() {
const frontBars = document.querySelector('.bg__bars--front');
const backBars = document.querySelector('.bg__bars--back');
for (let i = 0; i < barCounts; i++) {
createBar(frontBars);
createBar(backBars);
}
}
function createBar(barList) {
const bar = document.createElement('div');
bar.className = 'bg__bar';
const inner = document.createElement('div');
inner.className = 'bg__bar__inner';
bar.appendChild(inner);
barList.appendChild(bar);
}
createBars
負責根據條紋數量(barCounts
)生成條紋,分別為前景和背景創建條紋。createBar
則負責生成單個條紋,並將其插入到指定的條紋列表中。
getScale 函數
function getScale(index, barCounts, isBack, mousePosition) {
// 解構滑鼠座標
const { x, y } = mousePosition;
// 定義動畫參數
const WAVE_MULTIPLIER = isBack ? 3 : 4;
const MOUSE_MULTIPLIER = isBack ? 1.5 : -3;
const SCALE_OFFSET = isBack ? 0.15 : 0.5;
// 計算基礎縮放比例
const baseScale = Math.sin(
(index / barCounts) * (WAVE_MULTIPLIER * Math.PI) -
(x / window.innerWidth) * (MOUSE_MULTIPLIER * Math.PI)
) * 0.2;
// 根據滑鼠 Y 軸位置來影響條紋變化
const yScale = (1 - y / window.innerHeight) * 0.3;
// 如果是背景條紋,增加縮放量
// 反之是前景條紋,則減少縮放量
// 這只是讓互動更有據的小變化
return isBack
? 1 + baseScale + SCALE_OFFSET + yScale
: 1 - baseScale - SCALE_OFFSET - yScale;
}
getScale
相對複雜一點,它是計算每個條紋在動畫中的垂直縮放比例。條紋的縮放大小會根據它們的位置、滑鼠的位置、以及它們位於前景還是背景進行動態調整。
baseScale 計算基礎縮放比例
這是整個動畫的核心,利用三角函數 Math.sin
來製造波浪效果:
(index / barCounts) * (WAVE_MULTIPLIER * Math.PI)
:這段公式表示條紋在波浪中的位置。index / barCounts
確定條紋的相對位置,WAVE_MULTIPLIER * Math.PI
使波浪形狀在畫面中循環展現出來。(x / window.innerWidth) * (MOUSE_MULTIPLIER * Math.PI)
:這部分則是根據滑鼠水平位置來調整條紋的波形,讓我們移動滑鼠時,會影響每個條紋的縮放程度。MOUSE_MULTIPLIER
則是用來調整影響的大小和方向。
最後乘上 0.2
,縮小計算出的 sin
值,使條紋的縮放變化不要太強烈。
updateBarScale 和 updateBars 函數
function updateBarScale(bar, index, isBack) {
const scale = getScale(index, barCounts, isBack, mousePosition);
bar.style.transform = `scaleY(${scale})`;
}
function updateBars() {
const frontBars = document.querySelector('.bg__bars--front');
const backBars = document.querySelector('.bg__bars--back');
const frontBarList = frontBars.querySelectorAll('.bg__bar');
frontBarList.forEach((bar, i) => updateBarScale(bar, i, false));
const backBarList = backBars.querySelectorAll('.bg__bar');
backBarList.forEach((bar, i) => updateBarScale(bar, i, true));
}
updateBarScale
會根據條紋的索引及滑鼠位置計算縮放比例,並應用到條紋的scaleY
屬性上。updateBars
則負責更新所有條紋的縮放比例。
handleResize 與 handleMouseMove 函數
function handleResize() {
const width = window.innerWidth;
const width = window.innerWidth;
barCounts = Math.ceil(width / 200);
initBars();
createBars();
updateBars();
}
function handleMouseMove(e) {
mousePosition = { x: e.clientX, y: e.clientY };
requestAnimationFrame(updateBars);
}
接著用 handleResize
根據視窗寬度計算應該生成多少條紋,並更新條紋的排列;handleMouseMove
則根據滑鼠的位置更新條紋的縮放效果。
初始設置
window.addEventListener('resize', handleResize);
window.addEventListener('mousemove', handleMouseMove);
window.onload = function () {
handleResize();
};
接著監聽 resize
和 mousemove
事件就幾乎完成了整個 animation
Waveform - 內部條紋的脈動效果
最後我們要控制 bar__inner
的脈動效果,我原本是打算 JS 去製作,其實也是可以,不過比起用 CSS 會比較吃效能,所以在公司的專案中,我直接使用 SCSS 來製作 css animation。程式碼如下:
@for $i from 1 through 20 {
.bg__bar:nth-child(#{$i}) {
.bg__bar__inner {
animation: pulse-#{$i} 8s linear (random(100) / 100) + 0s infinite;
@keyframes pulse-#{$i} {
@for $percent from 0 through 10 {
// 在 0%, 10%, 20% ... 等時間點,scale 為 1。
#{$percent * 10%} {
transform: scaleY(1);
}
// 在 2% 3%, 12% 13%, 22% 23% ... 等時間點隨機 scale。、
@if ($percent * 10 < 100) {
#{$percent * 10 + 2%},
#{$percent * 10 + 3%} {
transform: scaleY((random(20) + 120) / 100);
}
}
}
}
}
}
}
總結
到這邊就是全部程式碼的內容拉!這邊放上 codepen 讓你去玩玩看~
這個特效挺有趣的~其實做起來也不會很難,核心就是利用 sin 函數做出波浪效果,趕快用到你的專案中吧!