Overview
Paper Shaders provide powerful sizing controls to manage how shader graphics scale and fit within their containers. This guide covers the sizing system, fit modes, and responsive strategies.
Container Sizing
Shaders automatically fill their container element:
// Use CSS to size the container
< MeshGradient
colors = { [ '#ff0000' , '#00ff00' ] }
style = { { width: 400 , height: 400 } }
/>
// Or use className
< MeshGradient
colors = { [ '#ff0000' , '#00ff00' ] }
className = "shader-container"
/>
// With inline width/height props
< MeshGradient
colors = { [ '#ff0000' , '#00ff00' ] }
width = { 400 }
height = { 400 }
/>
// Size via CSS
const container = document . getElementById ( 'shader-container' );
container . style . width = '400px' ;
container . style . height = '400px' ;
const shaderMount = new ShaderMount (
container ,
fragmentShader ,
uniforms
);
Shader canvas automatically resizes when the container size changes using
ResizeObserver. No manual intervention needed.
Fit Modes
The fit parameter controls how shader graphics scale within the container:
None
No automatic fitting - renders at natural scale:
< MeshGradient
fit = "none"
scale = { 1 }
colors = { [ '#ff0000' , '#00ff00' ] }
style = { { width: 400 , height: 400 } }
/>
Use cases:
Tiling patterns
Precise pixel control
Coordinated multi-shader layouts
Contain
Fits entire graphic inside container while maintaining aspect ratio:
< MeshGradient
fit = "contain"
colors = { [ '#ff0000' , '#00ff00' ] }
style = { { width: 400 , height: 300 } }
/>
Behavior:
Entire graphic visible
May have empty space on sides
Maintains aspect ratio
Default for most object-based shaders
Use cases:
Logos and graphics that must be fully visible
Aspect-ratio-sensitive designs
Card backgrounds
Cover
Fills entire container while maintaining aspect ratio:
< MeshGradient
fit = "cover"
colors = { [ '#ff0000' , '#00ff00' ] }
style = { { width: 400 , height: 300 } }
/>
Behavior:
Fills entire container
May crop parts of graphic
Maintains aspect ratio
No empty space
Use cases:
Hero backgrounds
Full-bleed sections
When cropping is acceptable
Visual Comparison
< div style = { { width: 400 , height: 200 , border: '2px solid' } } >
< MeshGradient
fit = "none"
colors = { [ '#ff0000' , '#00ff00' ] }
/>
</ div >
// Renders at natural scale, may overflow or have gaps
Sizing Parameters
Fine-tune shader positioning and scaling:
Scale
Zoom the shader graphic:
< MeshGradient
fit = "contain"
scale = { 1.5 } // 150% zoom
colors = { [ '#ff0000' , '#00ff00' ] }
/>
// Scale range: 0.01 to 4
// Default: 1
scale applies after fit calculations, so fit="contain" + scale={2}
will contain the graphic then zoom it 2x (likely cropping).
Rotation
Rotate the shader graphic:
< MeshGradient
rotation = { 45 } // 45 degrees clockwise
colors = { [ '#ff0000' , '#00ff00' ] }
/>
// Range: 0 to 360 degrees
// Default: 0
Origin
Set rotation and scaling pivot point:
< MeshGradient
originX = { 0.5 } // Center horizontally (0 = left, 1 = right)
originY = { 0.5 } // Center vertically (0 = top, 1 = bottom)
rotation = { 45 }
colors = { [ '#ff0000' , '#00ff00' ] }
/>
// Default: originX={0.5}, originY={0.5} (center)
Common origin points:
Top-left: originX={0}, originY={0}
Top-center: originX={0.5}, originY={0}
Center: originX={0.5}, originY={0.5} (default)
Bottom-right: originX={1}, originY={1}
Offset
Shift the graphic position:
< MeshGradient
offsetX = { 0.2 } // Shift right by 20%
offsetY = { - 0.1 } // Shift up by 10%
colors = { [ '#ff0000' , '#00ff00' ] }
/>
// Range: -1 to 1 (relative to container)
// Default: offsetX={0}, offsetY={0}
World Dimensions
Define virtual canvas size for precise control:
< MeshGradient
worldWidth = { 1000 } // Virtual width in arbitrary units
worldHeight = { 1000 } // Virtual height in arbitrary units
fit = "contain"
colors = { [ '#ff0000' , '#00ff00' ] }
/>
// Default: worldWidth={0}, worldHeight={0} (auto)
// When 0, uses container dimensions
Use cases:
Matching shader scale across different containers
Consistent sizing in responsive layouts
Precise alignment with other elements
Complete Sizing Example
Combine all sizing parameters:
< MeshGradient
// Fit mode
fit = "cover"
// Scaling and rotation
scale = { 1.2 }
rotation = { 15 }
// Position
originX = { 0.3 }
originY = { 0.7 }
offsetX = { 0.1 }
offsetY = { - 0.05 }
// Virtual dimensions
worldWidth = { 1200 }
worldHeight = { 800 }
// Visual parameters
colors = { [ '#ff0000' , '#00ff00' , '#0000ff' ] }
distortion = { 0.8 }
style = { { width: 600 , height: 400 } }
/>
Sizing in Vanilla JS
Set sizing parameters as uniforms:
import { ShaderFitOptions } from '@paper-design/shaders' ;
const uniforms = {
// Shader-specific uniforms
u_colors: [ /*...*/ ],
u_colorsCount: 3 ,
// Sizing uniforms
u_fit: ShaderFitOptions . cover , // 0=none, 1=contain, 2=cover
u_scale: 1.2 ,
u_rotation: 15 ,
u_originX: 0.3 ,
u_originY: 0.7 ,
u_offsetX: 0.1 ,
u_offsetY: - 0.05 ,
u_worldWidth: 1200 ,
u_worldHeight: 800 ,
};
const shaderMount = new ShaderMount (
container ,
fragmentShader ,
uniforms
);
// Update sizing dynamically
shaderMount . setUniforms ({
u_scale: 1.5 ,
u_rotation: 30 ,
});
Responsive Strategies
Fluid Container
Let shader scale naturally with container:
< div style = { { width: '100%' , maxWidth: 600 } } >
< MeshGradient
fit = "cover"
colors = { [ '#ff0000' , '#00ff00' ] }
style = { { width: '100%' , height: 400 } }
/>
</ div >
Aspect Ratio Container
Maintain aspect ratio on resize:
< div style = { { width: '100%' , aspectRatio: '16 / 9' } } >
< MeshGradient
fit = "cover"
colors = { [ '#ff0000' , '#00ff00' ] }
style = { { width: '100%' , height: '100%' } }
/>
</ div >
Breakpoint-Based Sizing
Adjust sizing parameters by screen size:
import { useState , useEffect } from 'react' ;
function ResponsiveSizing () {
const [ scale , setScale ] = useState ( 1 );
useEffect (() => {
const updateScale = () => {
if ( window . innerWidth < 768 ) setScale ( 0.8 );
else if ( window . innerWidth < 1024 ) setScale ( 1 );
else setScale ( 1.2 );
};
updateScale ();
window . addEventListener ( 'resize' , updateScale );
return () => window . removeEventListener ( 'resize' , updateScale );
}, []);
return (
< MeshGradient
fit = "cover"
scale = { scale }
colors = { [ '#ff0000' , '#00ff00' ] }
style = { { width: '100%' , height: 400 } }
/>
);
}
Container Queries
Use container queries for component-level responsive sizing:
.shader-wrapper {
container-type : inline-size;
}
@container (min-width: 600px) {
.shader-container {
height : 400 px ;
}
}
@container (max-width: 599px) {
.shader-container {
height : 300 px ;
}
}
< div className = "shader-wrapper" >
< MeshGradient
fit = "cover"
colors = { [ '#ff0000' , '#00ff00' ] }
className = "shader-container"
style = { { width: '100%' } }
/>
</ div >
Pattern vs Object Sizing
Different shader types have different default sizing:
Pattern Shaders
Default: fit="none" for tiling:
import { defaultPatternSizing } from '@paper-design/shaders' ;
console . log ( defaultPatternSizing );
// {
// fit: 'none',
// scale: 1,
// rotation: 0,
// offsetX: 0,
// offsetY: 0,
// originX: 0.5,
// originY: 0.5,
// worldWidth: 0,
// worldHeight: 0,
// }
// Examples: DotGrid, Waves, Voronoi
< DotGrid
fit = "none" // Default
scale = { 0.5 } // Adjust pattern density
colors = { [ '#ff0000' ] }
/>
Object Shaders
Default: fit="contain" for full visibility:
import { defaultObjectSizing } from '@paper-design/shaders' ;
console . log ( defaultObjectSizing );
// {
// fit: 'contain',
// scale: 1,
// rotation: 0,
// offsetX: 0,
// offsetY: 0,
// originX: 0.5,
// originY: 0.5,
// worldWidth: 0,
// worldHeight: 0,
// }
// Examples: MeshGradient, Metaballs, SmokeRing
< MeshGradient
fit = "contain" // Default
colors = { [ '#ff0000' , '#00ff00' ] }
/>
Advanced: Coordinated Sizing
Sync sizing across multiple shaders:
function CoordinatedShaders () {
const [ rotation , setRotation ] = useState ( 0 );
useEffect (() => {
const interval = setInterval (() => {
setRotation ( r => ( r + 1 ) % 360 );
}, 50 );
return () => clearInterval ( interval );
}, []);
const sharedSizing = {
fit: 'cover' ,
scale: 1.5 ,
rotation ,
worldWidth: 1000 ,
worldHeight: 1000 ,
};
return (
< div style = { { position: 'relative' , width: 600 , height: 400 } } >
{ /* Background layer */ }
< MeshGradient
{ ... sharedSizing }
colors = { [ '#ff0000' , '#00ff00' ] }
style = { { position: 'absolute' , inset: 0 } }
/>
{ /* Foreground layer */ }
< DotOrbit
{ ... sharedSizing }
colors = { [ '#0000ff' , '#ffff00' ] }
style = { {
position: 'absolute' ,
inset: 0 ,
mixBlendMode: 'screen' ,
} }
/>
</ div >
);
}
Sizing Best Practices
Choose the Right Fit Mode
Use cover for backgrounds that should fill completely
Use contain for graphics that must be fully visible
Use none for tiling patterns and precise layouts
Use percentage-based container widths
Consider aspect-ratio CSS property
Adjust scale parameter for mobile vs desktop
Test on actual devices, not just browser resize
Performance Considerations
Coordinate Multiple Shaders
Use matching worldWidth and worldHeight
Share rotation and scale for synchronized effects
Consider offsetX/offsetY for layered compositions
Common Sizing Patterns
Full-Screen Background
< div style = { { width: '100vw' , height: '100vh' , overflow: 'hidden' } } >
< MeshGradient
fit = "cover"
colors = { [ '#ff0000' , '#00ff00' ] }
style = { { width: '100%' , height: '100%' } }
/>
</ div >
Hero Section
< section style = { { position: 'relative' , height: '70vh' } } >
< MeshGradient
fit = "cover"
colors = { [ '#ff0000' , '#00ff00' ] }
style = { {
position: 'absolute' ,
inset: 0 ,
zIndex: - 1 ,
} }
/>
< div style = { { position: 'relative' , zIndex: 1 } } >
< h1 > Hero Content </ h1 >
</ div >
</ section >
Card Background
< div style = { { position: 'relative' , borderRadius: 16 , overflow: 'hidden' } } >
< MeshGradient
fit = "cover"
colors = { [ '#ff0000' , '#00ff00' ] }
style = { {
position: 'absolute' ,
inset: 0 ,
zIndex: - 1 ,
} }
/>
< div style = { { padding: 24 , position: 'relative' } } >
< h2 > Card Title </ h2 >
< p > Card content </ p >
</ div >
</ div >
Tiling Pattern
< div style = { { width: '100%' , height: 400 } } >
< DotGrid
fit = "none"
scale = { 0.5 } // Controls dot density
colors = { [ '#ff0000' ] }
colorBack = "#000000"
style = { { width: '100%' , height: '100%' } }
/>
</ div >
Troubleshooting
Shader not visible
Check container has dimensions
Ensure container has explicit width and height (CSS or inline)
Verify fit mode
Try fit="cover" to ensure shader fills container
Check scale and offset
Ensure scale isn’t too large or offset isn’t pushing content out
Shader looks cropped
Try fit="contain" instead of cover
Reduce scale parameter
Adjust offset to recenter
Shader doesn’t resize
Verify container size actually changes (check DevTools)
ResizeObserver works automatically - no manual action needed
Check for fixed dimensions preventing resize
Next Steps
Performance Optimize shader resolution and rendering
Customization Customize shader visual parameters
React Usage React-specific sizing patterns
Vanilla Usage Vanilla JS sizing control