uuCanvas.js のサブセットとして VMLCanvas.js を切り出しました + 速くしました
uuCanvas.js は、ExplorerCanvas をヒントに作成した、canvas を VML, Silverlight, Flash でレンダリングするライブラリです。
uuCanvas.js から VML 限定版として VMLCanvas.js を切り出しました。
# uuCanvas.js は 1万行。VMLCanvas.js は 2000行 です。
VMLCanvas.js は、機能的に ExplorerCanvas の上位互換となっており、より高機能、より高速、より簡単です(たぶん)。
uuCanvas.js と違い、VMLCanvas.js は外部ライブラリに依存しません。mofmof.js にも依存しておらず単体で機能します。
IE 8 で canvas が使いたくなった場合に、候補の一つとして検討してみてください。
追記
VMLCanvas.js-1.1.0.js 公開しました。
以下の変更とメソッドを追加しています。
- mod CanvasRenderingContext2D#lock
- add CanvasRenderingContext2D#animFillPath
- add CanvasRenderingContext2D#animStrokePath
- add CanvasRenderingContext2D#animFillRect
- add CanvasRenderingContext2D#animStrokeRect
上記のメソッドが適用できる Jump Dots のケースでは、ExplorerCanvas に比べて、5~10%程度CPU負荷が低減し、20~30%程度高速化しています。
速度比較動画はこちらから http://screencast.com/t/C5jbJWSOVZSP ExplorerCanvas → uuCanvas.js (VML Backend) → VMLCanvas.js
アニメーション系 API の仕組み
anim*** API は、一度生成した VMLNode のリサイクルを行うことで、insertAdjacentHTML によるノードの生成と innerHTML = "" による削除時間をカットしています。
2回目以降の呼び出しでは、生成済みの VMLNode を検索し、パス情報と色情報だけを更新することで描画を更新しています。
また、lock() の実装を変更し、
lock(false) で canvas を display:none に変更し、unlock() で display:inline-block に戻すことで、リサイクル中のちらつきを極力低減しています。
内部実装の都合がAPIにまで出てしまっており、かなり残念な感じもありますが、
喉から手が出るほど速度が欲しい場合に、こういう奥の手があるよということで。
// --- Extends --------------------------------------------- // CanvasRenderingContext2D.prototype.lock function lock(clear) { // @arg Boolean(= false): lazy clear if (this._lockState & 0x1) { throw new TypeError("DUPLICATE_LOCK"); } this._lockState = 0x1; if (clear) { this._lockState |= 0x2; } else { this._lockState |= 0x4; this._view.style.display = "none"; } } // CanvasRenderingContext2D.prototype.unlock function unlock() { if (this._lockState & 0x1) { if (this._lockState & 0x2) { this.clear(); } if (this._lockStack.length) { this._view.insertAdjacentHTML("BeforeEnd", this._lockStack.join("")); this._lockStack = []; } if (this._lockState & 0x4) { this._view.style.display = "inline-block"; } this._lockState = 0x0; } } // CanvasRenderingContext2D.prototype.clear function clear() { // reset state this._zindex = 0; this._view.innerHTML = ""; // clear all } function animFillPath(id, // @arg String: id x, // @arg Number: moveTo(x) y, // @arg Number: moveTo(y) lines) { // @arg NumberArray: lineTo([<x0, y0>, <x1, y1>, ...]) this.animStrokePath(id, x, y, lines, true); } function animStrokePath(id, // @arg String: id x, // @arg Number: moveTo(x) y, // @arg Number: moveTo(y) lines, // @arg NumberArray: lineTo([<x0, y0>, <x1, y1>, ...]) fill) { // @arg Boolean(= false): true is fill var path = []; // --- create path --- { // moveTo(x, y) var m = this._matrix, ix = (x * m[0] + y * m[3] + m[6]) * 10 - 5, iy = (x * m[1] + y * m[4] + m[7]) * 10 - 5; path.push("m ", Math.round(ix), " ", Math.round(iy)); var i = 0, iz = lines.length; // lineTo(x, y) for (; i < iz; i += 2) { x = lines[i]; y = lines[i + 1]; ix = (x * m[0] + y * m[3] + m[6]) * 10 - 5; iy = (x * m[1] + y * m[4] + m[7]) * 10 - 5; path.push("l ", Math.round(ix), " ", Math.round(iy)); } path.push(" x"); } var node = _nodeCache[id]; if (node) { node.path = path.join(""); var color = _color(fill ? this.fillStyle : this.strokeStyle), child = node.firstChild; child.color = color.hex; child.opacity = this.globalAlpha * color.a; } else { _applyProperties(this, fill); var fg; if (fill) { fg = '<v:shape id="' + id + '" style="position:absolute;width:10px;height:10px;z-index:0' + '" filled="t" stroked="f" coordsize="100,100" path="' + path.join("") + '"><v:fill color="' + this.__fillStyle.hex + '" opacity="' + (this.globalAlpha * this.__fillStyle.a) + '" /></v:shape>'; } else { var props = _buildStrokeProps(this); fg = '<v:shape id="' + id + '" style="position:absolute;width:10px;height:10px;z-index:0' + '" filled="f" stroked="t" coordsize="100,100" path="' + path.join("") + '"><v:stroke color="' + this.__strokeStyle.hex + '" opacity="' + (this.globalAlpha * this.__strokeStyle.a) + props + '" /></v:shape>'; } this._view.insertAdjacentHTML("BeforeEnd", fg); _nodeCache[id] = document.getElementById(id); // { id: node } } } // CanvasRenderingContext2D.prototype.animFillRect function animFillRect(id, x, y, w, h) { this.animStrokeRect(id, x, y, w, h, 1); } // CanvasRenderingContext2D.prototype.animStrokeRect function animStrokeRect(id, // @arg String: id x, // @arg Number: y, // @arg Number: w, // @arg Number: h, // @arg Number: fill) { // @arg Boolean(= false): true is fill var node = _nodeCache[id]; if (node) { node.path = _rect(this, x, y, w, h); var color = _color(fill ? this.fillStyle : this.strokeStyle), child = node.firstChild; child.color = color.hex; child.opacity = this.globalAlpha * color.a; } else { this.stroke(_rect(this, x, y, w, h), 1); _nodeCache[id] = document.getElementById(id); // { id: node } } }