23

I have been encountering an issue when using multiple select select fields within Safari 8 on OS X Yosemite. If the select field has an applied width, either in-line or as a class, I am unable to use the keyboards arrow keys to scroll down through the select as per normal behaviour.

<select size="5" name="selectMultiple" multiple="multiple">

Multiple select JSFiddle.

<select size="5" name="selectMultiple" multiple="multiple" style="width:100%;">

with style tag JSFiddle.

When the select has style the selection moves out of view instead of scrolling the list downwards keeping the selected item in view.

Is this a bug in the version of Safari (Version 8.0 (10600.1.25)) I am using. I am using BrowserStack for my testing. Or is this something I can address with a fix through my code?

Thank you.

VC1
  • 1,600
  • 4
  • 23
  • 40
Jonathan
  • 1,715
  • 11
  • 14

1 Answers1

1

I think this is indeed some type of bug that has to do with the width of the select element vs the scrollHeight of the element.

The more options you have, the wider it can be and still work fine. If I have a select tag with 39 options, the max seems to be around 510px before it messes up.

On average, the max width that a select can handle seems to be approx 13px per option. So if you have a selector with 13 options, then the max is about 169px (13 * 13)

When you scroll to the 2nd option, the scrollTop is 14px and to the 3rd option, 28px. So each element that you scroll to is 14px. So as long as the width is less than the scrollHeight minus a certain number of pixels, it works... If you use the 13 pixels per option, it seems to work fine.

So, you have 2 options.

  1. Make sure that the width of your select is less than 13 * the number of options

OR

  1. Use javascript to get your desired behavior... I came up with a JsFiddle that works. And for those that like using jQuery, try this JsFiddle

You just have to listen to the keydown event and adjust the scroll so that the selected element is in view before it is selected.

Also, in order to make the scrollByLines(numberOfLines) method work on the scroll element, it has to have the following style:

overflow-y: scroll;

Here is a quick HTML document that works

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <script type="text/javascript">

        // This happens on document load
        function myOnLoad() {

            // Get the selector element
            var mySelector = document.getElementById('mySelector');

            // If the selector is doomed to glitch out on us because it's wider than the max allowed width, we need to fix it
            if (mySelector.offsetWidth > 13 * mySelector.options.length) {

                // Figure out the pixels for a single scroll line
                mySelector.scrollByLines(1);
                var scrollLineHeight = mySelector.scrollTop;

                // Scroll back to the top
                mySelector.scrollTop = 0;

                // Add a keydown event listener so that we can scroll programatically before it messes up
                mySelector.addEventListener('keydown', function (e) {

                    // Only listen to up and down arrows
                    if (e.keyCode !== 38 && e.keyCode !== 40) {
                        return;
                    }

                    // Figure out where the selector is scrolled to
                    var scrollTop = this.scrollTop;
                    var scrolledToLine = parseInt(scrollTop / scrollLineHeight);

                    // If we hit the up arrow and the selected index is equal to the scrolled line, simply move us up by one
                    if (e.keyCode === 38 && this.selectedIndex === scrolledToLine) {
                        this.scrollByLines(-1);
                    }

                    // If we hit the down arrow and the selected index is equal to the scrolled line + the number of visible lines - 1, move us down by one
                    if (e.keyCode === 40 && this.selectedIndex === scrolledToLine + (this.size - 1)) {
                        this.scrollByLines(1);
                    }
                });
            }
        }
    </script>
</head>
<body onload="myOnLoad();">
<select size="5" name="selectMultiple" multiple="multiple" style="width:100%; overflow-y: scroll;" id="mySelector">
    <option value="0">line 0</option>
    <option value="1">line 1</option>
    <option value="2">line 2</option>
    <option value="3">line 3</option>
    <option value="4">line 4</option>
    <option value="5">line 5</option>
    <option value="6">line 6</option>
    <option value="7">line 7</option>
    <option value="8">line 8</option>
    <option value="9">line 9</option>
    <option value="10">line 10</option>
    <option value="11">line 11</option>
    <option value="12">line 12</option>
</select>
</body>
</html>

And here is the jQuery version:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
    <script type="text/javascript">

        $( document ).ready(function() {

            // Get the selector element
            var mySelectorObj = $('#mySelector');
            var mySelector = mySelectorObj[0];

            // If the selector is doomed to glitch out on us because it's wider than the max allowed width, we need to fix it
            if (mySelector.offsetWidth > 13 * mySelector.options.length) {

                // Figure out the pixels for a single scroll line
                mySelector.scrollByLines(1);
                var scrollLineHeight = mySelector.scrollTop;

                // Scroll back to the top
                mySelector.scrollTop = 0;

                // Add a keydown event listener so that we can scroll programatically before it messes up
                mySelectorObj.on('keydown', function(e) {

                    // Only listen to up and down arrows
                    if (e.keyCode !== 38 && e.keyCode !== 40) {
                        return;
                    }

                    // Figure out where the selector is scrolled to
                    var scrollTop = this.scrollTop;
                    var scrolledToLine = parseInt(scrollTop / scrollLineHeight);

                    // If we hit the up arrow and the selected index is equal to the scrolled line, simply move us up by one
                    if (e.keyCode === 38 && this.selectedIndex === scrolledToLine) {
                        this.scrollByLines(-1);
                    }

                    // If we hit the down arrow and the selected index is equal to the scrolled line + the number of visible lines - 1, move us down by one
                    if (e.keyCode === 40 && this.selectedIndex === scrolledToLine + (this.size - 1)) {
                        this.scrollByLines(1);
                    }
                });
            }
        });
    </script>
</head>
<body>
<select size="5" name="selectMultiple" multiple="multiple" style="width:100%; overflow-y: scroll;" id="mySelector">
    <option value="0">line 0</option>
    <option value="1">line 1</option>
    <option value="2">line 2</option>
    <option value="3">line 3</option>
    <option value="4">line 4</option>
    <option value="5">line 5</option>
    <option value="6">line 6</option>
    <option value="7">line 7</option>
    <option value="8">line 8</option>
    <option value="9">line 9</option>
    <option value="10">line 10</option>
    <option value="11">line 11</option>
    <option value="12">line 12</option>
</select>
</body>
</html>
Ray Perea
  • 4,734
  • 1
  • 28
  • 37