有没有办法使此自定义滑块输入更光滑?
我正在为问题开发一个自定义输入,其中只有两个可能的答案,例如:“是” 或“ no” 。 这是一个工作示例,在这里您可以读取源代码(也可以在触摸屏中使用)。
这个想法是使用与“打开”滑块输入的类似原则,但我试图使拇指放大一点阻力。我的实现包括计算拖动运动的速度并使拇指对其进行响应,因此,尽管指针(手指或鼠标)可能接近答案,如果速度降低,则拇指恢复了原点。因此,实际上不是要拖动拇指,而是要迅速朝着所需答案的方向移动指针。
问题是,目前有点奇怪而摇摇欲坠。那么,有没有办法使拇指移动更光滑?我的实施没有任何部分是永久的,因此请随时进行实验和修改。另外,我无论如何都不是JS的专家,所以请不要太容易了吗?提前致谢。欢呼
代码也在这里。
html
<!DOCTYPE html>
<html>
<head>
<title>Yes or No?</title>
</head>
<body>
<canvas id="display"></canvas>
</body>
</html>
js
const displayCanvas = document.querySelector("#display");
const displayContext = displayCanvas.getContext("2d");
const maxX = displayCanvas.width = 400;
const maxY = displayCanvas.height = 100;
const bgColor = "#000";
const fgColor = "#fff";
const thumbRestX = maxX / 2;
let thumbX = thumbRestX;
let thumbY = maxY / 2;
let yesAnswerX = (maxX / 6) * 5;
let yesAnswerY = maxY / 2;
let noAnswerX = maxX / 6;
let noAnswerY = maxY / 2;
let pointerPrevX = thumbX;
let pointerX = thumbX;
let isDragging = false;
let isAnswered = false;
const setup = () => {
const startDragging = () => {
isDragging = true;
};
const stopDragging = () => {
isDragging = false;
};
const monitorPointer = (newX) => {
pointerPrevX = pointerX;
pointerX = newX;
};
displayCanvas
.addEventListener("mousedown", startDragging);
displayCanvas
.addEventListener("mouseup", stopDragging);
displayCanvas
.addEventListener("mousemove", (e) => {
monitorPointer(
e.clientX - e.target.getBoundingClientRect().left);
});
displayCanvas
.addEventListener("touchstart", (e) => {
e.preventDefault();
startDragging();
});
displayCanvas
.addEventListener("touchend", stopDragging);
displayCanvas
.addEventListener("touchmove", (e) => {
const touch = e.touches[0];
monitorPointer(
touch.clientX - e.target.getBoundingClientRect().left);
});
};
const evaluate = () => {
if (!isAnswered && isDragging) {
thumbX = thumbRestX + (pointerX - pointerPrevX - 1) * 2;
if (thumbX >= yesAnswerX) {
isAnswered = true;
thumbX = yesAnswerX;
}
if (thumbX <= noAnswerX) {
isAnswered = true;
thumbX = noAnswerX;
}
}
};
const render = () => {
const ctx = displayContext;
ctx.clearRect(0, 0, maxX, maxY);
// Background
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, maxX, maxY);
// Thumb
ctx.fillStyle = fgColor;
ctx.beginPath();
ctx.arc(thumbX, thumbY, 20, 0, Math.PI * 2, true);
ctx.fill();
// Yes answer
ctx.fillStyle = fgColor;
ctx.font = "50px monospace";
ctx.textAlign = "center";
ctx.fillText("YES", yesAnswerX, yesAnswerY);
// No answer
ctx.fillStyle = fgColor;
ctx.font = "50px monospace";
ctx.textAlign = "center";
ctx.fillText("NO", noAnswerX, noAnswerY);
};
function run () {
const evaluateTimeoutRate = 20;
let evaluateTimeoutID;
setup();
const evaluateLoop = () => {
evaluate();
evaluateTimeoutID =
setTimeout(evaluateLoop, evaluateTimeoutRate);
};
evaluateTimeoutID =
setTimeout(evaluateLoop, evaluateTimeoutRate);
const renderLoop = () => {
render();
requestAnimationFrame(renderLoop);
};
requestAnimationFrame(renderLoop);
}
run();
I'm developing a custom input for questions with only two possible answers, eg.: "Yes" or "No". Here's a working example and here you can read the source code (it also work in touch screens).
The idea is to use a similar principle to the "Slide to open" slider input, but I'm trying to make the thumb put a bit of resistance. My implementation consists of calculating the speed of the dragging movement and make the thumb respond to it, so, though the pointer (finger or mouse) might be close to the answer, if the speed decreases, the thumb gets back to origin. So, it's not so much about actually dragging the thumb but about moving the pointer quickly in the direction of the desired answer.
The problem is, it's currently a bit wonky and shaky. So, is there a way to make the thumb move smoother? No part of my implementation is permanent, so feel free to experiment and modify anything. Also, I'm no expert in JS by any means, so don't be too hasrh, please? Thanks in advance. Cheers
The code is here too.
HTML
<!DOCTYPE html>
<html>
<head>
<title>Yes or No?</title>
</head>
<body>
<canvas id="display"></canvas>
</body>
</html>
JS
const displayCanvas = document.querySelector("#display");
const displayContext = displayCanvas.getContext("2d");
const maxX = displayCanvas.width = 400;
const maxY = displayCanvas.height = 100;
const bgColor = "#000";
const fgColor = "#fff";
const thumbRestX = maxX / 2;
let thumbX = thumbRestX;
let thumbY = maxY / 2;
let yesAnswerX = (maxX / 6) * 5;
let yesAnswerY = maxY / 2;
let noAnswerX = maxX / 6;
let noAnswerY = maxY / 2;
let pointerPrevX = thumbX;
let pointerX = thumbX;
let isDragging = false;
let isAnswered = false;
const setup = () => {
const startDragging = () => {
isDragging = true;
};
const stopDragging = () => {
isDragging = false;
};
const monitorPointer = (newX) => {
pointerPrevX = pointerX;
pointerX = newX;
};
displayCanvas
.addEventListener("mousedown", startDragging);
displayCanvas
.addEventListener("mouseup", stopDragging);
displayCanvas
.addEventListener("mousemove", (e) => {
monitorPointer(
e.clientX - e.target.getBoundingClientRect().left);
});
displayCanvas
.addEventListener("touchstart", (e) => {
e.preventDefault();
startDragging();
});
displayCanvas
.addEventListener("touchend", stopDragging);
displayCanvas
.addEventListener("touchmove", (e) => {
const touch = e.touches[0];
monitorPointer(
touch.clientX - e.target.getBoundingClientRect().left);
});
};
const evaluate = () => {
if (!isAnswered && isDragging) {
thumbX = thumbRestX + (pointerX - pointerPrevX - 1) * 2;
if (thumbX >= yesAnswerX) {
isAnswered = true;
thumbX = yesAnswerX;
}
if (thumbX <= noAnswerX) {
isAnswered = true;
thumbX = noAnswerX;
}
}
};
const render = () => {
const ctx = displayContext;
ctx.clearRect(0, 0, maxX, maxY);
// Background
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, maxX, maxY);
// Thumb
ctx.fillStyle = fgColor;
ctx.beginPath();
ctx.arc(thumbX, thumbY, 20, 0, Math.PI * 2, true);
ctx.fill();
// Yes answer
ctx.fillStyle = fgColor;
ctx.font = "50px monospace";
ctx.textAlign = "center";
ctx.fillText("YES", yesAnswerX, yesAnswerY);
// No answer
ctx.fillStyle = fgColor;
ctx.font = "50px monospace";
ctx.textAlign = "center";
ctx.fillText("NO", noAnswerX, noAnswerY);
};
function run () {
const evaluateTimeoutRate = 20;
let evaluateTimeoutID;
setup();
const evaluateLoop = () => {
evaluate();
evaluateTimeoutID =
setTimeout(evaluateLoop, evaluateTimeoutRate);
};
evaluateTimeoutID =
setTimeout(evaluateLoop, evaluateTimeoutRate);
const renderLoop = () => {
render();
requestAnimationFrame(renderLoop);
};
requestAnimationFrame(renderLoop);
}
run();
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
根据我的经验,如果您对其进行建模以表现得像现实世界中的事物,则UI通常会感觉平稳。
对于此控件,一种有趣的方法是实现a mass-spring-damper模型。
我想像弹簧
将要连接到控件中心的圆形手柄。
要移动中心磁盘,我附着第二个弹簧,然后开始向左或向右拉动。一旦磁盘设法超过“否”或“是”标签,我(1)将中心弹簧分离,(2)将中心弹簧连接到新标签,(3)切断我正在拉的字符串(全部在一秒钟内)。
为了确保当我放开春天时,控制不会永远振荡,我会引入阻尼器。
物理学
控件的中心磁盘具有位置(
p
),速度(v
)和加速度(a
)。它的位置产生2个力:
其速度产生1力:
f = m * a
的速度,我们现在可以得出磁盘的加速度:
呈现
每个渲染循环,我们计算以下内容:
p
和v 要计算弹簧和阻尼力
a
v += a
)更新速度p += v <
)更新位置/code>)将它们全部放在一起
注意,我有点忽略了您提供的代码。我希望这个答案很有用,不是因为您可以复制粘贴,而是因为您可以实现其一些想法
注意:
In my experience, UI often feels smooth if you model it to behave a bit like things in the real world.
For this control, a fun way to do that would be to implement a mass-spring-damper model.
The "model"
I imagine the circular handle to be connected to the center of the control by a spring.
To move the center disk, I attach a second spring and start pulling it left or right. Once the disk manages to get above the "No" or "Yes" label, I (1) detach the center spring, (2) connect the center spring to the new label, and (3) cut off the string I was pulling (all in a split second).
To ensure the control doesn't keep oscillating forever when I let go of our spring, I introduce a damper.
The physics
The control's center disk has a position (
p
), speed (v
) and acceleration (a
).Its position produces 2 forces:
Its velocity produces 1 force:
Using
F = m * a
, we can now derive the disk's acceleration:Rendering
Each render loop, we calculate the following:
p
andv
to calculate spring and damper forcesa
v += a
)p += v
)Putting it all together
Note, I kind of ignored the code you provided. I hope this answer is useful not because you can copy paste it, but because you can implement some of its ideas
Notes: