Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Safari 8 multiple select scrolling issue

Tags:

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.

like image 232
Jonathan Avatar asked Nov 24 '14 16:11

Jonathan


1 Answers

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> 
like image 132
Ray Perea Avatar answered Oct 05 '22 11:10

Ray Perea