根据所选选项的宽度自动调整SELECT元素的大小

40

我有一个包含不同选项的 select 元素。通常情况下,select 元素的宽度将根据最大的 option 元素获得,但我希望 select 元素具有默认 option 值的宽度,即较短的宽度。当用户选择另一个 option 后,select 元素应该调整大小,以便整个文本始终可见于元素中。

$(document).ready(function() {
    $('select').change(function(){
        $(this).width($('select option:selected').width());
    });
});

问题:

  • 在Chrome (Canary)浏览器上,它总是返回0的宽度。
  • 在Firefox浏览器上,当选择较短的选项时,宽度会被添加而不是调整大小。
  • Safari浏览器:与Chrome相同的结果。

JSFiddle演示


1
获取option元素的宽度没有可靠的方法。相反,您可以获取text属性的长度,并根据此估计所需的宽度。 - Rory McCrossan
@RoryMcCrossan 我也考虑过这个问题,但是在不同的浏览器屏幕分辨率设置下,结果会不同吗?我还发现了这个可能的解决方案:http://jsfiddle.net/zERB7/3/ - Th3Alchemist
简单的解决方案在这里:https://dev59.com/M3VC5IYBdhLWcg3wcgqd#55343177 - João Pimentel Ferreira
这里是一种更现代的原生JS解决方法: https://dev59.com/9WIj5IYBdhLWcg3wmWG1#67239947 - kilian
14个回答

51

您是正确的,没有一种简单或内置的方法来获取特定选择选项的大小。这里有一个JsFiddle可以实现您想要的功能。

最新版本的Chrome、Firefox、IE、Opera和Safari都可以使用。

我添加了一个隐藏的选择器#width_tmp_select来计算我们想要自动调整大小的可见选择器#resizing_select的宽度。我们将隐藏的选择器设置为只有一个选项,其文本与可见选择器当前选定选项的文本相同。然后,我们使用隐藏选择器的宽度作为可见选择器的宽度。请注意,使用隐藏而不是隐藏选择器也可以很好地工作,但是如下评论中sami-al-subhi指出的那样,宽度会略微偏差。

$(document).ready(function() {
  $('#resizing_select').change(function(){
    $("#width_tmp_option").html($('#resizing_select option:selected').text()); 
    $(this).width($("#width_tmp_select").width());  
  });
});
#resizing_select {
  width: 50px;
} 
 
#width_tmp_select{
  display : none;
} 
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js"></script>

<select id="resizing_select">
  <option>All</option>
  <option>Longer</option>
  <option>A very very long string...</option>
</select>

<select id="width_tmp_select">
  <option id="width_tmp_option"></option>
</select>


你的 Fiddle 链接和 OP 的一样。 - Rory McCrossan
好的方法,但您必须确保<option>文本与<span>的字体/大小相同。如果您删除#width_tmp{display : none;},您会发现<span>更大。这将导致比应该更大的<select>。文本越长,在<select>中留下的空白空间就越大。 - Sami Al-Subhi
2
这是一个纯JavaScript尝试版本:http://jsfiddle.net/FSsG8/635/ 你也可以尝试通过匹配两个选择器之间的字体来获得一些进展,有点类似:https://dev59.com/bVjUa4cB1Zd3GeqPQlkB#6385741 - rogerdpack
检查一下我的解决方案,它更简单、更整洁 :) https://dev59.com/M3VC5IYBdhLWcg3wcgqd#55343177 - João Pimentel Ferreira
这是一种更现代的原生 JS 方法来解决这个问题: https://dev59.com/9WIj5IYBdhLWcg3wmWG1#67239947 - kilian

22
这是我刚为这个问题编写的一个插件,它可以动态地创建和销毁模拟的span,从而不会使你的html混乱。这有助于分离关注点,让你委托功能,并允许在多个元素之间轻松重用。
在任何地方包含此JS:
(function($, window){
  $(function() {
    let arrowWidth = 30;

    $.fn.resizeselect = function(settings) {  
      return this.each(function() { 

        $(this).change(function(){
          let $this = $(this);

          // get font-weight, font-size, and font-family
          let style = window.getComputedStyle(this)
          let { fontWeight, fontSize, fontFamily } = style

          // create test element
          let text = $this.find("option:selected").text();
          let $test = $("<span>").html(text).css({
            "font-size": fontSize, 
            "font-weight": fontWeight, 
            "font-family": fontFamily,
            "visibility": "hidden" // prevents FOUC
          });

          // add to body, get width, and get out
          $test.appendTo($this.parent());
          let width = $test.width();
          $test.remove();

          // set select width
          $this.width(width + arrowWidth);

          // run on start
        }).change();

      });
    };

    // run by default
    $("select.resizeselect").resizeselect();                       
  })
})(jQuery, window);

您可以通过以下两种方式之一初始化插件:
  1. HTML - Add the class .resizeselect to any select element:

    <select class="btn btn-select <b>resizeselect</b>">
       <option>All</option>
       <option>Longer</option>
       <option>A very very long string...</option>
     </select>
     
  2. JavaScript - Call .resizeselect() on any jQuery object:

    $("select").<b>resizeselect</b>()

jsFiddle和StackSnippets中的演示:

(function($, window){
  $(function() {
    let arrowWidth = 30;

    $.fn.resizeselect = function(settings) {  
      return this.each(function() { 

        $(this).change(function(){
          let $this = $(this);

          // get font-weight, font-size, and font-family
          let style = window.getComputedStyle(this)
          let { fontWeight, fontSize, fontFamily } = style

          // create test element
          let text = $this.find("option:selected").text();
          let $test = $("<span>").html(text).css({
            "font-size": fontSize, 
            "font-weight": fontWeight, 
            "font-family": fontFamily,
            "visibility": "hidden" // prevents FOUC
          });

          // add to body, get width, and get out
          $test.appendTo($this.parent());
          let width = $test.width();
          $test.remove();

          // set select width
          $this.width(width + arrowWidth);

          // run on start
        }).change();

      });
    };

    // run by default
    $("select.resizeselect").resizeselect();                       
  })
})(jQuery, window);
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js"></script>

<select class="resizeselect">
  <option>All</option>
  <option>Longer</option>
  <option>A very very long string...</option>
</select>

已更新,包括GarywooEric Warnke的尺码建议。


1
我建议将行var $test = $("<span>").html(text);更改为var $test = $("<span style=\"font-size:"+$this.css("font-size")+"\">").html(text);,以确保创建的临时 span 的字体大小与选择元素的字体大小相匹配。如果 span 的字体大小较小或较大,则动态调整大小将受到大小差异的影响。 - Garywoo
1
大部分都能正常工作!使用Bootstrap的默认CSS会将选择元素的宽度始终设置为100%,导致宽度不正确。我将附加项从“body”更改为“#myForm”,并将“$(<span>)”更改为“$('<span style =“visibility:hidden”>')”,只是为了让自己感到安心。 - Eric Warnke
不幸的是,它在iPad/Android平板电脑上无法工作,有什么想法吗?我认为可能是这些设备中选项的字体大小不同,但如果我设置静态字体大小,它并不能解决问题。 - lopezi
我通过为表单元素设置静态字体大小来解决了上述问题,而我之前建议的是在脚本本身上设置静态字体大小。 - lopezi
检查一下我的解决方案,它更简单、更整洁 :) https://dev59.com/M3VC5IYBdhLWcg3wcgqd#55343177 - João Pimentel Ferreira
1
这是一个更现代的原生JS方法来解决这个问题:https://dev59.com/9WIj5IYBdhLWcg3wmWG1#67239947 - kilian

13

这个工作解决方案在这里使用了一个临时的辅助 select,选中选项从主要的 select 中克隆进去,这样我们可以评估主要的 select 应该具有的真实宽度。

这里的好处是,只需添加此代码并适用于每个选择,因此无需使用 ids 和额外的命名。

$('select').change(function(){
  var text = $(this).find('option:selected').text()
  var $aux = $('<select/>').append($('<option/>').text(text))
  $(this).after($aux)
  $(this).width($aux.width())
  $aux.remove()
}).change()
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<select>
  <option>REALLY LONG TEXT, REALLY LONG TEXT, REALLY LONG TEXT</option>
  <option>ABCDEFGHIJKL</option>
  <option>ABC</option>
</select>


6

基于@Kmas的答案的2022年纯JavaScript版本。


function resize(event) {
  const fakeEl = document.querySelector('#fakeEl');
  const option = event.target.options[event.target.selectedIndex];
  
  fakeEl[0].innerHTML = event.target.value;
  event.target.style.width = fakeEl.getBoundingClientRect().width + 'px';
}

for (let e of document.querySelectorAll('select.autoresize')) {
  e.onchange = resize;
  e.dispatchEvent(new Event('change'));
}
<select class='autoresize'>
    <option>Foo</option>
    <option>FooBar</option>
    <option>FooBarFooBarFooBar</option>
</select>

<select id='fakeEl' style='visibility: hidden'><option></option></select>

你也许不需要jQuery

5
这里是一个更加现代化的纯JS方法来解决这个问题。大致上与这个答案的原理相同,只是没有使用jQuery。
  1. 获取选择元素并监听其变化。
  2. 创建一个新的选择元素和选项,并将当前selectedIndex的文本传递给该选项。
  3. 向新的选择元素添加position:fixedvisibility:hidden样式。这确保它不会影响布局,但其边界框仍然可以测量。
  4. 将选项附加到选择列表中。
  5. 将选择列表附加到原始选择元素中。
  6. 使用getBoundingClientRect().width获取新元素的所需尺寸。
  7. 根据新元素的尺寸设置原始元素的宽度。
  8. 删除新元素。
  9. 调度更改事件以最初触发此逻辑。

const select = document.querySelector('select')

select.addEventListener('change', (event) => {
  let tempSelect = document.createElement('select'),
      tempOption = document.createElement('option');

  tempOption.textContent = event.target.options[event.target.selectedIndex].text;
  tempSelect.style.cssText += `
      visibility: hidden;
      position: fixed;
      `;
  tempSelect.appendChild(tempOption);
  event.target.after(tempSelect);
  
  const tempSelectWidth = tempSelect.getBoundingClientRect().width;
  event.target.style.width = `${tempSelectWidth}px`;
  tempSelect.remove();
});

select.dispatchEvent(new Event('change'));
<select>
  <option>Short option</option>
  <option>Some longer option</option>
  <option>A very long option with a lot of text</option>
</select>


我还需要添加 event.target.classList.forEach((className) => tempSelect.classList.add(className)),因为大小有点不对。 - pmishev

3

尝试以下简单的JavaScript代码,并将其转换为jQuery :)

<html><head><title>Auto size select</title>

<script>
var resized = false;

function resizeSelect(selected) {
  if (resized) return;
  var sel = document.getElementById('select');
  for (var i=0; i<sel.options.length; i++) {
    sel.options[i].title=sel.options[i].innerHTML;
    if (i!=sel.options.selectedIndex) sel.options[i].innerHTML='';
  }
  resized = true;
  sel.blur();
}

function restoreSelect() {
  if (!resized) return;
  var sel = document.getElementById('select');
  for (var i=0; i<sel.options.length; i++) {
    sel.options[i].innerHTML=sel.options[i].title;
  }  
  resized = false;
}
</script>

</head>
<body onload="resizeSelect(this.selectedItem)">
<select id="select" 
  onchange="resizeSelect(this.selectedItem)"
  onblur="resizeSelect(this.selectedItem)"
  onfocus="restoreSelect()">
<option>text text</option>
<option>text text text text text</option>
<option>text text text </option>
<option>text text text text </option>
<option>text</option>
<option>text text text text text text text text text</option>
<option>text text</option>
</select>
</body></html>

这里有一个与之相关的jsfiddle:https://jsfiddle.net/sm4dnwuf/1/ 基本上,它在不聚焦时暂时移除未选中的元素(导致其大小仅为所选元素的大小)。

1
这是另一种纯jQuery解决方案:

$('#mySelect').change(function(){
    var $selectList = $(this);

    var $selectedOption = $selectList.children('[value="' + this.value + '"]')
        .attr('selected', true);
    var selectedIndex = $selectedOption.index();

    var $nonSelectedOptions = $selectList.children().not($selectedOption)
        .remove()
        .attr('selected', false);

    // Reset and calculate new fixed width having only selected option as a child
    $selectList.width('auto').width($selectList.width());

    // Add options back and put selected option in the right place on the list
    $selectedOption.remove();
    $selectList.append($nonSelectedOptions);
    if (selectedIndex >= $nonSelectedOptions.length) {
         $selectList.append($selectedOption);
    } else {
         $selectList.children().eq(selectedIndex).before($selectedOption);
    }
});

1
您可以使用一个简单的函数:

// Function that helps to get the select element width.
$.fn.getSelectWidth = function() {
  var width = Math.round($(this).wrap('<span></span>').width());
  $(this).unwrap();
  return width;
}

然后在您的表单选择元素上使用它:
$(this).getSelectWidth();

0

试一试这个:

const select = $('select');

function calcTextWidth(text, font) {
    let canvas = calcTextWidth.canvas || (calcTextWidth.canvas = document.createElement("canvas"));
    let context = canvas.getContext("2d");
    context.font = font;
    let metrics = context.measureText(text);
    return metrics.width;
}

select.change(function () {
    select.width(calcTextWidth(select.find(":selected").text(), 'normal normal 500 15px sans-serif'));
});

0

以下是我如何实现以下行为的方法:

  1. 文档加载时:选择框的宽度已经基于所选选项的宽度(不会发生重绘)。

  2. 更改时:选择框的宽度将更新为新选择选项的宽度。

/* WIDTH, ADJUSTMENT, CONTENT BASED */
$( document ).on( 'change', '.width-content-based', function(){
 let text_modified_font;
 let text_modified_string;
 let descendants_emptied_elements;
 
 text_modified_font =
  $( this ).css( 'font-weight' ) + ' ' + $( this ).css( 'font-size' ) + ' ' + $( this ).css( 'font-family' );
 
 if
 (
  $( this ).is( 'select' )
 )
 {
  text_modified_string =
   $( this ).find( ':selected' ).text();
  
  descendants_emptied_elements =
   $( this ).find( '[data-text]' );
 }
 
 else
 {
  text_modified_string =
   $( this ).val();
 }
 
 $( this ).css( { 'width': text_width_estimate( text_modified_string, text_modified_font ) + 'px' } );
 
 if
 (
  descendants_emptied_elements.length
 )
 {
  descendants_emptied_elements.each( function(){
   $( this ).text( $( this ).attr( 'data-text' ) ).removeAttr( 'data-text' );
  });
 }
});

/* DOCUMENT, ON READY */
$( document ).ready( function(){
 
 $( '.width-content-based' ).trigger( 'change' );
 
});

/* TEXT WIDTH ESTIMATE */ function text_width_estimate( text, font ) { let canvas = text_width_estimate.canvas || ( text_width_estimate.canvas = document.createElement( 'canvas' ) ); let context = canvas.getContext( '2d' ); context.font = font; let metrics = context.measureText( text ); return metrics.width + 3; }
select { -webkit-appearance: none; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<select class="width-content-based">
 <option value="AF" data-text="Afghanistan"></option>
 <option value="AX" data-text="Åland Islands"></option>
 <option value="AL" selected>Albania</option>
</select>


你的代码示例格式不太好看,阅读起来有些困难 - 可以考虑稍微整理一下 :) - Tammy Shipps

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接