react-modal icon indicating copy to clipboard operation
react-modal copied to clipboard

Prevent scroll to top upon opening

Open ewendel opened this issue 8 years ago • 24 comments

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?

ewendel avatar Jan 14 '16 07:01 ewendel

+1, I'm having a similar issue.

sp00ne avatar Jan 18 '16 14:01 sp00ne

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

zlwaterfield avatar Feb 27 '16 23:02 zlwaterfield

Appending overflow:hidden to the styles of the body is not working for me. Is there a concrete resolution to this?

LPayyapilli avatar Mar 22 '16 19:03 LPayyapilli

:+1:

sibelius avatar Apr 02 '16 17:04 sibelius

@sibeliusseraphini @LPayyapilli Setting overflow: hidden on the body should work unless there's a conflicting CSS property.

Two different ways you can achieve this:

  1. When react-modal has a modal open it adds a CSS class named .ReactModal__Body--open to the body element. From your CSS you can:
.ReactModal__Body--open {
  overflow: hidden;
}
  1. 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;
}

seanabrahams avatar Apr 03 '16 19:04 seanabrahams

For me it scrolls to top after I close the modal. overflow: hidden is not an option because the page width would change

catamphetamine avatar Apr 17 '16 14:04 catamphetamine

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.

catamphetamine avatar Apr 17 '16 14:04 catamphetamine

The question is: How can I opt-out of react-modal restoring focus? (maybe I don't need to do that)

catamphetamine avatar Apr 17 '16 14:04 catamphetamine

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.

catamphetamine avatar Apr 17 '16 14:04 catamphetamine

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)
	}
}

catamphetamine avatar Apr 17 '16 14:04 catamphetamine

@seanabrahams This worked very well for me! Thanks a lot!

.ReactModal__Body--open {
  overflow: hidden;
}

Is there any scenario that this will start failing?

c0debreaker avatar Oct 12 '16 03:10 c0debreaker

In case of mobile overflow: hidden does not seem to work. Adding position:relative works but jumps to top.

soniapatel avatar Jan 04 '17 21:01 soniapatel

For those searching on how to achieve these two things in the meantime, react-aria-modal behaves like this by default.

AlecRust avatar May 17 '17 17:05 AlecRust

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!

folz avatar Oct 01 '17 21:10 folz

Thanks a lot, folz. I'd tried everything already, but couldn't fix the jump-to-top problem.

lcnogueira avatar Nov 09 '17 01:11 lcnogueira

Apparently there's a new preventScroll option to the focus method: https://mobile.twitter.com/rob_dodson/status/933111752547430402

OliverJAsh avatar Nov 22 '17 11:11 OliverJAsh

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?

darrylsepeda avatar May 30 '18 10:05 darrylsepeda

shouldFocusAfterRender={false}

works fine for me.

bertho-zero avatar Mar 27 '19 15:03 bertho-zero

this worked for me: .ReactModal__Body--open { overflow: visible; position: relative; }

ffacal avatar May 19 '19 19:05 ffacal

Re. https://github.com/reactjs/react-modal/issues/117#issuecomment-346318027

Apparently there's a new preventScroll option to the focus method:

More on this: https://webplatform.news/issues/2019-04-19#you-can-prevent-browsers-from-scrolling-dynamically-focused-elements-into-view

OliverJAsh avatar May 20 '19 19:05 OliverJAsh

shouldFocusAfterRender={false}

works fine for me.

work for me too, thanks:)

adamcanray avatar May 30 '20 11:05 adamcanray

shouldFocusAfterRender={false}

works fine for me.

Thank you @bertho-zero. Why isn't this prop documented? Can I help put it into README?

pixelfreak avatar Jul 30 '20 04:07 pixelfreak

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?

pushp-1992 avatar Oct 30 '20 12:10 pushp-1992

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

varelaz avatar Sep 21 '21 16:09 varelaz