diff --git a/CHANGELOG.md b/CHANGELOG.md index be7f89b7..726de791 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## 9.3.21 (not released) + +- Fixed memory leaks on Tween destroy +- Fixed incorrect export of stage/layer when internal nodes used buffer canvas for rendering + ## 9.3.20 (2025-03-20) - Fix text rendering when ellipses are used diff --git a/src/Container.ts b/src/Container.ts index 73d77a0b..c70026fa 100644 --- a/src/Container.ts +++ b/src/Container.ts @@ -120,8 +120,6 @@ export abstract class Container< * layer.add(shape1, shape2, shape3); * // empty arrays are accepted, though each individual child must be defined * layer.add(...shapes); - * // remember to redraw layer if you changed something - * layer.draw(); */ add(...children: ChildType[]) { if (children.length === 0) { diff --git a/src/Layer.ts b/src/Layer.ts index 8e94c1d1..2ffc12f3 100644 --- a/src/Layer.ts +++ b/src/Layer.ts @@ -385,7 +385,7 @@ export class Layer extends Container { // empty pixel return {}; } - drawScene(can?: SceneCanvas, top?: Node) { + drawScene(can?: SceneCanvas, top?: Node, bufferCanvas?: SceneCanvas) { const layer = this.getLayer(), canvas = can || (layer && layer.getCanvas()); @@ -397,7 +397,7 @@ export class Layer extends Container { canvas.getContext().clear(); } - Container.prototype.drawScene.call(this, canvas, top); + Container.prototype.drawScene.call(this, canvas, top, bufferCanvas); this._fire(DRAW, { node: this, diff --git a/src/Node.ts b/src/Node.ts index 86ee06f7..7e1d9d37 100644 --- a/src/Node.ts +++ b/src/Node.ts @@ -693,39 +693,32 @@ export abstract class Node { evtStr: K, handler: KonvaEventListener ) { - this._cache && this._cache.delete(ALL_LISTENERS); + if (this._cache) { + this._cache.delete(ALL_LISTENERS); + } if (arguments.length === 3) { return this._delegate.apply(this, arguments as any); } - let events = (evtStr as string).split(SPACE), - len = events.length, - n, - event, - parts, - baseEvent, - name; + const events = (evtStr as string).split(SPACE); /* * loop through types and attach event listeners to * each one. eg. 'click mouseover.namespace mouseout' * will create three event bindings */ - for (n = 0; n < len; n++) { - event = events[n]; - parts = event.split('.'); - baseEvent = parts[0]; - name = parts[1] || ''; + for (let n = 0; n < events.length; n++) { + const event = events[n]; + const parts = event.split('.'); + const baseEvent = parts[0]; + const name = parts[1] || ''; // create events array if it doesn't exist if (!this.eventListeners[baseEvent]) { this.eventListeners[baseEvent] = []; } - this.eventListeners[baseEvent].push({ - name: name, - handler: handler, - }); + this.eventListeners[baseEvent].push({ name , handler }); } return this; @@ -2208,7 +2201,7 @@ export abstract class Node { * node.addName('selected'); * node.name(); // return 'red selected' */ - addName(name) { + addName(name: string) { if (!this.hasName(name)) { const oldName = this.name(); const newName = oldName ? oldName + ' ' + name : name; @@ -2380,7 +2373,7 @@ export abstract class Node { const topListeners = this._getProtoListeners(eventType); if (topListeners) { - for (var i = 0; i < topListeners.length; i++) { + for (let i = 0; i < topListeners.length; i++) { topListeners[i].handler.call(this, evt); } } @@ -2389,7 +2382,7 @@ export abstract class Node { // because events can be added/removed while firing const selfListeners = this.eventListeners[eventType]; if (selfListeners) { - for (var i = 0; i < selfListeners.length; i++) { + for (let i = 0; i < selfListeners.length; i++) { selfListeners[i].handler.call(this, evt); } } diff --git a/src/Shape.ts b/src/Shape.ts index 70305ec2..b98e3cf9 100644 --- a/src/Shape.ts +++ b/src/Shape.ts @@ -594,13 +594,12 @@ export class Shape< // 3 - when node is cached and we need to draw it into layer const layer = this.getLayer(); - let canvas = can || layer!.getCanvas(), + const canvas = can || layer!.getCanvas(), context = canvas.getContext() as SceneContext, cachedCanvas = this._getCanvasCache(), drawFunc = this.getSceneFunc(), - hasShadow = this.hasShadow(), - stage, - bufferContext; + hasShadow = this.hasShadow(); + let stage, bufferContext; const skipBuffer = canvas.isCache; const cachingSelf = top === this; @@ -633,7 +632,7 @@ export class Shape< bufferContext.save(); bufferContext._applyLineJoin(this); // layer might be undefined if we are using cache before adding to layer - var o = this.getAbsoluteTransform(top).getMatrix(); + const o = this.getAbsoluteTransform(top).getMatrix(); bufferContext.transform(o[0], o[1], o[2], o[3], o[4], o[5]); drawFunc.call(this, bufferContext, this); @@ -651,7 +650,7 @@ export class Shape< context._applyLineJoin(this); if (!cachingSelf) { - var o = this.getAbsoluteTransform(top).getMatrix(); + const o = this.getAbsoluteTransform(top).getMatrix(); context.transform(o[0], o[1], o[2], o[3], o[4], o[5]); context._applyOpacity(this); context._applyGlobalCompositeOperation(this); diff --git a/src/Stage.ts b/src/Stage.ts index 495f2b68..71ce4910 100644 --- a/src/Stage.ts +++ b/src/Stage.ts @@ -214,11 +214,11 @@ export class Stage extends Container { */ setContainer(container) { if (typeof container === STRING) { + let id; if (container.charAt(0) === '.') { const className = container.slice(1); container = document.getElementsByClassName(className)[0]; } else { - var id; if (container.charAt(0) !== '#') { id = container; } else { diff --git a/src/Tween.ts b/src/Tween.ts index bb2d8278..c1d61b78 100644 --- a/src/Tween.ts +++ b/src/Tween.ts @@ -200,8 +200,7 @@ export class Tween { nodeId = node._id, easing = config.easing || Easings.Linear, yoyo = !!config.yoyo; - let duration, - key; + let duration, key; if (typeof config.duration === 'undefined') { duration = 0.3; @@ -268,11 +267,7 @@ export class Tween { _addAttr(key, end) { const node = this.node, nodeId = node._id; - let diff, - len, - trueEnd, - trueStart, - endRGBA; + let diff, len, trueEnd, trueStart, endRGBA; // remove conflict from tween map if it exists const tweenId = Tween.tweens[nodeId][key]; @@ -352,14 +347,7 @@ export class Tween { _tweenFunc(i) { const node = this.node, attrs = Tween.attrs[node._id][this._id]; - let key, - attr, - start, - diff, - newVal, - n, - len, - end; + let key, attr, start, diff, newVal, n, len, end; for (key in attrs) { attr = attrs[key]; @@ -528,11 +516,26 @@ export class Tween { this.pause(); + // Clean up animation + if (this.anim) { + this.anim.stop(); + } + + // Clean up tween entries for (const key in attrs) { delete Tween.tweens[nodeId][key]; } + // Clean up attrs entry delete Tween.attrs[nodeId][thisId]; + + // Clean up parent objects if empty + if (Object.keys(Tween.tweens[nodeId]).length === 0) { + delete Tween.tweens[nodeId]; + } + if (Object.keys(Tween.attrs[nodeId]).length === 0) { + delete Tween.attrs[nodeId]; + } } } diff --git a/src/shapes/Image.ts b/src/shapes/Image.ts index a091b3ec..b4ef1935 100644 --- a/src/shapes/Image.ts +++ b/src/shapes/Image.ts @@ -163,7 +163,6 @@ export class Image extends Shape { * Konva.Image.fromURL(imageURL, function(image){ * // image is Konva.Image instance * layer.add(image); - * layer.draw(); * }); */ static fromURL( diff --git a/src/shapes/Line.ts b/src/shapes/Line.ts index 664fc3d5..f41e762d 100644 --- a/src/shapes/Line.ts +++ b/src/shapes/Line.ts @@ -102,26 +102,24 @@ export class Line< } _sceneFunc(context: Context) { - let points = this.points(), + const points = this.points(), length = points.length, tension = this.tension(), closed = this.closed(), - bezier = this.bezier(), - tp, - len, - n; + bezier = this.bezier(); if (!length) { return; } + let n = 0; context.beginPath(); context.moveTo(points[0], points[1]); // tension if (tension !== 0 && length > 4) { - tp = this.getTensionPoints(); - len = tp.length; + const tp = this.getTensionPoints(); + const len = tp.length; n = closed ? 0 : 4; if (!closed) { diff --git a/src/shapes/Text.ts b/src/shapes/Text.ts index eeeb6026..129ff3af 100644 --- a/src/shapes/Text.ts +++ b/src/shapes/Text.ts @@ -171,7 +171,7 @@ function checkDefaultFill(config?: TextConfig) { * @param {String} [config.fontVariant] can be normal or small-caps. Default is normal * @param {String} [config.textDecoration] can be line-through, underline or empty string. Default is empty string. * @param {String} config.text - * @param {String} [config.align] can be left, center, or right + * @param {String} [config.align] can be left, center, right or justify * @param {String} [config.verticalAlign] can be top, middle or bottom * @param {Number} [config.padding] * @param {Number} [config.lineHeight] default is 1 @@ -885,6 +885,8 @@ Factory.addGetterSetter(Text, 'padding', 0, getNumberValidator()); * * // align text to right * text.align('right'); + * + * // justify text */ Factory.addGetterSetter(Text, 'align', LEFT); diff --git a/test/unit-tests.html b/test/unit-tests.html index a65a077c..8540e821 100644 --- a/test/unit-tests.html +++ b/test/unit-tests.html @@ -11,6 +11,7 @@