react-scroll-blocker
Version:
A modern React 18 compatible scroll lock component for preventing body scroll
317 lines (244 loc) ⢠8.61 kB
Markdown
18 compatible library for preventing body scroll when components are mounted.
[](https://www.npmjs.com/package/react-scroll-blocker)
[](https://opensource.org/licenses/MIT)
This library was created as a modern alternative to [`react-scrolllock`](https://github.com/jossmac/react-scrolllock), which is no longer maintained and doesn't support React 18. React Scroll Blocker provides the same functionality with modern React practices, TypeScript support, and React 18 compatibility.
- ā
**React 18 Compatible** - Full support for React 18 and its concurrent features
- ā
**TypeScript Support** - Written in TypeScript with full type definitions
- ā
**Zero Dependencies** - No external dependencies except React
- ā
**Mobile Friendly** - Handles iOS Safari scroll issues correctly
- ā
**Layout Shift Prevention** - Accounts for scrollbar width to prevent layout jumps
- ā
**Multiple Instances** - Support for nested/multiple scroll locks
- ā
**Hook Support** - Includes both component and hook APIs
- ā
**Touch Scrollable Areas** - Allow specific elements to remain scrollable
- ā
**Server-Side Rendering** - Works with SSR/Next.js
```bash
npm install react-scroll-blocker
yarn add react-scroll-blocker
pnpm add react-scroll-blocker
```
```tsx
import React, { useState } from 'react'
import ScrollBlocker from 'react-scroll-blocker'
function MyModal() {
const [isOpen, setIsOpen] = useState(false)
return (
<div>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
{isOpen && (
<>
{/* This will prevent body scroll while the modal is open */}
<ScrollBlocker />
<div
className='modal-overlay'
onClick={() => setIsOpen(false)}>
<div
className='modal-content'
onClick={(e) => e.stopPropagation()}>
<h2>Modal Title</h2>
<p>Modal content here...</p>
<button onClick={() => setIsOpen(false)}>Close</button>
</div>
</div>
</>
)}
</div>
)
}
```
The main component that prevents body scroll when mounted.
```tsx
import ScrollBlocker from 'react-scroll-blocker'
;<ScrollBlocker
isActive={true} // Optional: whether the blocker is active (default: true)
accountForScrollbars={true} // Optional: prevent layout shift (default: true)
>
{/* Optional: scrollable content for mobile */}
</ScrollBlocker>
```
| Prop | Type | Default | Description |
| ---------------------- | ----------- | ----------- | ------------------------------------------------------------- |
| `isActive` | `boolean` | `true` | Whether the scroll blocker is active |
| `accountForScrollbars` | `boolean` | `true` | Whether to add padding to compensate for removed scrollbar |
| `children` | `ReactNode` | `undefined` | Child elements that should remain scrollable on touch devices |
A React hook that provides programmatic control over scroll blocking.
```tsx
import { useScrollBlocker } from 'react-scroll-blocker'
function MyComponent() {
const { blockScroll, unblockScroll, isBlocked } = useScrollBlocker()
return (
<div>
<button onClick={blockScroll}>Block Scroll</button>
<button onClick={unblockScroll}>Unblock Scroll</button>
<p>Scroll is {isBlocked ? 'blocked' : 'unblocked'}</p>
</div>
)
}
```
| Parameter | Type | Default | Description |
| ---------------------- | --------- | ------- | -------------------------------------- |
| `isBlocked` | `boolean` | `false` | Whether scroll should be blocked |
| `accountForScrollbars` | `boolean` | `true` | Whether to account for scrollbar width |
| Property | Type | Description |
| --------------- | ------------ | ----------------------------------- |
| `blockScroll` | `() => void` | Function to manually block scroll |
| `unblockScroll` | `() => void` | Function to manually unblock scroll |
| `isBlocked` | `boolean` | Current block state |
A component that allows its children to remain scrollable even when ScrollBlocker is active. This is particularly useful for mobile devices.
```tsx
import { ScrollBlocker, TouchScrollable } from 'react-scroll-blocker'
function ScrollableModal() {
return (
<>
<ScrollBlocker />
<TouchScrollable>
<div className='scrollable-content'>
{/* This content will be scrollable on mobile */}
</div>
</TouchScrollable>
</>
)
}
```
When you pass children to ScrollBlocker, they are automatically wrapped in TouchScrollable:
```tsx
<ScrollBlocker>
<div className='scrollable-content'>
{/* Automatically scrollable on mobile */}
</div>
</ScrollBlocker>
```
```tsx
import React, { useState } from 'react'
import ScrollBlocker from 'react-scroll-blocker'
function Modal() {
const [isOpen, setIsOpen] = useState(false)
return (
<div>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
{isOpen && (
<>
<ScrollBlocker />
<div
style={
{
/* modal styles */
}
}>
<h2>Modal Content</h2>
<button onClick={() => setIsOpen(false)}>Close</button>
</div>
</>
)}
</div>
)
}
```
```tsx
import ScrollBlocker from 'react-scroll-blocker'
function ConditionalLock({ shouldLock }) {
return (
<div>
<ScrollBlocker isActive={shouldLock} />
<p>Scroll is {shouldLock ? 'locked' : 'unlocked'}</p>
</div>
)
}
```
```tsx
import { useScrollBlocker } from 'react-scroll-blocker'
function HookExample() {
const [isModalOpen, setIsModalOpen] = useState(false)
const { lockScroll, unlockScroll } = useScrollBlocker(isModalOpen)
return (
<div>
<button onClick={() => setIsModalOpen(true)}>Open Modal</button>
{/* Modal JSX */}
</div>
)
}
```
```tsx
import ScrollBlocker from 'react-scroll-blocker'
function ScrollableModal() {
return (
<ScrollBlocker>
<div
style={{
position: 'fixed',
top: '10%',
bottom: '10%',
left: '10%',
right: '10%',
overflow: 'auto', // This content will be scrollable
}}>
<div style={{ height: '200vh' }}>Long scrollable content...</div>
</div>
</ScrollBlocker>
)
}
```
```tsx
import ScrollBlocker from 'react-scroll-blocker'
function NestedModals() {
const [modal1Open, setModal1Open] = useState(false)
const [modal2Open, setModal2Open] = useState(false)
return (
<div>
{modal1Open && <ScrollBlocker />}
{modal2Open && <ScrollBlocker />}
{/* Both modals can be open simultaneously */}
{/* Scroll is locked while any modal is open */}
{/* Scroll is restored only when both are closed */}
</div>
)
}
```
- ā
Chrome (latest)
- ā
Firefox (latest)
- ā
Safari (latest)
- ā
Edge (latest)
- ā
iOS Safari
- ā
Chrome Mobile
- ā
Samsung Internet
React Scroll Blocker is designed as a drop-in replacement for react-scrolllock:
```tsx
// Before (react-scrolllock)
import ScrollLock, { TouchScrollable } from 'react-scrolllock'
// After (react-scroll-blocker)
import ScrollBlocker, { TouchScrollable } from 'react-scroll-blocker'
// Note: Component name changed from ScrollLock to ScrollBlocker
```
- **React 18 Required**: This library requires React 18+
- **TypeScript**: Full TypeScript support (may require type updates)
- **Modern Build**: Uses modern build tools and ES modules
## Contributing
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
## License
MIT Ā© lomelo-x
## Acknowledgments
This library is inspired by and serves as a modern replacement for [`react-scrolllock`](https://github.com/jossmac/react-scrolllock) by [Joss Mackison](https://github.com/jossmac). Thanks for the original implementation and inspiration!
š A modern, React