@eduvidu/react-autoscale
v0.2.0
Published
A production-ready, enterprise-grade universal scaling engine for React. Zero config, SSR-safe, tree-shakeable.
Readme
@eduvidu/react-autoscale
A production-ready, enterprise-grade universal scaling engine for React.
Zero config • SSR-safe • Tree-shakeable • ~3KB gzipped
Features
- 🪶 ~3KB gzipped — zero runtime dependencies
- ⚡ ResizeObserver + rAF batching — no layout thrashing
- 🔒 SSR-safe — works with Next.js (App Router & Pages Router)
- 🌳 Tree-shakeable — import only what you use
- 🎯 Zero config — works with zero props out of the box
- 📐 5 scale modes —
width,height,contain,cover, custom - 🪆 Nested scaling — context-aware deep nesting support
- 🔌 Plugin-style — custom calculators, custom measurers
- 📦 Dual ESM + CJS — with full TypeScript declarations
Installation
npm install @eduvidu/react-autoscale
# or
yarn add @eduvidu/react-autoscale
# or
pnpm add @eduvidu/react-autoscalePeer dependencies: react >= 16.8.0
Compatibility
| React Version | Status | Notes | | ------------- | ------------ | ------------------------------------------ | | 16.8+ | ✅ Supported | Hooks baseline | | 17 | ✅ Tested | Full support | | 18 | ✅ Tested | StrictMode safe, concurrent rendering safe | | 19 | ✅ Tested | Full support |
StrictMode: The library is fully compatible with React 18+ StrictMode. In development mode, React double-invokes effects to catch bugs — useAutoScale correctly creates and tears down ResizeObserver and the rAF scheduler in each mount/unmount cycle with no duplicate observers or memory leaks.
Concurrent rendering: The library uses only stable React public APIs (useState, useRef, useCallback, useEffect, useMemo, useContext, forwardRef, useImperativeHandle, createContext). No internal or private React APIs are used. No assumptions that break under concurrent rendering.
SSR: All browser APIs (window, document, ResizeObserver, requestAnimationFrame) are guarded behind isBrowser() / isResizeObserverSupported() checks. The library renders safely in Next.js App Router, Pages Router, and any Node.js SSR environment — returning scale: 1 and isReady: false until the client hydrates.
Quick Start
Component API
import { AutoScale } from '@eduvidu/react-autoscale';
function Dashboard() {
return (
<div style={{ width: '100vw', height: '100vh' }}>
<AutoScale>
<div style={{ width: 1920, height: 1080 }}>
{/* Your fixed-size content scales to fit */}
<DashboardGrid />
</div>
</AutoScale>
</div>
);
}Hook API
import { useAutoScale } from '@eduvidu/react-autoscale';
function ScalableTable() {
const { containerRef, contentRef, scale, isReady } = useAutoScale({
mode: 'width',
});
return (
<div ref={containerRef} style={{ width: '100%', height: '100%' }}>
<div
ref={contentRef}
style={{
transform: `scale(${scale})`,
transformOrigin: 'top left',
opacity: isReady ? 1 : 0,
}}
>
<LargeTable />
</div>
</div>
);
}API Reference
<AutoScale>
Declarative scaling wrapper. All props optional.
| Prop | Type | Default | Description |
| --------------------- | --------------------------------------------- | -------------- | ------------------------------------------- |
| mode | 'width' \| 'height' \| 'contain' \| 'cover' | 'contain' | Scale strategy |
| minScale | number | — | Minimum scale factor |
| maxScale | number | — | Maximum scale factor |
| customCalculator | (container, content) => number | — | Custom scale function (overrides mode) |
| observeParent | boolean | false | Observe parent element instead of container |
| transformOrigin | string | 'top left' | CSS transform-origin |
| throttle | number | — | Min interval (ms) between recalculations |
| debounce | number | — | Debounce delay (ms) |
| disabled | boolean | false | Disable scaling entirely |
| debug | boolean | false | Log measurements to console |
| compensationMode | 'none' \| 'width' \| 'height' \| 'both' | 'none' | Adjust container size after scaling |
| onScaleChange | (scale, details) => void | — | Called when scale changes |
| measurementStrategy | 'scrollSize' \| 'boundingRect' \| 'custom' | 'scrollSize' | How to measure content size |
| customMeasurer | (element) => { width, height } | — | Custom measurement function |
| contentClassName | string | — | Class for inner content wrapper |
| contentStyle | CSSProperties | — | Style for inner content wrapper |
All standard <div> attributes (className, id, data-*, onClick, etc.) are forwarded to the outer container.
useAutoScale(options?)
Returns:
| Property | Type | Description |
| --------------------- | --------------------------- | ------------------------------------- |
| containerRef | RefObject<HTMLDivElement> | Attach to container element |
| contentRef | RefObject<HTMLDivElement> | Attach to content element |
| scale | number | Current scale factor (default 1) |
| dimensions | { width, height } | Scaled content dimensions |
| containerDimensions | { width, height } | Container dimensions |
| contentDimensions | { width, height } | Natural (unscaled) content dimensions |
| isReady | boolean | true after first measurement |
ScaleProvider / useScaleContext()
For nested scaling awareness:
import { useScaleContext } from '@eduvidu/react-autoscale';
function NestedWidget() {
const { scale, parentScale, depth } = useScaleContext();
// scale = this level's scale
// parentScale = accumulated scale from all ancestors
// depth = nesting level (0 = no AutoScale ancestor)
}Core Utilities
These are framework-agnostic and can be used independently:
import {
calculateScale,
clampScale,
measureElement,
measureContainer,
createScheduler,
isBrowser,
canUseDom,
} from '@eduvidu/react-autoscale';Examples
Nested Scaling
<AutoScale mode="contain">
<div style={{ width: 1920, height: 1080 }}>
<Header />
<AutoScale mode="width">
<DataTable columns={50} />
</AutoScale>
<Footer />
</div>
</AutoScale>Custom Scale Calculator
<AutoScale
customCalculator={(container, content) => {
// Scale to 80% of container width
return (container.width * 0.8) / content.width;
}}
>
<WidgetPanel />
</AutoScale>Scale Change Tracking
<AutoScale
onScaleChange={(scale, { previousScale, containerDimensions }) => {
analytics.track('scale_changed', { scale, previousScale });
}}
>
<Dashboard />
</AutoScale>Compensation Mode
When you need surrounding content to flow around the scaled content:
<AutoScale compensationMode="height" mode="width">
{/* Container height adjusts to match scaled content height */}
<FixedWidthContent />
</AutoScale>With Clamping
<AutoScale minScale={0.5} maxScale={2}>
<ResponsivePanel />
</AutoScale>Debounced Scaling
<AutoScale debounce={150}>
<ExpensiveChart />
</AutoScale>Next.js Usage
Works out of the box with both App Router and Pages Router:
// app/dashboard/page.tsx (App Router)
'use client';
import { AutoScale } from '@eduvidu/react-autoscale';
export default function DashboardPage() {
return (
<div style={{ width: '100vw', height: '100vh' }}>
<AutoScale>
<div style={{ width: 1920, height: 1080 }}>
<DashboardContent />
</div>
</AutoScale>
</div>
);
}Note: Since
AutoScaleusesuseRefanduseEffect, it must be a client component. Add'use client'at the top of the file when using App Router.
Vite Usage
// src/App.tsx
import { AutoScale } from '@eduvidu/react-autoscale';
function App() {
return (
<AutoScale style={{ width: '100vw', height: '100vh' }}>
<div style={{ width: 1440, height: 900 }}>
<MyApp />
</div>
</AutoScale>
);
}Performance Notes
How it works
- ResizeObserver watches the container for size changes
- rAF batching coalesces rapid resize events into a single frame
- Epsilon comparison (
1e-4) skips React re-renders when scale hasn't meaningfully changed - Stable refs prevent unnecessary observer re-creation
- transform: scale() is GPU-accelerated — no reflow triggered
What we avoid
- ❌
zoomproperty (inconsistent cross-browser) - ❌ Measuring scaled dimensions (leads to feedback loops)
- ❌ Layout thrashing (no interleaved reads/writes)
- ❌ ResizeObserver loop errors (rAF wrapping)
- ❌ Memory leaks (observer disconnect + scheduler destroy on unmount)
Anti-Patterns
// ❌ BAD: Don't set explicit width/height on the AutoScale container
// (it needs to fill available space to calculate scale)
<AutoScale style={{ width: 500, height: 300 }}>...</AutoScale>
// ✅ GOOD: Let it fill its parent
<div style={{ width: 500, height: 300 }}>
<AutoScale>...</AutoScale>
</div>// ❌ BAD: Don't apply transform to children manually
<AutoScale>
<div style={{ transform: 'scale(2)' }}>...</div>
</AutoScale>
// ✅ GOOD: Let AutoScale handle all transforms
<AutoScale>
<div>...</div>
</AutoScale>Competitor Comparison
| Feature | @eduvidu/react-autoscale | react-fit | react-zoom-pan-pinch | | ------------------ | ----------------------------------------- | --------- | -------------------- | | Bundle (gzip) | ~3KB | ~8KB | ~45KB | | Dependencies | 0 | Multiple | Multiple | | Scale modes | 5 (width/height/contain/cover/custom) | 1 | Pan/zoom only | | SSR safe | ✅ | ❌ | ❌ | | Tree-shakeable | ✅ | ❌ | ❌ | | rAF batching | ✅ | ❌ | Partial | | Nested scaling | ✅ | ❌ | ❌ | | Zero config | ✅ | ❌ | ❌ | | Plugin calculators | ✅ | ❌ | ❌ | | Compensation modes | ✅ | ❌ | ❌ |
Known Limitations
Content must have intrinsic size — the content children should have explicit or natural dimensions for measurement to work. Flexbox/grid children that stretch may report incorrect sizes.
CSS transitions on scaled content — adding
transition: transformto the content div may cause visual jitter during rapid resizing.Hidden containers — if the container is
display: noneat mount time, the initial measurement will be0. Scale will recalculate when the container becomes visible.iframe content — content inside iframes cannot be measured by the parent's ResizeObserver.
Publishing
# 1. Build
npm run build
# 2. Test
npm test
# 3. Type-check
npm run typecheck
# 4. Dry run
npm pack --dry-run
# 5. Publish
npm publish --access publicDevelopment
# Install
npm install
# Dev mode (watch)
npm run dev
# Test (watch)
npm run test:watch
# Test with coverage
npm run test:coverage
# Lint
npm run lint
# Format
npm run format
# Build
npm run buildFuture Roadmap
- [ ]
useAutoScaleTransition— animated scale transitions - [ ]
direction: 'rtl'support for transform-origin - [ ] Resize breakpoint callbacks
- [ ] Performance observer integration
- [ ] React Native support (experimental)
- [ ] Storybook examples
- [ ] Playground website
License
MIT © eduvidu
