react-modal
react-modal copied to clipboard
Prevent scroll to top upon opening
I've tried looking at the docs but can't seem to find any options to control whether the app scrolls to top when opening the modal.
I've got a rather long page and would like to prevent scrolling the background when the modal is opened.
How can this be achieved?
+1, I'm having a similar issue.
I had the same issue, since the modal is appended to the body, i just add overflow:hidden;
to the root of the my app when a modal is open
Appending overflow:hidden to the styles of the body is not working for me. Is there a concrete resolution to this?
:+1:
@sibeliusseraphini @LPayyapilli Setting overflow: hidden
on the body
should work unless there's a conflicting CSS property.
Two different ways you can achieve this:
- When
react-modal
has a modal open it adds a CSS class named.ReactModal__Body--open
to thebody
element. From your CSS you can:
.ReactModal__Body--open {
overflow: hidden;
}
- Or, set the overflow style property from your React component:
openModal() {
this.setState({
modalIsOpen: true,
originalBodyOverflow: document.body.style.overflow
});
// Set overflow hidden so that the background doesn't scroll
document.body.style.overflow = 'hidden';
}
closeModal() {
this.setState({modalIsOpen: false});
// Set overflow back to original value
document.body.style.overflow = this.state.originalBodyOverflow;
}
For me it scrolls to top after I close the modal.
overflow: hidden
is not an option because the page width would change
If I clicked a button and the button was half-shown then after the modal is closed it will return the focus
afterClose: function() {
focusManager.returnFocus();
focusManager.teardownScopedFocus();
},
https://github.com/reactjs/react-modal/blob/6c03d17e45486d26ec219966af55264b4bcfadf4/lib/components/ModalPortal.js#L112-L115
Then the scroll position will jump to the top of the button.
If the button was fully visible, then still all the scrolling made while the modal was open will be discarded after restoring the focus.
The question is:
How can I opt-out of react-modal
restoring focus?
(maybe I don't need to do that)
The overflow issue should not be resolved with adding this hack:
.ReactModal__Body--open
{
// disables page scrolling when modal is presented,
// but introduces page width jumps.
height: 100%;
overflow: hidden;
}
It should be properly resolved by not propagating scroll
events below the overlay.
That would be the correct solution.
Or maybe one can't stop scroll
propagation.
http://stackoverflow.com/questions/5802467/prevent-scrolling-of-parent-element
If that's still the case then there's no correct solution.
My way (without page width alteration): https://github.com/halt-hammerzeit/react-responsive-ui/blob/master/source/modal.js
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import React_modal from 'react-modal'
// when changing this also change
// your .ReactModal__Overlay and .ReactModal__Content
// css transition times accordingly
const default_close_timeout = 150 // ms
export default class Modal extends Component
{
static propTypes =
{
isOpen : PropTypes.bool.isRequired,
onRequestClose : PropTypes.func.isRequired,
onAfterOpen : PropTypes.func,
closeTimeoutMS : PropTypes.number,
style : PropTypes.object
}
constructor(props, context)
{
super(props, context)
this.onRequestClose = this.onRequestClose.bind(this)
this.onAfterOpen = this.onAfterOpen.bind(this)
}
render()
{
let { isOpen, closeTimeoutMS } = this.props
const markup =
(
<React_modal
ref="modal"
isOpen={isOpen}
onAfterOpen={this.onAfterOpen}
onRequestClose={this.onRequestClose}
closeTimeoutMS={closeTimeoutMS || default_close_timeout}
className="modal"
style={style.modal}>
{this.props.children}
</React_modal>
)
return markup
}
onAfterOpen()
{
const { afterOpen } = this.props
// A dummy `<div/>` to measure
// the difference in width
// needed for the "full-width" elements
// after the main (body) scrollbar is deliberately hidden.
const div = document.createElement('div')
div.style.position = 'fixed'
div.style.left = 0
div.style.right = 0
document.body.appendChild(div)
// Calculate the width of the dummy `<div/>`
// before the main (body) scrollbar is deliberately hidden.
const width_before = div.clientWidth
// Hide the main (body) scrollbar
// so that when a user scrolls in an open modal
// this `scroll` event doesn't go through
// and scroll the main page.
document.body.style.overflow = 'hidden'
// All "full-width" elements will need their
// width to be adjusted by this amount
// because of the now-hidden main (body) scrollbar
// Calculate the width of the dummy `<div/>`
// after the main (body) scrollbar is deliberately hidden.
const width_adjustment = div.clientWidth - width_before
// "full-width" elements include `document.body`
// and all `position: fixed` elements
// which should be marked with this special CSS class.
const full_width_elements = Array.from(document.querySelectorAll('.rrui__fixed-full-width'))
full_width_elements.push(document.body)
// Adjust the width of all "full-width" elements
// so that they don't expand by the width of the (now absent) scrollbar
for (const element of full_width_elements)
{
element.style.marginRight = width_adjustment + 'px'
}
// If the user scrolled on a previously shown react-modal,
// then reset that previously scrolled position.
document.querySelector('.ReactModal__Overlay').scrollTop = 0
if (afterOpen)
{
afterOpen()
}
}
onRequestClose()
{
const { closeTimeout, bodyOverflowX, bodyOverflowY, afterClose } = this.props
setTimeout(() =>
{
if (afterClose)
{
afterClose()
}
// All "full-width" elements will need their
// width to be restored back to the original value
// now that the main (body) scrollbar is being restored.
// "full-width" elements include `document.body`
// and all `position: fixed` elements
// which should be marked with this special CSS class.
const full_width_elements = Array.from(document.querySelectorAll('.rrui__fixed-full-width'))
full_width_elements.push(document.body)
// Adjust the width of all "full-width" elements back to their original value
// now that the main (body) scrollbar is being restored.
for (const element of full_width_elements)
{
element.style.marginRight = 0
}
// Restore the main (body) scrollbar
document.body.style.overflowX = bodyOverflowX
document.body.style.overflowY = bodyOverflowY
},
closeTimeout)
}
}
@seanabrahams This worked very well for me! Thanks a lot!
.ReactModal__Body--open {
overflow: hidden;
}
Is there any scenario that this will start failing?
In case of mobile overflow: hidden does not seem to work. Adding position:relative works but jumps to top.
For those searching on how to achieve these two things in the meantime, react-aria-modal behaves like this by default.
I experienced this issue just now, but it turned out to be my own fault 😂 I opened the modal with <a href='#' onClick={this.openModal}>
and I forgot to call event.preventDefault()
in my onClick handler. The jump-to-top was just the default browser behavior, nothing to do with react-modal at all!
Thanks a lot, folz. I'd tried everything already, but couldn't fix the jump-to-top problem.
Apparently there's a new preventScroll
option to the focus
method: https://mobile.twitter.com/rob_dodson/status/933111752547430402
I experienced the same issue, cannot be solved by @folz solution because my modal is controlled by react state.
Is there any way to make the body not scrollable without making the body jump to the top of the page?
shouldFocusAfterRender={false}
works fine for me.
this worked for me:
.ReactModal__Body--open { overflow: visible; position: relative; }
Re. https://github.com/reactjs/react-modal/issues/117#issuecomment-346318027
Apparently there's a new
preventScroll
option to thefocus
method:
More on this: https://webplatform.news/issues/2019-04-19#you-can-prevent-browsers-from-scrolling-dynamically-focused-elements-into-view
shouldFocusAfterRender={false}
works fine for me.
work for me too, thanks:)
shouldFocusAfterRender={false}
works fine for me.
Thank you @bertho-zero. Why isn't this prop documented? Can I help put it into README?
Appending overflow:hidden to the styles of the body is not working for me. Is there a concrete resolution to this?
Which browser are you using?
I found one solution. If your modal positioned "fixed" it won't scroll to top. If your modal positioned with position: "absolute", set style={{content: {top: (window.scrollY || window.pageYOffset) + 20}}}
and it will be 20px from top and upon focus page won't scroll