如何在JavaScript的输入框值改变时的回调中显示等待光标?
在输入元素的值改变回调结束时,我正在调用一个耗时的计算,并且想显示一个等待光标。我尝试过的方法是将document.body.className设置为 'waiting';并在CSS中有
body.waiting * {cursor:wait!important;}
然后 document.body.focus();,并最终把耗时计算(ldc)函数放在一个 setTimeout(ldc,1); 里面,这样在计算开始前屏幕就会被重新绘制。 (下面的示例中,ldc被称为 'plot',并使用一个100的超时而不是1;似乎并不相关。) ldc以 document.body.className = ''; 结束
但在该计算过程中并不会显示等待光标。如果我不执行 body.focus(),插入点就只会出现在输入元素上。
当我在另一个元素上的按钮点击回调中做几乎相同的事情时,我确实看到了预期的等待光标。
是否有办法在每次输入变化事件发生时都显示等待光标?
下面给出一个相对最简的代码来演示问题。引发问题的行为是通过单击一个输入元素、用键盘编辑其文本,并以回车或Tab结束来演示的。最相关的部分是updateLimits函数,它被用作#go按钮(点击时)的回调,以及输入元素(change时)的回调。触发输入元素改变有多种方式;当change回调被回车或Tab触发时会出现问题,但即使点击在改变的输入元素之外(即使点击的是另一个输入元素),也不会触发。
var body; // #body
var pcv; // canvas
var pcx; // context
const side = 513;
function init() { // initialize the world
body = document.body;
initplot();
}
function initplot() {
pcv = document.getElementById('plotarea');
pcv.width = side;
pcv.height = side;
pcx = pcv.getContext('2d');
pcx.lineWidth = 1;
pcx.lineCap = 'round';
pcx.lineJoin = 'round';
pcx.strokeStyle = 'black';
document.getElementById('bot').value = -1;
document.getElementById('top').value = 1;
document.getElementById('lft').value = -1;
document.getElementById('rgt').value = 1;
b = document.getElementsByTagName('input');
for (i = 0; i < b.length; i++) {
b[i].step = .125;
b[i].addEventListener('change', updateLimits);
}
document.getElementById('go').addEventListener('click', updateLimits);
}
function updateLimits(event) {
event.preventDefault();
event.stopPropagation();
body.focus();
body.className = 'waiting';
setTimeout(plot, 100);
}
function plot() { // just take some time
var n;
for (n = 1; n < 999999; n++) {
factor(n, Math.sqrt(n) + 1);
}
body.className = '';
}
function factor(n, b) { // factor n into primes up to (but not including) b
if (!Number.isInteger(n) || n < 1) {
throw new TypeError('Only positive integers can be factored');
}
var f = {};
if (n == 1) return f;
for (var p = 2; p < b; p = (p + 1) | 1) {
while (!(n % p)) {
f[p] = (f[p] || 0) + 1;
n /= p;
}
if (n == 1) return f;
}
f[n] = 1;
return f;
}
body {
touch-action: none;
}
body.waiting * {
cursor: wait !important;
}
#canvas {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
.varlabel {
font-family: serif;
font-style: italic;
text-align: center;
margin: 0;
}
#x {
grid-row-start: 4;
grid-column-start: 3;
}
#y {
grid-row-start: 2;
grid-column-start: 1;
align-self: center;
}
#plot {
float: left;
display: grid;
grid-template-columns: 20px 100px 313px 100px;
grid-template-rows: 100px 313px 100px 20px;
margin-top: 10px;
margin-left: 10px;
}
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type=number] {
width: 100px;
height: 20px;
margin: 0;
box-sizing: border-box;
-moz-appearance: textfield;
appearance: textfield;
}
#top {
grid-column-start: 1;
grid-row-start: 1;
transform: rotate(-90deg);
transform-origin: 50px 50px;
text-align: right;
}
#bot {
grid-column-start: 1;
grid-row-start: 3;
transform: rotate(-90deg);
transform-origin: 50px 50px;
text-align: left;
}
#lft {
grid-column-start: 2;
grid-row-start: 4;
text-align: left;
}
#rgt {
grid-column-start: 4;
grid-row-start: 4;
text-align: right;
}
#plotarea {
grid-column-start: 2;
grid-column-end: span 3;
grid-row-start: 1;
grid-row-end: span 3;
width: 513px;
height: 513px;
}
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body onload='init(this)'>
<button id='go'>GO</button>
<div id='plot'>
<input id='top' class='lim' type='number'></input>
<p id='y' class='varlabel'>y</p>
<input id='bot' class='lim' type='number'></input>
<canvas id='plotarea'></canvas>
<input id='lft' class='lim' type='number'></input>
<p id='x' class='varlabel'>x</p>
<input id='rgt' class='lim' type='number'></input>
</div>
</body>
</html>
解决方案
问题在于,当你在输入字段中按下Enter或 Tab时,浏览器在你的 setTimeout 运行之前实际上并不会更新光标,即使有延迟也一样。请改用 requestAnimationFrame:
function updateLimits(event) {
event.preventDefault();
event.stopPropagation();
body.className = 'waiting';
requestAnimationFrame(() => {
plot();
});
}
这会告诉浏览器在下一次屏幕更新之前的时刻等待,这样它就有时间真正显示等待光标。如果仍然不起作用,尝试先让输入框失焦:
function updateLimits(event) {
event.preventDefault();
event.stopPropagation();
if (document.activeElement) {
document.activeElement.blur();
}
body.className = 'waiting';
requestAnimationFrame(() => {
plot();
});
}
之所以在按钮点击时有效而在Enter/Tab时无效,是因为点击按钮会自动从输入框移除焦点,而Enter/Tab会保持焦点。