Skip to content

Commit ddbf0c1

Browse files
authored
Optimizing DOM handling in block.js (#4985)
* Optimize DOM handling in block.js Cached selectors: Stopped repetitive DOM lookups by storing references. Input reuse: Prevented layout thrashing by reusing existing elements. GPU acceleration: Swapped label positioning to CSS transforms for smoother rendering. * Linting Updates
1 parent e113bb2 commit ddbf0c1

File tree

1 file changed

+79
-25
lines changed

1 file changed

+79
-25
lines changed

js/block.js

Lines changed: 79 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,21 @@ const _blockMakeBitmap = (data, callback, args) => {
227227
img.src = "data:image/svg+xml;base64," + window.btoa(base64Encode(data));
228228
};
229229

230+
// Optimization: Cache static DOM elements to avoid repetitive querySelector/getElementById calls
231+
const _domCache = {
232+
wheelDiv: null,
233+
contextWheelDiv: null,
234+
helpfulWheelDiv: null,
235+
labelDiv: null
236+
};
237+
238+
const _getStatic = id => {
239+
if (!_domCache[id]) {
240+
_domCache[id] = docById(id);
241+
}
242+
return _domCache[id];
243+
};
244+
230245
/**
231246
* Define block instance objects and any methods that are intra-block.
232247
* Represents a block instance with associated methods.
@@ -2785,7 +2800,7 @@ class Block {
27852800
this._calculateBlockHitArea();
27862801

27872802
this.container.on("mouseover", () => {
2788-
docById("contextWheelDiv").style.display = "none";
2803+
_getStatic("contextWheelDiv").style.display = "none";
27892804

27902805
if (!that.activity.logo.runningLilypond) {
27912806
document.body.style.cursor = "pointer";
@@ -2807,14 +2822,17 @@ class Block {
28072822
* @param {Event} event - The click event.
28082823
*/
28092824
this.container.on("click", event => {
2810-
if (docById("helpfulWheelDiv") && docById("helpfulWheelDiv").style.display !== "none") {
2811-
docById("helpfulWheelDiv").style.display = "none";
2825+
if (
2826+
_getStatic("helpfulWheelDiv") &&
2827+
_getStatic("helpfulWheelDiv").style.display !== "none"
2828+
) {
2829+
_getStatic("helpfulWheelDiv").style.display = "none";
28122830
}
28132831
// We might be able to check which button was clicked.
28142832
if ("nativeEvent" in event) {
28152833
if ("button" in event.nativeEvent && event.nativeEvent.button == 2) {
28162834
that.blocks.stageClick = true;
2817-
docById("wheelDiv").style.display = "none";
2835+
_getStatic("wheelDiv").style.display = "none";
28182836
that.blocks.activeBlock = thisBlock;
28192837
piemenuBlockContext(that);
28202838
return;
@@ -3427,7 +3445,7 @@ class Block {
34273445
labelValue = this.value;
34283446
}
34293447

3430-
const labelElem = docById("labelDiv");
3448+
const labelElem = _getStatic("labelDiv");
34313449

34323450
const safetext = text => {
34333451
// Best to avoid using these special characters in text strings
@@ -3447,12 +3465,29 @@ class Block {
34473465
};
34483466

34493467
if (this.name === "text") {
3450-
labelElem.innerHTML =
3451-
'<input id="textLabel" style="position: absolute; -webkit-user-select: text;-moz-user-select: text;-ms-user-select: text;" class="text" type="text" value="' +
3452-
safetext(labelValue) +
3453-
'" />';
3468+
// Reuse existing input element or create a new one
3469+
if (!_domCache.textLabelInput) {
3470+
const el = document.createElement("input");
3471+
el.id = "textLabel";
3472+
el.style.position = "absolute";
3473+
el.style.webkitUserSelect = "text";
3474+
el.style.mozUserSelect = "text";
3475+
el.style.msUserSelect = "text";
3476+
el.className = "text";
3477+
el.type = "text";
3478+
_domCache.textLabelInput = el;
3479+
}
3480+
3481+
// Ensure it is the child of labelElem
3482+
if (_domCache.textLabelInput.parentNode !== labelElem) {
3483+
labelElem.innerHTML = "";
3484+
labelElem.appendChild(_domCache.textLabelInput);
3485+
}
3486+
3487+
this.label = _domCache.textLabelInput;
3488+
this.label.value = safetext(labelValue);
3489+
this.label.style.display = "";
34543490
labelElem.classList.add("hasKeyboard");
3455-
this.label = docById("textLabel");
34563491

34573492
// set the position of cursor to the end (for text value)
34583493
const valueLength = this.label.value.length;
@@ -3993,12 +4028,29 @@ class Block {
39934028
break;
39944029
}
39954030
} else {
3996-
labelElem.innerHTML =
3997-
'<input id="numberLabel" style="position: absolute; -webkit-user-select: text;-moz-user-select: text;-ms-user-select: text;" class="number" type="number" value="' +
3998-
labelValue +
3999-
'" />';
4031+
// Reuse existing input element or create a new one
4032+
if (!_domCache.numberLabelInput) {
4033+
const el = document.createElement("input");
4034+
el.id = "numberLabel";
4035+
el.style.position = "absolute";
4036+
el.style.webkitUserSelect = "text";
4037+
el.style.mozUserSelect = "text";
4038+
el.style.msUserSelect = "text";
4039+
el.className = "number";
4040+
el.type = "number";
4041+
el.step = "any";
4042+
_domCache.numberLabelInput = el;
4043+
}
4044+
4045+
// Ensure it is the child of labelElem
4046+
if (_domCache.numberLabelInput.parentNode !== labelElem) {
4047+
labelElem.innerHTML = "";
4048+
labelElem.appendChild(_domCache.numberLabelInput);
4049+
}
4050+
4051+
this.label = _domCache.numberLabelInput;
4052+
this.label.value = safetext(labelValue);
40004053
labelElem.classList.add("hasKeyboard");
4001-
this.label = docById("numberLabel");
40024054

40034055
// set the position of cursor to the end (for number value)
40044056
const valueLength = this.label.value.length;
@@ -4075,16 +4127,18 @@ class Block {
40754127
that._labelChanged(false, true);
40764128
});
40774129

4078-
this.label.style.left =
4079-
Math.round(
4080-
(x + this.activity.blocksContainer.x) * this.activity.getStageScale() +
4081-
canvasLeft
4082-
) + "px";
4083-
this.label.style.top =
4084-
Math.round(
4085-
(y + this.activity.blocksContainer.y) * this.activity.getStageScale() +
4086-
canvasTop
4087-
) + "px";
4130+
// Use GPU acceleration (transform) instead of left/top to avoid layout thrashing
4131+
const left = Math.round(
4132+
(x + this.activity.blocksContainer.x) * this.activity.getStageScale() + canvasLeft
4133+
);
4134+
const top = Math.round(
4135+
(y + this.activity.blocksContainer.y) * this.activity.getStageScale() + canvasTop
4136+
);
4137+
4138+
this.label.style.transform = `translate3d(${left}px, ${top}px, 0)`;
4139+
this.label.style.left = "0px";
4140+
this.label.style.top = "0px";
4141+
40884142
this.label.style.width =
40894143
Math.round((selectorWidth * this.blocks.blockScale * this.protoblock.scale) / 2) +
40904144
"px";

0 commit comments

Comments
 (0)