Add 3D hero orb and scroll-driven, scroll-aware backdrop#7
Conversation
|
Azure Static Web Apps: Your stage site is ready! Visit it here: https://witty-mud-031b23a0f-7.eastus2.2.azurestaticapps.net |
There was a problem hiding this comment.
Pull request overview
This PR introduces scroll-driven visual enhancements to the portfolio site, including a 3D animated hero orb with orbital rings and parallax backdrop effects that respond to scroll position.
Key Changes:
- Implements scroll progress tracking using
requestAnimationFramefor performance-optimized scroll detection - Adds CSS custom property
--scroll-progressthat updates dynamically as users scroll - Introduces a 3D animated orb with spinning rings in the header section, positioned behind content using z-index layering
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| src/App.jsx | Adds scroll progress tracking with useEffect hook that updates --scroll-progress CSS variable |
| src/styles/GlobalStyles.js | Defines --scroll-progress variable and creates scroll-reactive backdrop layers using body::before and body::after pseudo-elements |
| src/Components/Header.jsx | Introduces HeroScene, Orb, and Ring styled components with 3D transforms and animations; adds z-index layering to ensure content appears above decorative elements |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <HeroScene> | ||
| <Orb> | ||
| <Ring /> | ||
| <Ring /> | ||
| <Ring /> | ||
| </Orb> | ||
| </HeroScene> |
There was a problem hiding this comment.
The 3D orb and rings are purely decorative but may confuse screen reader users. Add aria-hidden="true" to the HeroScene component to hide it from assistive technologies.
| @@ -1,6 +1,6 @@ | |||
| // src/App.js | |||
There was a problem hiding this comment.
The file comment says "src/App.js" but the file is named "App.jsx". Update the comment to match the actual filename.
| // src/App.js | |
| // src/App.jsx |
| 0 | ||
| ) | ||
| scale(1.02); | ||
| filter: blur(0px); |
There was a problem hiding this comment.
The blur filter is set to 0px which has no effect. Either remove this line if no blur is intended, or set it to a non-zero value if a blur effect is desired.
| filter: blur(0px); |
| @keyframes orbSpin { | ||
| from { | ||
| transform: rotateX(18deg) rotateY(0deg); | ||
| } | ||
| to { | ||
| transform: rotateX(18deg) rotateY(360deg); | ||
| } | ||
| } |
There was a problem hiding this comment.
The keyframes animation is defined inside the styled component definition. While this works, it creates duplicate keyframe definitions in the generated CSS each time the component is used. Move the keyframes definition outside the styled component using styled-components' keyframes helper to avoid duplication and improve performance.
| @keyframes ringFloat { | ||
| 0%, | ||
| 100% { | ||
| filter: drop-shadow(0 0 8px rgba(255, 111, 97, 0.2)); | ||
| } | ||
| 50% { | ||
| filter: drop-shadow(0 0 18px rgba(73, 135, 255, 0.3)); | ||
| } | ||
| } |
There was a problem hiding this comment.
The keyframes animation is defined inside the styled component definition. While this works, it creates duplicate keyframe definitions in the generated CSS each time the component is used. Move the keyframes definition outside the styled component using styled-components' keyframes helper to avoid duplication and improve performance.
|
Azure Static Web Apps: Your stage site is ready! Visit it here: https://witty-mud-031b23a0f-7.eastus2.2.azurestaticapps.net |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| updateScrollProgress(); | ||
| window.addEventListener('scroll', handleScroll, { passive: true }); | ||
| window.addEventListener('resize', handleScroll); |
There was a problem hiding this comment.
The resize event listener should use the passive option for better scroll performance, similar to the scroll listener. Resize handlers that don't call preventDefault can benefit from being marked as passive.
| window.addEventListener('resize', handleScroll); | |
| window.addEventListener('resize', handleScroll, { passive: true }); |
| inset: -20%; | ||
| pointer-events: none; | ||
| z-index: -1; | ||
| transition: transform 0.25s ease-out; |
There was a problem hiding this comment.
Using both transform and transition on pseudo-elements that update via CSS custom properties can cause layout thrashing. Since --scroll-progress changes frequently during scrolling, the transition: transform 0.25s will attempt to animate every scroll update, creating a laggy effect. Consider removing the transition property to allow transforms to update smoothly with the scroll position, or use will-change: transform to optimize rendering.
| transition: transform 0.25s ease-out; | |
| will-change: transform; |
| <HeroScene> | ||
| <Orb> | ||
| <Ring /> | ||
| <Ring /> | ||
| <Ring /> | ||
| </Orb> | ||
| </HeroScene> |
There was a problem hiding this comment.
The HeroScene, Orb, and Ring elements are decorative and use pointer-events: none, but they should have aria-hidden="true" to ensure screen readers skip them entirely. Consider adding this attribute to the HeroScene component to improve accessibility.
| ) | ||
| rotate(calc(var(--scroll-progress) * 2deg)); | ||
| } | ||
|
|
There was a problem hiding this comment.
Users with vestibular motion disorders may experience discomfort from the scroll-driven parallax effects on the backdrop pseudo-elements. Consider adding a prefers-reduced-motion media query to disable the transform animations for users who have requested reduced motion in their system settings.
| @media (prefers-reduced-motion: reduce) { | |
| body::before, | |
| body::after { | |
| transition: none; | |
| transform: none; | |
| } | |
| } |
| &:nth-child(1) { | ||
| transform: rotateX(65deg) rotateZ(20deg) translateZ(25px); | ||
| } | ||
|
|
||
| &:nth-child(2) { | ||
| transform: rotateY(75deg) rotateZ(-15deg) translateZ(-10px); | ||
| animation-delay: 1.5s; | ||
| } | ||
|
|
||
| &:nth-child(3) { | ||
| transform: rotateX(80deg) rotateZ(80deg) translateZ(10px); | ||
| animation-delay: 3s; | ||
| } |
There was a problem hiding this comment.
The animation on the Ring component will not work as intended. The nth-child selectors apply static transforms that override the animation property. The ringFloat animation will be superseded by the transform declarations in the nth-child rules. Either remove the transform from nth-child selectors and apply them via CSS variables in the animation, or use a different approach to differentiate the rings.
Motivation
Description
App.jsxthat publishes--scroll-progressto:rootusing arequestAnimationFrame-friendly scroll listener.src/styles/GlobalStyles.jswith--scroll-progress, subtle backdrop layers (body::beforeandbody::after) and scroll-tied transforms/opacity to create a parallax/backdrop effect.src/Components/Header.jsxwith a positionedHeroScenecontaining an animated 3DOrbandRingelements, and adjust header layering (z-index) so content sits above the visuals.HeaderSectionposition: relativeandoverflow: hiddento contain the scene and enable parallax translation viavar(--scroll-progress).Testing
npm start(Parcel) — bundling completed successfully (server reported "Built in ...").HTTP/1.1 200 OKviacurl.Codex Task