菜鸟读文档-Vant Weapp-12(点击弹出输入框实践-3)

1,804 阅读7分钟

正文

在本期文章中, 我们将对上期已经初具雏形的点击弹出输入框结构进行进一步的优化

点击控制

二次弹出

如图可以看出, 当我们再次点击按钮弹出输入框时, 本机键盘并不会上拉, 而且输入框会固定在上一次的位置

这一问题主要是: 我们在第一次点击按钮时, 对控制本机键盘上拉的变量 inputFocus 和控制输入框位置的变量 keyboardHeight 进行设值后未对其初始化, 使其仍保留着之前的数值, 从而在第二次点击时本机键盘不会上拉, 输入框也会出现在先前的位置

显然, 要解决该问题, 我们要从变量 inputfocus 和变量 keyboardHeight 入手

  • inputfocuse

    为了保证在点击按钮弹出输入框时, 变量 inputFocus 是从 false 变为 true, 我们只需要在点击隐藏输入框时, 将 inputFocus 重置为 false 即可

    但我们比较点击显示和点击隐藏这两个函数可以发现, 变量 inputFocus 是随着控制整个结构显示隐藏的变量 showOverlay 变化而变化的

    因此, 比起在两个函数中添加相同的修改 inputFocus 语句, 我们可以使用微信小程序在 Component 构造器中提供的数据监听器 observers, 实现变量 inputFocus 随变量 showOverlay 变化而变化, 同时避免相同的逻辑编写两次

    observers: {
        showOverlay: function () {
          setTimeout(() => {
            this.setData({
              inputFocus: !this.data.inputFocus
            })
          }, 100);
        }
      },
    

    由下图可以看出, 修改后, 此时第二次点击按钮也会弹出本机键盘

  • keyboardheight

    但我们从图中也可以看出: 当第二次点击按钮时, 由于变量 keyboardheight 已经于第一次获取到了本机键盘上方的位置数值, 本次输入框的出现较第一次会快很多, 从而产生一种脱节感

    对于该问题, 目前我的解决办法是: 因为第一次点击弹出输入框时, 本机键盘上拉和输入框位置变化之间的配合效果较好, 所以我在点击隐藏输入框时对变量 keyboardheight 进行一次重置, 这样就保证每一次输入框出现的效果都会和第一次一样

    onClickHide: function () {
      this.setData({
        showOverlay: !this.data.showOverlay,
        keyboardHeight: 0
      });
    },
    

    改进后的效果如下图

下拉键盘隐藏输入框

目前, 我们隐藏整个结构是通过点击遮罩层实现. 但除此之外, 我们点击下拉本机键盘也表明我们需要隐藏整个结构, 而目前我们对于此功能尚未实现

对于此问题, 我们可以简化为: 当本机键盘收回时, 触发某一事件

我们首先查看 field 组件, 发现当键盘高度发生变化的时触发的事件 bind:keyboardheightchange 可以用于实现我们的需求

但该事件并不仅仅是在键盘下拉时触发, 键盘上拉时也会触发. 因此我们考虑用该事件替换之前用于在键盘上拉时设置输入框位置的事件 bind:focus

该事件需要实现的逻辑是

  • 键盘上拉时设置输入框的位置
  • 键盘下拉时隐藏整个结构
definePosition: function (e) {
  if (this.data.keyboardHeight !== 0 && e.detail.height === 0) {
    // 下拉键盘则隐藏输入框
    this._hideInput();
  } else {
    // 弹出键盘则将设置输入框位置
    this.setData({
      keyboardHeight: e.detail.height
    });
  }
},

// 由于下拉键盘和点击遮罩层触发的逻辑相同, 我们将该部分独立
_hideInput: function () {
  this.setData({
    showOverlay: !this.data.showOverlay,
    keyboardHeight: 0
  });
},

自此, 我们就实现了下拉键盘隐藏整个结构的功能

点击输入

输入确认

输入框的功能是: 当用户完成内容输入后, 点击右侧按钮提交输入内容. 由于用户可能需要多次输入内容, 我们希望在点击提交按钮后, 整个输入结构仍能保留

但此时在我们的结构中, 若点击右侧因开启 is-link 产生的箭头按钮, 并不会触发点击尾部图标对应的事件 bind:click-icon, 因此我们将其用 icon 接口替换

<van-field
  model:value="{{ value }}"
  focus="{{ inputFocus }}"
  placeholder="打算做什么呢?"
  left-icon="circle"
  right-icon="envelop-o"
  confirm-hold
  adjust-position="{{ false }}"
  bind:keyboardheightchange="definePosition"
  bind:click-icon="submit"
/>

替换后却又出现一个问题, 右侧的提交按钮时灵时不灵. 但无论哪种情况, 进行一次点击后, 整个输入结构都会消失, 效果如下图所示

经过测试可以发现, 当我们点击右侧确认输入按钮, 无论是成功还是失败, 都会触发位于父元素 overlay 上的 bind:click="onClickHide" 事件, 即隐藏整个输入结构

可以看出, 在本次点击过程中, click 发生了事件冒泡, 对此, 我们可以在先前用于控制输入框位置的父元素 view 中放置一个 catch 事件来阻止事件冒泡

同时, 我们打开 field 组件开放的接口 hold-keyboard, 即 focus 时, 点击页面不会收起键盘

  • WXML

    <view
      class="input-wrap"
      style="bottom: {{ keyboardHeight }}px !important;"
      catch:tap="noop"
    >
      <van-field
        model:value="{{ value }}"
        focus="{{ inputFocus }}"
        placeholder="打算做什么呢?"
        left-icon="circle"
        right-icon="envelop-o"
        confirm-hold
        hold-keyboard
        adjust-position="{{ false }}"
        bind:keyboardheightchange="definePosition"
        bind:click-icon="submit"
      />
    </view>
    
  • JavaScript

    noop: function () {}
    

修改后的效果如下图所示, 可见目前已经基本实现了多次输入的功能

输入刷新

此时我们的输入仍存在一点小问题, 那就是我们在输入框中输入一些内容, 但不点击右侧图标确认, 而是隐藏结构, 当我们再次点击按钮弹出输入框时, 输入框中仍会保存上一次输入的内容, 如下图所示:

对此呢, 我们可以在每次弹出输入框时, 对输入框的内容进行一次刷新

onClickShow: function () {
  this.setData({
    showOverlay: !this.data.showOverlay,
    value: ""
  });
}

改进后的效果如下图所示:

样式

弹性

在我们的点击弹出输入框结构中, 还剩下输入框没有实现弹性, 对其我们需要实现的弹性大致有三部分, 分别是:

  1. 输入框的高度
  2. 左右图标的大小
  3. 输入文字的大小

field 组件文档中我们可以看出: 对于输入框, 文本和图标, 组件开放了三个外部样式接口, 且图标仅开放了右侧图标, 因此使用外部样式类来实现弹性显然会较为繁复

因此, 我们将采取使用 CSS 变量的方法来实现输入框的弹性

通过查看 field 组件的源码我们可以发现: 整个输入框都是放置于一个 cell 组件中, 而通过查看该 cell 组件的样式(如下图), 我们可以发现其中对于高度, 图标大小和文字大小都开放了 CSS 变量

我们可以在一个样式类中对这三个 CSS 变量进行设置, 然后将其直接绑定到 field 组件上即可实现整个输入框的弹性

  • CSS

    .input {
      --cell-line-height: 4vh;
      --cell-font-size: 3vh;
      --field-icon-size: 4vh;
    }
    
  • WXML

    <van-field
      class="input"
      model:value="{{ value }}"
      focus="{{ inputFocus }}"
      placeholder="打算做什么呢?"
      left-icon="circle"
      right-icon="envelop-o"
      confirm-hold
      hold-keyboard
      adjust-position="{{ false }}"
      bind:keyboardheightchange="definePosition"
      bind:click-icon="submit"
    />
    
  • iPhone 6/7/8

  • iPhone X

按钮变化

为了让用户在使用时能够明确理解右侧按钮是用于确认本次输入的, 我们可以在用户进行输入后让右侧的确认按钮图标发生一些变化

在此处我采用的方案是放置两个 icon 元素, 然后使用 wx:if 判断输入内容 value 是否为空来判断显示哪一个 icon 元素, 这样做的好处是可以将确认输入事件绑定在 value 不为空对应的 icon 上, 从而避免了当输入内容为空时点击右侧确认按钮触发无效事件

<van-field
  class="input"
  model:value="{{ value }}"
  focus="{{ inputFocus }}"
  placeholder="打算做什么呢?"
  confirm-hold
  hold-keyboard
  adjust-position="{{ false }}"
  bind:keyboardheightchange="definePosition"
  bind:confirm="submit"
  data-value="{{ value }}"
>

  <van-icon
    wx:if="{{ value === '' }}"
    slot="right-icon"
    name="fire-o"
  />
  <van-icon
    wx:else
    slot="right-icon"
    name="fire"
    bind:click="submit"
    data-value="{{ value }}"
  />

</van-field>

最终呈现出的效果如下图

预告

在本次文章中, 我们一步步地对我们的点击弹出输入框结构进行优化, 终于达到了一个勉强称得上是可用的层次

但作为整个页面的一小部分, 该结构对应的代码量却不小, 这显然不是一个好的实践

在下一期, 我们将利用之前学到的组件知识, 将整个结构处理为自定义组件, 给我们的代码减减负:)