javascript 如何创建一个科学的HTML滑块与输入类型=“范围”

lqfhib0f  于 2023-03-28  发布在  Java
关注(0)|答案(4)|浏览(182)

bounty将在4天后过期。回答此问题可获得+100的声望奖励。Basj正在寻找规范答案

我需要创建一个科学输入滑块。更准确地说:

  • n个刻度,例如:无论范围(0 - 200、1 - 11等)如何,我都希望11个刻度覆盖整个范围
  • 每个刻度下方数字值标签
  • 当滑块光标靠近刻度时,没有“粘合”吸引效果

实体模型:

注:

下面的代码生成了一个带刻度的HTML滑块,它可以工作。但是,它不符合上面的第二个和第三个条件。

input { width: 400px; }
<input type=range min=0 max=200 value=0 step=1 list=tickmarks>
<datalist id=tickmarks>
    <option>0</option>
    <option>20</option>
    <option>40</option>
    <option>60</option>
    <option>80</option>
    <option>100</option>
    <option>120</option>
    <option>140</option>
    <option>160</option>
    <option>180</option>
    <option>200</option>
 </datalist>

HTML <input type="range">是否有一个属性来启用这些“数字标记”的刻度?
满足所有三个标准的实现是可能的吗?

3zwjbxry

3zwjbxry1#

你可以通过在你的区域下面放置一个div来实现这一点,你可以将它分成几个部分。下面的代码只是一个概念验证,它已经工作了,但是当你真正将它集成到你的项目中时,你需要使它可重用和可调整。

window.addEventListener("load", function() {
    let div = document.getElementById("ranger-div");
    let html = [];
    for (let i = 0; i <= 200; i += 20) {
        html.push(`<div><div style="with:100%;">|</div>${i}</div>`);
    }
    div.innerHTML = html.join("");
});
.ranger { width: 400px; }
#ranger-div {display: inline-flex; width: 440px;}
#ranger-div > div {
    color: red;
    width: 100%;
}
<input class="ranger" type=range min=0 max=200 value=0 step=1>
<div id="ranger-div"></div>
c9qzyr3d

c9qzyr3d2#

正如Lajos Arpad的回答所指出的,最好避免使用datalist,因为您的需求超出了它的能力。您需要手动创建具有数值的刻度。
下面的代码使用与您的问题中的原始代码类似的HTML布局,并具有适合的样式。请注意,宽度是在父元素上设置的,如果父元素太小,则刻度数字将换到新的一行。要解决此问题,您可能需要减小刻度数字字体大小。
为了让刻度在所有主流浏览器中正确排列,最好设置滑块的样式,使其在不同浏览器中看起来相同。包括针对Chrome和Firefox特定伪元素的范围滑块样式。结果在所有基于Chromium和Mozilla的浏览器中看起来应该相同。
请注意,如问题中所示,填充范围滑块的真正跨浏览器方法只能使用JavaScript实现。

const input = document.querySelector("#input input")
input.addEventListener( 'input', function() {
    const value = (this.value-this.min)/(this.max-this.min)*100;
    this.style.background = 'linear-gradient(to right, #35b0f2 0%, #35b0f2 ' + value + '%, #ccc ' + value + '%, #ccc 100%)';
} );
#input {
    width: 400px;
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
}
#input span {
    position: relative;
    margin: 15px -5px 0 -5px;
    flex-basis: 0;
    flex-grow: 1;
    text-align: center;
    font-size: 0.85em;
    user-select: none;
}
#input span::after {
    content: "";
    position: absolute;
    width: 1px;
    height: 8px;
    left: 0;
    right: 0;
    margin: auto;
    top: -12px;
    background-color: #ccc;
}
#input input {
    width: 100%;
    margin: 5px 10px;
    position: relative;
    background-color: #ccc;
    border-radius: 99px;
    z-index: 10;
    height: 7px;
    -webkit-appearance: none;
}
#input input::-moz-range-thumb {
    border: none;
    height: 16px;
    width: 16px;
    border-radius: 99px;
    background: #35b0f2;
    cursor: pointer;
}
#input input::-webkit-slider-thumb {
  box-shadow: none
  border: none;
  height: 16px;
  width: 16px;
  border-radius: 99px;
  background-color: #35b0f2;
  cursor: pointer;
  -webkit-appearance: none;
}
<div id="input">
    <input type=range min=0 max=200 value=0 step=1>
    <span>0</span>
    <span>20</span>
    <span>40</span>
    <span>60</span>
    <span>80</span>
    <span>100</span>
    <span>120</span>
    <span>140</span>
    <span>160</span>
    <span>180</span>
    <span>200</span>
</div>
ttvkxqim

ttvkxqim3#

一个可能的解决方案是动态创建一个规模。有很多细节需要考虑,例如步骤。您可以从这里开始:

document.querySelectorAll('.range').forEach(el => {
  const input = el.querySelector('input');
  if(!input){
    return;
  }
  const min = Number(input.min) || 0;
  const step = Number(input.step) || 1;
  const max = Number(input.max);
  let ticks = Number(input.dataset.ticks);
  if(!max || !ticks || step !== 1 ){
    return;
  }
  if(ticks > max - min ){
    ticks = max - min + 1;
  }
  ticks--;
  let increment = (max - min) / ticks;
  let value = min;
  let html = '';
  for(let i = 0; i <= ticks; i++ ){
    html += `<div class="range-tick">${value.toFixed()}</div>`
    value += increment;
  }
  el.insertAdjacentHTML('beforeend', `<div class="range-ticks">${html}</div>`);
})
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
.range {
  width: 100%;
  max-width: 600px;
  --thumb-size: 12px;
  --track-size: 2px;
  --track-color: #ccc;
  margin-top: calc(var(--thumb-size) / 2);
}
.range input {
  display: block;
  width: 100%;
  -webkit-appearance: none;
  background: none;
}
.range input::-webkit-slider-thumb {
  width: var(--thumb-size);
  height: var(--thumb-size);
  -webkit-appearance: none;
  border-radius: 50%;
  background-color: blue;
  margin-top: calc(var(--thumb-size) / -2 + var(--track-size) / 2);
}
.range input::-webkit-slider-runnable-track {
  height: 2px;
  background-color: var(--track-color);
  -webkit-appearance: none;
}
.range-ticks {
  display: flex;
  justify-content: space-between;
  margin-top: 12px;
}
.range-tick {
  font-size: 14px;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: var(--thumb-size);
}
.range-tick:before {
  content: '';
  width: 2px;
  height: 8px;
  background-color: var(--track-color);
}
<div class="range">
  <input type="range" min="0" max="200" value="0" step="1" data-ticks="11" oninput="output.value = this.value">
</div>
<output id="output">0</output>
1qczuiv0

1qczuiv04#

您可以将datalistoption列表一起使用,但它需要您添加label文本,因为value不会用作刻度标记值。

通用设置(MDN:input type=“range”,添加刻度线)

<input type="range" list="tickmark-list">
    <datalist id="tickmark-list">
        <option value=1 label="1"/>
        <option value=N label="N"/>
    </datalist>

对于标签,您有两个选项:

选项1:使用label与文本

<option value="1" label="tickmark-value-1"/>
<option value="N" label="tickmark-value-N"/>

选项2:使用 * 选项文本 *

<option value="1">tickmark-value-1</option>
<option value="N">tickmark-value-N</option>

当同时使用label文本和 *option文本 * 时,后者将被忽略。对于代码片段,我使用 option 1

标记

  • input type="range"datalist必须具有相等的width。在代码片段中,我使用 *CSS自定义属性 * --slider-width: 100%来设置两个元素的宽度。
  • datalist作为带有justify-content: space-between的Flexbox行容器非常适合分布刻度线标签,它将外部标签推到它们各自的父级左边距和右边距,同时将其余标签均匀分布在剩余空间上。
  • 为了均匀分布tickmark文本,最好给予它们一个width,等于最大的文本。在代码片段中,我取了标签200的宽度。
  • 要将标签文本置于刻度线下方的中心位置,请使用text-align: center
  • 刻度线默认由范围滑块本身插入,不需要任何额外的代码。

根据使用的标签文本,可能需要对margin/paddingoption:first-child和/或option:last-child进行一些修改。

Bonus在代码片段中,我添加了类.vertical,以展示如何使用flex-direction: columnwriting-mode: vertical-lr轻松获得90度旋转的刻度标记标签。
更新

正如最初评论的那样,上述方法并不能解决Firefox浏览器中datalist刻度线引起的“胶水”效应。此外,Firefox在第二次单击刻度线时会打开option选择列表,将datalist视为select。虽然Firefox特定的行为可能被用户所知/首选,但它可能不适合网站。
然而,经过调查,上面提到的行为似乎很难,如果不是不可能的话,禁用。人们可以选择使用Javascript创建一个完全自定义的范围滑块,但这可能被证明是矫枉过正:为单个浏览器创建一个解决方案,禁用将来可能更改或不更改的行为。
尝试方法失败:

  • datalist,option { pointer-events: none }
  • <option disabled />工作正常,但删除了刻度线
  • <datalist type="range" autocomplete="off">
  • <input type="range" autocomplete="off">
  • <option autocomplete="off">
  • <option label=".."/>不带value=".."
  • <option value="..">..</option>文本而不是label
  • <input type="range">嵌入到<form>中,所有元素为autocomplete="off"
  • 在浏览器中禁用自动完成历史记录隐私设置

一个三个三个一个

相关问题