【前端】 在前端利用数学函数知识+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 | 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位
值域区间和宽度的关系
什么是区间
![](https://gss0.bdstatic.com/94o3dSag_xI4khGkpoWK1HF6hhy/baike/pic/item/f2deb48f8c5494ee809f18b82ff5e0fe98257ed5.jpg)
![](https://gss1.bdstatic.com/9vo3dSag_xI4khGkpoWK1HF6hhy/baike/pic/item/21a4462309f79052818a509f0ef3d7ca7acbd5fd.jpg)
![](https://gss1.bdstatic.com/9vo3dSag_xI4khGkpoWK1HF6hhy/baike/pic/item/a9d3fd1f4134970aa1da078e97cad1c8a6865de1.jpg)
![](https://gss3.bdstatic.com/7Po3dSag_xI4khGkpoWK1HF6hhy/baike/pic/item/c75c10385343fbf28d60b182b27eca8064388fc6.jpg)
![](https://img2018.cnblogs.com/blog/893115/201912/893115-20191201204010963-1569143472.png)
好了,理解了上面的东西,万事俱备,接下来就是更复杂一点的问题了!
接下来,工程问题,曲线
目标,使用函数
实现开头引用文章中,利用 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画苦逼脸:
加点动画玩玩
延续
数学与编程,有时候真的是相依相承的东西。从工程的角度来说,数学和程序算法有非常重要的关系,推荐大家阅读《数学与泛型编程》(高效编程的奥秘),受益匪浅,感觉整个程序职业生涯有了一次很棒的升华!
完。