Skip to content

Commit 0b70c22

Browse files
committed
Batch DOM ops, add passive touch, chunk loads
1 parent 895b769 commit 0b70c22

File tree

3 files changed

+99
-51
lines changed

3 files changed

+99
-51
lines changed

js/activity.js

Lines changed: 63 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,15 @@ class Activity {
452452
this.hideBlocksContainer = null;
453453
this.collapseBlocksContainer = null;
454454

455+
// --- DOM reads (batched to avoid forced synchronous layout) ---
455456
this.searchWidget = document.getElementById("search");
457+
this.progressBar = document.getElementById("myProgress");
458+
const pasteEl = document.getElementById("paste");
459+
new createjs.DOMElement(pasteEl);
460+
this.paste = pasteEl;
461+
this.toolbarHeight = document.getElementById("toolbars").offsetHeight;
462+
463+
// --- DOM writes (after all reads complete) ---
456464
this.searchWidget.style.visibility = "hidden";
457465
this.searchWidget.placeholder = _("Search for blocks");
458466

@@ -461,15 +469,9 @@ class Activity {
461469
this.helpfulSearchWidget.style.visibility = "hidden";
462470
this.helpfulSearchWidget.placeholder = _("Search for blocks");
463471
this.helpfulSearchWidget.classList.add("ui-autocomplete");
464-
this.progressBar = document.getElementById("myProgress");
465472
this.progressBar.style.visibility = "hidden";
466-
467-
new createjs.DOMElement(document.getElementById("paste"));
468-
this.paste = document.getElementById("paste");
469473
this.paste.style.visibility = "hidden";
470474

471-
this.toolbarHeight = document.getElementById("toolbars").offsetHeight;
472-
473475
this.helpfulWheelItems = [];
474476

475477
this.setHelpfulSearchDiv();
@@ -2633,47 +2635,55 @@ class Activity {
26332635
* Handles touch start event on the canvas.
26342636
* @param {TouchEvent} event - The touch event object.
26352637
*/
2636-
myCanvas.addEventListener("touchstart", event => {
2637-
if (event.touches.length === 2) {
2638-
for (let i = 0; i < 2; i++) {
2639-
initialTouches[i][0] = event.touches[i].clientY;
2640-
initialTouches[i][1] = event.touches[i].clientX;
2638+
myCanvas.addEventListener(
2639+
"touchstart",
2640+
event => {
2641+
if (event.touches.length === 2) {
2642+
for (let i = 0; i < 2; i++) {
2643+
initialTouches[i][0] = event.touches[i].clientY;
2644+
initialTouches[i][1] = event.touches[i].clientX;
2645+
}
26412646
}
2642-
}
2643-
});
2647+
},
2648+
{ passive: true }
2649+
);
26442650

26452651
/**
26462652
* Handles touch move event on the canvas.
26472653
* @param {TouchEvent} event - The touch event object.
26482654
*/
2649-
myCanvas.addEventListener("touchmove", event => {
2650-
if (event.touches.length === 2) {
2651-
for (let i = 0; i < 2; i++) {
2652-
const touchY = event.touches[i].clientY;
2653-
const touchX = event.touches[i].clientX;
2654-
2655-
if (initialTouches[i][0] !== null && initialTouches[i][1] !== null) {
2656-
const deltaY = touchY - initialTouches[i][0];
2657-
const deltaX = touchX - initialTouches[i][1];
2658-
2659-
if (deltaY !== 0) {
2660-
closeAnyOpenMenusAndLabels();
2661-
that.blocksContainer.y -= deltaY;
2662-
}
2655+
myCanvas.addEventListener(
2656+
"touchmove",
2657+
event => {
2658+
if (event.touches.length === 2) {
2659+
for (let i = 0; i < 2; i++) {
2660+
const touchY = event.touches[i].clientY;
2661+
const touchX = event.touches[i].clientX;
2662+
2663+
if (initialTouches[i][0] !== null && initialTouches[i][1] !== null) {
2664+
const deltaY = touchY - initialTouches[i][0];
2665+
const deltaX = touchX - initialTouches[i][1];
2666+
2667+
if (deltaY !== 0) {
2668+
closeAnyOpenMenusAndLabels();
2669+
that.blocksContainer.y -= deltaY;
2670+
}
26632671

2664-
if (that.scrollBlockContainer && deltaX !== 0) {
2665-
closeAnyOpenMenusAndLabels();
2666-
that.blocksContainer.x -= deltaX;
2667-
}
2672+
if (that.scrollBlockContainer && deltaX !== 0) {
2673+
closeAnyOpenMenusAndLabels();
2674+
that.blocksContainer.x -= deltaX;
2675+
}
26682676

2669-
initialTouches[i][0] = touchY;
2670-
initialTouches[i][1] = touchX;
2677+
initialTouches[i][0] = touchY;
2678+
initialTouches[i][1] = touchX;
2679+
}
26712680
}
2672-
}
26732681

2674-
that.refreshCanvas();
2675-
}
2676-
});
2682+
that.refreshCanvas();
2683+
}
2684+
},
2685+
{ passive: true }
2686+
);
26772687

26782688
/**
26792689
* Handles touch end event on the canvas.
@@ -2946,8 +2956,12 @@ class Activity {
29462956
window.addEventListener("mousemove", resetIdleTimer);
29472957
window.addEventListener("mousedown", resetIdleTimer);
29482958
window.addEventListener("keydown", resetIdleTimer);
2949-
window.addEventListener("touchstart", resetIdleTimer);
2950-
window.addEventListener("wheel", resetIdleTimer);
2959+
window.addEventListener("touchstart", resetIdleTimer, {
2960+
passive: true
2961+
});
2962+
window.addEventListener("wheel", resetIdleTimer, {
2963+
passive: true
2964+
});
29512965

29522966
// Periodic check for idle state
29532967
setInterval(() => {
@@ -6865,15 +6879,16 @@ class Activity {
68656879

68666880
const img = new Image();
68676881
img.src = "data:image/svg+xml;base64," + window.btoa(base64Encode(name));
6882+
// Accessibility: derive alt text from the button label
6883+
const altText = label ? label.replace(/\s*\[.*\]$/, "") : "Toolbar button";
6884+
img.setAttribute("alt", altText);
68686885

6886+
// Batch DOM reads before writes to avoid forced synchronous layout
6887+
const rightPos = document.body.clientWidth - x;
68696888
container.appendChild(img);
68706889
container.setAttribute(
68716890
"style",
6872-
"position: absolute; right:" +
6873-
(document.body.clientWidth - x) +
6874-
"px; top: " +
6875-
y +
6876-
"px;"
6891+
"position: absolute; right:" + rightPos + "px; top: " + y + "px;"
68776892
);
68786893
document.getElementById("buttoncontainerBOTTOM").appendChild(container);
68796894
return container;
@@ -7291,14 +7306,17 @@ class Activity {
72917306
* Inits everything. The main function.
72927307
*/
72937308
this.init = async () => {
7309+
// Batch DOM reads before any writes to avoid forced synchronous layout
72947310
this._clientWidth = document.body.clientWidth;
72957311
this._clientHeight = document.body.clientHeight;
72967312
this._innerWidth = window.innerWidth;
72977313
this._innerHeight = window.innerHeight;
72987314
this._outerWidth = window.outerWidth;
72997315
this._outerHeight = window.outerHeight;
7316+
const loaderEl = document.getElementById("loader");
73007317

7301-
document.getElementById("loader").className = "loader";
7318+
// DOM write: apply class after all geometry reads
7319+
loaderEl.className = "loader";
73027320

73037321
/*
73047322
* Run browser check before implementing onblur -->

js/blocks.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5797,7 +5797,38 @@ class Blocks {
57975797
const blockOffset = this.blockList.length;
57985798
const firstBlock = this.blockList.length;
57995799

5800-
for (let b = 0; b < this._loadCounter; b++) {
5800+
/**
5801+
* Chunked block-loading: yield to the main thread every ~50ms
5802+
* so the browser can paint and remain interactive during
5803+
* large-project loads. Each chunk processes CHUNK_SIZE blocks
5804+
* synchronously, then schedules the next chunk via setTimeout(0).
5805+
*/
5806+
const CHUNK_SIZE = 20;
5807+
const totalBlocks = this._loadCounter;
5808+
let bIndex = 0;
5809+
5810+
const processChunk = () => {
5811+
const chunkEnd = Math.min(bIndex + CHUNK_SIZE, totalBlocks);
5812+
for (let b = bIndex; b < chunkEnd; b++) {
5813+
this._processOneBlock(b, blockObjs, blockOffset, firstBlock);
5814+
}
5815+
bIndex = chunkEnd;
5816+
if (bIndex < totalBlocks) {
5817+
setTimeout(processChunk, 0);
5818+
}
5819+
};
5820+
5821+
processChunk();
5822+
};
5823+
5824+
/**
5825+
* Processes a single block object during load. Extracted from
5826+
* the former monolithic for-loop inside loadNewBlocks so the
5827+
* loop can be chunked across frames.
5828+
* @private
5829+
*/
5830+
this._processOneBlock = (b, blockObjs, blockOffset, firstBlock) => {
5831+
{
58015832
const thisBlock = blockOffset + b;
58025833
const blkData = blockObjs[b];
58035834
let blkInfo;

js/turtles.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -906,15 +906,14 @@ Turtles.TurtlesView = class {
906906
};
907907
const img = new Image();
908908
img.src = "data:image/svg+xml;base64," + window.btoa(base64Encode(svg));
909+
img.setAttribute("alt", object.label || object.name || "Canvas button");
909910

911+
// Batch DOM reads before writes to avoid forced synchronous layout
912+
const rightPos = document.body.clientWidth - x;
910913
container.appendChild(img);
911914
container.setAttribute(
912915
"style",
913-
"position: absolute; right:" +
914-
(document.body.clientWidth - x) +
915-
"px; top: " +
916-
y +
917-
"px;"
916+
"position: absolute; right:" + rightPos + "px; top: " + y + "px;"
918917
);
919918
docById("buttoncontainerTOP").appendChild(container);
920919
return container;

0 commit comments

Comments
 (0)