用js计算三次贝塞尔曲线
发布在1970年1月1日view:14083
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。
这篇文章可能偏一点,用到了一些线性代数的知识,但是在网上发现有人在问这方面的问题用js如何实现,于是这里就顺带讨论一下贝塞尔曲线以及我的一个实现方式.我的实现只提供数据接口,只有用什么方式来展现这些数据,可以自己定义,这里我提供了两个实现, 一个是用div画图,在任何浏览器下都可以运行: http://html-js.com/mj/version1.0.3/test/Bezier_html.html 一个是canvas实现,只能在支持canvas的浏览器中执行: http://html-js.com/mj/version1.0.3/test/Bezier.html 如果只是想在程序中画出普通的贝塞尔曲线,请使用程序绘图api,很简单,例如html5 canvas中的arc方法,本文只做研究用.

希望本文可以对正在查找此方面资料的人有所帮助,如果觉得没啥用的可以直接飘走了,因为这个的确没啥大用,目前来说.

1.什么是三次贝塞尔曲线?和前端开发有什么鸟关系?

看一段官方的解释:贝塞尔曲线就是这样的一条曲线,它是依据四个位置任意的点坐标绘制出的一条光滑曲线。在历史上,研究贝塞尔曲线的人最初是按照已知曲线参数方程来确定四个点的思路设计出这种矢量曲线绘制法。贝塞尔曲线的有趣之处更在于它的“皮筋效应”~也就是说,随着点有规律地移动,曲线将产生皮筋伸引一样的变换,带来视觉上的冲击。1962年,法国数学家Pierre Bézier第一个研究了这种矢量绘制曲线的方法,并给出了详细的计算公式,因此按照这样的公式绘制出来的曲线就用他的姓氏来命名~是为贝塞尔曲线。 关键就是用四个点来定义一条曲线.就像你在上面的demo中看到的一样.由ABCD四个点定义了这条曲线的形状. 那我们平常会在什么地方接触到贝塞尔曲线呢?首先,最常见的就是画图工具中的钢笔工具,初学钢笔工具的人可能觉得用起来不是很顺手,但是这的确是一种产生平滑曲线的很有效的方法. 对于前端开发来说,如果你有关注过css3的动画的滑动效果,你会发现这样一个缓动的效果:
transition-timing-function
Values: ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier(x1, y1, x2, y2)
最后的cubic-bezier的意思就是三次贝塞尔曲线,参数中定义的分别是两个控制点的位置(起始点和结束点是已知的).然后我们就可以让效果任意变化了.这里我们通过三次贝塞尔创造出了平滑的曲线,然后生成了平滑的动画. 在很多框架和库中都提供了动画的缓动效果,不过这些效果和css3的效果比起来就要逊色很多了,他们的效果都是预定义的,没有通过用户自定义来实现贝塞尔平滑的,有的程序提供了自定义,但是因为不是贝塞尔方法,定义的效果不是很平滑.

2.贝塞尔的计算方法

贝塞尔曲线是个很复杂的数学模型,对于我们这些学过一点点线性代数甚至没有学过线性代数的人来说,他的原理实在是太深奥了,如果感兴趣,可以去看看这篇文章,他讲述了如何用c++来实现贝塞尔曲线,但是这篇文章中也没有将太多原理,而只是在最后给出了一个计算公式,我们需要的就是这个公式: 哦,这个公式我看了半天没看懂这些参数到底指的是什么.下面其实还有一个公式,我用的是这个: 这个公式看起来很清楚,p0,p1,p2,p3分别代表四个点,其中p0和p3是起始点,p1,p2是控制点.t参数表示一个时间参数.最后输出的结果p(t)则是一个在某个时刻计算出来的点的坐标.注意,这里的t是平滑的时间变动,并不代表坐标轴上的任何意义,是和空间相互独立的. p0,p1,p2,p3以及最后得出的结果都是一个二维的点的坐标. 在我的框架中有一个对象,叫做Vector,它表示所有二维的值,例如坐标,速度,加速度,力的大小等等,它有两个值,分别是x和y. 好吧,这是一个矩阵计算,如果你不知道如何计算矩阵,可以去买一本<<线性代数>>看看,其实很简单,如果不想看,可以看我的矩阵类是如何实现的,这个矩阵计算的类叫做:Matrix:
var matrix=function(config){
        this._init(config)
    }
    matrix.prototype={
        _init:function(config){
            if(config&&config.data)
                this.data=config.data;
        },
        /**
         * 矩阵相乘
         * @param {matrix} m 被乘的矩阵
         */
        mul:function(m){
            var r=[],s=this.data,m=m.data,
            p=s[0].length //每次运算相加的次数
            if(m.length!=s[0].length) {
                T.trace("矩阵不能相乘")
            }
            for(var i =0;i<s.length;i++){
                r[i]=[]
                for(var n=0;n<m[0].length;n++){
                    r[i][n]=0;
                    for(var l=0;l<p;l++){
                        r[i][n]+=s[i][l]*m[l][n];
                    }
                }
            }
            this.data=r;
            return this;
        },
        set:function(data){
            this.data=data;
        },
        get:function(){
            return this.data
        },
        toString:function(){
            return this.data.to_s()
        }
    }
一个矩阵对象的数据(data)长这个样子:
 var m1=new $.matrix({
            data:[
                [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],
                [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],
                [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],
                [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],
                [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],
                [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],
                [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],
                [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],
                [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],
                [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],
                [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
            ]
        })

3.让我们定义一个表示三次贝塞尔的对象.

首先,我们根据上面的矩阵公式,定义一个三次贝塞尔的对象:叫做:cubicBezier,这个对象的作用就是,你传入四个用Vector表示位置的点(两个起始点和终止点,两个控制点).然后传入一个t的大小,它就会返回一个计算后的点给引擎:
 var cubicBezier=function(config){
        this._init(config)
    }
    cubicBezier.prototype={
        _init:function(config){
            var p=this.points=config.points;
            this.m1=new MJ.Matrix();
            this.m2=new MJ.Matrix({
                data:[
                [1,0,0,0],
                [-3,3,0,0],
                [3,-6,3,0],
                [-1,3,-3,1]
                ]
            });
            this.m3=new MJ.Matrix({
                data:[
                p.p0.toArray(),
                p.p1.toArray(),
                p.p2.toArray(),
                p.p3.toArray()
                ]
            })
            this.m=null
        },
        /**
         * 获取某个时间点计算出来的坐标值,时间线不由此类控制
         */
        get:function(t){
            this.m1.set([
                [1,t*t,t*t*t,t*t*t*t]
                ]);
            this.m=this.m1.mul(this.m2).mul(this.m3)
            return new MJ.Vector(this.m.get()[0][0],this.m.get()[0][1]);
        }
    }

4.好了,开始玩吧!

万事具备了,可以开始玩了...现在我们有了一个 cubicBezier对象,你只需要给他四个点和一个时间参数,它就可以返回一个计算结果给你.那我们就定义四个点,然后让t从0开始不断变大,没变大一次的时候,我们就把四个点和t传给cubicBezier,然后得到一个返回的点的坐标,我们就用js把这个坐标画上一个东西或者最后把这些点连线起来,这就是我上面的demo做的事情:
var points={
            p0:new $.Vector(0,0),
            p1:new $.Vector(100,620),
            p2:new $.Vector(1000,33),
            p3:new $.Vector(800,600)
        }
        var cb=new $.CubicBezier({
            points:points
        });
        var p;
        var inter=setInterval(function(){
            t+=0.01;
            if(t>1){
                end()
            }
            p=cb.get(t)
          //根据p的坐标画点
        },10)
希望对正在查找这方面资料的人有帮助,不清楚的可以直接咨询我的qq:676588498或者email:xinyu198736@gmail.com. 转载请注明出处:http://www.html-js.com/?p=1031 5.附上一个缓动的方法库,里面是一些常用的缓动函数,计算效率比贝塞尔高很多:
tween: {
            easeInQuad: function(pos){
                return Math.pow(pos, 2);
            },

            easeOutQuad: function(pos){
                return -(Math.pow((pos-1), 2) -1);
            },

            easeInOutQuad: function(pos){
                if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,2);
                return -0.5 * ((pos-=2)*pos - 2);
            },

            easeInCubic: function(pos){
                return Math.pow(pos, 3);
            },

            easeOutCubic: function(pos){
                return (Math.pow((pos-1), 3) +1);
            },

            easeInOutCubic: function(pos){
                if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,3);
                return 0.5 * (Math.pow((pos-2),3) + 2);
            },

            easeInQuart: function(pos){
                return Math.pow(pos, 4);
            },

            easeOutQuart: function(pos){
                return -(Math.pow((pos-1), 4) -1)
            },

            easeInOutQuart: function(pos){
                if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,4);
                return -0.5 * ((pos-=2)*Math.pow(pos,3) - 2);
            },

            easeInQuint: function(pos){
                return Math.pow(pos, 5);
            },

            easeOutQuint: function(pos){
                return (Math.pow((pos-1), 5) +1);
            },

            easeInOutQuint: function(pos){
                if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,5);
                return 0.5 * (Math.pow((pos-2),5) + 2);
            },

            easeInSine: function(pos){
                return -Math.cos(pos * (Math.PI/2)) + 1;
            },

            easeOutSine: function(pos){
                return Math.sin(pos * (Math.PI/2));
            },

            easeInOutSine: function(pos){
                return (-.5 * (Math.cos(Math.PI*pos) -1));
            },

            easeInExpo: function(pos){
                return (pos==0) ? 0 : Math.pow(2, 10 * (pos - 1));
            },

            easeOutExpo: function(pos){
                return (pos==1) ? 1 : -Math.pow(2, -10 * pos) + 1;
            },

            easeInOutExpo: function(pos){
                if(pos==0) return 0;
                if(pos==1) return 1;
                if((pos/=0.5) < 1) return 0.5 * Math.pow(2,10 * (pos-1));
                return 0.5 * (-Math.pow(2, -10 * --pos) + 2);
            },

            easeInCirc: function(pos){
                return -(Math.sqrt(1 - (pos*pos)) - 1);
            },

            easeOutCirc: function(pos){
                return Math.sqrt(1 - Math.pow((pos-1), 2))
            },

            easeInOutCirc: function(pos){
                if((pos/=0.5) < 1) return -0.5 * (Math.sqrt(1 - pos*pos) - 1);
                return 0.5 * (Math.sqrt(1 - (pos-=2)*pos) + 1);
            },

            easeOutBounce: function(pos){
                if ((pos) < (1/2.75)) {
                    return (7.5625*pos*pos);
                } else if (pos < (2/2.75)) {
                    return (7.5625*(pos-=(1.5/2.75))*pos + .75);
                } else if (pos < (2.5/2.75)) {
                    return (7.5625*(pos-=(2.25/2.75))*pos + .9375);
                } else {
                    return (7.5625*(pos-=(2.625/2.75))*pos + .984375);
                }
            },

            easeInBack: function(pos){
                var s = 1.70158;
                return (pos)*pos*((s+1)*pos - s);
            },

            easeOutBack: function(pos){
                var s = 1.70158;
                return (pos=pos-1)*pos*((s+1)*pos + s) + 1;
            },

            easeInOutBack: function(pos){
                var s = 1.70158;
                if((pos/=0.5) < 1) return 0.5*(pos*pos*(((s*=(1.525))+1)*pos -s));
                return 0.5*((pos-=2)*pos*(((s*=(1.525))+1)*pos +s) +2);
            },

            elastic: function(pos) {
                return -1 * Math.pow(4,-8*pos) * Math.sin((pos*6-1)*(2*Math.PI)/2) + 1;
            },

            swingFromTo: function(pos) {
                var s = 1.70158;
                return ((pos/=0.5) < 1) ? 0.5*(pos*pos*(((s*=(1.525))+1)*pos - s)) :
                0.5*((pos-=2)*pos*(((s*=(1.525))+1)*pos + s) + 2);
            },

            swingFrom: function(pos) {
                var s = 1.70158;
                return pos*pos*((s+1)*pos - s);
            },

            swingTo: function(pos) {
                var s = 1.70158;
                return (pos-=1)*pos*((s+1)*pos + s) + 1;
            },

            bounce: function(pos) {
                if (pos < (1/2.75)) {
                    return (7.5625*pos*pos);
                } else if (pos < (2/2.75)) {
                    return (7.5625*(pos-=(1.5/2.75))*pos + .75);
                } else if (pos < (2.5/2.75)) {
                    return (7.5625*(pos-=(2.25/2.75))*pos + .9375);
                } else {
                    return (7.5625*(pos-=(2.625/2.75))*pos + .984375);
                }
            },

            bouncePast: function(pos) {
                if (pos < (1/2.75)) {
                    return (7.5625*pos*pos);
                } else if (pos < (2/2.75)) {
                    return 2 - (7.5625*(pos-=(1.5/2.75))*pos + .75);
                } else if (pos < (2.5/2.75)) {
                    return 2 - (7.5625*(pos-=(2.25/2.75))*pos + .9375);
                } else {
                    return 2 - (7.5625*(pos-=(2.625/2.75))*pos + .984375);
                }
            },

            easeFromTo: function(pos) {
                if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,4);
                return -0.5 * ((pos-=2)*Math.pow(pos,3) - 2);
            },

            easeFrom: function(pos) {
                return Math.pow(pos,4);
            },

            easeTo: function(pos) {
                return Math.pow(pos,0.25);
            },

            linear:  function(pos) {
                return pos
            },

            sinusoidal: function(pos) {
                return (-Math.cos(pos*Math.PI)/2) + 0.5;
            },

            reverse: function(pos) {
                return 1 - pos;
            },

            mirror: function(pos, transition) {
                transition = transition || tween.sinusoidal;
                if(pos<0.5)
                    return transition(pos*2);
                else
                    return transition(1-(pos-0.5)*2);
            },

            flicker: function(pos) {
                var pos = pos + (Math.random()-0.5)/5;
                return tween.sinusoidal(pos < 0 ? 0 : pos > 1 ? 1 : pos);
            },

            wobble: function(pos) {
                return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
            },

            pulse: function(pos, pulses) {
                return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
            },

            blink: function(pos, blinks) {
                return Math.round(pos*(blinks||5)) % 2;
            },

            spring: function(pos) {
                return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
            },

            none: function(pos){
                return 0
            },

            full: function(pos){
                return 1
            }
        }
评论
发表评论
2年前

这个图爆了

WRITTEN BY
芋头
思考,去做,做好,做的比别人好
TA的新浪微博

我的收藏