正文
导航
公告
大家好,我叫李可,对大前端比较感兴趣,乐于分享技术,目前在北京工作。QQ群:471838073,欢迎大家多多交流。
昵称:
李可在江湖
园龄:
4年3个月
粉丝:
61
关注:
6
+加关注
友情链接
搜索
我的标签
随笔分类
随笔档案
js
最新评论
阅读排行榜
评论排行榜
推荐排行榜
阅读目录
回到顶部
先抛一个动画模拟的一个例子,吊一吊Xing趣(4次)
回到顶部
不够强?再来一个
回到顶部
这样子,满足你。demo说明
git仓库地址示例
我眼睛花,没看懂,能暂停不了?
可以控制动画暂停与继续。(供大家清楚地时刻看到每一帧)
我研究,先不追求性能,能控制播放时间不了?
可以是setInterval代替requestAnimationFrame控制每一帧的时间(已经注释,大家可以注释开控制时间)
回到顶部
起因
研究css中提供了2次、3次bezier,但是没有对n次bezier实现。对n次的实现有很大兴趣,所以就用js的canvas搞一下,顺便把过程动画模拟了一下。 投入真实生产之中,偏少。
回到顶部
好像很吊的样子,怎么实现的?------------------------------------------------------------------------1:只画一个bezier曲线,看我BB
回到顶部
看我是这样理解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的人是组合哪个符号,表示不明白,举爪。
回到顶部
控制点固定,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>
运行画
回到顶部
现在你明白了画一个bezier如此简单,是否特别想怎么用动画模仿出来这个贝塞尔的过程?-------------2:动画模拟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)
}
回到顶部
最后,要持久啊,给点颜色看看
给中间折线上上随机色啊,增加丢丢美感。 为显目,第一轮折线为白色,最后贝塞尔线确定为红色