mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-04-27 00:44:41 +00:00
Fix some caching in group issues related to deep checks (#4032)
* backport * better fix
This commit is contained in:
parent
efc2a216c7
commit
7a88afb2d3
5 changed files with 54 additions and 26 deletions
|
|
@ -15,35 +15,34 @@
|
|||
}
|
||||
|
||||
function _isEqual(origValue, currentValue, firstPass) {
|
||||
if (!fabric.isLikelyNode && origValue instanceof Element) {
|
||||
// avoid checking deep html elements
|
||||
return origValue === currentValue;
|
||||
if (origValue === currentValue) {
|
||||
// if the objects are identical, return
|
||||
return true;
|
||||
}
|
||||
else if (origValue instanceof Array) {
|
||||
else if (Array.isArray(origValue)) {
|
||||
if (origValue.length !== currentValue.length) {
|
||||
return false;
|
||||
}
|
||||
for (var i = 0, len = origValue.length; i < len; i++) {
|
||||
if (origValue[i] !== currentValue[i]) {
|
||||
if (!_isEqual(origValue[i], currentValue[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (origValue && typeof origValue === 'object') {
|
||||
if (!firstPass && Object.keys(origValue).length !== Object.keys(currentValue).length) {
|
||||
var keys = Object.keys(origValue), key;
|
||||
if (!firstPass && keys.length !== Object.keys(currentValue).length) {
|
||||
return false;
|
||||
}
|
||||
for (var key in origValue) {
|
||||
for (var i = 0, len = keys.length; i < len; i++) {
|
||||
key = keys[i];
|
||||
if (!_isEqual(origValue[key], currentValue[key])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return origValue === currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -56,11 +55,11 @@
|
|||
*/
|
||||
hasStateChanged: function(propertySet) {
|
||||
propertySet = propertySet || originalSet;
|
||||
propertySet = '_' + propertySet;
|
||||
if (!Object.keys(this[propertySet]).length) {
|
||||
var dashedPropertySet = '_' + propertySet;
|
||||
if (Object.keys(this[dashedPropertySet]).length < this[propertySet].length) {
|
||||
return true;
|
||||
}
|
||||
return !_isEqual(this[propertySet], this, true);
|
||||
return !_isEqual(this[dashedPropertySet], this, true);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -54,6 +54,13 @@
|
|||
*/
|
||||
subTargetCheck: false,
|
||||
|
||||
/**
|
||||
* Groups are container, do not render anything on theyr own, ence no cache properties
|
||||
* @type Boolean
|
||||
* @default
|
||||
*/
|
||||
cacheProperties: [],
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} objects Group objects
|
||||
|
|
@ -373,8 +380,11 @@
|
|||
}
|
||||
for (var i = 0, len = this._objects.length; i < len; i++) {
|
||||
if (this._objects[i].isCacheDirty(true)) {
|
||||
var dim = this._getNonTransformedDimensions();
|
||||
this._cacheContext.clearRect(-dim.x / 2, -dim.y / 2, dim.x, dim.y);
|
||||
if (this._cacheCanvas) {
|
||||
// if this group has not a cache canvas there is nothing to clean
|
||||
var x = this.cacheWidth / this.zoomX, y = this.cacheHeight / this.zoomY;
|
||||
this._cacheContext.clearRect(-x / 2, -y / 2, x, y);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -806,8 +806,8 @@
|
|||
stateProperties: (
|
||||
'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' +
|
||||
'stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit ' +
|
||||
'angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor ' +
|
||||
'skewX skewY'
|
||||
'angle opacity fill globalCompositeOperation shadow clipTo visible backgroundColor ' +
|
||||
'skewX skewY fillRule'
|
||||
).split(' '),
|
||||
|
||||
/**
|
||||
|
|
@ -815,8 +815,8 @@
|
|||
* @type Array
|
||||
*/
|
||||
cacheProperties: (
|
||||
'fill stroke strokeWidth strokeDashArray width height stroke strokeWidth strokeDashArray' +
|
||||
' strokeLineCap strokeLineJoin strokeMiterLimit fillRule backgroundColor'
|
||||
'fill stroke strokeWidth strokeDashArray width height' +
|
||||
' strokeLineCap strokeLineJoin strokeMiterLimit backgroundColor'
|
||||
).split(' '),
|
||||
|
||||
/**
|
||||
|
|
@ -1108,7 +1108,7 @@
|
|||
* Retrieves viewportTransform from Object's canvas if possible
|
||||
* @method getViewportTransform
|
||||
* @memberOf fabric.Object.prototype
|
||||
* @return {Boolean} flipY value // TODO
|
||||
* @return {Boolean}
|
||||
*/
|
||||
getViewportTransform: function() {
|
||||
if (this.canvas && this.canvas.viewportTransform) {
|
||||
|
|
@ -1117,13 +1117,23 @@
|
|||
return fabric.iMatrix.concat();
|
||||
},
|
||||
|
||||
/*
|
||||
* @private
|
||||
* return if the object would be visible in rendering
|
||||
* @memberOf fabric.Object.prototype
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isNotVisible: function() {
|
||||
return this.opacity === 0 || (this.width === 0 && this.height === 0) || !this.visible;
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders an object on a specified context
|
||||
* @param {CanvasRenderingContext2D} ctx Context to render on
|
||||
*/
|
||||
render: function(ctx) {
|
||||
// do not render if width/height are zeros or object is not visible
|
||||
if ((this.width === 0 && this.height === 0) || !this.visible) {
|
||||
if (this.isNotVisible()) {
|
||||
return;
|
||||
}
|
||||
if (this.canvas && this.canvas.skipOffscreen && !this.group && !this.isOnScreen()) {
|
||||
|
|
@ -1152,6 +1162,7 @@
|
|||
this.drawCacheOnCanvas(ctx);
|
||||
}
|
||||
else {
|
||||
this.dirty = false;
|
||||
this.drawObject(ctx);
|
||||
if (this.objectCaching && this.statefullCache) {
|
||||
this.saveState({ propertySet: 'cacheProperties' });
|
||||
|
|
@ -1221,13 +1232,16 @@
|
|||
* on parent canvas.
|
||||
*/
|
||||
isCacheDirty: function(skipCanvas) {
|
||||
if (!skipCanvas && this._updateCacheCanvas()) {
|
||||
if (this.isNotVisible()) {
|
||||
return false;
|
||||
}
|
||||
if (this._cacheCanvas && !skipCanvas && this._updateCacheCanvas()) {
|
||||
// in this case the context is already cleared.
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
if (this.dirty || (this.statefullCache && this.hasStateChanged('cacheProperties'))) {
|
||||
if (!skipCanvas) {
|
||||
if (this._cacheCanvas && !skipCanvas) {
|
||||
var width = this.cacheWidth / this.zoomX;
|
||||
var height = this.cacheHeight / this.zoomY;
|
||||
this._cacheContext.clearRect(-width / 2, -height / 2, width, height);
|
||||
|
|
|
|||
|
|
@ -29,8 +29,11 @@
|
|||
return;
|
||||
}
|
||||
|
||||
var stateProperties = fabric.Object.prototype.stateProperties.concat();
|
||||
stateProperties.push('path');
|
||||
|
||||
var cacheProperties = fabric.Object.prototype.cacheProperties.concat();
|
||||
cacheProperties.push('path');
|
||||
cacheProperties.push('path', 'fillRule');
|
||||
|
||||
/**
|
||||
* Path class
|
||||
|
|
@ -71,6 +74,8 @@
|
|||
|
||||
cacheProperties: cacheProperties,
|
||||
|
||||
stateProperties: stateProperties,
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens)
|
||||
|
|
|
|||
|
|
@ -1156,7 +1156,7 @@
|
|||
});
|
||||
|
||||
test('isCacheDirty statefullCache disabled', function() {
|
||||
var object = new fabric.Object({ scaleX: 3, scaleY: 2});
|
||||
var object = new fabric.Object({ scaleX: 3, scaleY: 2, width: 1, height: 2});
|
||||
equal(object.dirty, true, 'object is dirty after creation');
|
||||
object.cacheProperties = ['propA', 'propB'];
|
||||
object.dirty = false;
|
||||
|
|
@ -1168,7 +1168,7 @@
|
|||
});
|
||||
|
||||
test('isCacheDirty statefullCache enabled', function() {
|
||||
var object = new fabric.Object({ scaleX: 3, scaleY: 2});
|
||||
var object = new fabric.Object({ scaleX: 3, scaleY: 2, width: 1, height: 2});
|
||||
object.cacheProperties = ['propA', 'propB'];
|
||||
object.dirty = false;
|
||||
object.statefullCache = true;
|
||||
|
|
|
|||
Loading…
Reference in a new issue