浏览 7219 次
锁定老帖子 主题:运动曲线研究(缓动效果)
精华帖 (1) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|||||||
---|---|---|---|---|---|---|---|
作者 | 正文 | ||||||
发表时间:2007-10-14
缓动曲线的概念: 缓动曲线是一个0为起点的连续函数曲线,x轴表示时间变化,y轴表示位移变化。曲线的斜率反映出运动的数度。 缓动效果在Flash动画中比较常见,用于模拟一些现实中常见的运动轨迹,或者制造一些超绚的效果。 而且新版本的Flash中,内置了一些常用的缓动曲线函数。 可惜,Flash的这些曲线函数不是开源的,我们不知道内部如何实现,也就无法将其移植到JS中。感受其绚丽的同时,未免有一丝遗憾。 于是乎,自己琢磨琢磨。 首先,我对Flash的渐变函数接口非常不满。 搞那么多参数干吗? 要描述一个区间的渐变运动特征,只需一个y = f(x)足已。那么一大堆参数,真够罗嗦。 //原理:我们以终点位移为参考,只需要知道中间个点相对于最终位移,我们就能确定运动的规律。 y= f(x) //约定 //x ∈ [0,1] #将x变化换算成[0,1]是最简单不过的操作 //f(0) = 0 #运动是连续的嘛^_^. //f(1) != 0 #如果f(1) = 0了,那不就没有运动嘛,中间即使有位移,我也无法计算中间的位移相对于总体位移的比例。 曲线转换 每种类型的渐变都有三种变形
其中,我们只要知道一个的曲线,其他两个都可以转换生成: 知道渐入曲线之后,将其相对于(0.5,0.5)点绘制镜像,就是一个缓出运动,分段叠加就是一个完整的缓入缓出运动。 首先,常见的加速/减速运动: 初中物理就能搞定。 加速渐变函数为(easeIn): y=x*x; //y轴比例常数无需考虑 这是一个简单的2次曲线,表现一个渐入运动。 简单的变换一下:y = 1-(1-x)*(1-x) 减速运动(easeOut) 复杂一点: y = x>0.5? 1-2(1-x)*(1-x) : 2*x*x : 先加速后减速运动(easeBoth) 既然有二次曲线,很自然就想到三次、四次曲线。是的,这些曲线都有类似特征,区别在中间更陡峭,两头平缓(缓入缓出) 接下来,我就想实现一下弹动效果: 这类效果就好像一个甲虫飞到蜘蛛网上,在网上抖动两下,静下来听天由命。 抖动,周期运动,好,我们很快就想到正弦曲线。 方法基本正确,不过我起初还是走弯路了,我自作聪明的想着延长开始的半周期(x轴边形处理,振动让周期先大后小)。 但最终发现效果非常不理想,最后查看yui的实现。模仿一下,走出了这个误区。 我们通常看到的振荡移位效果,都是开始移动了较长位移,给人一种开始的振动周期更长的错觉,振动周期是不需要变化的。 纠正这个错误后,实现曲线函数如下: y = Math.pow(1024,x-1)*Math.sin(x*((2*(period||1)+0.5)*Math.PI)); 利用指数函数的第二象限的渐变特征变形,取处理正弦波形的振幅,达到一个衰减的效果。 趁热打铁,看看yui的其他几类渐变效果: 回退起步效果。 喜欢看动画片的话,你一定记得这个常见的场面,当一个家伙想快跑的时候,一点要先回撤一段距离,能后如突然加速前进。ok要的就是这个效果。 实现其实也很简单,一个二次曲线就可以搞定 y = x*(x-(backDistance||0.1)*4) 撞墙效果 这个名字可能不太合适吧,应该叫撞地效果更合适,鉴于撞墙这个名词更常见一些,也就标题党一回好了:) 玩过弹球吧,弹球的运动规律一定还记得。 对就是这种轨迹。 运功轨迹就是若干条二次曲线的分段拼接。改写一个yui里面的模拟实现。 this.bounceOut = function (x) { if (x < (1/2.75)) { return x*x; } else if (x < (2/2.75)) { return (x-=(1.5/2.75))*x + .75/7.5625; } else if (x < (2.5/2.75)) { return (x-=(2.25/2.75))*x + .9375/7.5625; } return (x-=(2.625/2.75))*x + .984375/7.5625; }; 这里手动指出了一大堆参数,其实,这些参数都可以通过计算得出,偷个懒,就这么地吧,^_^ 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|||||||
返回顶楼 | |||||||
发表时间:2007-10-14
演示url (IE不支持)
|
|||||||
返回顶楼 | |||||||
发表时间:2007-10-14
以前也写过加速减速动画代码,也发上来
|
|||||||
返回顶楼 | |||||||
发表时间:2007-10-14
google的code网站里面
搜索tweener项目。ou 里面有很多曲线函数 |
|||||||
返回顶楼 | |||||||
发表时间:2007-10-14
我以前曾经写过Tween类,用来达到类似flash的补间效果。
用法如下: new Tween(div.style, 'left', new Measure(div.style.left), new Measure('0px'), 3000, 40, Math.sqrt); 表示产生一个补间动画,让div.style.left在3秒内从当前的值变到0px。刷新间隔为40毫秒,运动方程是Math.sqrt。 最后两个参数可以省略,默认间隔是40毫秒,默认方程是Tween.linear,此外Tween上还提供了Tween.quadic方程。 如果运动方程使用自定义函数的话,函数f(x)应该在[0,1]区间上有定义,并满足f(0)=0, f(1)=1。 代码如下: (代码仅供参考,因为没有包括所有用到的方法,所以不能直接运行) Tween.prototype.start = function () { this.startTime = new Date().getTime(); this.before(); this._task = setTimeout(refresh, this.tick); var self = this; function refresh() { self.refresh(); } } Tween.prototype.stop = function () { //clearInterval(this._task); this.after(); this.startTime = null; } Tween.prototype.refresh = function () { var offset = new Date().getTime() - this.startTime; var x = offset / this.time; if (x >= 1) { this.stop(); } else { var y = this.f(x); var v = this.toValue.minus(this.fromValue).multiply(y).plus(this.fromValue); this.assign(v); this._task = setTimeout(refresh, this.tick); } var self = this; function refresh() { self.refresh(); } } Tween.linear = function linear(x) { return x; } Tween.quadric = function quadric(x) { return 1 - Math.sqrt(1 - x * x); } function Tween(target, field, fromValue, toValue, time, tick, f) { this.fromValue = fromValue; this.toValue = toValue; this.time = time; this.tick = tick || 40; this.f = f || Tween.linear; this.before = new EventHandler(); this.after = new EventHandler(); this.before.add(function () { this.assign(this.fromValue); }); this.after.add(function () { this.assign(this.toValue); }); if (isFunction(field)) { this.assign = function (value) { field.call(target, value); } } else if (isFunction(target[field])) { this.assign = function (value) { target[field](value); } } else { this.assign = function (value) { target[field] = value; } } } Number.prototype.plus = function (x) { assert (isNumber(x)); return this + x; } Number.prototype.minus = function (x) { assert (isNumber(x)); return this - x; } Number.prototype.multiply = function (x) { if (isNumber(x)) return this * x; if (x.multiply) return x.multiply(this); assert (false); } Number.prototype.divide = function (x) { assert (isNumber(x)); return this / x; } Measure.prototype.plus = function measure_plus(x) { if (this.unit == x.unit) return new Measure(this.qt + x.qt, this.unit); assert (false, String.concat(this, ' is incompatible with ', x)); } Measure.prototype.minus = function measure_minus(x) { if (this.unit == x.unit) return new Measure(this.qt - x.qt, this.unit); assert (false, String.concat(this.unit, ' is incompatible with ', x.unit)); } // TODO: composite unit Measure.prototype.multiply = function measure_multiply(x) { if (isNumber(x)) return new Measure(this.qt * x, this.unit); assert (false, String.concat(x)); } Measure.prototype.divide = function measure_divide(x) { if (isNumber(x)) return new Measure(this.qt / x, this.unit); else if (Measure.isInstance(x) && this.unit == x.unit) return this.qt / x.qt; assert (false); } Measure.prototype.toString = function measure_toString(x) { return String.concat(this.qt, this.unit); } function Measure(qt, unit) { if (!isNumber(qt)) { var s = /[0-9.+-]*/(qt)[0]; unit = String.substring(qt, s.length); qt = parseFloat(s); } this.qt = qt; this.unit = unit; } |
|||||||
返回顶楼 | |||||||
发表时间:2007-10-14
hax 写道 如果运动方程使用自定义函数的话,函数f(x)应该在[0,1]区间上有定义,并满足f(0)=0, f(1)=1。 这个f(1)=1约束还是可以省去的. 只要f(1) != 0就可以了,我们要的只是一个比例.如果强制f(1)=1,那又多出了一处比例换算. 不过,加上这个约束也有一个好处,那就是方程变换的时候简单一些. |
|||||||
返回顶楼 | |||||||
发表时间:2007-10-14
可以参考一下script.aculo.us的曲线函数,内置了不少呢
|
|||||||
返回顶楼 | |||||||
发表时间:2007-10-15
jindw 写道 hax 写道 如果运动方程使用自定义函数的话,函数f(x)应该在[0,1]区间上有定义,并满足f(0)=0, f(1)=1。 这个f(1)=1约束还是可以省去的. 只要f(1) != 0就可以了,我们要的只是一个比例.如果强制f(1)=1,那又多出了一处比例换算. 不过,加上这个约束也有一个好处,那就是方程变换的时候简单一些. 是可以省去,直接f'(x) = f(x) / f(1)用f'就可以了。实际上甚至f(0)=0也不必满足,因为总是可以换算到[0,1]区间上。不过做好规约比较严谨一点,你可以提供一个额外的换算函数来协助用户进行方程变换。 function map01(f, x0, x1) { x0 = x0 || 0 x1 = x1 || 1 x10 = x1 - x0 f0 = f(x0) f1 = f(x1) f10 = f1 - f0 return function (x) { return (f(x * x10 + x0) - f0) / f10 } } |
|||||||
返回顶楼 | |||||||
发表时间:2007-10-15
我以前做过一些类似的东西(但不是js的)
利用的就是 物理里面的运动学基础知识. 我把楼主说的那些网页特效当作是一个3维空间内的运动. 如 移动可以看作是 X Y轴上的变化. 淡入淡出可以看作是Z轴上的变化. 常规运动可以分为: 匀速运动 匀加速运动 匀减速运动 "加速度匀速变大"的变加速(加速度<0时为变减速)运动 "加速度匀速减小"的变加速(加速度<0时为变减速)运动 具体是那种运动,由初速度 加速度 时间 当前位置 这4者来决定. 把网页效果用运动学的知识来模拟. 例如弹球运动, 传入位置(高度) 重力加速度, 每次碰撞损失的动能(忽略空气阻力等,只记录碰撞损耗) 程序自动演算出运动情况. 平抛运动 传入位置, XY初速度, 重力加速度, X加速度(<0), 很多复杂的运动 如类似正弦曲线那种 都可以通过对加速度的控制来实现. |
|||||||
返回顶楼 | |||||||
发表时间:2007-10-15
hax 写道 jindw 写道 hax 写道 如果运动方程使用自定义函数的话,函数f(x)应该在[0,1]区间上有定义,并满足f(0)=0, f(1)=1。 这个f(1)=1约束还是可以省去的. 只要f(1) != 0就可以了,我们要的只是一个比例.如果强制f(1)=1,那又多出了一处比例换算. 不过,加上这个约束也有一个好处,那就是方程变换的时候简单一些. 是可以省去,直接f'(x) = f(x) / f(1)用f'就可以了。实际上甚至f(0)=0也不必满足,因为总是可以换算到[0,1]区间上。不过做好规约比较严谨一点,你可以提供一个额外的换算函数来协助用户进行方程变换。 function map01(f, x0, x1) { x0 = x0 || 0 x1 = x1 || 1 x10 = x1 - x0 f0 = f(x0) f1 = f(x1) f10 = f1 - f0 return function (x) { return (f(x * x10 + x0) - f0) / f10 } } 从实现来看,省略f(1) =1的约束是可以简化程序的,而省略f(0) =0,回增加代码。 反正我们需要的是一个高度的相对比例。 我们只要再动画开始计算出 var end = f(1) 能后每点的位移是 y = f(x)*max/end; 如果我们要强制f(1) =1。 那么: f(x) = f(x)/end; y=(f(x)/end)*max; 看似代码一样,但是一般的实现来说,最终的换算是在统一的地方,我们完全可以吧这个换算放再这个统一的地方。而让繁多的曲线函数的代码简单一些。 但是,如果我们允许 f(0) !=0. 那么程序就繁杂多了。 var begin = f(0) y=((f(x)-begin )/(end-begin))*max; 写到这里,都有点怀疑自己的立场了,呵呵,不过,还是坚持我的观点,为什么?呵呵,一个有点差强的理由,省略f(1) ==1 不会影响程序效率,而省略 f(0) ==0则有点 |
|||||||
返回顶楼 | |||||||