原生js实现常规ui组件之input-number篇

在上一节,我们学会了实现一个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: hiddenborder-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插件通过以下关键技术实现了功能完整、用户体验良好的数字输入组件:

  1. DOM结构设计:Flexbox布局,无缝连接的按钮和输入框
  2. 数值处理算法:范围限制、步长对齐、输入验证
  3. 事件处理机制:实时验证、失焦修正、按钮操作
  4. CSS样式实现:跨浏览器兼容、视觉统一、交互反馈
  5. 状态管理:内部状态、DOM同步、回调通知

该插件在保持简单易用的同时,提供了强大的数值控制功能,是一个设计精良的UI组件实现。

感谢阅读,希望本文对你有帮助,如有帮助,望不吝啬点赞收藏。

相关推荐
夕水4 小时前
原生js实现常规ui组件之checkbox篇
前端·javascript
2503_928411565 小时前
9.2 BOM对象
前端·javascript
chinesegf5 小时前
浏览器内存 (JavaScript运行时内存)存储的优劣分析
开发语言·javascript·ecmascript
CaptainDrake6 小时前
React 中 key 的作用
前端·javascript·react.js
全栈技术负责人6 小时前
移动端富文本markdown中表格滚动与页面滚动的冲突处理:Touch 事件 + 鼠标滚轮精确控制方案
前端·javascript·计算机外设
珍宝商店6 小时前
Vue.js 中深度选择器的区别与应用指南
前端·javascript·vue.js
ZTStory7 小时前
JS 处理生僻字字符 sm4 加密后 Java 解密字符乱码问题
javascript·掘金·金石计划
货拉拉技术7 小时前
微前端中的错误堆栈问题探究
前端·javascript·vue.js
掘金017 小时前
Axios 请求去重机制解析:从原理到实践
javascript·前端框架