Javascript scrollbar class and mousewheel speed in different browsers
Asked Answered
G

1

3

I'm getting many reports that the mousewheel behaves differently in different browsers when using this scrollbar class. In some browsers (like Firefox) it's extremely slow while in others (mostly newer versions of Safari on Snow Leopard) it's perfect.

Any ideas what's going on here and how to fix it? I'm using the Mootools library. One line to pay attention to here is the wheel: (Browser.firefox) ? 20 : 1 line. This is where you set the speed or steps for the mousewheel.

Here it is set up in a jsFiddle: http://jsfiddle.net/brandondurham/6SUyM/

var ScrollBar = new Class({

    Implements: [Events, Options],

    options: {
        wheel: (Browser.firefox) ? 20 : 1
    },

    initialize: function(main, options) {

        this.setOptions(options);

        this.main = $(main);
        this.content = this.main.getFirst();

        this.vScrollbar = new Element('div', {
            'class': 'scrollbar'
        }).inject(this.content, 'after');

        this.vTrack = new Element('div', {
            'class': 'track'
        }).inject(this.vScrollbar);

        this.vThumb = new Element('div', {
            'class': 'handle'
        }).inject(this.vTrack);

        this.bound = {
            'vStart': this.vStart.bind(this),
            'end': this.end.bind(this),
            'vDrag': this.vDrag.bind(this),
            'vTouchDrag': this.vTouchDrag.bind(this),
            'wheel': this.wheel.bind(this),
            'vPage': this.vPage.bind(this),
        };

        this.vScrollbar.set('tween', {
            duration: 200,
            transition: 'cubic:out'
        });
        this.main.addEvent('mouseenter', function(event){
            this.vScrollbar.get('tween').cancel();
            this.vScrollbar.tween('width', 12);
        }.bind(this));
        this.main.addEvent('mouseleave', function(event){
            this.vScrollbar.get('tween').cancel();
            this.vScrollbar.tween('width', 0);
        }.bind(this));

        this.vPosition = {};
        this.vMouse = {};
        this.update();
        this.attach();

        this.scrollContent = new Fx.Scroll(this.content, {
            duration: 800,
            transition: Fx.Transitions.Cubic.easeOut,
        });
        this.scrollThumb = new Fx.Morph(this.vThumb, {
            duration: 400,
            transition: Fx.Transitions.Cubic.easeOut,
        });
    },

    update: function() {

        var panel_id = (this.content.getFirst()) ? this.content.getFirst().get('id') : '';

        if ((this.content.scrollHeight <= this.main.offsetHeight) || panel_id == 'random-doodle') this.main.addClass('noscroll');
        else this.main.removeClass('noscroll');

        this.vContentSize = this.content.offsetHeight;
        this.vContentScrollSize = this.content.scrollHeight;
        this.vTrackSize = this.vTrack.offsetHeight;

        this.vContentRatio = this.vContentSize / this.vContentScrollSize;

        this.vThumbSize = (this.vTrackSize * this.vContentRatio).limit(12, this.vTrackSize);

        this.vScrollRatio = this.vContentScrollSize / this.vTrackSize;

        this.vThumb.setStyle('height', this.vThumbSize);

        this.vUpdateThumbFromContentScroll();
        this.vUpdateContentFromThumbPosition();

    },

    vUpdateContentFromThumbPosition: function() {
        this.content.scrollTop = this.vPosition.now * this.vScrollRatio;
    },

    vUpdateContentFromThumbPosition2: function() {
        var pos = this.vPosition.now * this.vScrollRatio;
        this.scrollContent.start(0, pos);
    },

    vUpdateThumbFromContentScroll: function() {
        this.vPosition.now = (this.content.scrollTop / this.vScrollRatio).limit(0, (this.vTrackSize - this.vThumbSize));
        this.vThumb.setStyle('top', this.vPosition.now);
    },

    vUpdateThumbFromContentScroll2: function(pos) {
        this.vPosition.now = (this.content.scrollTopNew / this.vScrollRatio).limit(0, (this.vTrackSize - this.vThumbSize));           
        this.scrollThumb.start({
            'top': this.vPosition.now       
        });
    },

    attach: function() {
        if (this.options.wheel) this.content.addEvent('mousewheel', this.bound.wheel);
        this.content.addEvent('touchstart', this.bound.vStart);
        this.vThumb.addEvent('mousedown', this.bound.vStart);
        this.vTrack.addEvent('mouseup', this.bound.vPage);
    },

    wheel: function(event) {
        this.content.scrollTop -= event.wheel * this.options.wheel;
        this.vUpdateThumbFromContentScroll();
        event.stop();
    },

    scrollTo: function(pos){
        myInstance = this;
        this.content.scrollTopNew = pos;
        this.scrollContent.start(0, this.content.scrollTopNew);
        myInstance.vUpdateThumbFromContentScroll2(pos);
    },

    vPage: function(event) {
        // if scrolling up
        if (event.page.y > this.vThumb.getPosition().y) {
            myInstance = this;
            this.content.scrollTopNew = this.content.scrollTop.toInt() + this.content.offsetHeight.toInt();
            this.scrollContent.start(0, this.content.scrollTopNew);
        }
        // if scrolling down
        else {
            myInstance = this;    
            this.content.scrollTopNew = this.content.scrollTop.toInt() - this.content.offsetHeight.toInt();    
            this.scrollContent.start(0, this.content.scrollTopNew);       
        }
        myInstance.vUpdateThumbFromContentScroll2(event.page.y);
        event.stop();
    },

    vStart: function(event) {
        this.vMouse.start = event.page.y;
        this.vPosition.start = this.vThumb.getStyle('top').toInt();
        document.addEvent('touchmove', this.bound.vTouchDrag);
        document.addEvent('touchend', this.bound.end);
        document.addEvent('mousemove', this.bound.vDrag);
        document.addEvent('mouseup', this.bound.end);
        this.vThumb.addEvent('mouseup', this.bound.end);
        event.stop();
    },

    end: function(event) {
        document.removeEvent('touchmove', this.bound.vTouchDrag);
        document.removeEvent('mousemove', this.bound.vDrag);
        document.removeEvent('mouseup', this.bound.end);
        this.vThumb.removeEvent('mouseup', this.bound.end);
        event.stop();
    },

    vTouchDrag: function(event) {
        this.vMouse.now = event.page.y;
        this.vPosition.now = (this.vPosition.start - (this.vMouse.now - this.vMouse.start)).limit(0, (this.vTrackSize - this.vThumbSize));
        this.vUpdateContentFromThumbPosition();
        this.vUpdateThumbFromContentScroll();
        event.stop();
    },

    vDrag: function(event) {
        this.vMouse.now = event.page.y;
        this.vPosition.now = (this.vPosition.start + (this.vMouse.now - this.vMouse.start)).limit(0, (this.vTrackSize - this.vThumbSize));
        this.vUpdateContentFromThumbPosition();
        this.vUpdateThumbFromContentScroll();
        event.stop();
    }

});
Googins answered 2/12, 2010 at 18:0 Comment(5)
post a www.jsfiddle.net example how you use it, its not exactly obvious - or a link to another demo.Berthoud
hrm event.wheel always comes back as -1 on scroll down for me - in ie8, ff 3.6.13 and chrome 7 - only firefox seems ok due to this.options.wheel being 20. any particular reason why you wouldn't use a step of 20 for all browsers? also, you may want to track dragging via this.dragging = true; and check for that in the mouseleave function that hides the scrollbar if you mouseout - you shouldn't disappear it whilst dragging. change end to set this.dragging to false again and check if moused out so you can hide it if left the container.Berthoud
If I use 20 for all browsers it's REALLY fast in Safari. I changed that line to be "wheel: (Browser.safari5) ? 1 : 20" because I think it's really Safari 5 that's the odd man out.Googins
Also, you should post your suggestion as an answer so I can accept it. :) While I have you, though, can you see a reliable way to set a static height for the handle instead of a dynamic one without messing up the math behind the scrolling function? I need to set the handle to always be 200px high.Googins
Added in dragging check: jsfiddle.net/brandondurham/6SUyM/2Googins
P
1

The mouse wheel event is very dodgy in javascript, main issue being usually Safari as they used to adjust the ratio on every point release, and even then the values reported by the event are not the same in all major browsers.

There has been some discussion about this on the MooTools tracker some time ago (link) and comparing different solutions I concluded that there's no standard way to normalize the event.
The last message on that issue shows a possible solution for normalizing (link), but it breaks wheel acceleration in Safari (and probably any other acceleration that other Browser/OS/Mouse Driver combination offers) so it is a tradeoff that you will have to evaluate if it fits to the requirements of your usage scenario.

Pruinose answered 23/2, 2011 at 16:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.