From 10aa8941e1d90007ed120df71244903adae9cfea Mon Sep 17 00:00:00 2001 From: Seif Soliman Date: Wed, 25 Jun 2025 16:27:49 +0300 Subject: [PATCH] fix(caret): prevent misalignment on initial load and resize (@byseif21, @NadAlaba) (#6641) ### Description This PR fixes two related caret positioning issues that occurred **before any user input**: * **Resize Bug**: Resizing the browser immediately after a page refresh caused the caret to appear misaligned. The position only corrected itself after the user typed the first character. * **Navigation Bug**: Navigating away from the test page and back (especially after a refresh) caused the caret to initially render in the wrong position, then animate into place. --- ### Solution * Refactor `Caret.updatePosition()` to use `getComputedStyle` for width/height instead of `getBoundingClientRect()` and `offsetHeight` makes caret positioning more accurate. * Redundant calls to Caret.updatePosition() have been removed from the resize handler, relying instead on the more comprehensive caret re-initialization. These changes resolve caret alignment and animation issues after loading, resizing, or navigating, including in tape mode. --- > ~~Replaced early calls to `Caret.updatePosition()` with `Caret.show()` when no input has been entered yet.~~ > ~~`Caret.show()` ensures the caret is:~~ > ~~* Visible~~ > ~~* Properly initialized~~ > ~~* Animated via `startAnimation()`~~ > ~~* Positioned correctly via internal `updatePosition()` call~~ > ~~Once the caret is initialized (i.e., after first input), we continue using `updatePosition()` directly for subsequent updates.~~ > --- Closes #6639 --- frontend/src/ts/test/caret.ts | 10 +++++----- frontend/src/ts/ui.ts | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/frontend/src/ts/test/caret.ts b/frontend/src/ts/test/caret.ts index e4ad5fc86..d54803e3a 100644 --- a/frontend/src/ts/test/caret.ts +++ b/frontend/src/ts/test/caret.ts @@ -133,9 +133,9 @@ function getTargetPositionLeft( } export async function updatePosition(noAnim = false): Promise { - const caretWidth = Math.round( - document.querySelector("#caret")?.getBoundingClientRect().width ?? 0 - ); + const caretComputedStyle = window.getComputedStyle(caret); + const caretWidth = parseInt(caretComputedStyle.width) || 0; + const caretHeight = parseInt(caretComputedStyle.height) || 0; const fullWidthCaret = ["block", "outline", "underline"].includes( Config.caretStyle @@ -170,10 +170,10 @@ export async function updatePosition(noAnim = false): Promise { const letterPosTop = currentLetter?.offsetTop ?? lastWordLetter?.offsetTop ?? 0; - const diff = letterHeight - caret.offsetHeight; + const diff = letterHeight - caretHeight; let newTop = activeWordEl.offsetTop + letterPosTop + diff / 2; if (Config.caretStyle === "underline") { - newTop = activeWordEl.offsetTop + letterPosTop - caret.offsetHeight / 2; + newTop = activeWordEl.offsetTop + letterPosTop - caretHeight / 2; } let letterWidth = currentLetter?.offsetWidth; diff --git a/frontend/src/ts/ui.ts b/frontend/src/ts/ui.ts index 85db946a0..d23cf013e 100644 --- a/frontend/src/ts/ui.ts +++ b/frontend/src/ts/ui.ts @@ -92,7 +92,6 @@ window.addEventListener("beforeunload", (event) => { }); const debouncedEvent = debounce(250, () => { - void Caret.updatePosition(); if (getActivePage() === "test" && !TestUI.resultVisible) { if (Config.tapeMode !== "off") { void TestUI.scrollTape(); @@ -102,7 +101,7 @@ const debouncedEvent = debounce(250, () => { setTimeout(() => { void TestUI.updateWordsInputPosition(); if ($("#wordsInput").is(":focus")) { - Caret.show(); + Caret.show(true); } }, 250); }