運用 CSS offset-path 讓圖形不規則地動起來

Eason Lin
11 min readSep 29, 2023

--

Photo by Lili Popper on Unsplash

近期執行的專案有一個動畫需要讓一個紙飛機的圖案進行不規則的移動,塑造出「正在飛行」的感覺,原本以為這會是一個比較困難的需求,在前端同事提供範例程式碼後才發覺「這東西好像沒有這麼難!」

offset-path

offset-path 是一個 CSS 屬性,它指定了元素要遵循的路徑,並確定該元素在路徑的父容器或 SVG 座標系統中的定位。這條路徑可以是一條直線、一個曲線或一個幾何形狀,沿著這條路徑元素將被定位或移動。它可以與其他 CSS 屬性如 offset-distanceoffset-rotateoffset-anchor 屬性一起使用,以控制元素沿著路徑的位置及方向。

如果曾經將一個設計師提供或繪製的 SVG 檔案開啟,可能會看到像是這樣的內容:

<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<path
fill="none"
stroke="red"
d="M 10,30
A 20,20 0,0,1 50,30
A 20,20 0,0,1 90,30
Q 90,60 50,90
Q 10,60 10,30 z"
/>
</svg>

這個由 mdn web docs 提供的範例最終會繪製出一張愛心圖:

其中 <path> 元素的 fill 代表了形狀中間的填色、stroke 代表了描繪的邊框的顏色,而 d 則代表 “drawn”,表示路徑如何被繪製。d 也是一個「表現屬性」(Presentation Attribute),可以被使用在 CSS 中。

以下是一個讓一個圓圈依照心型路徑進行移動的範例:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
body {
padding: 60px;
}
.box {
width: 100px;
height: 100px;
border: 1px solid #000;
}
.circle {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: red;
}
.moving {
offset-path: path(
"M 10,30 A 20,20 0,0,1 50,30 A 20,20 0,0,1 90,30 Q 90,60 50,90 Q 10,60 10,30 z"
);
animation: heart-moving 5s linear infinite;
}

@keyframes heart-moving {
0% {
offset-distance: 0%;
}
100% {
offset-distance: 100%;
}
}
</style>
</head>
<body>
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<path
fill="none"
stroke="red"
d="M 10,30
A 20,20 0,0,1 50,30
A 20,20 0,0,1 90,30
Q 90,60 50,90
Q 10,60 10,30 z"
/>
</svg>
<div class="box">
<div class="circle moving"></div>
</div>
</body>
</html>

實際呈現如下:

這個範例我們使用了一個長寬各是 20px,底色為紅色的圓形 div。針對它設定一個 offset-path 並給予與愛心圖 d 屬性完全相同的值,並透過 animation 讓其的 offset-distance0% 變成 100%。這裡的 offset-distance 的概念是:定義元素在 offset-path 路徑的哪個位置,以上面的愛心圖為例,它的起始點會是:

我們可以調整一下頁面,新增三個按鈕:

<button onclick="moveTo(0)">移動至 0%</button>
<button onclick="moveTo(50)">移動至 50%</button>
<button onclick="moveTo(80)">移動至 80%</button>

一個 function

<script>
function moveTo(num) {
const circle = document.querySelector(".circle");
circle.style.offsetDistance = num + "%";
}
</script>

和異動 CSS:

.circle {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: red;
transition: offset-distance 2s linear;
}
.moving {
offset-path: path(
"M 10,30 A 20,20 0,0,1 50,30 A 20,20 0,0,1 90,30 Q 90,60 50,90 Q 10,60 10,30 z"
);
/* animation: heart-moving 5s linear infinite; */
}

當我們在點擊移動到 50% 時,它就會依照繪製的愛心路徑移動至 50% 處:

看過範例後,我們就可以看看offset-distanceoffset-rotateoffset-anchor 分別代表的意思。

offset-distance

offset-distance 表示元素的 offset-path 移動到的位置,預設為 0,也因此

@keyframes heart-moving {
0% {
offset-distance: 0%;
}
100% {
offset-distance: 100%;
}
}

這個 keyframes 才能讓元素依循繪製的路徑去移動。

offset-rotate

offset-rotate 代表了元素在移動時的「方向」。例如以 MDN 的範例來看:

offset-rotate 不下任何值時

這是 offset-rotate 下了 90deg 時:

它也可以分別給予水平、垂直方向不同的值,這是 offset-rotate 下了 auto 90deg 時:

offset-anchor

offset-anchor 可以給予一個值,這個值代表了元素在路徑中所處的位置,有一點點類似 transform-origin 的用法,以 MDN 提供的範例,例如當 offset-anchor 的值為 left bottom 時,會像這樣:

當值為 right-top 時則會是:

實際應用

實際應用所需的要素其實就是兩個重點:

  1. 要進行移動的圖片
  2. 要進行移動的路徑

這兩項應該都會由專門的設計師進行繪製並提供,我們只需要將其帶入網頁中即可。如果真的需要自己進行路徑的繪製,也能使用像是 Vectr 這樣的工具。

以下是一個飛機飛行的範例:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.plane {
offset-path: path(
"m111 483.5c0 0 172-283 432-215 260 68 213.5 78.2 420 225 206.5 146.9 443.5-44.8 475-180"
);
position: absolute;
top: 0;
left: 0;
}
.fly {
animation: plane-fly 5s infinite ease-in-out;
}

@keyframes plane-fly {
100% {
offset-distance: 100%;
}
}
</style>
</head>
<body>
<div>
<svg
version="1.2"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1640 664"
width="1640"
height="664"
fill="none"
stroke="#000"
stroke-width="5"
>
<path
d="m111 483.5c0 0 172-283 432-215 260 68 213.5 78.2 420 225 206.5 146.9 443.5-44.8 475-180"
/>
</svg>
<img
class="plane fly"
src="./plane-23.png"
alt=""
width="200"
height="200"
/>
</div>
</body>
</html>

實際呈現會長這個樣子:

--

--

Eason Lin
Eason Lin

Written by Eason Lin

Frontend Web Developer | Books

No responses yet