序
今天正在刷數學函數相關題目,刷到了下面這篇文章,哇哦~有意思。 利用cos和sin實現復雜的曲線。傳送門在下面。
CSS 技巧一則 -- 在 CSS 中使用三角函數繪制曲線圖形及展示動畫
正巧在復習一些數學知識,遂動手實踐了一把使用 數學中的函數 使用css畫連續曲線。
函數: 第一步
在數學中 函數 是指 ,一組定義域通過一組表達式, 映射到一組值域,也就是說 函數 f(x) = x^2 表示一個集合,每個輸入x,固定通過x^2返回一個值y,由此定義可得:
當集合 X = {-2, -1, 0, 1, 2} 輸入到函數f,得到的值域集合 Y = { y | y >= 0 }。
我們也可以通過列表格,更直觀的列舉出函數的值:
當x = 1 時 y等於 1
當x = 2時 y等於 4
| x= | -2 | -1 | 0 | 1 | 2 | ... |
| y= | 4 | 1 | 0 | 1 | 4 | ... |
由這個表格,我們可以在坐標軸畫出關於y = f(x) = x^2的函數的樣子:

開口控制
如果我們在y = x^2 加個負號會怎么樣呢,y = f(x) = -(x ^ 2)
圖像會倒過來變成像 n 這樣的樣子?
就這樣,我們可以通過這個函數,得到兩種曲線,正的u 和 反的n。 那么問題來了,要畫任意曲線,那么意味着,曲線要可大可小,可以在圖中的任意一個位置,要怎么辦呢?
嗯 仔細想想,如果函數 f(x) = x^2 再讓它除以-2呢
f(x) = x^2 / -2
| x= | -2 | -1 | 0 | 1 | 2 | ... |
| y= | -2 | -1/2 | 0 | -1 /2 | -2 | ... |
手動畫一下圖像大約長下面這樣:

y會因為除以2變得更小(想象一下兩側的y值會變小),當x = 2 , y就會等於2, 這樣的結果是曲線變寬。
那么我們也可以知道 如果 換成 f(x) = x^2 * 2, 當x=2,y等於4,曲線會變窄。
如果除以的數變成了負數,開口就會向下。
由上面我們可以得到一個可以控制曲線開口大小的函數
也可以換算到 f(x) = x^2 / t 當t 大於0,曲線開口向上,t小於0,曲線開口向下
左右偏移控制
現在我們可以控制開口大小,那么怎么樣控制曲線左右移動呢?
假設左右偏移量是P
設函數 f(x) = (x - P)^2,P = 1 得到下面的表格:
| x= | -2 - 1 | -1 - 1 | 0 - 1 | 1 - 1 | 2 - 1 | 3 - 1 |
| y= | 9 | 4 | 1 | 0 | 1 | 4 |
還是用圖像,大概長這樣:

可以看到,P的取值影響圖像的左右偏移
上下偏移控制
控制上下偏移,實際上就是控制函數 f(x) = x ^2的值y的大小,只需要將 f(x) = x ^2 - H 就可以控制上下啦
假設上下偏移量是H
設函數 f(x) = x^2 + H,H = 1 得到下面的表格:
| x= | -2 | -1 |
0 |
1 |
2 |
... |
| y= | 5 | 2 | 1 | 2 | 4 | ... |
圖就不畫啦,可以直接看到x=0時,頂點已經不再0上了,向上偏移了1位
值域區間和寬度的關系
什么是區間
好了,理解了上面的東西,萬事俱備,接下來就是更復雜一點的問題了!
接下來,工程問題,曲線
目標,使用函數

實現開頭引用文章中,利用 cos和sin實現的曲線。
分析
通過上面對函數的分析我們可以得到一個式子:
設 拋物線開口 = T
設 左右偏移 = P
設 上下偏移 = H
設 定義域 = [a, b] (開區間a到開區間b)
函數 f(x) = (x - P) ^ 2 / T - H, T > 0 開口向上
函數g(x) = (x - P) ^ 2 / (T) - H, T < 0 開口向下
現在我們要使弧線A的結束點是弧線B的起始點,並且調換方向,那么:

如圖的推理過程,首先反轉A,將A向下移動H,再向左移動P,得到一個新的弧度,以此類推遞歸:
然后用js實現一個簡單的算法如下:
// g(x) = f(x-(b-a)) - 2* f(a), T < 0 function g (x, T, P, range) { const [a, b] = range return f(x - (b - a), T, P, range) - 2 * f(0, T, P, range) } // 當 T < 0 相當於上面圖中的 p(x) = (x - (- (T / B * f(b - a)))) / T, T < 0 // 當 T > 0 直接計算 f(x) = (x - P) ^ 2 / T, T > 0 function f (x, T, P, range, s) { const [a, b] = range if (T < 0 && !s) { return Math.pow(x - (-(T / b * f(b - a, T, P, range, true))), 2) / T } if (T > 0 || s) { return Math.pow(x - P, 2) / T } } // 選擇初始函數 function getY (x, T, P, range) { if (T > 0) { return f(x, T, P, range) } else { return g(x, T, P, range) } } //獲取一堆x,y點組成的集合, size = 波浪數量,origin=原點,item = 配置P H T變量,points和ysize為遞歸存儲數據 function GetPoints(size, origin, item, points = [], ysize) { if (ysize === undefined) { ysize = size } if (size <= 0) { return points } const z = size % 2 === 0 const M = 1 // 密度 const width = item.b - item.a // 寬度 let i = width; while (i >= -width) { const point = [ (origin[0] + i) + (ysize - size) * (width * 2), // x origin[1] + getY(i, (z ? item.T : -item.T), item.P, [ // y item.a, item.b ]) ] points.push(point) i -= M; } GetPoints(size-1, origin, { a: item.a, b: item.b, T: item.T, P: item.P }, points, ysize) return points; }
效果
通過一連串懵逼式的計算和換算,我們有了一個可以獲取固定數量相連的曲線,通過T控制開口,P控制x偏移,定義域[a,b]控制寬度,我們來實現騷操作:

拉到本地跑一跑:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.circle {
position: absolute;
width: 1px;
height: 1px;
background: #333;
border-radius: 50%;
left: 0px;
top: 0px;
transition: all 200ms;
}
</style>
</head>
<body>
<div class="circle" id="circle"></div>
<script>
function g (x, T, P, range) {
const [a, b] = range
return f(x - (b - a), T, P, range) - 2 * f(0, T, P, range)
}
function f (x, T, P, range, s) {
const [a, b] = range
if (T < 0 && !s) {
return Math.pow(x - (-(T / b * f(b - a, T, P, range, true))), 2) / T
}
if (T > 0 || s) {
return Math.pow(x - P, 2) / T
}
}
function getY (x, T, P, range) {
if (T > 0) {
return f(x, T, P, range)
} else {
return g(x, T, P, range)
}
}
function GetPoints(size, origin, item, points = [], ysize) {
if (ysize === undefined) {
ysize = size
}
if (size <= 0) {
return points
}
const z = size % 2 === 0
const M = 1 // 密度
const width = item.b - item.a // 寬度
let i = width;
while (i >= -width) {
const point = [
(origin[0] + i) + (ysize - size) * (width * 2), // x
origin[1] + getY(i, (z ? item.T : -item.T), item.P, [ // y
item.a,
item.b
])
]
points.push(point)
i -= M;
}
GetPoints(size-1, origin, {
a: item.a,
b: item.b,
T: item.T,
P: item.P
}, points, ysize)
return points;
}
/**
* 生成box-shadow參數
*/
function getBoxShadow (color = '#333') {
let points = GetPoints(6, [500, 100], {
a : 0,
b : 100,
T : 200,
P : 0
})
// const s = []
const s = points.map((point) => `${point[0]}px ${point[1]}px 0 0 ${color}`)
return s.join(',')
}
document.querySelector('#circle').style.cssText = `box-shadow: ${getBoxShadow()}; transform: rotate(90deg) translate(-500px, -500px)`
</script>
</body>
</html>
一毛一樣,大功告成。

展望
利用數學函數,我們也可以畫出使用sin / cos一毛一樣的曲線,更多的,我們也可以用它來描繪一個物體的運動動作,例如波浪運動,拋物線運動。
甚至可以用css畫苦逼臉:

加點動畫玩玩
延續
數學與編程,有時候真的是相依相承的東西。從工程的角度來說,數學和程序算法有非常重要的關系,推薦大家閱讀《數學與泛型編程》(高效編程的奧秘),受益匪淺,感覺整個程序職業生涯有了一次很棒的升華!
完。
