在上一节,我们学会了实现一个checkbox,本节我们将学会实现一个input-number组件。
实现思路
要想实现input-number,我们就要知道这个插件是做什么的,按照前面实现checkbox的思路,我们就可以很快速的实现一个input-number。我们先来看插件是如何使用的:
javascript
// 创建数字输入框
const inputNumber = new InputNumber({
container: '#my-container',
min: 0,
max: 100,
step: 1,
value: 50,
onChange: (value) => {
console.log('当前值:', value);
}
});
// 获取当前值
const currentValue = inputNumber.getValue();
// 设置新值
inputNumber.setValue(75);
根据使用方式,我们知道了插件的配置属性和一些使用方法,接下来我们就按照使用方式来实现,首先还是初始化一个类。
javascript
class InputNumber {
constructor(options) // 构造函数,初始化数字输入框
createInputElement() // 创建DOM元素结构
setupEventListeners() // 设置事件监听器
setValue(value) // 设置数值(核心方法)
getValue() // 获取当前数值
mount(container) // 挂载到指定容器
}
实现原理详解
DOM结构构建
首先,我们还是需要理清dom结构,本质上就是一个减号按钮,一个加号按钮以及一个输入框(限制只允许输入数字)。
组件结构设计
组件元素结构如下所示:
javascript
createInputElement() {
const container = document.createElement("div"); // 外层容器
const decreaseBtn = document.createElement("button"); // 减少按钮
const input = document.createElement("input"); // 数字输入框
const increaseBtn = document.createElement("button"); // 增加按钮
// 组装DOM结构
container.appendChild(decreaseBtn);
container.appendChild(input);
container.appendChild(increaseBtn);
}
DOM结构如下:
html
<div class="ew-input-number">
<button class="input-number-button decrease" type="button">-</button>
<input type="text" class="input-number-input" value="0" />
<button class="input-number-button increase" type="button">+</button>
</div>
布局设计原理
- Flexbox布局 :使用
display: inline-flex
实现水平排列 - 按钮固定宽度:左右按钮各占32px固定宽度
- 输入框自适应 :中间输入框使用
flex: 1
占据剩余空间 - 无缝连接 :通过
overflow: hidden
和border-radius
实现视觉统一
css代码如下所示:
css
.ew-input-number {
display: inline-flex;
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
width: 120px;
}
.input-number-button {
width: 32px;
border: none;
background: #f5f5f5;
color: #666;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
padding: 0;
}
.input-number-button:hover {
background: #e8e8e8;
}
.input-number-button:active {
background: #d9d9d9;
}
.input-number-input {
flex: 1;
border: none;
text-align: center;
padding: 4px;
width: 50px;
-moz-appearance: textfield;
}
.input-number-input::-webkit-outer-spin-button,
.input-number-input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.input-number-input:focus {
outline: none;
border: none;
box-shadow: none;
}
事件处理机制
输入框事件处理
javascript
// 输入事件 - 实时验证和格式化
input.addEventListener("input", (e) => {
let value = parseFloat(e.target.value);
if (isNaN(value)) {
value = this.options.min; // 无效输入时重置为最小值
}
this.setValue(value); // 通过setValue进行标准化处理
});
// 失焦事件 - 确保显示正确的值
input.addEventListener("blur", () => {
input.value = this.value; // 显示标准化后的值
});
按钮事件处理
javascript
// 减少按钮
decreaseBtn.addEventListener("click", () => {
this.setValue(this.value - this.options.step);
});
// 增加按钮
increaseBtn.addEventListener("click", () => {
this.setValue(this.value + this.options.step);
});
事件处理策略:
- 实时验证:输入时立即验证和格式化
- 失焦修正:确保显示的值是经过标准化的
- 步进控制:按钮点击时按步长增减
核心算法实现
数值标准化算法
javascript
setValue(value) {
// 1. 范围限制:确保值在[min, max]范围内
value = Math.min(Math.max(value, this.options.min), this.options.max);
// 2. 步长对齐:确保值是步长的整数倍
value = Math.round(value / this.options.step) * this.options.step;
// 3. 状态更新
this.value = value;
// 4. DOM同步
const input = this.element.querySelector(".input-number-input");
input.value = value;
// 5. 回调触发
this.onChange(value);
}
算法分析:
步骤 | 操作 | 目的 | 示例 |
---|---|---|---|
范围限制 | Math.min(Math.max(value, min), max) |
确保值在有效范围内 | min=0, max=100, value=150 → 100 |
步长对齐 | Math.round(value/step) * step |
确保值是步长的整数倍 | step=0.5, value=1.3 → 1.5 |
状态同步 | 更新内部状态和DOM | 保持数据一致性 | 内部this.value 和DOMinput.value 同步 |
输入验证机制
javascript
// 输入验证流程
input.addEventListener("input", (e) => {
let value = parseFloat(e.target.value);
// 无效输入处理
if (isNaN(value)) {
value = this.options.min; // 重置为最小值
}
// 标准化处理
this.setValue(value);
});
验证策略:
- 类型检查 :使用
parseFloat()
解析数值 - NaN处理:无效输入时重置为最小值
- 实时标准化:每次输入都经过完整的标准化流程
CSS样式实现分析
容器样式
css
.ew-input-number {
display: inline-flex; /* Flexbox水平布局 */
border: 1px solid #ddd; /* 统一边框 */
border-radius: 4px; /* 圆角 */
overflow: hidden; /* 隐藏内部元素溢出 */
width: 120px; /* 固定宽度 */
}
按钮样式
css
.input-number-button {
width: 32px; /* 固定宽度 */
border: none; /* 移除默认边框 */
background: #f5f5f5; /* 浅灰背景 */
color: #666; /* 深灰文字 */
cursor: pointer; /* 手型光标 */
display: flex; /* Flexbox居中 */
align-items: center;
justify-content: center;
font-size: 16px; /* 字体大小 */
padding: 0; /* 无内边距 */
}
/* 悬停效果 */
.input-number-button:hover {
background: #e8e8e8;
}
/* 点击效果 */
.input-number-button:active {
background: #d9d9d9;
}
输入框样式
css
.input-number-input {
flex: 1; /* 占据剩余空间 */
border: none; /* 移除边框 */
text-align: center; /* 文字居中 */
padding: 4px; /* 内边距 */
width: 50px; /* 最小宽度 */
-moz-appearance: textfield; /* Firefox移除数字输入框样式 */
}
/* Webkit浏览器移除数字输入框样式 */
.input-number-input::-webkit-outer-spin-button,
.input-number-input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* 焦点样式 */
.input-number-input:focus {
outline: none; /* 移除默认焦点轮廓 */
border: none; /* 移除边框 */
box-shadow: none; /* 移除阴影 */
}
状态管理机制
数据流设计
javascript
// 数据流向:用户输入 → 验证 → 标准化 → 状态更新 → DOM同步 → 回调触发
用户输入/按钮点击
↓
输入验证 (parseFloat)
↓
范围限制 (Math.min/Math.max)
↓
步长对齐 (Math.round)
↓
状态更新 (this.value)
↓
DOM同步 (input.value)
↓
回调触发 (onChange)
状态同步策略
javascript
// 内部状态管理
this.value = this.options.value; // 内部状态
// DOM状态同步
const input = this.element.querySelector(".input-number-input");
input.value = value; // DOM状态
// 回调通知
this.onChange(value); // 外部通知
核心特性分析
数值范围控制
javascript
// 范围限制实现
value = Math.min(Math.max(value, this.options.min), this.options.max);
// 配置示例
const inputNumber = new InputNumber({
min: 0, // 最小值
max: 100, // 最大值
step: 1, // 步长
value: 50 // 初始值
});
步长控制机制
javascript
// 步长对齐算法
value = Math.round(value / this.options.step) * this.options.step;
// 步长示例
step: 0.5 → 值只能是 0, 0.5, 1.0, 1.5, 2.0...
step: 10 → 值只能是 0, 10, 20, 30, 40...
step: 0.1 → 值只能是 0, 0.1, 0.2, 0.3, 0.4...
输入验证策略
输入类型 | 处理方式 | 结果 |
---|---|---|
有效数字 | 直接处理 | 正常显示和计算 |
无效输入 | 重置为最小值 | 防止错误状态 |
超出范围 | 限制在范围内 | 自动修正 |
非步长倍数 | 对齐到最近步长 | 保持步长一致性 |
用户体验优化
- 实时反馈:输入时立即验证和格式化
- 视觉统一:无缝连接的按钮和输入框
- 操作便捷:支持键盘输入和按钮点击
- 状态清晰:失焦时显示标准化后的值
配置选项详解
基础配置
javascript
const inputNumber = new InputNumber({
container: '#container', // 挂载容器
min: 0, // 最小值
max: 100, // 最大值
step: 1, // 步长
value: 50, // 初始值
onChange: (value) => { // 变化回调
console.log('新值:', value);
}
});
高级配置
javascript
const inputNumber = new InputNumber({
min: -100, // 支持负数
max: 100, // 正数范围
step: 0.1, // 小数步长
value: 0, // 初始值
placeholder: '请输入数字', // 占位符
name: 'quantity' // 表单字段名
});
性能优化策略
DOM操作优化
javascript
// 缓存DOM元素引用
const input = this.element.querySelector(".input-number-input");
const decreaseBtn = this.element.querySelector(".decrease");
const increaseBtn = this.element.querySelector(".increase");
事件处理优化
- 事件委托:合理使用事件监听器
- 防抖处理:避免频繁的数值计算
- 条件判断:减少不必要的DOM操作
计算优化
javascript
// 高效的数值处理
value = Math.min(Math.max(value, this.options.min), this.options.max);
value = Math.round(value / this.options.step) * this.options.step;
通过如上的代码,我们就可以得到如下一个inputnumber插件。

📝 总结
InputNumber插件通过以下关键技术实现了功能完整、用户体验良好的数字输入组件:
- DOM结构设计:Flexbox布局,无缝连接的按钮和输入框
- 数值处理算法:范围限制、步长对齐、输入验证
- 事件处理机制:实时验证、失焦修正、按钮操作
- CSS样式实现:跨浏览器兼容、视觉统一、交互反馈
- 状态管理:内部状态、DOM同步、回调通知
该插件在保持简单易用的同时,提供了强大的数值控制功能,是一个设计精良的UI组件实现。
感谢阅读,希望本文对你有帮助,如有帮助,望不吝啬点赞收藏。