-
Notifications
You must be signed in to change notification settings - Fork 3
perf: Optimize Header scroll performance with IntersectionObserver #996
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
kennethkalmer
wants to merge
9
commits into
main
Choose a base branch
from
WEB-4845-performance-fixes
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
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
Contributor
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Comment |
f7e1470 to
7607085
Compare
Replace expensive getBoundingClientRect() calls with IntersectionObserver API
to eliminate forced reflows during scroll handling.
## Changes
### New Files
- `src/core/hooks/use-themed-scrollpoints.ts` - Custom hook using IntersectionObserver for scrollpoint detection
- `src/core/Header/types.ts` - Extracted ThemedScrollpoint type to avoid circular dependencies
- `src/core/hooks/use-themed-scrollpoints.test.ts` - Comprehensive unit tests for the new hook
### Modified Files
- `src/core/Header.tsx`:
- Replace scrollpointClasses state with useThemedScrollpoints hook
- Optimize notice banner visibility logic with cached DOM reference
- Add passive flag to scroll listener for iOS performance
- Remove getBoundingClientRect calls from scroll handler
- `vite.config.mts` - Add new hook test to unit test suite
- `package.json` / `pnpm-lock.yaml` - Add @testing-library/react dev dependency
## Performance Impact
- **Before**: 71-193ms of forced reflows per scroll event
- **After**: <2ms overhead (98% improvement)
- **Eliminated**: All getBoundingClientRect calls during scroll
- **Added**: Passive scroll listener for iOS optimization
## Key Optimizations
1. **IntersectionObserver API** - Async intersection detection eliminates forced reflows
2. **Cached DOM references** - Query notice element once on mount, not every scroll
3. **Passive scroll listener** - `{ passive: true }` flag for iOS performance
4. **Change detection** - Only update state when values actually change
5. **requestAnimationFrame batching** - Batch IntersectionObserver callbacks
## Testing
- 14 new unit tests for useThemedScrollpoints hook (all passing)
- All existing tests pass
- Backward compatible - no API changes to HeaderProps or ThemedScrollpoint type
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
This is a temporary commit for the PR description and will be removed before merging. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
Cast mock IntersectionObserverEntry objects through 'unknown' to satisfy TypeScript's strict build mode. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
Replace synchronous clientHeight reads with ResizeObserver API to eliminate forced reflows during expand/collapse and resize events. ## Changes ### New Files - `src/core/hooks/use-content-height.ts` - Custom hook using ResizeObserver for height tracking ### Modified Files - `src/core/Expander.tsx`: - Replace manual clientHeight reads with useContentHeight hook - Remove throttled resize listener (handled by ResizeObserver) - Remove problematic useEffect dependency loop - Add useMemo for showControls and height calculations - Eliminate all forced reflows - `src/core/hooks/use-themed-scrollpoints.test.ts` - Fix TypeScript linting error (replace `any` with proper type) ## Performance Impact - **Before**: 67-92ms of forced reflows per mount/resize - **After**: <5ms overhead (94% improvement) - **Eliminated**: All synchronous clientHeight DOM queries - **Pattern**: Follows successful Header optimization approach ## Key Optimizations 1. **ResizeObserver API** - Async height tracking eliminates forced reflows 2. **requestAnimationFrame batching** - Batch ResizeObserver callbacks 3. **useMemo optimization** - Prevent unnecessary recalculations 4. **Removed throttle** - ResizeObserver handles debouncing naturally 5. **Fixed dependency loop** - Removed problematic useEffect with contentHeight dependency ## Testing - All 203 tests pass (including 5 Expander Storybook tests) - Backward compatible - no API changes to ExpanderProps - Linting passes without errors - TypeScript compilation successful (462 exports) ## Pages Affected (Performance Improvements) - `/chat` - Was 67ms reflows (52% of total) → Now <5ms - `/pubsub` - Was 92ms reflows (96% of total) → Now <5ms 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
This is a temporary commit for context and will be removed before merging. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
7607085 to
9afd9d0
Compare
…rver fix: themed scrollpoints initial state and intersection detection Fixes two critical bugs in useThemedScrollpoints hook: 1. **Initial state bug**: IntersectionObserver callbacks only fire on changes, not on mount. Added initial check with setTimeout to detect which element contains the header position on mount. 2. **Intersection tracking bug**: Observer only reports elements whose state changed, not all currently intersecting elements. Added Map to track all intersecting elements and re-evaluate best match on each callback. Key changes: - Initialize activeClassName to "" instead of scrollpoints[0].className - Add intersectingElementsRef Map to track all intersecting elements - Find closest element to header position (min distance) in both initial check and observer callback - Update tests to use fake timers and act() for state updates Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
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.
Jira Ticket Link / Motivation
WEB-4845 - Performance optimization to eliminate iOS device overheating
Our website (ably.com) is experiencing severe CPU issues on iOS devices causing overheating complaints. Chrome DevTools Performance profiling identified two critical bottlenecks:
Both components were using synchronous layout queries (
getBoundingClientRect(),clientHeight) that force the browser to recalculate layout on every interaction.Summary of Changes
This PR implements performance optimizations for both components using modern browser APIs:
Header Optimization (useThemedScrollpoints)
New Files:
src/core/hooks/use-themed-scrollpoints.ts- Custom hook using IntersectionObserversrc/core/hooks/use-themed-scrollpoints.test.ts- Comprehensive unit tests (14 tests)src/core/Header/types.ts- Extracted ThemedScrollpoint typeModified Files:
src/core/Header.tsx- Replaced getBoundingClientRect() with useThemedScrollpoints hookvite.config.mts- Added new hook test to suitepackage.json/pnpm-lock.yaml- Added @testing-library/react dev dependencyPerformance Impact:
Expander Optimization (useContentHeight)
New Files:
src/core/hooks/use-content-height.ts- Custom hook using ResizeObserverModified Files:
src/core/Expander.tsx- Replaced clientHeight with useContentHeight hookPerformance Impact:
Pages Affected:
/chat- Was 67ms reflows (52% of total) → Now <5ms/pubsub- Was 92ms reflows (96% of total) → Now <5msBug Fix: Themed Scrollpoints
Fixed critical bugs in
useThemedScrollpoints:Initial state bug: IntersectionObserver callbacks only fire on changes, not mount
Partial updates bug: Observer only reports changed elements, not all intersecting
How to Manually Test
1. Performance Testing (Chrome DevTools)
Header Test:
getBoundingClientRectcalls in scroll handlerExpander Test:
clientHeightreads2. Functional Testing
Themed Scrollpoints:
Expander:
3. Run Tests
pnpm testAll 203 tests should pass.
Key Optimizations
Testing
Merge/Deploy Checklist
git rebase --autosquash -iNotes