如何在JavaScript中实现一个仅用键盘就能实现无限滚动的功能?
真的很难用语言准确表达我想要的东西,所以让我让图片来多讲点儿吧。
这是我现在拥有的:

当我滚动到底部时,我希望出现的效果如下:

我不想在侧边出现滚动条。理想情况下,这个盒子的边框在视口中保持固定尺寸。当我添加更多条目时,尺寸会改变——这并非我所期望,因此如果能解决这个问题,也请帮忙。
我已经提供了HTML和 JS,方便你自己来尝试:
JS(其中大部分直接来自某位在前一个问题中的解法。我相信你能找到原始来源)
const ul = document.getElementById('list');
var liSelected;
var index = 0;
document.addEventListener('keydown', function(event) {
var len = ul.getElementsByTagName('li').length - 1;
// DOWN ARROW
if (event.which === 40) {
index++;
if (liSelected) {
removeClass(liSelected, 'selected');
next = ul.getElementsByTagName('li')[index];
if (typeof next !== undefined && index <= len) {
liSelected = next;
} else {
index = 0;
liSelected = ul.getElementsByTagName('li')[0];
}
addClass(liSelected, 'selected');
console.log(index);
} else {
index = 0;
liSelected = ul.getElementsByTagName('li')[0];
addClass(liSelected, 'selected');
}
}
// UP ARROW
else if (event.which === 38) {
if (liSelected) {
removeClass(liSelected, 'selected');
index--;
next = ul.getElementsByTagName('li')[index];
if (typeof next !== undefined && index >= 0) {
liSelected = next;
} else {
index = len;
liSelected = ul.getElementsByTagName('li')[len];
}
addClass(liSelected, 'selected');
} else {
index = 0;
liSelected = ul.getElementsByTagName('li')[len];
addClass(liSelected, 'selected');
}
}
}, false);
function removeClass(el, className) {
if (el.classList) {
el.classList.remove(className);
} else {
el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
}
};
function addClass(el, className) {
if (el.classList) {
el.classList.add(className);
} else {
el.className += ' ' + className;
}
};
#main-box {
text-align: left;
border: solid 2px;
margin-left: 30px;
margin-right: 30px;
/*padding-bottom: 20%;*/
}
ul {
list-style-type: none;
margin: 10px;
;
padding: 2px;
}
li {
padding: 0;
margin: 0;
width: 99%;
}
li.selected {
background-color: #ff0;
color: #000;
}
<div id="main-box">
<ul id="list">
<!--<li class="selected"><span>Option 1</span></li>-->
<li><span>Option 2</span></li>
<li><span>Option 3</span></li>
<li><span>Option 4</span></li>
<li><span>Option 5</span></li>
<li><span>Option 6</span></li>
<li><span>Option 7</span></li>
<li><span>Option 8</span></li>
<li><span>Option 9</span></li>
<li class="selected"><span>Option 10</span></li>
</ul>
</div>
解决方案
这是我对这项任务的理解:
- 将UL的 overflow设置为hidden
- 添加:
tabindex="0"属性到父元素,以捕获焦点,并随之处理键盘事件 - 让LI可以通过点击进行选择
- 上/下箭头循环选择上一个/下一个
// Utility functions
const el = (sel, par = document) => par.querySelector(sel);
const els = (sel, par = document) => [...par.querySelectorAll(sel)];
const mod = (n, m) => (n % m + m) % m; //stackoverflow.com/a/71167019/383904
// Task select loop on arrows up/down
const elBox = el("#main-box");
const elUL = el("#list", elBox)
const elsItems = els("li", elUL);
let i = 0; // Current selected index
const selectItem = (i) => {
i = mod(i, elsItems.length);
el("li.selected", elUL)?.classList.remove("selected");
elsItems[i].classList.add("selected");
elsItems[i].scrollIntoView({
behavior: "smooth",
block: "center",
inline: "center"
});
};
addEventListener("keydown", (ev) => {
if (document.activeElement !== elBox) return;
if (ev.key === "ArrowUp") {
selectItem(--i);
ev.preventDefault(); // prevent entire page scroll
} else if (ev.key === "ArrowDown") {
selectItem(++i);
ev.preventDefault(); // prevent entire page scroll
}
});
addEventListener("click", ({target, key}) => {
const elItem = target.closest("#main-box li");
if (!elItem) return;
i = elsItems.indexOf(elItem);
selectItem(i);
});
// Initialize!
selectItem(0); // 0 = first one
* {
margin: 0;
box-sizing: border-box;
}
#main-box {
border: solid 2px #000;
padding: 1em;
}
#list {
list-style: none;
height: 5lh;
overflow: hidden;
padding: 0;
outline: none;
li {
cursor: pointer;
&.selected {
background-color: #ff0;
}
}
}
<div id="main-box" tabindex="0">
<ul id="list">
<li><span>Option 1</span></li>
<li><span>Option 2</span></li>
<li><span>Option 3</span></li>
<li><span>Option 4</span></li>
<li><span>Option 5</span></li>
<li><span>Option 6</span></li>
<li><span>Option 7</span></li>
<li><span>Option 8</span></li>
<li><span>Option 9</span></li>
<li><span>Option 10</span></li>
</ul>
</div>
下面是另一个示例:不使用变量 i 来跟踪当前选中列表项的索引,而改用 prev/nextElementSibiling:
// Utility functions
const el = (sel, par = document) => par.querySelector(sel);
const els = (sel, par = document) => [...par.querySelectorAll(sel)];
// Task: Select loop on arrows up/down
const elUL = el("#list")
const elsItems = els("li", elUL);
const selectItem = (elItem) => {
el(".selected", elUL)?.classList.remove("selected");
elItem.classList.add("selected");
elItem.scrollIntoView({ behavior: "smooth", block: "center"});
};
const keyActions = {
ArrowUp: () => selectItem(el(".selected", elUL).previousElementSibling ?? elsItems[elsItems.length - 1]),
ArrowDown: () => selectItem(el(".selected", elUL).nextElementSibling ?? elsItems[0]),
};
addEventListener("keydown", (ev) => {
if (document.activeElement !== elUL) return;
ev.preventDefault(); // prevent entire page scroll
keyActions[ev.key]?.();
});
addEventListener("click", ({target, key}) => {
const elItem = target.closest("#main-box li");
elItem && selectItem(elItem);
});
// Initialize!
selectItem(elsItems[0]);
* {
margin: 0;
box-sizing: border-box;
}
#main-box {
border: solid 2px #000;
padding: 1em;
}
#list {
list-style: none;
height: 5lh;
overflow: hidden;
padding: 0;
outline: none;
li {
cursor: pointer;
transition: background-color 0.2s;
&.selected {
background-color: #ff0;
}
}
}
<div id="main-box">
<ul id="list" tabindex="0">
<li><span>Option 1 (click to focus here and use arrows up/dn)</span></li>
<li><span>Option 2</span></li>
<li><span>Option 3</span></li>
<li><span>Option 4</span></li>
<li><span>Option 5</span></li>
<li><span>Option 6</span></li>
<li><span>Option 7</span></li>
<li><span>Option 8</span></li>
<li><span>Option 9</span></li>
<li><span>Option 10</span></li>
</ul>
</div>
注意:
鉴于这些奇怪的任务要求,上述示例在触控设备上可能效果不佳。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。