Alpine.js Mask 插件完全指南:x-mask 与 $money 输入格式化实战
【免费下载链接】alpineA rugged, minimal framework for composing JavaScript behavior in your markup.项目地址: https://gitcode.com/gh_mirrors/al/alpine
Mask 是 Alpine.js 官方插件之一,用于在用户输入时自动将文本字段格式化为预定格式,适用于电话号码、信用卡号、金额、账号、日期等输入场景。本文以仓库中 Mask 插件官方文档 为骨架,结合 插件源码 与 Cypress 集成测试 深入讲解安装、通配符语法、动态掩码、金额格式化及与x-model的协作原理,读完即可在生产项目中直接落地使用。
插件简介
Alpine 的 Mask 插件让你无需手写任何 JavaScript,仅通过一个x-mask指令即可为<input>文本输入框提供"边输入边格式化"的能力。它的适用场景非常典型:
- 电话号码:
(999) 999-9999 - 信用卡号:
9999 9999 9999 9999 - 金额:
1,234,567.89 - 账号:
9999-9999-9999 - 日期:
99/99/9999
该插件以@alpinejs/mask为包名独立发布(仓库中对应 packages/mask,版本号见 package.json),通过 Alpine 的插件机制注册mask指令,核心实现仅一个 index.js 文件,约 240 行,轻量且无外部依赖。
安装
与 Alpine 其他官方插件一致,Mask 支持 CDN 和 NPM 两种引入方式。
通过 CDN 引入
使用<script>标签引入 CDN 构建产物,务必放在 Alpine 核心 JS 文件之前:
<!-- Alpine Plugins --> <script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/mask@3.x.x/dist/cdn.min.js"></script> <!-- Alpine Core --> <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>CDN 构建产物(仓库内对应 packages/mask/builds/cdn.js)内部实现很简洁:监听alpine:init事件,在 Alpine 初始化时自动调用window.Alpine.plugin(mask)完成注册,因此无需额外编写初始化代码。
通过 NPM 安装
在项目内使用打包器(如 Webpack、Vite)时,优先采用 NPM 方式:
npm install @alpinejs/mask然后在入口文件中初始化插件:
import Alpine from 'alpinejs' import mask from '@alpinejs/mask' Alpine.plugin(mask) // ... 其余 Alpine 初始化代码若使用 ES Module 构建产物,packages/mask/builds/module.js 还额外导出了stripDown等底层工具函数,便于高级用户直接复用内部格式化逻辑。
x-mask 指令基础用法
x-mask是该插件的核心 API。看一个最简单的日期输入示例:
<input x-mask="99/99/9999" placeholder="MM/DD/YYYY">用户输入时,输入框内的内容必须逐步符合x-mask提供的格式:9通配符位置只能输入数字,而/这类字面量字符即便用户没有手动输入,也会在满足前置条件时被自动补全。例如用户依次输入0、1、2、5,输入框会依次呈现0、01、01/2、01/25。
支持的三种通配符
| 通配符 | 描述 |
|---|---|
* | 任意字符 |
a | 仅字母字符(a-z, A-Z) |
9 | 仅数字字符(0-9) |
三种通配符在源码 stripDown 函数 中对应着三个正则:
let regexes = { '9': /[0-9]/, 'a': /[a-zA-Z]/, '*': /[a-zA-Z0-9]/, }注意*的实际含义是"字母或数字"(即[a-zA-Z0-9]),并非字面意义上的任意字符——严格来说它不允许空格、标点等符号。如果你的掩码中包含不在上述三种通配符内的普通字符(如b、-、空格),它们会被当作"字面量"处理,由插件自动插入,且不会被用户输入覆盖。这一点在 mask.spec.js 的ba9*b测试用例 中有直接验证:模板ba9*b中,用户输入a后值变为ba(首字符b由插件补出且不可覆盖),继续输入3得到ba3,输入z得到ba3zb。
掩码为空或为 false 的行为
从测试用例可以看出,如果x-mask的表达式结果为空字符串或字符串"false"(源码第 77-78 行 的守卫逻辑),插件会直接跳过格式化处理,输入框表现为普通文本框,任意字符均可输入(见 mask.spec.js 中x-mask=""与x-mask="false"的测试)。
格式化底层原理:stripDown 与 buildUp
x-mask之所以能"边输入边格式化",核心是 processInputValue → formatInput 这条处理链,其中两步是关键:
- stripDown(剥离):把当前输入值中"不属于模板"的字面量字符删掉,只保留与通配符匹配的原始字符序列。算法会先删除与模板字面量字符相同的字符,再按通配符顺序逐个校验正则并收集匹配字符,一旦某位不匹配就立即停止。
- buildUp(重建):用剥离后的干净字符序列,按模板结构重新填充:遇到通配符就从队列头部取出一个字符,遇到字面量就原样插入,字符耗尽则提前终止(见 buildUp 函数)。
"剥离 → 重建"两步合一的优势在于:无论用户粘贴的是已格式化文本(如(123) 456-7890)还是未格式化文本(如1234567890),最终都会被归一化为统一的掩码格式。这一行为在测试中被反复验证(mask.spec.js 粘贴场景)。
光标位置与退格键的特殊处理
直接给el.value赋值会把光标强制移动到末尾,影响中间编辑体验,因此源码做了三处精细处理:
- 光标恢复:restoreCursorPosition 函数 在改写值之前记录
selectionStart,改写后只对光标左侧的文本执行一次"剥离 + 重建",以其结果长度作为新的光标位置,并通过setSelectionRange恢复。由于 Safari 在blur时会重新聚焦造成焦点陷阱,恢复光标逻辑只在input事件中启用,blur事件(用于处理粘贴落定)则不恢复。 - 退格放行:当检测到
lastInputValue.length - el.value.length === 1(即本次操作删除了一个字符)时,直接跳过格式化(源码第 81-83 行),让用户能顺畅地删除字符,避免"删不掉"的体验。测试中断言连续退格时(123) 456-7890逐步回退为(123) 456-789、(123) 456-78……直至(123) 45,正是该逻辑的效果。 - 非法字符吞掉:由于剥离阶段只收集正则匹配的字符,在数字位输入字母、符号等非法字符时会被过滤掉,输入框值保持不变(测试中断言输入
a、-后值仍为(123) 45,见 mask.spec.js 第 26-27 行)。
动态掩码:x-mask:dynamic
当固定字面量掩码(如(999) 999-9999)无法满足需求时,可以使用x-mask:dynamic根据用户输入动态生成掩码。
典型的信用卡号场景:当卡号以34或37开头时,说明是 Amex(美国运通)卡,应采用9999 999999 99999格式;否则采用通用格式9999 9999 9999 9999:
<input x-mask:dynamic=" $input.startsWith('34') || $input.startsWith('37') ? '9999 999999 99999' : '9999 9999 9999 9999' ">每次输入,当前输入框的值都会以$input的身份传入表达式;表达式求值后返回的字符串即当前应使用的掩码。用户输入34开头的号码与普通号码时,输入框会自动切换为不同的格式。
动态掩码也可以是一个函数
x-mask:dynamic的表达式结果还可以是一个函数,插件会自动把$input作为第一个参数传入:
<input x-mask:dynamic="creditCardMask"> <script> function creditCardMask(input) { return input.startsWith('34') || input.startsWith('37') ? '9999 999999 99999' : '9999 9999 9999 9999' } </script>动态掩码的源码实现
从源码看,x-mask:dynamic(源码中称为 function/dynamic 分支) 使用了 Alpine 的evaluateLater延迟求值与effect响应式机制:模板函数templateFn会在每次输入时执行,得到当前掩码字符串;同时effect会追踪表达式中的响应式依赖,一旦依赖变化就自动重新计算掩码并重新格式化输入框。求值时通过Alpine.dontAutoEvaluateFunctions防止函数被自动执行,以便把函数本身作为掩码生成器使用,并注入$input与$money两个魔法变量。
金额输入:$money
为金额输入手写动态掩码表达式相当繁琐,因此插件内置了预制的金额格式化函数,并以$money魔法变量的形式暴露给x-mask:dynamic(或x-mask:function)使用。
一个开箱即用的金额输入框:
<input x-mask:dynamic="$money($input)">用户输入1234时显示1,234,输入567追加后显示1,234,567,再输入.89得到1,234,567.89——千分位自动按三位一组插入。
自定义小数分隔符(第二个参数)
某些货币使用逗号作为小数分隔符,此时交换小数点和逗号的角色即可:
<input x-mask:dynamic="$money($input, ',')">输入30,00后继续输入会得到30,05,千分位自动变为点号(如1.234.567,89),相关行为见 mask.spec.js 中的逗号/句点互换测试。
自定义千分位分隔符(第三个参数)
传入第三个参数可覆盖千分位分隔符,例如用空格分组:
<input x-mask:dynamic="$money($input, '.', ' ')">输入3000会显示为3 000,再输入567显示1 234 567.89(对应测试)。
自定义小数精度(第四个参数)
默认保留 2 位小数,通过第四个参数可改为任意精度,甚至支持 0 位(纯整数):
<input x-mask:dynamic="$money($input, '.', ',', 4)">测试覆盖了精度 0~3 的全部情况:输入1234.5678,精度为 0 时显示12,345,678,精度为 1 显示1,234.5,精度为 2 显示1,234.56,精度为 3 显示1,234.567(mask.spec.js 第 243-255 行)。
$money 的源码细节
formatMoney 函数 是$money的底层实现,有几个值得注意的行为:
- 负号支持:输入以
-开头时保留负号,测试断言-1234.50会被格式化为-1,234.50,且负号的插入/删除不会破坏掩码(mask.spec.js 负数测试)。 - 非法字符过滤:仅保留数字与小数分隔符,
A、ABC、$、/等字符全部被清除(对应测试);若输入中不含任何数字(如只输入了符号),会返回占位字符串'9',相当于一个等待数字输入的模板。 - 千分位默认值联动:当第三个参数未提供时,千分位自动取小数分隔符的反向字符(分隔符为
,则千分位用.,否则用,)。 - 光标微调:格式化后如果光标恰好在分隔符之后,会通过
setSelectionRange把光标前移一位,避免用户每次输入后光标停在错误的符号位置。
与 x-model 的协作
x-mask与x-model可以无缝配合:掩码格式化后的值会自动同步进数据模型,反之模型值变化也会反映到输入框。注意两点前提:
- 监听顺序:插件在
input事件上以capture(捕获)阶段监听(源码第 61-66 行),确保格式化先于x-model等潜在绑定执行,从而让模型拿到的是已格式化值。 - 指令注册顺序:插件通过
Alpine.directive('mask', ...).before('model')注册(源码第 110 行),保证x-mask在x-model之前初始化。
初始值同步与特殊值保护
初始化时,插件会把格式化后的el.value写回x-model(源码第 42-52 行),但有两个防抖保护:若模型值已等于输入框值、或模型值为null而输入框为空字符串,则不做覆盖,避免触发无意义的更新链。对应测试验证了:模型初始值为'1234567890'时,输入框与第二个展示x-model的输入框都会初始显示为(123) 456-7890;而模型初始值为null时,输入框保持为空且模型仍为null(mask.spec.js 初始值测试)。
资源清理
插件使用AbortController管理事件监听器,并在cleanup回调中调用controller.abort()(源码第 55-59 行),因此当 Alpine 销毁指令或组件时,监听器会被正确移除,不会造成内存泄漏。
常见问题与边界行为速查
- 粘贴已格式化/未格式化文本:
blur事件也会触发一次格式化(不恢复光标),粘贴后点击外部即可完成归一化;input事件则保证边贴边格式化。 - 中间插入编辑:光标恢复机制支持在已有值中间插入数字,测试验证了在
(123) 456-7890中间插入123456得到(123) 456-1234,以及金额中间插入数字后千分位自动重排(mask.spec.js 中间插入测试)。 - 退格:删除操作会被放行并自动保留合法掩码骨架。
*不是"任意字符":源码正则限定为[a-zA-Z0-9],空格与标点不会被*接受。- 掩码为空/为 false:跳过格式化,退化为普通输入框。
小结
x-mask用一条指令解决了前端表单中最常见的输入格式化需求,而x-mask:dynamic与$money进一步覆盖了信用卡号、多货币金额等复杂动态场景。理解其"剥离-重建"算法、光标恢复、捕获阶段监听等实现细节,能帮助你在项目中正确处理粘贴、退格、中间编辑等边界情况。仓库中 Mask 源码、集成测试 与 官方文档 三份资料互为印证,是深入学习与排查问题的最佳起点。
【免费下载链接】alpineA rugged, minimal framework for composing JavaScript behavior in your markup.项目地址: https://gitcode.com/gh_mirrors/al/alpine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考