Select2分组选择组件鼠标悬停滚动自动回顶问题求助
修复Select2 Optgroup分组选择时滚动条自动回顶的问题
我之前在做Select2分组多选的时候也碰到过一模一样的滚动跳顶问题,折腾了好一会儿才找到根源,咱们来一步步拆解解决它:
问题根源分析
看你的代码,主要有两个核心问题导致滚动条自动回顶:
- 分组没有唯一ID:你的分组
id都是空字符串或者重复的(比如两个Other分组),Select2在渲染下拉列表时,会把相同ID的元素视为同一个,每次执行query重新生成列表时,会销毁旧DOM再创建新的,这就导致滚动位置直接重置。 - 不必要的下拉列表重渲染:每次鼠标进入选择区域,Select2都会触发
query函数重新过滤分组,而你返回的是新生成的数组对象,Select2会认为数据完全更新,从而重新渲染整个下拉框,滚动条自然回到顶部。
修复方案
针对这两个问题,咱们从三个方面调整代码:
1. 给每个分组设置唯一ID
确保每个分组的id属性是唯一的,Select2需要这个标识来稳定渲染DOM元素,避免重复创建。
2. 优化查询过滤逻辑
简化过滤逻辑,基于原始数据进行过滤,而不是每次生成新数组,减少不必要的DOM重绘。
3. 记忆滚动位置
通过Select2的opened和closed事件,保存和恢复下拉框的滚动位置,彻底解决跳顶问题。
修改后的完整代码
<input type="hidden" id="fruitSelect" value="" style="width:300px;" /><br /> <br /> <button type="button" id="showValue">Show Value</button><br /> <br /> <div id="output"> </div>
// 给每个分组添加唯一ID,同时修正子项重复ID的问题 var FRUIT_GROUPS = [ { id: 'group-citrus', text: 'Citrus', children: [ { id: 'c1', text: 'Grapefruit' }, { id: 'c2', text: 'Orange' }, { id: 'c3', text: 'Lemon' }, { id: 'c4', text: 'Lime' } ] }, { id: 'group-other', text: 'Other', children: [ { id: 'o1', text: 'Apple' }, { id: 'o2', text: 'Mango' }, { id: 'o3', text: 'Banana' } ] }, { id: 'group-other2', text: 'Other2', children: [ { id: 'o1-2', text: 'Apple2' }, { id: 'o2-2', text: 'Mango2' }, { id: 'o3-2', text: 'Banana2' } ] }, { id: 'group-other3', text: 'Other3', children: [ { id: 'o1-3', text: 'Apple3' }, { id: 'o2-3', text: 'Mango3' }, { id: 'o3-3', text: 'Banana3' } ] }, { id: 'group-other4', text: 'Other4', children: [ { id: 'o1-4', text: 'Apple4' }, { id: 'o2-4', text: 'Mango4' }, { id: 'o3-4', text: 'Banana4' } ] } ]; $('#fruitSelect').select2({ multiple: true, placeholder: "Select fruits...", data: FRUIT_GROUPS, query: function(options) { var selectedIds = options.element.select2('val') || []; // 过滤出至少有一个未选中子项的分组 var selectableGroups = FRUIT_GROUPS.filter(function(group) { return group.children.some(child => !selectedIds.includes(child.id)); }); options.callback({ results: selectableGroups }); } }).on('select2-selecting', function(e) { var $select = $(this); // 仅处理分组项(判断是否包含children属性) if (e.choice && e.choice.children) { e.preventDefault(); var currentData = $select.select2('data') || []; // 避免重复添加已选中的子项 e.choice.children.forEach(child => { if (!currentData.some(item => item.id === child.id)) { currentData.push(child); } }); $select.select2('data', currentData); $select.select2('close'); } }).on('select2-opened', function() { // 打开下拉框时恢复之前的滚动位置 var $dropdown = $(this).data('select2').dropdown.$dropdown; var scrollTop = $(this).data('select2ScrollTop') || 0; // 用setTimeout确保DOM渲染完成后再设置滚动位置 setTimeout(() => { $dropdown.scrollTop(scrollTop); }, 0); }).on('select2-closed', function() { // 关闭下拉框时保存当前滚动位置 var $dropdown = $(this).data('select2').dropdown.$dropdown; $(this).data('select2ScrollTop', $dropdown.scrollTop()); }); $('#showValue').click(function() { $('#output').text($('#fruitSelect').val()); });
效果说明
修改后,你可以测试以下场景:
- 选择
Mango3后,滚动到下拉列表底部 - 关闭下拉框后再次打开,或者鼠标在选择区域内移动
- 滚动条会保持在之前的位置,不会自动回到顶部
另外我还顺便修正了子项ID重复的问题(比如多个o1),这也会避免Select2选中时的冲突问题。
内容的提问来源于stack exchange,提问作者devowiec
相关产品推荐
相关产品推荐

