簡體   English   中英

平衡輸入滑塊會產生不一致的結果

[英]Balancing input sliders gives inconsistent results

我有幾個范圍輸入滑塊,它們應該平衡自己,總和為 100¹,無論滑塊的數量如何。 對於這個例子,我將展示最少兩個,但問題出現在任意數量的情況下。 我正在使用 Svelte,但這可能也適用於香草 JS。

¹:全部要求:

  • 它們的總和必須為 100。
  • 當一個 slider 改變時,其他的必須保持它們的比例,因為它們是平衡的。 即:僅執行 100 - newValue 並將結果拆分到剩余的滑塊中是不夠的。
  • 該行為必須適用於任意數量的滑塊。
<script lang="ts">
  const sliders = [
    { id: 1, percentage: 100 },
    { id: 2, percentage: 100 },
    // { id: 3, percentage: 100 },
    // { id: 4, percentage: 100 },
    // { id: 5, percentage: 100 },
  ];

  const handleInput = (event: InputEvent) => {
    const element = (event.target as HTMLInputElement);
    const newValue = Number(element.value);
    const sliderId = Number(element.dataset["slider"]);
    sliders.find((slider) => slider.id == poolId).percentage = newValue;

    balanceSliders();
  }

  const balanceSliders = () => {
    const total = sliders
      .map((slider) => slider.percentage)
      .reduce((prev, next) => prev + next, 0);
    const normaliser = 100 / total;

    sliders.forEach((slider) => {
      slider.percentage = slider.percentage * normaliser;
    });
  };

  balanceSliders();
</script>

<form>
  {#each sliders as {id, percentage}}
    <div>
      <input type="range" min="0" max="100" step="0.01" data-slider={id} bind:value={percentage} on:input={handleInput} />
      <span>{percentage.toFixed(2)}</span>
    </div>
  {/each}
</form>

這可能會做得更好,我願意接受建議。 但主要問題是,當我將 slider 帶到接近 100 時,總和會超過 100。此外,如果我慢慢移動 slider,我會得到一些東西:
滑塊顯示 98.18 和 5.88

如果我快速移動它,錯誤會更大:
滑塊顯示 100 和 25.85

對於我的目的而言,這顯然是不可接受的。

速度影響錯誤的事實向我表明,問題出在 Svelte/事件處理部分的某個地方,而不是公式本身。

只有在用戶停止更改滑塊后,我才能讓滑塊自行平衡,但出於演示目的,我希望它們在輸入時也能保持平衡。

有任何想法嗎?

這是解決方案,您可以使其動態化並根據需要進行優化。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
  </head>

  <body>
    <div id="app"></div>
    <div class="slidecontainer">
      <p>Slider 1</p>
      <span class="slider1Val">50%</span>
      <input
        class="slider1"
        type="range"
        min="1"
        max="100"
        value="50"
        class="slider"
        id="myRange"
      />
      <p>Slider 2</p>
      <span class="slider2Val">50%</span>
      <input
        class="slider2"
        type="range"
        min="1"
        max="100"
        value="50"
        class="slider"
        id="myRange"
      />
    </div>
    <script src="src/index.js"></script>
  </body>
</html>

CSS

body {
  font-family: sans-serif;
}

.slidecontainer {
  display: flex;
  flex-direction: column;
}

Javascript

import "./styles.css";

const slider1 = document.querySelector(".slider1");
const slider2 = document.querySelector(".slider2");

const balanceSliders = (e) => {
  if (e.target.classList.contains("slider1")) {
    const slider1Val = document.querySelector(".slider1").value;
    slider2.value = 100 - slider1Val;
    document.querySelector(".slider1Val").innerHTML = slider1Val + "%";
    document.querySelector(".slider2Val").innerHTML = slider2.value + "%";
  } else {
    const slider2Val = document.querySelector(".slider2").value;
    slider1.value = 100 - slider2Val;
    document.querySelector(".slider2Val").innerHTML = slider2Val + "%";
    document.querySelector(".slider1Val").innerHTML = slider1.value + "%";
  }
};
slider1.addEventListener("input", (e) => balanceSliders(e));
slider2.addEventListener("input", (e) => balanceSliders(e));

演示代碼沙箱

我將刪除值綁定並使用稍微不同的邏輯處理handleInput中的所有更改。

假設起始值為 50-30-20 並且第一個 slider 移動到 60,則差值為 10。考慮到它們當前的比例,必須從其他滑塊中減去此差。 我會根據他們的百分比總和來計算這個比例。
開始50 - 30 - 20
30 + 20 = 50
30: 30/50 = 0.630 - 0.6 * 10
20: 20/50 = 0.420 - 0.4 * 10
結束60 - 24 - 16

這適用於所有情況,除了當一個 slider 為 100 並且其他的總和為 0 時。在這種情況下,它們的份額是 1/滑塊的數量,差異應該被分割到。
REPL

<script>
    let sliders = [
        { id: 1, percentage: 100 },
        { id: 2, percentage: 100 },
        { id: 3, percentage: 100 },
//      { id: 4, percentage: 100 },
//      { id: 5, percentage: 100 },
    ];

    sliders = sliders.map(slider => {
        slider.percentage = 100/sliders.length
        return slider
    })

    const handleInput = (event) => {
        const changedSlider = event.target;
        const sliderId = Number(changedSlider.dataset["slider"]);

        const slider = sliders.find((slider) => slider.id == sliderId)
        const currentValue = slider.percentage

        const newValue = Number(changedSlider.value);
        const difference = newValue - currentValue

        slider.percentage = newValue;

        const otherSliders = sliders.filter(slider => slider.id !== sliderId)
        const otherSlidersPercentageSum = otherSliders.reduce((sum, slider) => sum += slider.percentage, 0)

        otherSliders.forEach(slider => {
            const share = otherSlidersPercentageSum === 0 ? 1 / otherSliders.length : slider.percentage / otherSlidersPercentageSum
            slider.percentage = slider.percentage - difference * share
            return slider
        })

        sliders = sliders       
    }

</script>

<form>
    {#each sliders as {id, percentage}}
    <div>
        <input type="range" min="0" max="100" step="0.01" data-slider={id} value={percentage} on:input={handleInput} />
        <span>{Math.max(percentage.toFixed(2), 0)}</span>
<!--        Math.max to prevent -0 -->
    </div>
    {/each}
</form>

將此與您的方式進行比較

開始50 - 30 - 20
改變60 - 30 - 20
新總和110
標准化100 / 110 (~0.9)
結束54,54 - 27,27 - 18,18

雖然這總和為 100,但故意更改的值被再次直接修改。 從示例中刪除bind:value並在balanceSliders末尾添加sliders=sliders以便 Svelte 知道更改后,在將 slider 拖動到 100 REPL時可以注意到這一點

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM