reactjs / react-modal

Accessible modal dialog component for React
http://reactcommunity.org/react-modal
MIT License
7.35k stars 809 forks source link

Prevent scroll to top upon opening #117

Open ewendel opened 8 years ago

ewendel commented 8 years ago

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?

sp00ne commented 8 years ago

+1, I'm having a similar issue.

zlwaterfield commented 8 years ago

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

ghost commented 8 years ago

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

sibelius commented 8 years ago

:+1:

seanabrahams commented 8 years ago

@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;
}

2) 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;
}
catamphetamine commented 8 years ago

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

catamphetamine commented 8 years ago

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 commented 8 years ago

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

catamphetamine commented 8 years ago

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 commented 8 years ago

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)
    }
}
c0debreaker commented 7 years ago

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

.ReactModal__Body--open {
  overflow: hidden;
}

Is there any scenario that this will start failing?

soniapatel commented 7 years ago

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

AlecRust commented 7 years ago

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

folz commented 6 years ago

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!

lcnogueira commented 6 years ago

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

OliverJAsh commented 6 years ago

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

darrylsepeda commented 6 years ago

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?

bertho-zero commented 5 years ago
shouldFocusAfterRender={false}

works fine for me.

ffacal commented 5 years ago

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

OliverJAsh commented 5 years ago

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

adamcanray commented 4 years ago
shouldFocusAfterRender={false}

works fine for me.

work for me too, thanks:)

pixelfreak commented 4 years ago
shouldFocusAfterRender={false}

works fine for me.

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

pushp-1992 commented 3 years ago

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?

varelaz commented 2 years ago

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