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
This commit is contained in:
Seif Soliman 2025-06-25 16:27:49 +03:00 committed by GitHub
parent 217981ee32
commit 10aa8941e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 6 additions and 7 deletions

View file

@ -133,9 +133,9 @@ function getTargetPositionLeft(
}
export async function updatePosition(noAnim = false): Promise<void> {
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<void> {
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;

View file

@ -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);
}