Skip to content

tick&draw

何波 edited this page Feb 24, 2019 · 14 revisions

Stage (Extends Container)

tick入口方法

每次tick更新Stage.prototype.update()方法

A stage is the root level Container for a display list. Each time its tick method is called, it will render its display list to its target canvas.

let stage = new createjs.Stage("canvasElementId");
let image = new createjs.Bitmap("imagePath.png");
stage.addChild(image);
createjs.Ticker.addEventListener("tick", handleTick);
function handleTick(event) {
   image.x += 10;
   // 这里一定要加上event,如果不加event在tick的时候缺少很多事件属性,如不会有event.delta。
   // 则spriteSheet的data.framerate不会生效
   stage.update(event);
}

Stage源码分析

// 参数props:object对象。Should usually be a Ticker event object, or similar object with a delta property
p.update = function(props) {
    if (!this.canvas) { return; }

    // 执行tick方法
    if (this.tickOnUpdate) { this.tick(props); }
    if (this.dispatchEvent("drawstart", false, true) === false) { return; }
    createjs.DisplayObject._snapToPixelEnabled = this.snapToPixelEnabled;
    
    var r = this.drawRect, ctx = this.canvas.getContext("2d");
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    if (this.autoClear) {
        if (r) { ctx.clearRect(r.x, r.y, r.width, r.height); }
        else { ctx.clearRect(0, 0, this.canvas.width+1, this.canvas.height+1); }
    }
    ctx.save();
    if (this.drawRect) {
        ctx.beginPath();
        ctx.rect(r.x, r.y, r.width, r.height);
        ctx.clip();
    }
    // 本身没有updateContext方法,执行父类Container的父类DisplayObject的updateContext方法
    this.updateContext(ctx);
    // 执行父类Container的draw方法
    this.draw(ctx, false);
    ctx.restore();
    this.dispatchEvent("drawend");
};

// 在display list当中自动调用,除非TickOnUpdate设置为false,否则`update`方法将自动调用此函数。

// 如果将props对象传递给tick(),则它的所有属性都将复制到传播到侦听器的事件对象。

// easeljs中的一些基于时间的特性(例如sprite/framerate)要求将tick事件对象(或具有delta属性的等效对象)作为props参数传递给tick()

p.tick = function(props) {
  if (!this.tickEnabled || this.dispatchEvent("tickstart", false, true) === false) { return; }
  var evtObj = new createjs.Event("tick");
  if (props) {
    for (var n in props) {
      if (props.hasOwnProperty(n)) { evtObj[n] = props[n]; }
    }
  }

  // 注意,Stage自身无_tick方法,这里会调用父类Container的_tick方法。
  this._tick(evtObj);
  this.dispatchEvent("tickend");
};

Container (Extends DisplayObject)

p.draw = function(ctx, ignoreCache) {
    if (this.DisplayObject_draw(ctx, ignoreCache)) { return true; }

    // this ensures we don't have issues with display list changes that occur during a draw:
    var list = this.children.slice(); // 复制this.children数组
    // 这里为什么只循环Container中的一层子元素,因为整个Stage就是多个Container互相包裹而成,就是Stage->Container->Container->Shape等
    for (var i=0,l=list.length; i<l; i++) {
        var child = list[i];
        if (!child.isVisible()) { continue; }

        // draw the child:
        ctx.save();
      
        child.updateContext(ctx);
        // 遍历子元素,调用子元素的draw方法,子元素如果是Shape或Bitmap,则调用它们自身的draw方法
        child.draw(ctx);
        ctx.restore();
    }
    return true;
};

// evtObj事件对象在所有的tick事件中都会触发,evtObj对象在dispatchers中重复使用,以减少构造和垃圾回收的花费。
// 此方法在Stage的tick方法中被调用
p._tick = function(evtObj) {
    if (this.tickChildren) {
        for (var i=this.children.length-1; i>=0; i--) {
            var child = this.children[i];
            if (child.tickEnabled && child._tick) { child._tick(evtObj); }
        }
    }
    // 调用DisplayObject的_tick方法
    this.DisplayObject__tick(evtObj);
};

DisplayObject (Extends EventDispatcher)

DisplayObject是不应直接构造的抽象类。而是构造子类,如Container、Bitmap和Shape。DisplayObject是easeljs库中所有显示类的基类。它定义所有显示对象之间共享的核心属性和方法,例如转换属性(x、y、scalex、scaley等)、缓存和鼠标处理程序。

// 将显示对象绘制到指定的上下文中,忽略其可见visible、alpha、阴影shadow和转换transform。
// 如果draw方法调用返回true,(对于重写功能很有用)
// 注:此方法主要用于内部使用,但可能用于高级用途。
p.draw = function(ctx, ignoreCache) {
    var cache = this.bitmapCache;
    if(cache && !ignoreCache) {
        return cache.draw(ctx);
    }
    return false;
};

// 将此显示对象的转换、alpha、globalcompositeoperation、剪切路径 clipping path(mask)和阴影shadow应用于**指定的上下文**。
// 此方法通常在`DisplayObject.draw()`方法之前调用
p.updateContext = function(ctx) {
    var o=this, mask=o.mask, mtx= o._props.matrix;

    if (mask && mask.graphics && !mask.graphics.isEmpty()) {
        mask.getMatrix(mtx);
        ctx.transform(mtx.a,  mtx.b, mtx.c, mtx.d, mtx.tx, mtx.ty);

        mask.graphics.drawAsPath(ctx);
        ctx.clip();

        mtx.invert();
        ctx.transform(mtx.a,  mtx.b, mtx.c, mtx.d, mtx.tx, mtx.ty);
    }

    this.getMatrix(mtx); // getMatrix(mtx) 方法非常重要
    var tx = mtx.tx, ty = mtx.ty;
    if (DisplayObject._snapToPixelEnabled && o.snapToPixel) {
        tx = tx + (tx < 0 ? -0.5 : 0.5) | 0;
        ty = ty + (ty < 0 ? -0.5 : 0.5) | 0;
    }
    ctx.transform(mtx.a,  mtx.b, mtx.c, mtx.d, tx, ty);
    ctx.globalAlpha *= o.alpha;
    if (o.compositeOperation) { ctx.globalCompositeOperation = o.compositeOperation; }
    if (o.shadow) { this._applyShadow(ctx, o.shadow); }
};

p._tick = function(evtObj) {
    // because tick can be really performance sensitive, check for listeners before calling dispatchEvent.
    var ls = this._listeners;
    if (ls && ls["tick"]) {
        // reset & reuse the event object to avoid construction / GC costs:
        evtObj.target = null;
        evtObj.propagationStopped = evtObj.immediatePropagationStopped = false;
        this.dispatchEvent(evtObj);
    }
};

// Returns a matrix based on this object's current transform
p.getMatrix = function(matrix) {
  var o = this, mtx = matrix&&matrix.identity() || new createjs.Matrix2D();
  // mtx.appendTransform方法中的参数是createjs自定义的属性,如o.rotation在canvas是没有的,而通过把自定义的rotation通过mtx.appendTransform方法转换成一个矩阵对象,在DisplayObject的updateContext方法中,用ctx.transform(mtx.a,  mtx.b, mtx.c, mtx.d, mtx.tx, mtx.ty)来对DisplayObject进行矩阵变换
  return o.transformMatrix ?  mtx.copy(o.transformMatrix) : mtx.appendTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation, o.skewX, o.skewY, o.regX, o.regY);
};

Bitmap (extends DisplayObject)

p.draw = function(ctx, ignoreCache) {
    if (this.DisplayObject_draw(ctx, ignoreCache)) { return true; }
    var img = this.image, rect = this.sourceRect;
    if (img.getImage) { img = img.getImage(); }
    if (!img) { return true; }
    if (rect) {
        // some browsers choke on out of bound values, so we'll fix them:
        var x1 = rect.x, y1 = rect.y, x2 = x1 + rect.width, y2 = y1 + rect.height, x = 0, y = 0, w = img.width, h = img.height;
        if (x1 < 0) { x -= x1; x1 = 0; }
        if (x2 > w) { x2 = w; }
        if (y1 < 0) { y -= y1; y1 = 0; }
        if (y2 > h) { y2 = h; }
        ctx.drawImage(img, x1, y1, x2-x1, y2-y1, x, y, x2-x1, y2-y1);
    } else {
        ctx.drawImage(img, 0, 0);
    }
    return true;
};

Shape (Extends DisplayObject)

p.draw = function(ctx, ignoreCache) {
    if (this.DisplayObject_draw(ctx, ignoreCache)) { return true; }
    this.graphics.draw(ctx, this);
    return true;
};

Sprite (extends DisplayObject)

p.draw = function(ctx, ignoreCache) {
    if (this.DisplayObject_draw(ctx, ignoreCache)) { return true; }
    this._normalizeFrame();
    var o = this.spriteSheet.getFrame(this._currentFrame|0);
    if (!o) { return false; }
    var rect = o.rect;
    if (rect.width && rect.height) { ctx.drawImage(o.image, rect.x, rect.y, rect.width, rect.height, -o.regX, -o.regY, rect.width, rect.height); }
    return true;
};

MovieClip (extends Container)

p.draw = function(ctx, ignoreCache) {
    // draw to cache first:
    if (this.DisplayObject_draw(ctx, ignoreCache)) { return true; }
    this._updateState();
    this.Container_draw(ctx, ignoreCache);
    return true;
};

DOMElement (extends DisplayObject)

p.draw = function(ctx, ignoreCache) {
    // this relies on the _tick method because draw isn't called if the parent is not visible.
    // the actual update happens in _handleDrawEnd
    return true;
};

p._tick = function(evtObj) {
    var stage = this.stage;
    if(stage && stage !== this._oldStage) {
        this._drawAction && stage.off("drawend", this._drawAction);
        this._drawAction = stage.on("drawend", this._handleDrawEnd, this);
        this._oldStage = stage;
    }
    this.DisplayObject__tick(evtObj);
};

p._handleDrawEnd = function(evt) {
    var o = this.htmlElement;
    if (!o) { return; }
    var style = o.style;

    var props = this.getConcatenatedDisplayProps(this._props), mtx = props.matrix;

    var visibility = props.visible ? "visible" : "hidden";
    if (visibility != style.visibility) { style.visibility = visibility; }
    if (!props.visible) { return; }

    var oldProps = this._oldProps, oldMtx = oldProps&&oldProps.matrix;
    var n = 10000; // precision

    if (!oldMtx || !oldMtx.equals(mtx)) {
        var str = "matrix(" + (mtx.a*n|0)/n +","+ (mtx.b*n|0)/n +","+ (mtx.c*n|0)/n +","+ (mtx.d*n|0)/n +","+ (mtx.tx+0.5|0);
        style.transform = style.WebkitTransform = style.OTransform = style.msTransform = str +","+ (mtx.ty+0.5|0) +")";
        style.MozTransform = str +"px,"+ (mtx.ty+0.5|0) +"px)";
        if (!oldProps) { oldProps = this._oldProps = new createjs.DisplayProps(true, null); }
        oldProps.matrix.copy(mtx);
    }

    if (oldProps.alpha != props.alpha) {
        style.opacity = ""+(props.alpha*n|0)/n;
        oldProps.alpha = props.alpha;
    }
};

Clone this wiki locally