Stop propagating scroll events when modal dialogs are visible or scrollable popups have focus

It would be great if opening a modal dialog (e.g. lightbox like keyboard shortcuts or smilies selector) would stop me from interacting with the topics / posts in the background. So, no scrolling or anything.

The same goes for scrollable popups like the user menu or even the preview window when writing a post. When there’s a scrollbar visible, scroll event shouldn’t bubble up the event chain and start scrolling the topic list or posts.

Scrolling in the user menu:

Scrolling in the post preview:

Facebook does a great job in both cases:

  • Open an image in a lightbox. You can’t scroll the posts in your timeline that are still visible behind the image.
  • Open your notification or message popup menu and scroll to the bottom of those lists. Even if you are at the end of those lists, the posts in your timeline don’t start scrolling.
4 Likes

The editor is meant to be live and usable while you continue to browse the site. So I do not think your line of logic applies to that case.

I think it is more defensible for the menus, we had discussed “dimming” the main window when the menu is up.

On facebook when a dropdown menu is opened, if the mouse is over the dropdown you are only able to scroll the dropdown, if you leave the dropdown open but move the mouse onto the main window, you are only able to scroll the window. It feels very intuitive to me. It could work that way for both the menus and the editor on Discourse.

1 Like

@simon That’s exactly what I was trying to say.

Yes, that’s fine. I just realized my second GIF doesn’t really show what I was doing. The cursor is hovering over the editor and I’m scrolling to the bottom of the preview. When I’m at the bottom of the scrollable preview, the topic list starts scrolling. That shouldn’t happen. I’d need to explicitly move the cursor up to the topic list in order to scroll it.

4 Likes

You can get an idea of how it could work by adding something like this to menu-panel.js.es6. This doesn’t take care of touch events and is resetting the body height incorrectly. It only stops the scrolling on the body if the menu has a scrollbar.

  mouseEnter() {
    let height = $(window).height();
    let contentHeight = parseInt(this.$('.panel-body-contents').height());
    let menuHeight = this.$().height();
    if (contentHeight > menuHeight) {
      $('body').css({
        'overflow': 'hidden',
        'height': height
      });
    }
  },

  mouseLeave() {
    $('body').css({
      'overflow': 'visible',
      'height': 'auto',
    });
  },
2 Likes

Looks like Stack Overflow has the exact same issue, Scroll to bottom of inbox, and it just keeps on scrolling the page when done. (when using mouse wheel)

Perhaps @balpha has some ideas on how/if a fix like this should be implemented? I wonder if facebook go so far as to change positioning on the entire page

2 Likes

This works, when the mouse enters the menu, by storing the scrollTop value and then setting the body to position: fixed with a top-margin equal to the scrollTop value. When the mouse leaves the menu, position: fixed is removed from the body and the body is scrolled to the stored scrollTop value. The downside is that when the mouse enters the menu it can cause the site logo in the header to reappear - I assume that is triggered by the scroll position.

// css
.stop-scrolling {
  position: fixed;
  margin-top: 0;
  width: 100%;
}
// menu-panel.js.es6
...
y_offsetWhenScrollingDisabled: 0,

mouseEnter() {
  this.y_offsetWhenScrollingDisabled = $(window).scrollTop();
  $('body').addClass('stop-scrolling').css('margin-top', -this.y_offsetWhenScrollingDisabled);
},

mouseLeave() {
  $('body').removeClass('stop-scrolling').css('margin-top', 0);
  $(window).scrollTop(this.y_offsetWhenScrollingDisabled);
},

2 Likes

This seems to be it. This is only being applied to ‘wheel’ events here. The basic idea is that if you are scrolling with the mousewheel in the menu-panel, when you have scrolled to either the bottom or the top of the panel, stop the event and don’t let it bubble up to the window. This is tested on Chrome, Safari, and Firefox 17 on a mac. I guess it needs to be debounced - and removed in ‘willDestroyElement’.

// menu-panel.js.es6
...

@on('didInsertElement')
   _bindEvents() {

  ...

  this.$().on('DOMMouseScroll wheel', (e) => {
    let scrollTop = $('.panel-body').scrollTop();
    // Total height of the menu content
    let scrollHeight = $('.panel-body-contents')[0].scrollHeight;
    // Inner height of menu-panel - not including padding
    let height = this.$().height();
    // Amount and direction of pagescroll movement. 'DOMMouseScroll' is for Firefox
    let delta = parseInt((e.type === 'DOMMouseScroll' ? e.originalEvent.detail * 40 : e.originalEvent.deltaY), 10);
    // Not necessary but makes it easier to understand for now.
    let scrollDir = delta < 0 ? 'up' : 'down';

    let _prevent = () => {
      e.stopPropagation(); // Don't bubble up
      e.preventDefault(); // Don't perform the action
    };

    // Only apply this if there is scrollable content in the menu.
    // This test fails on menu-panel.slide-in when the menu-panel height is between 0 and 16px less than the scrollHeight - that is fixable.
    if (scrollHeight > height) {
      // Stop the action  and scroll to the bottom of the content if the mousewheel movement
      // is going to take us below the bottom of the content.
      if (scrollDir === 'down' && delta > scrollHeight - (height + scrollTop)) {
        $('.panel-body').scrollTop(scrollHeight - height);
        return _prevent();

        // Stop the action and take us to the top of the content if the mousewheel movement is
        // going to take us above the top of the content.
      } else if (scrollDir === 'up' && -delta > scrollTop) {
        $('.panel-body').scrollTop(0);
        return _prevent();
       } 
     }
  });
2 Likes