如何使用HTML5输入类型数字处理浮点数和小数分隔符

153

我正在构建一个主要面向移动浏览器的Web应用程序。我使用数字类型的输入字段,因此(大多数)移动浏览器仅调用数字键盘以提供更好的用户体验。这个Web应用程序主要用于小数分隔符为逗号而不是点的地区,因此我需要处理两种小数分隔符。

如何覆盖点和逗号的混乱情况?

我的发现:

桌面Chrome

  • 输入类型=数字
  • 用户在输入字段中输入“4,55”
  • $(“#my_input”)。val(); 返回“455”
  • 我无法从输入中获取正确的值

桌面Firefox

  • 输入类型=数字
  • 用户在输入字段中输入“4,55”
  • $(“#my_input”)。val(); 返回“4,55”
  • 没问题,我可以将逗号替换为点并获得正确的浮点数

Android浏览器

  • 输入类型=数字
  • 用户在输入字段中输入“4,55”
  • 当输入框失去焦点时,值被截断为“4”
  • 用户会感到困惑

Windows Phone 8

  • 输入类型=数字
  • 用户在输入字段中输入“4,55”
  • $(“#my_input”)。val(); 返回“4,55”
  • 没问题,我可以将逗号替换为点并获得正确的浮点数

在这种情况下,当用户可能使用逗号或点作为小数分隔符,并且我希望保持HTML输入类型为数字以提供更好的用户体验时,有哪些“最佳实践”?

我能否即时将逗号转换为点,绑定关键事件,它是否适用于数字输入?

编辑

目前我没有任何解决方案可以从类型为“数字”的输入中获取浮点值(作为字符串或数字)。如果最终用户输入“4,55”,Chrome始终返回“455”,Firefox返回“4,55”,这是很好的。

另外,令人非常烦恼的是,在Android(测试了4.2模拟器)中,当我将“4,55”输入到输入字段并将焦点改变到其他地方时,输入的数字被截断为“4”。


我的猜测是你们遇到的问题的根本原因是浏览器设置为使用点作为小数分隔符的美国区域设置。似乎Chrome和Android以不同的方式被这个问题搞糊涂了——Chrome将逗号视为数字分隔符,然后将输入转换为数字,再将其转换为字符串传递给.val()函数,而Android则会出现一些奇怪的问题。你能否检查一下当系统语言设置为适当的语言时它们是否行为相同? - millimoose
1
Nvm将此作为答案发布了。 - kjetilh
请参见此处:https://dev59.com/c4Dba4cB1Zd3GeqPK_R4#24423879 - Revious
情况比你描述的还要糟糕:根据你选择的键盘工作语言,WP小键盘上会出现小数点或逗号。而且没有办法找出输入区域设置(不一定是首选浏览器语言)...所以我能想到的最好解决方案(对我来说已经足够好了)是:```var number = $(...).get(0).valueAsNumber;``` ```if (isNaN(number)) number = parseFloat($(...).val().replace(',', '.');```但这个解决方案仅在输入的数字只包含1个逗号和没有额外的点的情况下有效... - bert bruynooghe
10个回答

123

我认为上面的答案中缺少了需要指定step属性的不同值的需求,该属性有一个默认值1。如果你希望输入验证算法允许浮点数值,请相应地指定步长。

例如,我想要美元金额,因此我指定了这样的步骤:

 <input type="number" name="price"
           pattern="[0-9]+([\.,][0-9]+)?" step="0.01"
            title="This should be a number with up to 2 decimal places.">

使用jQuery检索值没有问题,但直接使用DOM API获取元素的validity.valid属性会更有用。

我遇到了类似小数点的问题,但之所以意识到存在问题,是因为Twitter Bootstrap为具有无效值的数字输入添加的样式。

这里有一个演示增加step属性使其起作用,并测试当前值是否有效:

TL;DR:step属性设置为浮点值,因为默认值为1

注意:对我来说,逗号无法验证,但我怀疑如果我将操作系统语言/区域设置为使用逗号作为小数分隔符的某个地方,它将会正常工作。*注意事项中的注释*:在Ubuntu/Gnome 14.04中的操作系统语言/键盘设置*德语*不是这种情况。


10
如果您想允许任意数量的小数位,请将 step 属性指定为 "any",而不是像这里所示的 "0.01"。请参见 nathciketa 的 此修改版本 进行演示。 - Mark Amery
4
我也遇到了逗号无法验证的问题,尽管我在 <input type="number" step="any"> 中输入了 "1,234.56"。我无法找到关闭HTML5验证的方法。我可以关闭或自定义错误消息,但从输入中检索值总是靠运气。有什么想法吗? - Nick G.
6
当type属性的值为text、search、tel、url、email或password时,该属性起作用;否则该属性将被忽略。因此,在type="number"时,pattern是无用的。 - Michael Laffargue
就像迈克尔所说的那样,请将“type”更改为“text”,否则你的回答就没有意义了。 - phil294

30
根据w3.org,number inputvalue属性被定义为floating-point number。浮点数的语法似乎只接受点作为小数分隔符。
我列出了一些可能对您有帮助的选项:

1. 使用pattern属性

使用pattern属性,您可以以HTML5兼容的方式指定允许的格式,并在此处指定逗号字符是允许的,并提供有用的反馈信息,以便在模式失败时进行说明。
<input type="number" pattern="[0-9]+([,\.][0-9]+)?" name="my-num"
           title="The number input must start with a number and use either comma or a dot as a decimal character."/>

注意:不同浏览器的支持程度差别很大。可能是完全的、部分的或不存在的。

2. JavaScript验证

您可以尝试将简单的回调绑定到例如 onchange (和/或 blur )事件,这将替换逗号或一起进行验证。

3. 禁用浏览器验证 ##

第三,您可以尝试在数字输入上使用 formnovalidate 属性,以意图完全禁用该字段的浏览器验证。

<input type="number" formnovalidate />

4. 组合..?

<input type="number" pattern="[0-9]+([,\.][0-9]+)?" 
           name="my-num" formnovalidate
           title="The number input must start with a number and use either comma or a dot as a decimal character."/>

2
input.valueAsNumber
  • 没有帮助,因为它只能使用点作为小数分隔符,在我的情况下,最终用户可能会使用点或逗号。
Form标签novalidate
  • 没有发现任何影响。
- devha
很不幸,我已经没有更多的想法了,所以我不确定你是否能够在没有解决方案的情况下使其跨浏览器工作。我已更新我的答案,包括pattern属性,我还发现可以直接在输入字段上使用formnovalidate属性。我知道你不会喜欢它,但如果一切都失败了,而你绝对需要逗号,那么**input="text"**可能是唯一的方法。 - kjetilh
似乎输入类型为数字的问题比实际改善用户体验更多。在移动设备上,如果我想让用户只输入数字,那么仅打开数字键盘将是很好的选择。无论如何,我需要再次重新考虑这个问题... - devha
因为我们还需要支持使用点和逗号作为小数分隔符,所以我们计划在桌面端结合 type="text" 使用模式进行验证。然后,我们将添加功能检测来检测移动设备,并在适用时使用 type="number"。 - awe
对我而言有效的方法是将输入类型暂时修改为文本,添加“.0”,然后再改回“数字”类型。 - hsc

10
无论是使用逗号还是点作为小数分隔符完全取决于浏览器。浏览器根据操作系统或浏览器的本地设置进行决策,或者一些浏览器从网站获取提示。我制作了一个浏览器比较图表,展示了不同浏览器如何支持不同的本地化方法。Safari是唯一支持逗号和点互换的浏览器。
基本上,作为网络作者,您无法真正控制这个问题。一些解决方法涉及使用两个带有整数的输入字段。这允许每个用户按照您的期望输入数据。虽然不是特别好看,但对所有用户都可以在所有情况下工作。

9

使用文本类型,但强制出现数字键盘。

<input value="12,4" type="text" inputmode="numeric" pattern="[-+]?[0-9]*[.,]?[0-9]+">

输入模式标签是解决方案。

但它并不兼容所有浏览器,只能与最新版本的iOS Safari、Chrome、Chromium和Opera一起使用...https://caniuse.com/#feat=input-inputmode :( - pgarciacamou
此外,在移动设备上你还得不到这个漂亮的数字键盘。 - ˈvɔlə

7

使用 valueAsNumber 替代 .val()

input . valueAsNumber [ = value ]

返回表单控件的值(如果适用),否则返回 null。
可以设置为改变值。
如果控件既不是基于日期或时间,也不是数字,则会引发INVALID_STATE_ERR异常。


3
问题在于他希望将逗号作为十进制分隔符,并且在输入框上使用 type="number" 时,输入逗号会始终导致无效数字。 - awe
似乎在Windows Phone上无法稳定工作;总是返回NaN - bert bruynooghe
@bertbruynooghe,你分配了什么值作为数值? - Mike Samuel
似乎不起作用,无论是使用“9”、“9,1”还是“9.1”。手机配置为美国,键盘布局测试了en-US、nl-BE。 - bert bruynooghe

4
我尚未找到完美的解决方案,但我所能做的最好的办法是使用type="tel"并禁用HTML5验证(formnovalidate):
<input name="txtTest" type="tel" value="1,200.00" formnovalidate="formnovalidate" />

如果用户输入逗号,它将在我尝试的所有现代浏览器(最新的FF,IE,edge,opera,chrome,safari桌面版,android chrome)中输出逗号。
主要问题是:
- 移动用户将看到与数字键盘不同的手机键盘。 - 更糟糕的是,手机键盘可能甚至没有逗号键。
对于我的用例,我只需要:
- 显示带有逗号的初始值(因为firefox会将其删除,以便type=number) - 不失败html5验证(如果有逗号) - 使字段读取与输入完全相同(可能有逗号)
如果您有类似的要求,这应该适用于您。
注意:我不喜欢pattern属性的支持。 formnovalidate似乎效果更好。

1
当您调用$("#my_input").val();时,它返回一个字符串变量。因此,请使用parseFloatparseInt进行转换。 当您使用parseFloat时,您的桌面或手机本身可以理解变量的含义。
另外,您可以通过使用toFixed将浮点数转换为字符串,其中有一个参数是数字的位数,如下:
var i = 0.011;
var ss = i.toFixed(2); //It returns 0.01

无法工作。在IE11中尝试parseFloat("1,5")。返回1。操作系统和IE的语言均为德语。 - T3rm1

1

显然,浏览器仍然存在问题(尽管Chrome和Firefox Nightly表现良好)。 我有一个权宜之计适用于那些确实需要使用数字字段的人(也许是因为文本字段没有小箭头)。

我的解决方案

我添加了一个keyup事件处理程序到表单输入框中,并跟踪状态,在其再次有效时设置值。

Pure JS Snippet JSFIDDLE

var currentString = '';
var badState = false;
var lastCharacter = null;

document.getElementById("field").addEventListener("keyup",($event) => {
  if ($event.target.value && !$event.target.validity.badInput) {
          currentString = $event.target.value;
  } else if ($event.target.validity.badInput) {
    if (badState && lastCharacter && lastCharacter.match(/[\.,]/) && !isNaN(+$event.key)) {
      $event.target.value = ((+currentString) + (+$event.key / 10));
      badState = false;
    } else {
      badState = true;
    }
  }
  document.getElementById("number").textContent = $event.target.value;
  lastCharacter = $event.key;
});

document.getElementById("field").addEventListener("blur",($event) => {
 if (badState) {
    $event.target.value = (+currentString);
    badState = false;
  document.getElementById("number").textContent = $event.target.value;
  }
});
#result{
  padding: 10px;
  background-color: orange;
  font-size: 25px;
  width: 400px;
  margin-top: 10px;
  display: inline-block;
}

#field{
  padding: 5px;
  border-radius: 5px;
  border: 1px solid grey;
  width: 400px;
}
<input type="number" id="field" keyup="keyUpFunction" step="0.01" placeholder="Field for both comma separators">

<span id="result" >
 <span >Current value: </span>
 <span id="number"></span>
</span>

Angular 9 片段

@Directive({
  selector: '[appFloatHelper]'
})
export class FloatHelperDirective {
  private currentString = '';
  private badState = false;
  private lastCharacter: string = null;

  @HostListener("blur", ['$event']) onBlur(event) {
    if (this.badState) {
      this.model.update.emit((+this.currentString));
      this.badState = false;
    }
  }

  constructor(private renderer: Renderer2, private el: ElementRef, private change: ChangeDetectorRef, private zone: NgZone, private model: NgModel) {
    this.renderer.listen(this.el.nativeElement, 'keyup', (e: KeyboardEvent) => {
      if (el.nativeElement.value && !el.nativeElement.validity.badInput) {
        this.currentString = el.nativeElement.value;
      } else if (el.nativeElement.validity.badInput) {
        if (this.badState && this.lastCharacter && this.lastCharacter.match(/[\.,]/) && !isNaN(+e.key)) {
          model.update.emit((+this.currentString) + (+e.key / 10));
          el.nativeElement.value = (+this.currentString) + (+e.key / 10);
          this.badState = false;
        } else {
          this.badState = true;
        }
      }       
      this.lastCharacter = e.key;
    });
  }
  
}
<input type="number" matInput placeholder="Field for both comma separators" appFloatHelper [(ngModel)]="numberModel">


无法在be上工作,但逗号输入被忽略(chrome 99) - hsc

0

2
通常即使答案完全被链接的内容覆盖,也会对解决方案进行更详细的解释。 - IvanH
如果后端服务器决定语言环境而不是浏览器本身,这种方法似乎比其他方法更好。不幸的是,“toLocaleString”在各个平台上都不可靠。 - bert bruynooghe
跨平台支持在2015年已经足够广泛,可以参考例如Mozillas Devnet。然而,如果您仍然关心这个问题,可以在此处找到可能的解决方案。 - stackasec
Mozilla的Devnet几乎没有移动浏览器的兼容性,而数字文本字段恰恰可以提供最大的好处。 (请参见原始问题。) - bert bruynooghe
......并且已经展示和提到了足够多的替代方案。祝玩得愉快。 - stackasec

0
我的建议是?根本不要使用jQuery。我和你一样也遇到了同样的问题。我发现$('#my_input').val()总是返回一些奇怪的结果。
尝试使用document.getElementById('my_input').valueAsNumber代替$("#my_input").val();,然后使用Number(your_value_retrieved)尝试创建一个数字。如果值为NaN,则可以确定那不是一个数字。
需要补充的一点是,当您在输入框中输入数字时,输入框实际上会接受几乎任何字符(我可以写欧元符号、美元符号和所有其他特殊字符),因此最好使用.valueAsNumber来检索值,而不是使用jQuery。
哦,顺便说一下,这使您的用户可以添加国际化(即:支持逗号而不是点来创建小数)。只需让Number()对象为您创建一些内容,它就是十进制安全的,可以这么说。

问题不是由jQuery引起的,我发现valueAsNumber在不同的浏览器中也不能保持一致的工作。 - bert bruynooghe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接