Javascript scrollbar class and mousewheel speed in different browsers

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:

var ScrollBar = new Class({

    Implements: [Events, Options],

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

    initialize: function(main, 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'

        this.vThumb = new Element('div', {
            'class': 'handle'

        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.tween('width', 12);
        this.main.addEvent('mouseleave', function(event){
            this.vScrollbar.tween('width', 0);

        this.vPosition = {};
        this.vMouse = {};

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



    vUpdateContentFromThumbPosition: function() {
        this.content.scrollTop = * this.vScrollRatio;

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

    vUpdateThumbFromContentScroll: function() { = (this.content.scrollTop / this.vScrollRatio).limit(0, (this.vTrackSize - this.vThumbSize));

    vUpdateThumbFromContentScroll2: function(pos) { = (this.content.scrollTopNew / this.vScrollRatio).limit(0, (this.vTrackSize - this.vThumbSize));           

    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;

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

    vPage: function(event) {
        // if scrolling up
        if ( > 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);       

    vStart: function(event) {
        this.vMouse.start =;
        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);

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

    vTouchDrag: function(event) { =; = (this.vPosition.start - ( - this.vMouse.start)).limit(0, (this.vTrackSize - this.vThumbSize));

    vDrag: function(event) { =; = (this.vPosition.start + ( - this.vMouse.start)).limit(0, (this.vTrackSize - this.vThumbSize));



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.

Need Your Help

Is there a difference between 'valid xml' and 'well formed xml'?

xml validation xsd dtd

I wasn't aware of a difference, but a coworker says there is, although he can't back it up. What's the difference if any?

Mapkit and userTrackingMode

ios objective-c mapkit mkusertrackingmode

I have a question about MapKit. I set the userTrackingMode property to move the map according to the user position update.

About UNIX Resources Network

Original, collect and organize Developers related documents, information and materials, contains jQuery, Html, CSS, MySQL, .NET, ASP.NET, SQL, objective-c, iPhone, Ruby on Rails, C, SQL Server, Ruby, Arrays, Regex, ASP.NET MVC, WPF, XML, Ajax, DataBase, and so on.