【前端】 在前端利用數學函數知識+box-shadow解波浪圖形


今天正在刷數學函數相關題目,刷到了下面這篇文章,哇哦~有意思。 利用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 - 1 - 1 - 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 



...
y= 5 2 1 2 4 ...

圖就不畫啦,可以直接看到x=0時,頂點已經不再0上了,向上偏移了1位

 

值域區間和寬度的關系

什么是區間

集合的語言,我們定義各種區間為:
說人話就是有兩種區間 開區間 與 閉區間
開區間不包含0,閉區間包含0
 
區間和寬度的關系
 
我們要一個完整的半圓|半弧,那么必須要定義一個起始x和結束x,否則曲線就是無限延伸的沒有意義
 
我們從函數f(x) = x^2的圖像上任意取最小x a 和最大x b,b - a就是x的定義區間,也就是函數f(x) = x^2的定義域:

 

 

好了,理解了上面的東西,萬事俱備,接下來就是更復雜一點的問題了!

 

接下來,工程問題,曲線

目標,使用函數

 

 

實現開頭引用文章中,利用 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畫苦逼臉:

 

 

加點動畫玩玩

 

延續

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

完。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM