Skip to content

Conversation

@artokun
Copy link
Owner

@artokun artokun commented Sep 20, 2025

Summary

Adds pixelPerfect prop to both ScrollControls and Html components to prevent subpixel rendering blur caused by CSS transform3d values with decimal pixels.

Problem

HTML elements rendered with CSS transforms in 3D scenes often appear blurry due to subpixel positioning, especially during animations. This affects text readability and overall visual quality, as discussed in issues pmndrs#859 and pmndrs#2380.

Solution

Implements a pixelPerfect prop that rounds transform values to whole pixels using devicePixelRatio-based calculation for accurate rendering across all display types.

Key Features

  • ScrollControls: pixelPerfect prop for <Scroll html> components
  • Html: pixelPerfect prop for transform mode rendering
  • DevicePixelRatio Support: Proper high-DPI display handling via window.devicePixelRatio
  • Performance Optimized: Velocity-based conditional application (only when motion < 0.001)
  • Backward Compatible: Default behavior unchanged, opt-in via prop

Usage

// ScrollControls
<ScrollControls pages={3}>
  <Scroll html pixelPerfect>
    <div>Crisp text during scroll animations</div>
  </Scroll>
</ScrollControls>

// Html component
<Html transform pixelPerfect>
  <div>Crisp text during 3D transforms</div>
</Html>

Implementation Details

  • Helper Function: roundToPixelRatio() uses window.devicePixelRatio for accurate rounding
  • Velocity Detection: Only applies rounding when velocity < 0.001 to maintain smooth animation
  • Cross-Component: Consistent implementation across ScrollControls and Html components

Documentation & Examples

  • ✅ Updated component documentation with usage examples
  • ✅ Comprehensive Storybook stories with before/after comparisons
  • ✅ Interactive demos showing the difference in text crispness

Credit

Special thanks to @chris-xinhai-li for suggesting the devicePixelRatio approach in issue pmndrs#2380.

Fixes

Testing

The feature has been thoroughly tested with:

  • Various device pixel ratios (1x, 1.5x, 2x, etc.)
  • Different animation scenarios (scroll, transform, etc.)
  • Performance impact validation
  • Cross-browser compatibility

@artokun
Copy link
Owner Author

artokun commented Sep 20, 2025

This PR addresses the subpixel blur issues described in:

The pixelPerfect prop implementation includes:
✅ Velocity-based optimization (0.001 threshold) for responsive pixel snapping
✅ Maintains smooth scrolling during movement, crisp text when stopped
✅ Backward compatible - only applies when explicitly enabled
✅ Storybook integration for testing

The solution rounds transform3d values to whole pixels when scroll velocity drops below the threshold, eliminating subpixel rendering blur while preserving fluid animation during active scrolling.

@artokun
Copy link
Owner Author

artokun commented Sep 20, 2025

🚀 Updated: Added pixelPerfect to Html component!

The pixelPerfect feature now works for both ScrollControls and Html components:

Html Component:

<Html pixelPerfect transform>
  <div>Crisp text rendering!</div>
</Html>

ScrollControls Component:

<ScrollControls pages={3}>
  <Scroll html pixelPerfect>
    <h1>Pixel-perfect scrolling text</h1>
  </Scroll>
</ScrollControls>

Implementation Details:

  • Same velocity-based optimization (0.001 threshold) for both components
  • Transform mode support - handles complex matrix transformations
  • Non-transform mode support - handles translate3d positioning
  • Initial positioning - pixel-perfect from first render
  • Backward compatible - only applies when explicitly enabled

This comprehensively addresses the subpixel blur issues described in pmndrs#2380 and pmndrs#859 across all HTML rendering scenarios in drei! 🎯

@artokun
Copy link
Owner Author

artokun commented Sep 20, 2025

Updated Implementation

Thanks to the suggestion from @chris-xinhai-li in issue pmndrs#2380, I've updated the implementation to use for more accurate pixel-perfect rounding:

// Helper function for pixel-perfect rounding based on device pixel ratio
// Credit: https://github.com/chris-xinhai-li (pmndrs/drei#2380)
function roundToPixelRatio(value: number): number {
  const ratio = window.devicePixelRatio || 1
  return Math.round(value * ratio) / ratio
}

This properly handles high-DPI displays and ensures crisp text rendering across all device types.

The feature has also been extended to the Html component and includes:

  • ✅ Storybook examples for both components
  • ✅ Updated documentation
  • ✅ Velocity-based optimization (only applies rounding when motion is minimal)

Credit to @chris-xinhai-li for the devicePixelRatio suggestion! 🙏

Adds pixelPerfect prop to prevent subpixel rendering blur caused by CSS
transform3d values with decimal pixels. Uses devicePixelRatio-based
rounding for accurate rendering across all display types.

Features:
- ScrollControls: pixelPerfect prop for Scroll html components
- Html: pixelPerfect prop for transform mode
- Velocity-based optimization (only applies when motion < 0.001)
- devicePixelRatio support for high-DPI displays
- Comprehensive Storybook examples and documentation
- Performance optimized with conditional application

Credit: chris-xinhai-li for devicePixelRatio suggestion (pmndrs#2380)

Fixes: pmndrs#859, pmndrs#2380

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@artokun artokun force-pushed the feat/scroll-pixel-perfect branch from a7135bc to 9f0fc53 Compare September 20, 2025 01:56
@artokun
Copy link
Owner Author

artokun commented Sep 20, 2025

Closing as this was just a fork PR. The actual upstream PR is at pmndrs#2541

@artokun artokun closed this Sep 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Html: Blurry text caused by subpixel translate3d() positioning Html component render blurry when using transform attribute on firefox

2 participants