专栏名称: __ihhu
前段
目录
相关文章推荐
前端大全  ·  真的建议所有前端立即拿下软考(红利期) ·  5 天前  
前端大全  ·  Create React ... ·  6 天前  
前端大全  ·  React+AI 技术栈(2025 版) ·  4 天前  
商务河北  ·  经开区“美•强•优”三重奏 ·  4 天前  
51好读  ›  专栏  ›  __ihhu

可视化n次贝塞尔曲线及过程动画演示

__ihhu  · 掘金  · 前端  · 2018-06-05 01:52

正文

Fork me on GitHub

李可

导航

公告

lk_的头像 大家好,我叫李可,对大前端比较感兴趣,乐于分享技术,目前在北京工作。QQ群:471838073,欢迎大家多多交流。 Flag Counter 昵称: 李可在江湖
园龄: 4年3个月
粉丝: 61
关注: 6 +加关注

友情链接

搜索

我的标签

随笔分类

随笔档案

js

最新评论

阅读排行榜

评论排行榜

推荐排行榜

可视化n次贝塞尔曲线及过程动画演示--大宝剑

阅读目录

回到顶部

先抛一个动画模拟的一个例子,吊一吊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的人是组合哪个符号,表示不明白,举爪。

  • 两个圆括号(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>
回到顶部

现在你明白了画一个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)
        }
回到顶部

最后,要持久啊,给点颜色看看

给中间折线上上随机色啊,增加丢丢美感。 为显目,第一轮折线为白色,最后贝塞尔线确定为红色







请到「今天看啥」查看全文