起因

研究css中提供了2次、3次bezier,但是没有对n次bezier实现。对n次的实现有很大兴趣,所以就用js的canvas搞一下,顺便把过程动画模拟了一下。

投入真实生产之中,偏少。

n次bezier曲线,做前端实际生产中,并没有很大对帮助。仅仅学习研究之。

1,由于css样式中仅提供了2次/3次bezier曲线的形成,对n次bezier曲线的实现有很强的好奇心。

2,爱好数学之美和js动画,想实现bezier曲线的描绘过程,实现其过程演示动画。

故做此文。

先抛的两个例子,吊一吊Xing趣

demo提供的api概述

git仓库地址示例

  • 我眼睛花,没看懂,能暂停不了?

    • 可以控制动画暂停与继续。(供大家清楚地时刻看到每一帧)
  • 我研究,先不追求性能,能控制播放时间不了?
    • 可以是setInterval代替requestAnimationFrame控制每一帧的时间(已经注释,大家可以注释开控制时间)

1:只画一个bezier曲线,理解bezeir公式

好像很吊的样子,怎么实现的?我是这样最主要理解bezier曲线的公式,看我抄百度的贝塞尔公式图,看抄

  • 线的个数 辅助线的个数

    • n个节点(n>2),
    • 总线数:(n-1)+(n-2)+...+1,公差为1等差数列求和,S=(1+n-1)(n-1)/2=n(n-1)/2
    • 中间辅助线(包含最后一条):n*(n-1)/2-(n-1)
    • 假如:2个节点,总1条 0辅助
    • 假如:3个节点,总3条 1辅助
    • 假如:4个节点,总6条 3辅助
    • 假如:5个节点,总10条 6辅助
  • 我是这样子理解 t的(自变量t的范围)
    • 不论几次贝塞尔,t从0->1[0,1],这个过程:
    • 假如:描了100个点,就是把范围1分成100份 ,每份0.01
    • 假如:描了1000个点,就是把范围1分成100份 ,每份0.001

使用组合

数学偏low的人是组合哪个符号,表示不明白,举爪。

  • 两个圆括号(n i)是什么?是组合吗,组合不C n i吗。我也是数学偏low的,别墨迹,直接上解释 知乎大法好,组合表示法
  • 看我抄百度数学组合公式
  • 阶乘是啥,我不知道~
//组合
function C(n, i) {
return f(n) / f(i) / f(n - i)
}
//阶乘公式 n!
//阶乘 factorial
function f(n) {
if (n < 0) {
return -1
} else if (n === 0 || n === 1) {
return 1
} else {
return (n * f(n - 1))
}
}

获取曲线的一个点的坐标

控制点固定,t为【0,1】的一个值的时候,获取bezier曲线的一个点的x y坐标

//曲线上的一个点,分别求出x,和y
//points确定系数
//t是自变量,这里获取一个点的时候,需要t固定,画线的时候再赋值[0,1],分100份的话,每次t差距0.01,循环t
//公式中需要组合
function getOnePointXY(points, t) {
return {
x: Sigmar('x', points, t),
y: Sigmar('y', points, t)
}
}
//x或者y方向上的坐标,bezier曲线求和
function sigmar(direction, points, t) {
var result = 0
//n+1个节点,是n次bezier曲线
let n = points.length - 1
for (let [i, { x, y }] of points.entries()) {
var A = C(n, i)
var P = direction === 'x' ? x : direction === 'y' ? y : x//不传'x' 'y'默认x方向
var t1 = Math.pow(1 - t, n - i)
var t2 = Math.pow(t, i)
result += A * P * t1 * t2
}
return result
}

开始画一条曲线

点都确定了,开始画canvas

 var controlPoints = [{ x: 100, y: 500 }, { x: 150, y: 400 }, { x: 600, y: 300 }, { x: 400, y: 150 }]

        //一条bezier曲线上有多少个点,
//分100份的话,每次t差距0.01,循环。
//todo,用户配置--点--暂停--嵌入动画里面
var pointCount = 1000
var allBezeirPoints = nbezeirCurve(controlPoints, pointCount)
const pen = canvas.getContext('2d')
pen.moveTo(allBezeirPoints[0].x, allBezeirPoints[0].y)
//pen.moveTo(0, allBezeirPoints[0].y) for (let { x, y } of allBezeirPoints) {
pen.lineTo(x, y)
}
pen.stroke() console.log(nbezeirCurve(controlPoints, pointCount))
//得到n次bezier曲线的pointCount个数个点数组
function nbezeirCurve(controlPoints, pointCount, t = 0) {
var step = 1 / pointCount//t->step++[0,1]
var pointArr = []
while (t < 1) {
pointArr.push(getOnePointXY(controlPoints, t))
t += step
}
return pointArr
}

一个贝塞尔曲线demo

<!DOCTYPE html>
<html lang="en"> <head>
<meta charset="UTF-8">
<title>bezeir by 李可</title> </head> <body>
<canvas id="canvas" width="800" height="600"></canvas>
<script>
var controlPoints = [{ x: 100, y: 500 }, { x: 150, y: 400 }, { x: 600, y: 300 }, { x: 400, y: 150 }] //一条bezier曲线上有多少个点,
//分100份的话,每次t差距0.01,循环。
//todo,用户配置--点--暂停--嵌入动画里面
var pointCount = 1000
var allBezeirPoints = nbezeirCurve(controlPoints, pointCount)
const pen = canvas.getContext('2d')
pen.moveTo(allBezeirPoints[0].x, allBezeirPoints[0].y)
//pen.moveTo(0, allBezeirPoints[0].y) for (let { x, y } of allBezeirPoints) {
pen.lineTo(x, y)
}
pen.stroke() console.log(nbezeirCurve(controlPoints, pointCount))
//得到n次bezier曲线的pointCount个数个点数组
function nbezeirCurve(controlPoints, pointCount, t = 0) {
var step = 1 / pointCount//t->step++[0,1]
var pointArr = []
while (t < 1) {
pointArr.push(getOnePointXY(controlPoints, t))
t += step
}
return pointArr
} //曲线上的一个点,分别求出x,和y
//points确定系数
//t是自变量,这里获取一个点的时候,需要t固定,画线的时候再赋值[0,1],分100份的话,每次t差距0.01,循环t
//公式中需要组合
function getOnePointXY(points, t) {
return {
x: Sigmar('x', points, t),
y: Sigmar('y', points, t)
}
}
//x或者y方向上的坐标,bezier曲线求和
function Sigmar(direction, points, t) {
var result = 0
//n+1个节点,是n次bezier曲线
let n = points.length - 1
for (let [i, { x, y }] of points.entries()) {
var A = C(n, i)
var P = direction === 'x' ? x : direction === 'y' ? y : x//不传'x' 'y'默认x方向
var t1 = Math.pow(1 - t, n - i)
var t2 = Math.pow(t, i)
result += A * P * t1 * t2
}
return result
}
//组合
function C(n, i) {
return f(n) / f(i) / f(n - i)
}
//阶乘 factorial
function f(n) {
if (n < 0) {
return -1
} else if (n === 0 || n === 1) {
return 1
} else {
return (n * f(n - 1))
}
}
</script>
</body> </html>

运行画

2:动画模拟bezier曲线过程

现在你明白了画一个bezier如此简单,是否特别想怎么用动画模仿出来这个贝塞尔的过程?继续看我BB
模拟动画的思路,那让我们继续想,怎么画这个动画呢?

....想来想去------>每一帧,把t的所有连线都画好。下一帧把上一帧的连线抹除后,再画t=t+0.01(这里分了100份,每份0.01)的的所有连线。

所有线,每一帧到底有多少线需要画?见下图。



针对每一帧:根据t

假使画5次贝赛尔曲线,先画4个线,(得到4个点,先画3个线),(得到3个点,再画2条)。

假使画4次贝赛尔曲线,先画3个线,(得到3个点,再画2条)。

假使画3次贝赛尔曲线,(画2条)。

画一条折线


function drawBrokenLine(points, t = 1, lineColor = 'white', hasNode = true, nodeColor = 'white') {
if (points.length >= 2) {
for (var i = 0; i < points.length - 1; i++) {
var current = points[i]
var next = points[i + 1]
drawLine(current, next, lineColor)
hasNode && drawNode(current, nodeColor)
}
hasNode && drawNode(points[points.length - 1], nodeColor)
}
return getPercentPoints(points, t)
}

动画每一帧中的2个技术点

t固定下,怎么得到上个折线中对应下次点坐标折线集合?看图说话。顺便看下代码

function getPercentPoints(points, t) {
if (points.length <= 1) {
return points
}
const perPoints = []
var inx = 0
while (inx < points.length - 1) {
const current = points[inx]
const next = points[inx + 1]
var perPoint = {
x: current.x + (next.x - current.x) * t,
y: current.y + (next.y - current.y) * t
}
perPoints.push(perPoint)
inx++
}
return perPoints
}

递归画折线

直到剩下 1个点时候,就是besier曲线上的值了

function drawframe(points, t) {
var lineColors = getColors(points)
canvas.width = canvas.width
init(pen)
//画第一折线
var percentPoints = drawBrokenLine(points, t, 'white', true, 'yellow')
var i = 0
//循环画中间折线
while (percentPoints.length > 1) {
const currentColor = lineColors[++i]
percentPoints = drawBrokenLine(percentPoints, t, currentColor, true, currentColor)
}
//循环画贝塞尔折(曲)线
const bezeirPoints = getBezierPoints(controlPoints, step, t)
drawBrokenLine(bezeirPoints, t, 'red', false)
}

给折线上点颜色

给中间折线上上随机色啊,增加丢丢美感。

为显目,第一轮折线为白色,最后贝塞尔线确定为红色

一个贝塞尔曲线动画demo

最后的最后有完没完?还没BB完?完了..,不行,不要砍我........运行大宝剑

<!DOCTYPE html>
<html lang="en"> <head>
<meta charset="UTF-8">
<title>bezier by 李可</title> </head> <body>
<canvas id="canvas" width="1000" height="600"></canvas>
<br>
<input type="button" id="btn1" value="绘制">
<input type="button" id="btn2" value="清空">
<input type="button" id="btn3" value="暂停">
<script>
function getPercentPoints(points, t) {
if (points.length <= 1) {
return points
}
const perPoints = []
var inx = 0
while (inx < points.length - 1) {
const current = points[inx]
const next = points[inx + 1]
var perPoint = {
x: current.x + (next.x - current.x) * t,
y: current.y + (next.y - current.y) * t
}
perPoints.push(perPoint)
inx++
}
return perPoints
} function getBezierPoints(points, t, end = 1, start = 0) {
var pointArr = []
while (start <= end) {
var node = getOneBezierPoint(points, start)
pointArr.push(node)
start += t
}
return pointArr
} //曲线上的一个点,分别求出x,和y
//points确定系数
//t是自变量,这里获取一个点的时候,需要t固定,画线的时候再赋值[0,1],分100份的话,每次t差距0.01,循环t
//公式中需要组合
function getOneBezierPoint(points, t) {
return {
x: sigmar('x', points, t),
y: sigmar('y', points, t)
}
}
//x或者y方向上的坐标,bezier曲线求和
function sigmar(direction, points, t) {
var result = 0
//n+1个节点,是n次bezier曲线
let n = points.length - 1
for (let [i, { x, y }] of points.entries()) {
var A = C(n, i)
var P = direction === 'x' ? x : direction === 'y' ? y : x//不传'x' 'y'默认x方向
var t1 = Math.pow(1 - t, n - i)
var t2 = Math.pow(t, i)
result += A * P * t1 * t2
}
return result
}
//组合
function C(n, i) {
return f(n) / f(i) / f(n - i)
}
//阶乘 factorial
function f(n) {
if (n < 0) {
return -1
} else if (n === 0 || n === 1) {
return 1
} else {
return (n * f(n - 1))
}
}
</script>
<script>
const controlPoints = []//{ x: 100, y: 500 }, { x: 150, y: 400 }, { x: 600, y: 300 }, { x: 400, y: 150 } const pen = canvas.getContext('2d')
function init(pen) {
pen.fillStyle = "#444"
pen.fillRect(0, 0, canvas.width, canvas.height)
}
init(pen) canvas.onmousedown = function (e) {
const point = { x: e.offsetX, y: e.offsetY }
controlPoints.push(point)
drawText(point, controlPoints.length)
drawNode(point)
drawLastLine(controlPoints)
}
//显示点击位置
function drawText(point, inx, y = 10, font = 16) {
pen.fillStyle = "#fff"
pen.textAlign = 'end'
pen.textBaseline = 'hanging'
pen.font = `${font}px`//times
pen.fillText(`${point.x}x${point.y}:${inx}`, 1000 - 20, inx === 1 ? y : (inx - 1) * font + y) } function drawLastLine(points) {
//画最后两点连线 -折线
var count = points.length
var current = points[count - 2]
var next = points[count - 1]
if (count >= 2) {
drawLine(current, next)
}
}
function drawNode(point, nodeColor = 'white') {
//画节点
pen.beginPath()
pen.strokeStyle = nodeColor
pen.lineWidth = 2
pen.arc(point.x, point.y, 8, 0, 2 * Math.PI)
pen.stroke()
}
function drawLine(current, next, color = "white") {
//画最后两点连线 -折线
pen.beginPath()
pen.strokeStyle = color
pen.lineWidth = 2
pen.moveTo(current.x, current.y)
pen.lineTo(next.x, next.y)
pen.stroke()
} const pointCount = 100
const step = 1 / pointCount//t->step++[0,1]
//绘bezier曲线
function drawBrokenLine(points, t = 1, lineColor = 'white', hasNode = true, nodeColor = 'white') {
if (points.length >= 2) {
for (var i = 0; i < points.length - 1; i++) {
var current = points[i]
var next = points[i + 1]
drawLine(current, next, lineColor)
hasNode && drawNode(current, nodeColor)
}
hasNode && drawNode(points[points.length - 1], nodeColor)
} return getPercentPoints(points, t)
}
function getRandomColor() {
var color = "#"
for (let i = 0; i < 6; i++) {
color += Array.from('0123456789abcdef')[Math.floor(16 * Math.random())]
}
return color
}
//n次,画n-1条折线
var lineColors = []
function getColors(points) {
const len = points.length
for (let i = 0; i < len - 1; i++) {
lineColors.push(getRandomColor())
}
return lineColors
}
function drawframe(points, t) {
var lineColors = getColors(points)
canvas.width = canvas.width
init(pen)
var percentPoints = drawBrokenLine(points, t, 'white', true, 'yellow')
var i = 0
while (percentPoints.length > 1) {
const currentColor = lineColors[++i]
percentPoints = drawBrokenLine(percentPoints, t, currentColor, true, currentColor)
}
const bezeirPoints = getBezierPoints(controlPoints, step, t)
drawBrokenLine(bezeirPoints, t, 'red', false)
} var timer
var state
var runFlag = true
function startBezier(t, recursive = false) {//iteration
// timer = setInterval(() => {
// if (t <= 1) {
// drawframe(controlPoints, t)
// t += step
// state = t
// } else {
// clearInterval(timer)
// drawframe(controlPoints, 1)
// recursive && startBezier(0)
// }
// }, 200)
timer = requestAnimationFrame(function frame() {
if (runFlag) {
if (t <= 1) {
drawframe(controlPoints, t)
t += step
state = t
requestAnimationFrame(frame)
} else {
cancelAnimationFrame(timer)
drawframe(controlPoints, 1)
recursive && startBezier(0)
}
} else {
cancelAnimationFrame(timer)
}
})
// const bezeirPoints = getBezierPoints(controlPoints, step, 0.5)
// drawBrokenLine(bezeirPoints, 1, 'red')
}
btn1.onclick = function () {
startBezier(0)
}
btn2.onclick = function () {
controlPoints.splice(0, controlPoints.length)
canvas.width = canvas.width
// clearInterval(timer)
runFlag = true
init(pen)
}
var count = 0
btn3.onclick = function () {
if (++count % 2 === 1) {
btn3.value = '继续'
if (timer) {
//clearInterval(timer)
runFlag = false
}
} else {
btn3.value = '暂停'
console.log(state)
runFlag = true
startBezier(state)
}
} </script>
</body> </html>

运行大保健

真完了

欢迎大家加入QQ群471838073,一起大宝剑

可视化n次贝塞尔曲线及过程动画演示--大宝剑的更多相关文章

  1. 贝塞尔曲线与CSS3动画、SVG和canvas的应用

    简介 贝塞尔曲线是可以做出很多复杂的效果来的,比如弹跳球的复杂动画效果,首先加速下降,停止,然后弹起时逐渐减速的效果. 使用贝塞尔曲线常用的两个网址如下: 缓动函数:http://www.xuanfe ...

  2. 贝塞尔曲线 & CAShapeLayer & Stroke 动画 浅谈

    转载自:http://46aae4d1e2371e4aa769798941cef698.devproxy.yunshipei.com/qiaoqiaoqiao2014/article/details/ ...

  3. JS模拟CSS3动画-贝塞尔曲线

    一.什么是贝塞尔曲线 1962年,法国工程师皮埃尔·贝塞尔(Pierre Bézier),贝塞尔曲线来为为解决汽车的主体的设计问题而发明了贝塞尔曲线.如今,贝赛尔曲线是计算机图形学中相当重要的一种曲线 ...

  4. CSS总结六:动画(一)ransition:过渡、animation:动画、贝塞尔曲线、step值的应用

    transition-property transition-duration transition-timing-function transition-delay animation-name a ...

  5. Android 贝塞尔曲线解析

    相信很多同学都知道"贝塞尔曲线"这个词,我们在很多地方都能经常看到.利用"贝塞尔曲线"可以做出很多好看的UI效果,本篇博客就让我们一起学习"贝塞尔曲线 ...

  6. cubic-bezier贝塞尔曲线css3动画工具

    今天在一本叫<HTML5触摸界面设计与开发>上看到一个做弹跳球的复杂动画效果,首先加速下降,停止,然后弹起时逐渐减速.是用cubic-bezier贝塞尔曲线来完成的.所以特地去学习了一下关 ...

  7. 过渡与动画 - 缓动效果&基于贝塞尔曲线的调速函数

    难题 给过渡和动画加上缓动效果是一种常见的手法(比如具有回弹效果的过渡过程)是一种流行的表现手法,可以让界面显得更加生动和真实:在现实世界中,物体A点到B点往往也是不完全匀速的 以纯技术的角度来看,回 ...

  8. 再谈CSS动画 - 说点不知道的(一)贝塞尔曲线

    今天重新翻看<CSS 揭秘>"过渡与动画"一章,并把该章代码重新敲了一遍,代码托管在我的Github,在此总结一些心得. 动画的奥秘 在网页中添加动画的目的是让用户有更 ...

  9. 贝塞尔曲线(面)二三维可视化(Three+d3)

    贝塞尔曲线(面)二三维可视化(Three+d3) 在学完 games101 几何后开始实践,可视化贝塞尔曲线 我想实现三维的贝塞尔曲线,用 threejs,但是 threejs 控制太麻烦了,因此,我 ...

随机推荐

  1. 4.认识Angular组件之2

    11. 变化监测:Angular提供了数据绑定的功能.所谓的数据绑定就是将组件类的数据和页面的DOM元素关联起来.当数据发生变化时,Angular能够监测到这些变化,并对其所绑定的DOM元素 进行相应 ...

  2. 【转】Linux环境进程间通信(五) 共享内存(上)

    转自:https://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index1.html 采用共享内存通信的一个显而易见的好处是效率高,因为进程可以 ...

  3. 一个简单的python爬虫程序

    python|网络爬虫 概述 这是一个简单的python爬虫程序,仅用作技术学习与交流,主要是通过一个简单的实际案例来对网络爬虫有个基础的认识. 什么是网络爬虫 简单的讲,网络爬虫就是模拟人访问web ...

  4. 【Angular】——TypeScript之胖箭头(=>)函数

    前言:胖箭头(=>)函数是一种快速书写函数的简介语法. ES5和TypeScript比较:在ES5中,每当我们要用甘薯作为方法参数时,都必须用function关键字和紧随其后的花括号({})表示 ...

  5. [蓝桥杯]PREV-25.历届试题_城市建设

    问题描述 栋栋居住在一个繁华的C市中,然而,这个城市的道路大都年久失修.市长准备重新修一些路以方便市民,于是找到了栋栋,希望栋栋能帮助他. C市中有n个比较重要的地点,市长希望这些地点重点被考虑.现在 ...

  6. Elasticsearch-6.7.0系列(四)Metricbeat仪表盘。本身无端口,依赖kibana

    前提 centos7环境 https://www.cnblogs.com/zhuwenjoyce/p/10629320.html         elasticsearch搜索引擎 https://w ...

  7. 3阶马尔可夫链 自然语言处理python

    一.简介:       把每三个三个单词作为一个整体进行训练. 举一个例子: input:       my dream is that I can be an engineer, so I desi ...

  8. Spring boot 的 properties 属性值配置 application.properties 与 自定义properties

    配置属性值application.properties 文件直接配置: com.ieen.super.name="MDD" 自定义properties文件配置:src/main/r ...

  9. PhysicalBasedRendering(一)物理篇

    很多人对PBR的理解是存在偏差的,跳不出传统渲染模型的思维圈子,把它理解成一种模拟效果更为精确的算法公式,虽然在某种程度上是对的,但没有看到PBR的本质. PBR是对光在真实世界中与环境交互的一种近似 ...

  10. Linux内核中的printf实现

    1 #ifndef __PRINT_H_ 2 #define __PRINT_H_ 3 4 void print(char* fmt, ...); 5 void printch(char ch); 6 ...