You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

VueJS实现两位小数精度与平滑输入:解决双向监听抖动问题

Vue 双向监听输入抖动、光标跳转问题解决(不用Timeout,保留两位小数)

问题背景

用Vue的watch监听percentage和second实现联动时,输入百分比会触发第二个数值更新,反过来第二个数值更新又会触发百分比重新计算,形成循环导致输入抖动,光标还会跳到小数点末尾,要保留两位小数,又不想用Timeout来规避。

解决思路

  • 打断循环更新:不要每次值变化就触发更新,判断计算出的新值和当前值是否有明显差异(比如差值超过0.001),避免浮点数精度误差触发无意义的循环。
  • 精准控制小数位数:用toFixed(2)格式化数值,但要转成数值类型,避免字符串绑定引发的异常。
  • 保留光标位置:输入时记录光标位置,DOM更新后恢复光标,避免光标乱跳。

修改后的代码

<script setup>
import { reactive, watch, nextTick } from 'vue'

const data = reactive({
  first: 50000,
  second: 20000,
  percentage: ''
})

// 监听百分比,更新第二个数值
watch(() => data.percentage, (newValue) => {
  const numPercent = parseFloat(newValue)
  if (isNaN(numPercent)) return
  const calcSecond = (data.first * numPercent) / 100
  // 只有差异大于0.001才更新,避免循环触发
  if (Math.abs(calcSecond - data.second) > 0.001) {
    data.second = Number(calcSecond.toFixed(2))
  }
})

// 监听第二个数值,更新百分比
watch(() => data.second, (newValue) => {
  const numSecond = parseFloat(newValue)
  if (isNaN(numSecond)) return
  const calcPercent = (numSecond / data.first) * 100
  // 同样判断差异,避免循环
  if (Math.abs(calcPercent - parseFloat(data.percentage)) > 0.001) {
    data.percentage = calcPercent.toFixed(2)
  }
})

// 自定义指令:保存并恢复光标位置
const vPreserveCursor = {
  mounted(el) {
    el.addEventListener('input', function() {
      this._cursorPos = this.selectionStart
    })
  },
  updated(el) {
    nextTick(() => {
      if (el._cursorPos !== undefined) {
        el.setSelectionRange(el._cursorPos, el._cursorPos)
      }
    })
  }
}
</script>

<template>
  <div>
    <div>
      <label>First</label>
      <input type="number" v-model.number="data.first" step="1">
    </div>

    <div style="padding: 15px 0px">
      <label>Second</label>
      <input type="text" v-model="data.second" v-preserve-cursor>
    </div>

    <div>
      <label>Percentage</label>
      <input type="text" v-model="data.percentage" v-preserve-cursor>
    </div>
  </div>
</template>

关键改动说明

  • 循环打断:每个watch回调中增加差值判断,只有当计算值与当前值的差异超过0.001时才执行更新,从根源上避免了双向触发的循环,自然消除了抖动现象。
  • 小数精度处理:用toFixed(2)将数值固定为两位小数,再通过Number()转换为数值类型,避免字符串绑定带来的格式异常。
  • 光标修复:自定义v-preserve-cursor指令在用户输入时记录光标位置,DOM更新完成后通过nextTick恢复光标,保证输入时光标不会跳至末尾。
  • 输入优化:将First输入框改为type="number",搭配v-model.number确保输入为纯数值类型,减少非数字输入的处理逻辑。

补充说明

如果想用computed属性实现,也可以将second和percentage设置为带get/set的计算属性,核心思路还是通过差异判断避免循环更新,本质和上述方案一致。

内容的提问来源于stack exchange,提问作者shabxs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 05:47:43