vitch / jScrollPane

Pretty, customisable, cross browser replacement scrollbars

Home Page:http://jscrollpane.kelvinluck.com/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Fade on MouseEnter/Leave

plugdj opened this issue · comments

I made a few modifications to make the scrollbar fade in and out on mouse enter and leave. Not sure it's worth a pull request but here for anyone else who wants this functionality.

.jspVerticalBar {
  opacity: 0;
  transition: all 0.3s ease;
}
.jspVerticalBar.hover {
  opacity: 1;
  transition: all 0.2s ease;
}
// LINE 73
verticalDragPosition, horizontalDrag, dragMaxX, horizontalDragPosition, isOver,
// LINE 121
container = $('<div class="jspContainer" />')
    .on('mouseenter', function(){
    isOver = true;
    if (event.which !== 1) container.find('.jspVerticalBar').addClass('hover');
    else $(document).on('mouseup', checkOver);
})
    .on('mouseleave', function(event){
    isOver = false;
    if (event.which !== 1) container.find('.jspVerticalBar').removeClass('hover');
    else $(document).on('mouseup', checkOver);
})
    .css({
        'width': paneWidth + 'px',
        'height': paneHeight + 'px'
}
/// LINE 262 (after initialize)
function checkOver()
{
    if (!isOver) container.find('.jspVerticalBar').removeClass('hover');
    else container.find('.jspVerticalBar').addClass('hover');
    $(document).off('mouseup', checkOver);
}

Is there any reason you don't just use CSS for this?

e.g.

.jspScrollable .jspVerticalBar {
  opacity: 0;
  transition: opacity .5s ease-in;
}
.jspScrollable:hover .jspVerticalBar {
  opacity: 1;
}

(with the relevant browser prefixes etc)

I just tried this in the web inspector in Chrome and it seemed to work fine...

Yes. Here are two reasons.

Reason 1: If you press and hold and are scrolling and your mouse leaves the scroll pane, the scroll bar will disappear even though it's currently active.

Reason 2: If you have multiple scrollbars on the page, and you're currently scrolling with one (aka pressing with your mouse) and you roll out of that pane into another pane, then the other scrollbar will appear (and the first disappear see reason 1), even though it shouldn't because you are currently using the first one.

I took the time to figure out how to modify the source code in the least invasive and most efficient way possible to achieve this functionality which exists in native operating system scrollbars. The simple solution is to use CSS, and while that may have worked in a simple test, it fails to work properly in the real world. Using :hover is what I did at first but quickly realized that these two issues were a problem that could only be solved by modifying the Javascript source code.

I could have kept this modification to myself, but I figured I'd share it in the hopes that it would help other people with a feature that I'm sure many people would want, and I didn't want to share it until I figured out the easiest way to implement it. Unfortunately, due to the modification required, I couldn't just "patch" it on at the end. It required just a little bit of surgery.

OK - you should probably have mentioned that you had tried with just CSS and what the problems with it were.

However, I disagree that this is the least invasive way of implementing what you want to do. I can't see anything in your code which needs to go into the jScrollPane plugin, you could just add this code into your page after you initialise jScrollPane. e.g. something like this (untested):

$('.scroll-pane').jScrollPane().each(function() {
  $(this).parent().on('mouseenter', function() { /* stuff */}).on('mouseleave', function(e) { /* stuff */ });
});

I closed the issue because I didn't want to add this code to the plugin when it seemed that the use-case could be covered much more simply and I suggested a simpler solution for you.

If it had come in the form of a pull request including an example describing the functionality and explaining why it was necessary then I would have considered it. The functionality should also have a documented setting so you can turn it on or off (or the relevant CSS should only be included on the example page). And it should work for horizontal as well as vertical scrollbars.

If you want to make an example page (based off the other examples in this repository e.g. basic.html) showing how you can add the code after initialising the plugin and submit a pull request then I'd be happy to merge it. Otherwise your code will still be available here in the closed issues list for people who are searching for a solution to your problem

The reason I didn't make a pull request is for the very reason you mentioned, which was that there are many other things that go into making it complete. A way to include horizontal scrollbars, a way to make it optional, a cleaner way to integrate it, etc. I figured I would put my "hack" here as an idea on the minimum functionality to make it work properly.

Your proposed code snippet is problematic because you are using anonymous functions instead of underscore binds (or jQuery proxies) for the mouseenter and mouseleave listeners and would assign them to every found instance on the page of .scroll-pane, which means if you have multiple scroll panes, when each one is initialized, all of the previously initialized scrollpanes would get an additional listener that does the same thing. For example, if you had 3 scrollpanes, the first scrollpane would have 3 listeners that would trigger that would all do the same thing, the second scrollpane would have 2 and the third would have one. You have to use a bind or proxy function to avoid this.

This functionality requires a listener be added/removed from $(document) since you can mouseup outside of the scrollpane in which case it needs to know to hide the scrollbar since the scrollbar should not hide when you drag outside the pane while pressed, and it needs to be specific to the instance that is currently being targeted.

Injecting code to do this after the fact would make this modification more difficult because I couldn't find a reference to "container" otherwise. Is there a way to access the "container" variable from the api object that is returned? If that's possible, then I could potentially add this as a mod instead of an integration.

To be honest, fading scrollbar functionality is something I'd love to see included in the core functionality and I'd be happy to do a proper pull-request to get things started.

Did you try my proposed code? I don't think it would behave as you describe - maybe you didn't see the each? In terms of getting a reference to the "container", that's what the $(this).parent() call does...

In terms of being specific to the instance being targeted, that's also taken care of. Inside the closure of the each function $(this).parent() is the specific container you are currently initialising.

Actually, I just checked and it looks like I guessed how to access the container incorrectly when I took a quick look at the markup. The correct way to get the container within the each is $('>.jspContainer', this)

Anyway - I pasted your code into a JSFiddle to show how you can do what you want without modifying the plugin:

http://jsfiddle.net/6uwDe/1/

Here is the code in its entirety:

    $('.scroll-pane').jScrollPane({showArrows:true}).each(function() {
        var container = $('>.jspContainer', this);
        var isOver;
        var checkOver = function() {
            if (!isOver) {
                container.find('.jspVerticalBar').removeClass('hover');
            } else {
                container.find('.jspVerticalBar').addClass('hover');
            }
            $(document).off('mouseup', checkOver);
        }
        container.on('mouseenter', function(e) {
            isOver = true;
            if (e.which !== 1) {
                container.find('.jspVerticalBar').addClass('hover'); 
            } else {
                $(document).on('mouseup', checkOver);
            }
        });
        container.on('mouseleave', function(e) {
            isOver = false;
            if (event.which !== 1) {
                container.find('.jspVerticalBar').removeClass('hover'); 
            } else {
                $(document).on('mouseup', checkOver);
            }
        });
    });

I didn't try to understand exactly what your code is doing (why do you need to check event.which?) but it seems to work fine to me. Do you see any problems?

Very nice. Thank you. I check event.which to see if the mouse is currently being pressed when you mouse enter the scrollpane because you don't want to show a scrollbar if you're currently pressing another scrollbar. Once you release the mouse, you want to check to see if you're currently over a different scrollpane so that its scrollbar will then appear since it didn't because you were pressed when you rolled over it.

Something to note: This doesn't actually check whether or not the mouse press is related to one of this component's scrollbars. It just checks if the mouse is down at all. If you wanted to make it specific to this component, instead of event.which you would set a class variable (static variable) when you press and clear it when you release so that it knows that one of these scrollbars is being pressed. The problem with this technique is that if you have any native scrollbars that are being pressed, your scrollbar will appear on mouseenter (which it shouldn't), whereas other native scrollbars will not appear. So, I don't recommend this method and instead recommend just checking if the mouse is pressed using event.which.

I discovered an issue with this code. If you call reinitialize() because the size of the content has changed, it clears out the hover state even if you're over. You need to call checkOver() after you call reinitialize or the scrollbar will remain hidden until you rollout and roll over again.

Another related issue is if you are dragging something externally that is triggering scrolling of this pane via scrollToY (or scrollToX) and reinitialise() is called, it won't know it's dragging the current scrollbar because (event.which === 1). This necessitates further custom code on my end to make this work. :)