3
votes

I have a DSP application which can be controlled through a web interface. To allow the user to set certain numeric values, such as gain, sensitivity, volume, frequency limits of filters, etc., I use the JQuery Knob element from Anthony Therrien.

The JQuery Knob element may be actuated by placing the cursor over the knob and actuating the scroll wheel. However, the problem is that, when you do this, not only the knob's value changes, but also the document itself scrolls so that the knob moves away under your cursor as you attempt to change its value using the scroll wheel (or track pad, or touch screen, ...).

To ensure that the problem is not specific to my application and to have a code example for my question, I implemented a small test site which demonstrates the problem.

This is the (X)HTML(5) code of the web site.

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>
            Test page
        </title>
        <script src="js/include/jquery-3.2.1.js" type="text/javascript"></script>
        <script src="js/include/jquery.knob.js" type="text/javascript"></script>
        <script src="js/test.js" type="text/javascript"></script>
    </head>
    <body>
        <div id="content">
            <div>
                Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod
                tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim
                veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid
                ex ea commodi consequat. Quis aute iure reprehenderit in voluptate
                velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
                obcaecat cupiditat non proident, sunt in culpa qui officia deserunt
                mollit anim id est laborum.
            </div>
        </div>
        <div>
            <div class="param" style="display: inline-block">
                <input class="knob" value="50" data-min="0" data-max="100"
                    data-step="1" data-width="150" data-height="150"
                    data-angleoffset="-135" data-anglearc="270"
                    data-rotation="clockwise" data-cursor="false"
                    data-thickness=".3" data-displayprevious="true"
                    data-bgcolor="#181818" data-fgcolor="#ff8800"
                    type="text"/>
            </div>
        </div>
    </body>
</html>

And these are the contents of the js/test.js file referenced by the web site.

/*
 * This is called after the DOM initialized.
 */
function init() {
    var contentDiv = $('#content').get(0);
    var loremDiv = contentDiv.firstChild.nextSibling;

    /*
     * Replicate the lorem ipsum a couple of times to have
     * content on the site for scrolling.
     */
    for (var i = 0; i < 16; i++) {
        var newNode = loremDiv.cloneNode(true);
        contentDiv.appendChild(newNode);
    }

    /*
     * Turn the input element into a JQuery knob.
     */
    $('.knob').knob();
}

$(init);

The JavaScript code fills the site with a lot of blind text and turns the input element into a JQuery Knob. Load the site in a browser (I tested it using Firefox, though I don't suspect the issue to be browser specific), scroll down to the knob, place a cursor over the knob and actuate the scroll wheel. You will see that the scroll wheel event changes both the value of the knob and scrolls the document.

To prevent the document from scrolling when the user actuates the scroll wheel while the cursor is placed over the knob, I tried to register an event handler on the DIV element surrounding the JQuery Knob, which captures onwheel events. To achieve this, I just inserted the following code into the body of the init() function right at the end.

    var paramDiv = $('.param').get(0);

    /*
     * Prevent site from scrolling when knob is actuated.
     */
    paramDiv.onwheel = function(event) {
        event.preventDefault();
    }

(I just realized that the syntax highlighting at StackOverflow highlights event as a JavaScript key word. However, there seems to be no difference in behaviour whether the parameter is named event or e or whatever.)

This indeed prevents the document from scrolling when the mouse wheel is actuated while the cursor is placed over the knob (or rather, the DIV containing it). However, it also prevents the mouse wheel from actuating the knob itself, which defeats the entire purpose.

I don't understand why capturing the event at the surrounding DIV prevents the knob from being actuated using the scroll wheel. As far as I understand, events propagate "up the DOM tree", from the child to the parent nodes. So the Knob (or the DOM elements that it consists of) should get the mouse wheel events first, causing the knob to get actuated by the wheel. Then, I'd expect the event to propagate "upwards" (towards the parent nodes) in the DOM tree, so that it is eventually captured by the event handler on the surrounding DIV, which prevents the document from being scrolled.

However, this is not what happens. When I capture the mouse wheel event on the surrounding DIV, this indeed prevents the site from scrolling when the cursor is placed over the knob. However, it also prevents the knob from getting actuated using the mouse wheel. Anyone knows, why this happens and how to correctly handle this issue?

The behaviour I want to achieve is the following:

  • Actuating the scroll wheel while the cursor is placed over a knob only actuates the corresponding knob but does not scroll the document.
  • Actuating the scroll wheel while the cursor is not placed over a knob scrolls the document.

I have a modified version of JQuery Knob which I use in my specific application and which has several issues fixed. (The code of JQuery Knob is currently unmaintained. The last commit in the repository was in December 2015.) However, the problem also occurs with the original version, so when a fix/workaround is known which works for the original JQuery Knob implementation, chances are it will work with my custom version as well.

Any help is appreciated.

1

1 Answers

2
votes

I found a solution to this problem by adjusting the JQuery Knob code once more.

Locate the following lines (671 and 672 in original source on GitHub).

this.$c.bind("mousewheel DOMMouseScroll", mw);
this.$.bind("mousewheel DOMMouseScroll", mw);

Change them to the following.

this.$c.bind("wheel mousewheel DOMMouseScroll", mw);
this.$.bind("wheel mousewheel DOMMouseScroll", mw);

Then, locate the following lines (572 and 573 in original source on GitHub).

deltaX = ori.detail || ori.wheelDeltaX,
deltaY = ori.detail || ori.wheelDeltaY,

Change them to the following.

deltaX = ori.deltaX || ori.detail || ori.wheelDeltaX,
deltaY = ori.deltaY || ori.detail || ori.wheelDeltaY,

Now, the scroll event is correctly captured by JQuery Knob so that the document no longer scrolls when actuating the knob.

EDIT: In March 2018, I published both a patch to JQuery Knob, which fixes this and several other issues, as well as a clean-room implementation of a JavaScript knob, with a more straight-forward architecture, which does not depend on JQuery (or any other external library). You might want to consider using one of these in case you run into issues with JQuery Knob.