编辑器组件开发之数值输入组件

625 阅读2分钟

简介

在线设计协作工具 MasterGo/即时设计/Pixso 都有在线编辑器,编辑器中最常用的就是数值输入和颜色输入组件,本章节主要介绍如何实现一个体验尚可的数值输入组件。

适用场景

可视化编辑器、Web设计工具、管理平台个性化输入表单

功能分析

  • 实现手动输入数值
  • 可以拖动增减数值
  • 上下键增减数值
  • 最大和最小值限制
  • 可配置图标和单位显示
  • 可使用v-model绑定状态

技术实现

样式与结构

整体使用Flex 左右布局

  • 左边为图标,右边为数值和单位
  • 这里图标为必须的,因为拖动改变数值需要有一个响应区域
  • 单位单独一个元素不放在 Inputvalue 中的好处是不用去格式化 value

基本样式和结构

CSS

.prop-input-number {
    display: flex;
    &-left {
        width: 30px;
        height: 30px;
    }
    &-right {
        width: 60px;
        border: 1px solid #ccc;
        display:flex;
        &-input {
            outline: 0;
            border: 0;
            // 没有单位时,是100%宽,有单位时,是计算出来的固定宽度
            flex: 1;
        }
        &-unit {
            flex:1;
        }
    }
}

HTML

 <div class="prop-input-number">
    <div class="prop-input-number__pointer" style={this.pointerStyle}></div>
    <section
      class="prop-input-number__left"
      onMousedown={this.onMousedown}
    >
      {this.renderIcon()}
    </section>
    <section class="prop-input-number__right">
      <input
        class="prop-input-number__right-input"
        value={this.value}
        onInput={this.onInput}
        onFocus={this.onFocus}
        onKeydown={this.onKeydown}
        type="text"
        style={{
          width: this.unit ? `${(this.value + '').length * 8}px` : '100%',
        }}
      />
      <div class="prop-input-number__right-unit">{this.unit}</div>
    </section>
    </div>

功能实现

鼠标样式锁定

当鼠标在图标上的时候,显示鼠标样式为 cursor:ew-resize; 表示可以左右拖动,但实际上会有一个问题,当鼠标离开图标区域后,左右拖动的鼠标样式就失效了,参考了其它的编辑器实现方案,大部分是先生成一个div,fixed定位,默认隐藏, 当在鼠标拖动时,这个div显示到鼠标下方,动态调整这个div的位置,让div始终保持在鼠标下面,并给这个div设置鼠标样式为 cursor:ew-resize;

gif图

2022-05-06 09.35.16

实现

遮罩Div默认样式

    transform: translateX(-150px);
    width: 300px;
    height: 24px;
    position: fixed;
    left: 0;
    top: 13px;
    z-index: 999;
    display: none;

当鼠标按下时,设置 actived 为 true,显示遮罩div,并动态设置div的位置,位置可根据当前鼠标在屏幕上的位置动态计算出来

优化

这样可以实现鼠标在离开组件区域后,任然能保持鼠标样式,动态计算遮罩div样式也可以优化一下,比如:遮罩div默认全屏隐藏,当鼠标按下时,直接设置遮罩Div样式为block 显示就可以了。这样不用计算位置,减少性能消耗。

拖动改变数值

监听事件,计算数值,定义鼠标按下事件,监听 mousemovemouseup 事件,计算水平移动的距离,在鼠标抬起的时候并移除两个事件的注册,这里暂只记录一下水平距离就可以了,下面可以根据当前水平距离来计算当前数值。

onMousedown(e) {
  const startX = e.clientX;
  this.acitved = true;
  const move = (moveEvent) => {
    moveEvent.stopPropagation();
    moveEvent.preventDefault();
    const currX = moveEvent.clientX;
    const distance = currX - startX;
  };

  const up = () => {
    this.acitved = false;
    this.prevDistance = 0;
    document.removeEventListener('mousemove', move, true);
    document.removeEventListener('mouseup', up, true);
  };
  document.addEventListener('mousemove', move, true);
  document.addEventListener('mouseup', up, true);
},

数值递增与递减的控制

鼠标移动时,判断运动方向,如果是向右移动,则将当前数值加1,如果是向左移动,则将当前数据减1(这里我最初的实现是直接 value + distance, 这样虽然可以实现,但是数据的增加的频率过快,无法做到匀速递增,且不方便控制数值大小)

这里要实现匀速递增和递减,我的方法是,判断上一次的distance 和当前的ditance比较,如果上一次的移动的距离比当前移动的距离小,则判断为向右,即 value+=1,反之,则 value-=1,这里要处理上一次和这次相等的情况,相等时,则不做处理

const distance = currX - startX;
        
if (this.prevDistance === distance) return;
const isAdd = this.prevDistance < distance;
this.prevDistance = distance;
this.handleDragChange(isAdd);
 handleDragChange(isAdd) {
  const dis = isAdd ? 1 : -1;
  this.value = Math.floor(dis + this.value);
},

向上加向下减功能实现

这个很简单,监听 onKeydown 事件,判断键值是上还是下,进行加1或减1

onKeydown(e) {
  const { keyCode } = e;
  const { value } = this;
  if (![38, 40].includes(keyCode)) return;
  this.value = keyCode === 38 ? value + 1 : value - 1;
}

最小值和最大值限制

上面的 this.value 直接赋值改调用函数 calculateValue

// 计算数值
calculateValue(value) {
  // 限制最小值
  let limitValue = Math.max(value, this.min);
  // 限制最大值
  if (isNumber(this.max)) {
    limitValue = Math.min(limitValue, this.max);
  }
  this.$emit('change', limitValue);
},

单位显示

单位显示主要考虑样式布局,如果没有单位,那么input 输入框是 width: 100%, 如果有单位,他们被 flex 容器包裹,input 的宽度需要根据字符数来动态计算出来,这样可以保证单位紧跟在数值后面,具体请参考 Pixso 的交互效果

改进和优化

  • Light/Dark模式
  • 允许小数数值(需要考虑浮点数溢出)

总结

一个小小的输入组件,在实现的过程中,交互样式布局这些其实都可以参考第三方网站,内部js的逻辑其实也能猜到一二,但在实现的过程中还是有很多小的细节要注意,同时注重用户体验的提升,从美观度、易用度各方面都要找到一个平衡,编码也是种艺术,尤其是前端的开发工作。