From 5630b7e415984053bc25f8053ee9992dde43a525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Mon, 4 Nov 2013 11:46:20 +0100 Subject: [PATCH] Implement caching of drawn Canvas Path objects for better performance. Already supported on recent Chrome and Safari. --- src/path/CompoundPath.js | 22 ++++++++++++++++++---- src/path/Path.js | 37 ++++++++++++++++++++++++------------- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/path/CompoundPath.js b/src/path/CompoundPath.js index 3f5ba454..24cb438d 100644 --- a/src/path/CompoundPath.js +++ b/src/path/CompoundPath.js @@ -57,6 +57,13 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{ this.addChildren(Array.isArray(arg) ? arg : arguments); }, + _changed: function _changed(flags) { + _changed.base.call(this, flags); + // Delete cached native Path + if (flags & (/*#=*/ ChangeFlag.HIERARCHY | /*#=*/ ChangeFlag.GEOMETRY)) + delete this._currentPath; + }, + insertChildren: function insertChildren(index, items, _preserve) { // Pass on 'path' for _type, to make sure that only paths are added as // children. @@ -218,10 +225,17 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{ // Return early if the compound path doesn't have any children: if (children.length === 0) return; - ctx.beginPath(); - param = param.extend({ compound: true }); - for (var i = 0, l = children.length; i < l; i++) - children[i].draw(ctx, param); + + if (this._currentPath) { + ctx.currentPath = this._currentPath; + } else { + ctx.beginPath(); + param = param.extend({ compound: true }); + for (var i = 0, l = children.length; i < l; i++) + children[i].draw(ctx, param); + this._currentPath = ctx.currentPath; + } + if (!param.clip) { this._setStyles(ctx); var style = this._style; diff --git a/src/path/Path.js b/src/path/Path.js index 51618f1a..729ad228 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -115,14 +115,15 @@ var Path = PathItem.extend(/** @lends Path# */{ _changed: function _changed(flags) { _changed.base.call(this, flags); if (flags & /*#=*/ ChangeFlag.GEOMETRY) { + // Delete cached native Path + delete (this._compound ? this._parent : this)._currentPath; delete this._length; // Clockwise state becomes undefined as soon as geometry changes. delete this._clockwise; // Curves are no longer valid if (this._curves) { - for (var i = 0, l = this._curves.length; i < l; i++) { + for (var i = 0, l = this._curves.length; i < l; i++) this._curves[i]._changed(/*#=*/ Change.GEOMETRY); - } } } else if (flags & /*#=*/ ChangeFlag.STROKE) { // TODO: We could preserve the purely geometric bounds that are not @@ -1997,7 +1998,8 @@ var Path = PathItem.extend(/** @lends Path# */{ return { _draw: function(ctx, param) { var clip = param.clip, - compound = param.compound; + // Also mark this Path as _compound so _changed() knows about it + compound = this._compound = param.compound; if (!compound) ctx.beginPath(); @@ -2015,29 +2017,38 @@ var Path = PathItem.extend(/** @lends Path# */{ return dashArray[((i % dashLength) + dashLength) % dashLength]; } - // Prepare the canvas path if we have any situation that requires it - // to be defined. - if (hasFill || hasStroke && !dashLength || compound || clip) - drawSegments(ctx, this); - - if (this._closed) - ctx.closePath(); + // CompoundPath collects its own _currentPath + if (!compound && this._currentPath) { + ctx.currentPath = this._currentPath; + } else { + // Prepare the canvas path if we have any situation that + // requires it to be defined. + if (hasFill || hasStroke && !dashLength || compound || clip) + drawSegments(ctx, this); + if (this._closed) + ctx.closePath(); + if (!compound) + this._currentPath = ctx.currentPath; + } if (!clip && !compound && (hasFill || hasStroke)) { // If the path is part of a compound path or doesn't have a fill // or stroke, there is no need to continue. this._setStyles(ctx); - // If shadowColor is defined, clear it after fill, so it won't - // be applied to both fill and stroke. If the path is only - // stroked, we don't have to clear it. if (hasFill) { ctx.fill(style.getWindingRule()); + // If shadowColor is defined, clear it after fill, so it + // won't be applied to both fill and stroke. If the path is + // only stroked, we don't have to clear it. ctx.shadowColor = 'rgba(0,0,0,0)'; } if (hasStroke) { if (dashLength) { // We cannot use the path created by drawSegments above // Use CurveFlatteners to draw dashed paths: + // NOTE: We don't cache this path in another currentPath + // since browsers that support currentPath also support + // native dashes. ctx.beginPath(); var flattener = new PathFlattener(this), length = flattener.length,