From 9f5a7f0e2d9dd3536de30c5e1aeae01c65284458 Mon Sep 17 00:00:00 2001 From: tbo47 Date: Thu, 27 Mar 2025 08:58:12 +0000 Subject: [PATCH 1/5] better let const var variable --- src/Node.ts | 33 +++++++++++++-------------------- src/Shape.ts | 11 +++++------ src/Stage.ts | 2 +- 3 files changed, 19 insertions(+), 27 deletions(-) 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 { From 53a4b494ee72cfa3c86965ef591726bc7056c450 Mon Sep 17 00:00:00 2001 From: tbo47 Date: Thu, 27 Mar 2025 10:16:05 +0000 Subject: [PATCH 2/5] better let const var variable --- src/shapes/Line.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) 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) { From fd77f305d1bef081f0c23cd8822d5e1531a4953a Mon Sep 17 00:00:00 2001 From: Anton Lavrevov Date: Fri, 28 Mar 2025 12:52:40 -0500 Subject: [PATCH 3/5] fix buffer usage in export for stage and layer. fix #1903 --- src/Container.ts | 2 -- src/Layer.ts | 4 ++-- src/shapes/Image.ts | 1 - test/unit/Shape-test.ts | 47 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 5 deletions(-) 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/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/test/unit/Shape-test.ts b/test/unit/Shape-test.ts index 26758dda..70b1929d 100644 --- a/test/unit/Shape-test.ts +++ b/test/unit/Shape-test.ts @@ -1550,6 +1550,53 @@ describe('Shape', function () { compareCanvases(canvas2, canvas1, 240, 110); }); + it('export stage when buffer canvas is for line', async function () { + var stage = addStage(); + + var layer = new Konva.Layer(); + stage.add(layer); + + const group = new Konva.Group({ + id: 'group01', + draggable: false, + opacity: 0.99, + }); + layer.add(group); + + const arrow = new Konva.Arrow({ + x: 0, + y: 0, + points: [50, 25, 200, 25, 200, 225, 400, 225], + stroke: 'purple', + fill: 'purple', + strokeWidth: 4, + pointerAtEnding: true, + bezier: true, + }); + group.add(arrow); + + const bounds = layer.getClientRect({ relativeTo: stage }); + const pos = stage.getPosition(); + + const canvas1 = layer.toCanvas({ + pixelRatio: 1, + x: bounds.x + pos.x, + y: bounds.y + pos.y, + width: bounds.width, + height: bounds.height, + }); + group.opacity(1); + const canvas2 = layer.toCanvas({ + pixelRatio: 1, + x: bounds.x + pos.x, + y: bounds.y + pos.y, + width: bounds.width, + height: bounds.height, + }); + + compareCanvases(canvas1, canvas2, 240, 110); + }); + // ====================================================== it('optional disable shadow for stroke', function () { var stage = addStage(); From f32a416e035221ef1daf1185b7fec8644ee02baa Mon Sep 17 00:00:00 2001 From: Anton Lavrevov Date: Fri, 28 Mar 2025 13:08:04 -0500 Subject: [PATCH 4/5] Fixed tween destroy memory leak. close #1898 --- CHANGELOG.md | 5 +++++ src/Tween.ts | 33 ++++++++++++++++++--------------- test/unit-tests.html | 1 + test/unit/Tween-test.ts | 34 +++++++++++----------------------- 4 files changed, 35 insertions(+), 38 deletions(-) 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/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/test/unit-tests.html b/test/unit-tests.html index 1262ea64..5bf2ce73 100644 --- a/test/unit-tests.html +++ b/test/unit-tests.html @@ -11,6 +11,7 @@