aimingoo的专栏

.F{color:red}aimingoo

用户操作
[即时聊天] [发私信] [加为好友]
周爱民ID:aimingoo
408866次访问,排名123好友89人,关注者131
aimingoo的文章
原创 121 篇
翻译 0 篇
转载 0 篇
评论 706 篇
aimingoo的公告
新书出版:
china-pub在线购买
相关评论和文章

其它:
 相关评论和文章
 相关评论和文章
最近评论
cloudgamer:这篇真的很深入啊
佩服
sherryxuelian:读了《大道至简》,感觉真的收益匪浅。我对这本书的评价是:作者真的是一个善于深刻思考的人,这本书透过繁杂的技术神秘面纱,引领读者来到一个简单和纯净的本源之所,在这里,我看到我曾迷信着的,在纷乱中困惑着的,竟是如此的真实,合理和亲切。再次感谢作者提出对《移山之道》的建议,正好我也买了这本书,呵呵,其实还真是因为《大道至简》才买的,刚开始看书名还以为是《大道至简》的仿照版,(还在想微软这么资深的员……
jackhatedance:这个标题使我想到了martin fowler的blog,他说的是更直接:UML无用。作为敏捷的先锋,他认为MDA,UML都是没用的,DSL才是正道。他说敏捷团队基本不用UML。我想是因为他到了更高的境界,UML图已经可以在他的脑袋里画了。

链接如下:
http://martinfowler.com/bliki/ModelDrivenSoftwareDevelo……
cm_chenmin:老师的文章必定是掘地三尺呀
uvsjoh:看来我是成不了大师啊 ^_^, 那些算法、数学真难懂!
文章分类
收藏
    相册
    旅游
    我、joy与朋友们
    其它
    Hello World!
    ZDNet China软件技术专区
    我的链接
    aimingoo's 网上空地
    我的Delphi项目资源
    麦秸的垛
    我的朋友们
    kiki-玩java的国际游人
    Margaret
    叶卡-Online
    左左-网行者
    老孟-孟岩的孟
    存档
    订阅我的博客
    XML聚合  FeedSky

    原创 Qomolangma框架库(二):时间线与时间处理器收藏

    新一篇: 编程的另一面:从生死到变化~ | 旧一篇: 【原创】搞了个NetGear的路由器,为此写了个小程序来查Wan IP.

    ================================================================================
    Qomolangma OpenProject v1.0


    类别    :Rich Web Client
    关键词  :JS OOP,JS Framwork, Rich Web Client,RIA,Web Component,
              DOM,DTHML,CSS,JavaScript,JScript

    项目发起:aimingoo (aim@263.net)
    项目团队:..\..\Qomo_team.txt
    有贡献者:JingYu(zjy@cnpack.org)
    ================================================================================


    一、框架库:时间线与时间处理器
    ~~~~~~~~~~~~~~~~~~
    几乎所有的动画特效都与时间线有关系。在一般的应用软件里,会提供一个固定间隔的时间
    线,设计人员则在时间线上描述指定的时间里会发生的事件。这些事件被连续起来,就成为
    了动画;而一组时间线合并起来,就成了动画场景。

    Qomo里实现时间线的初衷,只是为了在绘制界面组件的效果时,提供一些用时间控制的效果。
    例如窗体关闭/隐藏时的卷入效果(以及在打开时的展开);又例如一个Outlook风格的纵向Bar
    在点击一个按钮时的展开。

    在Windows32和vista风格的界面设计中,这些组件都具有一些特殊的动画效果。例如窗体弹
    出或控件的淡入淡出。这些动态效果其实都是在时间线的基础上来实现的。

    但这些仍然只是单一组件,或一类组件的单一特效。这种情况下,只能算是“动态效果”。
    复杂的设计是“一组时间线+一组元素”,这种情况通常表现为动画场景。

    动画场景有些时候并不用“时间线”来控制,而是有“帧”来控制。多画面的帧切换也就构
    成了动画。所以,在每一个帧事件中的处理,通常就是“效果渲染”。当然,实际来做的时
    候会更加复杂,涉及到非常多的演染技术。但如今动画制作领域的关键技术,就是这里提到
    的时间线与帧。

    Qomo框架库同时实现时间线与帧,但对二者不提供有偏好的推荐。Qomo中,二者的区别表现
    在:时间线并不精确,而且并不按标准时间间隔提供;帧是纯序列化的演染空间和数据供应,
    但与时间并不精确的重叠。

    这一切根源在于IE等浏览器中并没有足够精度的时钟或日期对象。所以如果你真的打算用JS
    或Qomo来做过于复杂的、涉及时间线或帧序列的动画场景,那可能效果并不会如你想象的平
    滑。

    随本次文档发布的是Qomo Beta 2 update 2, 文档下载在:
    http://www.01cn.net/users/aimingoo/files/Qomo.V1(b2.upd2).zip
    将文档解压后,覆盖到Qomo Beta 2的目录中即可。Qomo Beta 2下载:
    http://www.01cn.net/users/aimingoo/files/Qomo.V1(b2).zip

    二、时间处理体系的设计
    ~~~~~~~~~~~~~~~~~~
    Qomo把一个动画效果理解为“时间变化”和“数据变化”两个部分。Qomo认为,无论时间变
    化还是数据变化,都可以导致动画效果的产生。

    例如在窗口弹出效果中,一方面可以是“每单位时间窗口变大的比例(data)成曲线”,另一
    方面也可以是“窗口每变大单位比例所用的时间(time)成曲线”,两者之任一,都可以产生
    动态效果,但代码却不一样:
    ------------------
    function resize_win(v) {
      win.width = win.width * v;
      win.height = win.height * v;
    }

    // 第一种
    var data = 1;
    setInterval("resize_win(data=data*1.2))", 10);


    // 第二种
    var time = 1000;
    setTimeout(function() {
      resize_win(1.2);
      setTimeout(arguments.callee, time=time*0.8);
    }, time=time*0.8);
    ------------------

    Qomo也认可时间与数据同时发生的变化,也就是“非固定间隔的时间线”下的数据变化。尽
    管在实用中这种变化很难处理,但可以产生独特的效果。

    Qomo把具备理解这种逻辑的能力对象称为“时间处理器(TimeMachine)”。这个类接口描述为:
    ------------------
    ITimeMachine = function() {
      this.start = Abstract;    // function(time, data) {}
      this.OnTimer = Abstract;  // function(step, data) {}
      this.stop = Abstract;
    }
    ------------------

    其中,ITimeMachine.start()方法的入口有time与data。它们不是单纯的值,而一个TSteper
    类型的对象。这种对象用于产生每个单位间隔可释出的值(新的数据,或者新的时间间隔)。

    三者的关系主要建立在OnTimer事件的step参数上。因为TSteper类的事件OnStep的类型声明为
    ------------------
    TOnStep = function(nStep, nLast) {}
    ------------------

    其中nStep表明第几步,nLast表明上一步产生的数据,作为此次产生数据使用的参考。

    因此,整个Qomo的时间处理体系表达的逻辑就是:
     - TimeMachine.OnTime,time.OnStep和data.OnStep在相同的step值时产生一组数据;
     - 在相同的step下,OnTime针对于data.OnStep产生的数据data所进行的处理;
     - TimeMachine通过time.OnStep来得到下一次发生处理的延时。


     所以,Qomo在时间处理体系上的类继承图设计如下:
    ---------------


    (images\timer_architectur.jpg)
    ---------------
    其中TTimer是对window.setInterval和window.setTimeout的一个封装;TTimeline派生自
    TTimeMachine,用于简化“固定间隔的时间线”的处理。TYuiSteper继承自TSteper,它的
    部分代码来自于Yahoo UI开源项目。其中有一个名为TStepTrigger类,它的作用是在每次
    step时计算数据/时间值的增量,是一个工具类,也来自于对Yahoo UI中相同功能的封装。


    三、测试代码及分析(1)
    ~~~~~~~~~~~~~~~~~~
    Qomo最初打算按Yahoo UI中的动画效果演示来做一个DEMO,因此这个DEMO最初的效果,只
    是在屏幕上的一个点,从位置A飞行到位置B。

    飞行过程有一点要求:
      - 飞行中速度是可控的,便如渐快/渐慢,或快-慢-快这样的变化;

    我们说过,速度可以表现为单位时间内的数据(飞行长度)变化,也可以表现为单位数据所
    耗的时间不同。在示例中,我们使用前者,也就是“单位时间”。那么显然,我们可以使
    用一个TTimeline组件来控制整个过程。

    所以基本的代码框架就是:
    ------------------
    <body>
    <div id=dot style="font-size:0; width:10px; height:10px; background:red; position:absolute"></div>
    </body>

    <script>
    // 0. 初始数据
    var el = document.getElementById('dot');
    var x0 = el.offsetLeft, x1 = 400;

    // 1. 构造一个时钟及其处理程序
    var doFly = function(step, data) {
      var sty = this.get('TimerData').style;
      sty.left = data;
    }
    var T2 = TTimeline.Create(doFly);
    T2.set('TimerData', el);

    // 2. 构建一个数据发生器, 用于向时钟提供数据
    provide = TYuiSteper.Create();
    provide.set('From', x0);
    provide.set('To', x1);
    // 缺省值
    // provide.set('Frames', 100);
    // provide.set('Easing', 'easeOut');

    // 4. 启动时钟
    T2.start(provide, 1);
    </script>
    ------------------
    这个示例的完整代码参见(DOCUMENTs\TestCase\T_TimeLine2.html)。


    运行效果参见(DOCUMENTs\TestCase\images\T_TimeLine2.jpg)。

    我们看到,数据发生器提供的一组参数的含义,就是“用100次的周期,产生x0~x1之间的平
    滑变化的数据,采用的数据变化的算法为easeOut”。

    这个数据发生器被作为Timeline的参数传入:
    ------------------
    T2.start(provide, 1);
    ------------------

    表明数据使用provide提供的值,而时间采用1ms为间隔的时间线。

    那么,在“100次的周期”中,时间处理器(T2)的变化是什么呢?这在T2被创建的时候就声明
    过了:
    ------------------
    var T2 = TTimeline.Create(doFly);
    T2.set('TimerData', el);
    ------------------

    创建时的这行代码与下面的代码是相同的:
    ------------------
    var T2 = new Timeline(doFly);

    var T2 = TTimeline.Create();
    T2.OnTimer.add(doFly);
    ------------------
    而后我们初始化了TimerData属性的值,它表明这个时间关注的数据对象是el。


    在每次时间处理器被激活时,我们看到的doFly操作是这样:
    ------------------
    var doFly = function(step, data) {
      var sty = this.get('TimerData').style;
      sty.left = data;
    }
    ------------------

    我们从TimerData属性中取出el元素,并修改el.style.left,就完成了飞行动画。

    我们在T2.start()之前,可以调整一些数据发生器的参数,就可以改变这个飞行动画的效果。
    这些参数包括:
    ------------------
    this.set('Easing', 'easeOut'); // 数据产生的方法
    this.set('Frames', 100); // 帧数,控制step的总数
    this.set('Fps', 200);   // 帧速率, qomo beta2中未实现.
    ------------------
    其中Easing的取值参考StepTrigger.js中TStepTrigger类的方法,目前包括:
    ------------------
    easeNone
    easeIn
    easeOut
    easeBoth
    backIn
    backOut
    backBoth
    ------------------


    四、测试代码及分析(2)
    ~~~~~~~~~~~~~~~~~~
    接下来,对飞行过程又增加了一点要求:
      - 飞行的路线是可控的,而不是单纯的A-B的直线。

    路线可控是通过“控制点”来实现的。贝赛尔曲线的特点是“在两点之间增加一个控制点,
    即可以形成贝赛尔曲线”。用来做“飞行路线”,那么即是:无论在两点之间增加多少个
    控制点,则通过贝赛尔曲线的连结,最终可以从A点飞行到B点。

    这与上面的示例还有一点明显不一致的地方:飞行的坐标是x,y同时发生变化,而非单一
    地在x方向上平移。所以这个需求其实包含了两个技术要点:
      - 处理器(TTimeline)与提供者(TYuiSteper)都需要能处理两个以上的数据
      - 提供者能够有更复杂的运算能力

    但是,需要留意的是,这个需求的基本逻辑并没有变化:要求一个文档对象(element)能
    做飞行的动态效果。因此我们在上面的代码框架上做一些修改:
    ------------------
    // 0. 初始数据
    var el = document.getElementById('dot');
    var fromPoint = [el.offsetLeft, el.offsetTop];

    // 1. 构造一个时钟及其处理程序
    var doFly = function(step, data) {
      var sty = this.get('TimerData').style;
      sty.left = data[0];
      sty.right = data[1];
    }
    var T2 = TTimeline.Create(doFly);
    T2.set('TimerData', el);

    // 2. 构建一个数据发生器, 用于向时钟提供数据
    provide = TYuiSteper.Create();
    provide.set('Points', [
     fromPoint,    // from: x0, y0
     [400, 400]    // to:   x1, y1
    ]);

    // 5. 启动时钟
    T2.start(provide, 1);
    ------------------
    这个示例的完整代码参见(DOCUMENTs\TestCase\T_TimeLine3.html)。


    我们看到,基本上我们只重新约定了doFly与provide交互的数据格式(从原来的单一值,
    变成数组表示的坐标点),然后我们就完成了主要代码。

    但是我们前面说过,路线可控是通过贝赛尔曲线来实现的。当贝赛尔曲线只有起始点与
    结束点,而没有中间控制点时,其实将绘制为一条直线。也就是说,上例的实飞行路线
    的效果是从fromPoint到[400, 400]的一条直线。

    但我们只需要调整控制点,即可以完成“可控的飞行路线”。例如:
    ------------------
    provide.set('Points', [
     fromPoint,    // from: x0, y0
     [200, 180],
     [500, 600],
     [200, 400],
     [1024, 200],
     [100,320],
     [400, 400]    // to:   x1, y1
    ]);
    ------------------

    我们注意到一点事实:我们并没有修改任何屏幕表现的算法,也没有修改代码框架,就
    实现了UI上的表达效果的可控。这其实是“数据提供”与“数据表现”分离所带来的效
    果。在现代的UI设计上,数据表现与数据提供,以及业务逻辑三者的分离,是一个非常
    关键的话题。


    五、测试代码及分析(3)
    ~~~~~~~~~~~~~~~~~~
    在阅读代码的朋友可能已经发现,真实代码中的示例与这里讲述的稍有差异。但更仔细
    地看代码,你会发现更有价值的东西:我们在界面上绘制了一条曲线,但没有修改原来
    框架中的任何代码!

    我们前面提到的例子,都只是要求“一个点在界面上从A飞行到B”。但是,这个飞行的
    效果却难以被观察。——大家知道,点的轨迹是线。因此如果我们将这个飞行过程中的
    点连接起来,就应该是一条直线/曲线表达的飞行轨迹。

    但从业务需求上来说,这条“飞行轨迹”并不是原始需求中的部分。——它只是我们在
    开发过程中,需要进行的观察。简而言之,要么我们写一些代码嵌在原来的业务逻辑中,
    要么我们做一个新系统在原来的业务之外观察它。

    这个“在原来的业务之外”观察的系统,其实就可以用AOP来实现。因为AOP本来就是用
    来观察一批对象的行为的。所以我们在T_TimeLine2.html和T_TimeLine3.html这两个示
    例中都用了AOP。

    两个代码中只有极少的不同,主要差异还是在于data提供的格式不一致。我们以T_Time-
    Line3.html为例:
    ------------------
    // 3. 使用切面来观察绘制过程
    var asp_OnTimer = new ObjectAspect(T2, 'OnTimer', 'Event', fromPoint); //push a meta_data
    asp_OnTimer.OnAfter.add(function(o, n, p, a, v) {
      var data = a[1], pt = this.get('MetaData')[0];
      drawLine(pt[0], pt[1], data[0], data[1], 'red', 1, 0);
      this.set('MetaData', [data]);
      $debug(a[0], ':', data);
    });
    ------------------
    运行效果参见(DOCUMENTs\TestCase\images\T_TimeLine3_1.jpg)。


    这里用到了一个以前在讲AOP时未详述的meta_data。所谓meta_data,是与一个切面相关
    的数据,它应当在切面进入时通过aspect自身可访问到。它是切面方法执行过程中的参
    考数据。

    这个aspect需要一个起点,来做轨迹绘制的第一个坐标。这个起点就是fromPoint。它在切
    面创建时被传入。所以"MetaData"属性的值。——MetaData是一个数组,所以fromPoint实
    际上是该属性值的第一个元素。

    所以我们看到了切面的OnAfter中添加了一个事件处理函数。其中:
    ------------------
    var data = a[1], pt = this.get('MetaData')[0];
    // ...

    this.set('MetaData', [data]);
    ------------------

    data是当前的“被观察系统”正在处理的数据,而pt则是上一次的数据,它被不断更新着。
    该事件的入口参数a,是被观察者T2.OnTimer调用时的参数。我们知道这个事件的声明是:
    ------------------
    T2.OnTimer = function(step, data) { }
    ------------------

    所以第一个参数就是data,这就是data=a[1]的由来。

    最后,由于我们知道data与切面的元数据(MetaData[0])都是表示点的数据。所以下面这
    行代码就是画线了:
    ------------------
    drawLine(pt[0], pt[1], data[0], data[1], ...);
    ------------------

    我们回顾一下前面的内容,由于OnTimer的调用与数据提供者有关。也就是说,provide提
    供多少数据,则界面上显示多少数据。——而这个“多少数据”是由TSteper中的step值来
    控制的。也就是OnTimer中的第一个参数,或说是OnStep中的第一个参数。因此,在切面中,
    我们可以通过下面的代码显示出数据的变化:
    ------------------
    $debug(a[0], ':', data);
    ------------------

    在这个示例中,如果我们可以改变provide的一些属性,那么就可以在界面上看到这些变化
    了。例如缺省情况下,Frames值为100帧,所以界面上显示了100条数据。但如果改成10帧,
    那么显示数据也减少了,而曲线也就不足够平滑了:
    ------------------
    // 2. 构建一个数据发生器, 用于向时钟提供数据
    provide = TYuiSteper.Create();
    provide.set('Frames', 10);
    ------------------

    运行效果参见(DOCUMENTs\TestCase\images\T_TimeLine3_2.jpg)。

    而如果你修改时间线的间隔,也会发现显示效果不够平滑了。例如:
    ------------------
    // 5. 启动时钟
    T2.start(provide, 200);
    ------------------


    六、测试代码及分析(4)
    ~~~~~~~~~~~~~~~~~~
    这一小节的分析,我们只是简单地说一下这个测试代码的界面控制。

    如果一个时间线被设计得过长,例如1000 * 1ms,那么我们就可能需要一个随时可以中断
    的时间线。也就是说,时间不但能被start,也能被stop。因此,TTimer这个基类就设计了
    一些基本的控制逻辑。包括:
    ------------------
    TTimer.start()
      TTimer.OnStart
      TTimer.OnTimer
    TTimer.stop()
      TTimer.OnStop
    ------------------

    因此,为了测试这些逻辑,示例中也包括下面的一些代码:
    ------------------
    // 4. 测试基类中的控制方法
    T2.OnStart.add(function() {
      $debug(' -- timer start --');
    });
    T2.OnStop.add(function() {
      $debug(' -- timer stop --');
    });
    document.onclick = function() {
      T2.stop();
    }
    ------------------

     

    七、其它
    ~~~~~~~~~~~~~~~~~~
    本文档中,我们讲述了Qomo的时间框架,也结合实例讲述了AOP的用法。


      1. 关于在界面上的图形绘制
      -----------
      大家在本文档中看到的图形都比较精细,但运行代码包中示例时看到的线条却比较粗糙。这是
    因为文档中使用的drawLine()来自于一个更精细的图形代码包。我将在后续的版本发布中,公开
    这个图形库。——目前在Qomo中有一个VML的图形库,还有在TestCase中用的drawLine.js。但这
    些都不是Qomo最终的图形框架。

       更新的图形框架在设计和实现中,并没有完成,故暂不公开。


      2. 关于兼容性问题
      -----------
      目前大家看到的示例代码在FireFox上不能运行。其实只有一个原因,就是FF里没有insertAdj-
    acentHTML方法,因此也就不能用drawLine.js中的代码在界面上画图,以及用$debug来输出文字。
    因此去掉相关的代码后,就可以运行示例了。

      在开发中的版本,已经解决过insertAdjacentHTML等问题,因此是可以run在firefox上的。但
    这些代码放在一个还没有被pub出来的DOM兼容层上,因此也暂时不公开。

      Qomo试图在DOM上很好的兼容Firefox等浏览器,但目前看起仍有较大的困难。:(


      3. 关于TTimer类
      -----------
      TTimer类是对window.setTimeout和window.setInterval的封装。它明显的特点是规范了这两个
    方法的使用流程。——在Qomo中,二者的使用将没有明显的差异。

      由于TTimer.OnTimer事实上已经支持使用TSteper,因此TTimer能使用Steper.js中的各种类。这
    个示例在T_Timeline.html中。

      TTimer类另一个最大的特点,是便得OnTimer中能够使用类方法,并正确地传入对象的this引用。
    ——而setTimeout与setInterval只能传入函数引用,不能传入方法。因此this引用总是指向window。
    -----------
    setTimeout(function() {
      alert(this === window);
    }, 1000);
    -----------

      4. Qomo时间框架上的一些TODO
      -----------
      事实上目前的Qomo时间框架并不完整。例如还没有处理Fps(帧速率),以及修正由IE的setTimeout()
    精度带来的时间线不规则。——这其实需要在运算中做时间补偿。
      Qomo应该还有一个能表现“时间与数据同时变化”的效果的示例。

      这些代码将在beta 3发布时统一提供。 

    发表于 @ 2006年10月22日 12:49:00|评论(loading...)|编辑

    新一篇: 编程的另一面:从生死到变化~ | 旧一篇: 【原创】搞了个NetGear的路由器,为此写了个小程序来查Wan IP.

    评论

    #aimingoo 发表于2006-10-23 10:11:00  IP: 61.172.241.*
    有一个极细微的bug。哈俣。下个版本再修吧。
    --------
    在YuiSteper.js的第15行,将:
    if (method && step<frames) {
    改成:
    if (method && step<=frames) {
    就OK了。
    --------

    少算了一个帧。
    发表评论  


    当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
    Csdn Blog version 3.1a
    Copyright © aimingoo