在支付场景的网页里,输入交易密码是整个流程中安全要求最高的一步。浏览器原生的密码输入框依赖物理键盘或系统软键盘,输入过程可能被键盘记录程序捕获,而且在手机上原生密码框的样式和行为各不相同,体验很难统一。支付宝的做法是干脆不用原生输入框,而是自己绘制一个数字键盘和密码格子控件,既屏蔽了系统键盘的监听风险,又能保证跨端体验一致。本文就带你用jQuery一步步实现一个仿支付宝的数字键盘加密码输入框控件。

一、整体结构与样式设计
先规划页面的HTML结构。整个控件分为三部分:密码显示区、遮罩层、数字键盘面板。密码区不使用<input>标签,而是用六个格子模拟,这样可以从根本上避免原生输入框带来的一系列问题,比如长按弹出系统菜单、自动填充、被脚本读取值等。
<div class="pay-container">
<div class="pwd-box">
<div class="pwd-input" id="pwdInput">
<span class="pwd-cell"></span>
<span class="pwd-cell"></span>
<span class="pwd-cell"></span>
<span class="pwd-cell"></span>
<span class="pwd-cell"></span>
<span class="pwd-cell"></span>
</div>
</div>
<div class="mask" id="mask"></div>
<div class="keyboard" id="keyboard">
<ul class="key-list" id="keyList"></ul>
</div>
</div>样式方面,密码格子需要横向排列且等分宽度,用flex布局最省事。每个格子居中显示一个圆点,圆点用伪元素绘制,避免在DOM里留下任何明文痕迹。键盘面板默认隐藏在屏幕底部,弹出时通过transform做位移动画,性能比改top值好得多。
.pwd-box {
width: 320px; margin: 50px auto;
border: 1px solid #ddd; border-radius: 6px;
overflow: hidden;
}
.pwd-input {
display: flex; width: 100%; cursor: pointer;
-webkit-user-select: none; user-select: none;
}
.pwd-cell {
flex: 1; height: 46px; line-height: 46px;
text-align: center; border-right: 1px solid #ddd;
font-size: 22px; position: relative;
}
.pwd-cell:last-child { border-right: none; }
.pwd-cell.filled::after {
content: ''; display: inline-block;
width: 12px; height: 12px; border-radius: 50%;
background: #333; vertical-align: middle;
}
.mask {
display: none; position: fixed; left: 0; top: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,.5); z-index: 998;
}
.keyboard {
position: fixed; left: 0; bottom: -320px;
width: 100%; background: #f5f5f5;
transition: bottom .3s ease; z-index: 999;
}
.keyboard.show { bottom: 0; }
.key-list { display: flex; flex-wrap: wrap; margin: 0; padding: 8px; list-style: none; }
.key-list li {
width: 31.5%; margin: 1%; height: 48px; line-height: 48px;
background: #fff; text-align: center; font-size: 22px;
border-radius: 6px; cursor: pointer;
}
.key-list li:active { background: #e0e0e0; }
.key-list li.disabled { background: #e8e8e8; color: #ccc; }这里有两个细节值得注意。一是密码区和键盘都要加上user-select: none,禁止用户选中文字,防止通过双击或长按看到残留内容。二是键盘用position: fixed固定在底部,配合遮罩层阻止用户在键盘弹出时误触页面其他区域,这也是支付宝等支付页面的常见交互习惯。
二、数字键盘的动态生成与随机打乱
键盘按键不写死在HTML里,而是用jQuery动态生成,这样可以实现一个重要的安全特性:每次弹出键盘时随机打乱数字顺序。固定布局的键盘存在被旁人偷看按键位置、或者通过屏幕录制回放分析手指轨迹来推测密码的风险,随机化布局能有效缓解这类肩窥攻击。
function buildKeyboard() {
var keys = ['1','2','3','4','5','6','7','8','9','0'];
// Fisher-Yates洗牌算法打乱数字顺序
for (var i = keys.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = keys[i];
keys[i] = keys[j];
keys[j] = temp;
}
var html = '';
// 前9个位置放打乱后的数字,第10格放空键
for (var k = 0; k < keys.length; k++) {
if (k === 9) {
html += '<li class="disabled"></li>';
}
html += '<li>' + keys[k] + '</li>';
}
html += '<li class="disabled"></li><li>0</li>'.replace('0', keys[9] === '0' ? '' : '');
html = html.replace('0', keys[9]);
$('#keyList').html(html);
// 追加功能键
$('#keyList').append('<li class="disabled"></li><li id="keyDel">删除</li><li id="keyDone">完成</li>');
}上面的写法为了演示核心逻辑略有简化,实际项目中建议把布局写成更清晰的二维数组结构,逐行渲染,代码可读性更好。洗牌部分用的是标准的Fisher-Yates算法,它的时间复杂度是O(n),能保证每种排列出现的概率完全相等,这一点比简单的sort加随机比较函数要可靠,因为后者的概率分布并不均匀。
随机化也有代价:数字位置每次变化,用户找数字的速度会变慢,输错率略高。所以支付宝实际采用的方式是数字区随机、删除键和完成键位置固定,这个平衡点值得借鉴。如果安全等级要求更高,比如银行类应用,还可以在每输入一位后重新洗牌一次,但普通支付场景做到每次弹出时随机就够了。
三、密码掩码渲染与事件处理
密码数据保存在一个JS字符串变量中,每输入或删除一位,就重新渲染格子的圆点状态。渲染函数非常简单,遍历所有格子,索引小于密码长度的加filled类,其余的去掉。虽然直接操作DOM没有性能问题,但要养成最小化DOM操作的习惯,只在状态变化的格子上做增删类的操作。
var pwd = '';
function renderPwd() {
var cells = $('#pwdInput .pwd-cell');
cells.removeClass('filled');
for (var i = 0; i < pwd.length; i++) {
cells.eq(i).addClass('filled');
}
}按键点击用事件委托的方式绑定在ul上,而不是给每个li单独绑定,这样键盘重建后监听器依然有效,也不需要重复解绑。判断点击的是数字、删除还是完成,分别做追加、截断和提交处理。密码长度上限设为6位,超过就忽略输入。
$('#keyList').on('click', 'li', function () {
var $li = $(this);
if ($li.hasClass('disabled')) return;
var text = $li.text();
if (text === '删除') {
pwd = pwd.slice(0, -1);
renderPwd();
return;
}
if (text === '完成') {
if (pwd.length === 6) {
submitPwd(pwd);
closeKeyboard();
pwd = '';
renderPwd();
} else {
alert('请输入完整的6位密码');
}
return;
}
if (/^[0-9]$/.test(text) && pwd.length < 6) {
pwd += text;
renderPwd();
if (pwd.length === 6) {
// 输满后自动提交,也可以改成等待用户点完成
setTimeout(function () { submitPwd(pwd); }, 200);
}
}
});
function submitPwd(value) {
// 实际项目中应加密后通过ajax提交到后端
console.log('提交密码:', value);
}弹窗和收起的控制放在两个入口事件里:点击密码区弹出键盘并重建按键,点击遮罩层收起。收起的同时把已输入的密码清空,这是支付宝的做法,避免用户下次弹出时残留上次的输入造成误提交。
$('#pwdInput').on('click', function () {
buildKeyboard();
$('#mask').show();
$('#keyboard').addClass('show');
});
function closeKeyboard() {
$('#keyboard').removeClass('show');
$('#mask').hide();
pwd = '';
renderPwd();
}
$('#mask').on('click', closeKeyboard);四、安全加固:这个控件到底防住了什么
要客观看待自定义键盘的安全价值。它主要防的是两类威胁:一是系统层面的键盘记录器,因为整个过程没有触发任何真实的键盘事件,Hook系统按键的恶意程序抓不到任何内容;二是肩窥和屏幕录制回放,随机布局加圆点掩码让旁观者难以推断输入内容。
但有几个攻击面它自己是防不住的,必须配合其他手段。首先是网络传输,明文密码绝不能直接POST出去,应该在前端用RSA公钥加密后再提交,后端用私钥解密,这样即使抓包也拿不到原文。其次是页面本身被注入恶意脚本的情况,如果有XSS漏洞,任何前端控件都形同虚设,所以输入框的转义过滤、CSP策略这些基础工作不能省。最后,密码变量在提交后要立即清空,不要长时间留在内存里,更不要往localStorage或cookie里写。
还有一个容易被忽视的细节:这个控件禁用了粘贴功能,用户只能手动逐位输入。这看起来是体验的倒退,实际上是刻意的安全设计。如果允许粘贴,攻击者可以通过诱导用户复制恶意内容,或者用户自己粘贴被篡改的密码,都会绕开逐位确认的过程。支付宝坚持只允许键盘输入,正是基于这个考虑。
总结一下,用jQuery实现仿支付宝数字键盘的核心在于三点:不用原生输入框而是自绘格子来切断系统键盘链路、随机打乱数字布局来对抗肩窥、配合前端加密和及时清空内存来完成整条安全链路。代码量不大,但每一处设计都对应着具体的安全考量,理解了这些思路,你就可以根据自己项目的安全等级灵活裁剪,做出既安全又好用的支付密码输入控件。
jQuery数字键盘密码输入框安全输入控件修改时间:2026-09-08 20:14:02