perf(textarea-autosize): eliminate layout thrashing, cache computed style, replace mousemove with ResizeObserver#48
Open
Copilot wants to merge 8 commits into
Open
Conversation
…move with ResizeObserver
…me x/y, ResizeObserver guard, idempotent mousedown)
Copilot
AI
changed the title
[WIP] Improve runtime performance of autosize function
perf(textarea-autosize): eliminate layout thrashing, cache computed style, replace mousemove with ResizeObserver
Jun 23, 2026
…rformance perf(autosize): fix maxHeight regression under min-height CSS; add Vitest browser test suite
mattcosta7
reviewed
Jun 29, 2026
| @@ -0,0 +1,180 @@ | |||
| import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest' | |||
Member
There was a problem hiding this comment.
@copilot we should run this in ci - generate a workflow for it.
There was a problem hiding this comment.
Pull request overview
This PR optimizes the autosize() hot path to reduce forced synchronous reflows during typing and to avoid always-on pointer-move work, while adding a browser-based test suite to guard regressions.
Changes:
- Reworks
sizeToFitto batch DOM reads before writes and caches border/box-sizing-derived values across keystrokes. - Replaces the always-on
mousemoveresize detection withResizeObserver(and a gated mouse fallback). - Adds Vitest browser-mode tests (Playwright/Chromium) and related configuration.
Show a summary per file
| File | Description |
|---|---|
src/index.js |
Refactors sizing logic to reduce layout thrashing; adds caching and resize detection via ResizeObserver with fallback cleanup. |
test/autosize.test.js |
Adds browser tests covering grow/shrink, min-height handling, user drag-resize disabling, form reset re-enable, and unsubscribe cleanup. |
vitest.config.js |
Enables Vitest browser mode using the Playwright provider (Chromium headless). |
package.json |
Switches test scripts to Vitest and adds Vitest/Playwright devDependencies. |
package-lock.json |
Locks the newly introduced test/tooling dependency graph. |
tsconfig.json |
Adds skipLibCheck to reduce TS compile overhead. |
.gitignore |
Ignores test screenshot output directory. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 4/8 changed files
- Comments generated: 1
- Review effort level: Low
Comment on lines
28
to
+32
| "eslint": "^7.21.0", | ||
| "eslint-plugin-github": "^4.1.2", | ||
| "typescript": "^4.2.3" | ||
| "playwright": "^1.61.1", | ||
| "typescript": "^4.2.3", | ||
| "vitest": "^4.1.9" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three hot-path performance issues in
autosize()caused forced synchronous reflows on every keystroke and unnecessary work on every mouse movement.Changes
1. Batch DOM reads in
sizeToFitAll layout reads (
overflowOffset,getComputedStyle, container heights) now happen before any writes. Previously,textarea.style.maxHeightwas written beforegetComputedStyle(container).heightwas read, forcing 2–3 synchronous layout recalculations per keystroke. The only unavoidable write-then-read is theheight='auto'→scrollHeightmeasure pattern; it's now isolated with a comment explaining why.Before (interleaved read/write/read):
After (all reads first, then writes):
2. Cache
getComputedStyleborder/box-sizing valuescachedBorderAddOn(derived fromborderTopWidth,borderBottomWidth,boxSizing) is computed once and reused across keystrokes. The library-trackedheightstring is also reused for themaxHeightcalculation, avoiding a secondgetComputedStyle(textarea)call on steady-state keystrokes. Cache is invalidated on external resize and form reset.3. Replace always-on
mousemovewithResizeObserverResizeObserverfires only when dimensions actually change. It comparestextarea.style.heightagainst the last library-written value; a mismatch means the user dragged the resize handle → setsisUserResized = true, clearsmaxHeight, then goes idle.ResizeObserver): Themousemovehandler is gated behindmousedown/mouseup(withmouseupondocumentto handle pointer release outside the element), keeping it off the hot path during normal use.unsubscribe()callscleanupResizeDetection(), which disconnects the observer or removes all registered listeners.