记录微信小程序Input的坑(Taro)

3,817 阅读5分钟

input优先级非常高

在我们日常页面布局中,经常会遇到上(Header)中(Content)下(Footer)这样的布局,如下图所示。header和footer均fixed布局。如果页面布局不止一屏,且input布局靠后,由于input优先级非常高,当input和Footer重合时,就会出现文字透出。

解决方案:

1.使用cover-view

cover-view是可以覆盖在原生组件之上的文本视图。本文的代码实现都是基于Taro框架实现的。

<View
    className='footer'
    style={{ position: 'fixed', height: '140rpx', lineHeight: '140rpx', textAlign: 'center' }}
>
    FOOTER
</View>

将View用CoverView代替。由于CoverView只支持嵌套 cover-view、cover-image,button,所以内部的view也得用CoverView替代。

<CoverView
    className='footer'
    style={{ position: 'fixed', height: '140rpx', lineHeight: '140rpx', textAlign: 'center' }}
>
    FOOTER
</CoverView>

用CoverView代替后,在页面滚动时,input的文字也不会透出了。

缺陷一:当输入框在footer下面时,这时如果点击input,文字仍会透出。

缺陷二:舍弃自定义组件。我们常常在一个项目中,会将footer封装成组件,因为项目中会常常用到。footer还会嵌套一些自定义组件,但是由于CoverView嵌套限制,footer内部自定义组件就只能舍弃了。

缺陷三:CoverView的修补方式,是通过改造非input组件来避免文字透出的问题,会导致一旦交互上会覆盖在input上的组件,都需要CoverView来封装。比如弹窗。

补充:就本文讨论的Footer。我们在页面中常常会用到这个组件。由于它是固定定位,所以是不占文本流的。为了完整展示出我们的Content部分,不被Footer遮住,我们需要一个placeholder来把他的位置保留下来。

<View>
  <CoverView
    className='footer'
    style={{ position: 'fixed', height: '140rpx', lineHeight: '140rpx', textAlign: 'center' }}
  >
    FOOTER
  </CoverView>
  <View className='placeholder' style={{ height: '160rpx' }} />
</View>

2.input和view相互替换

import Taro from '@tarojs/taro';
import { Input, View } from '@tarojs/components';

import './index.scss';
import useState = Taro.useState;

interface ZInputProps {
  onInput?: (value) => void;
  onBlur?: () => void;
}

const ZInput = (props: ZInputProps) => {
  const { onInput, onBlur } = props;
  const [value, setValue] = useState('');
  const [focus, setFocus] = useState(false);

  const handleInput = (e) => {
    const value = e.target.value;
    setValue(value);
    onInput && onInput(value);
  };

  const handleBlur = () => {
    setFocus(false);
    onBlur && onBlur();
  };

  return (
    <View>
      {
        focus ? (
          <Input value={value} className='input' focus={focus} onBlur={handleBlur} onInput={handleInput} />
        ) : (
          <View hidden={false} className='input-view' onClick={() => setFocus(true)}>{value + ''}</View>
        )
      }
    </View>
  );
};

export default ZInput;

优点:由于这个方案是通过改造input组件来实现的,所以其他可能会覆盖在input的组件不需要额外的处理,比如弹窗。

缺陷一:由于在input和view中切换,当点击view,input虽然获得焦点,但是光标的位置将永远在文末。

缺陷二:和方案一一样,当input(view)和footer重合时,如果这时候编辑input,文字仍会透出,只是持续时间很短,因为一旦focus了,键盘就会往上推。

3.用ScrollView包裹Content + input和view相互替换

由于方案一二均有input(view)和footer重合时,编辑input,文字仍会透出的问题,为了修这个问题。我们将ScrollView和方案二进行结合,用scrollView来包裹Content,这样避免了input(view)和footer的重合,如果有交互组件需要覆盖在input上面,input和view相互替换,也不会有文字透出的问题。

缺陷一:由于有ScrollView的影响,可能会导致input没有全部显示出来,且在输入中,用户可能会看不到自己的输入。所以不太建议采用改方案。

总结

几个方案,都各有瑕疵。几个方案相比较,个人认为第二个方案要好一些。因为从封装 input自身出发,避免了无穷尽的用cover-view封装各个可能会覆盖在input的组件,且也不会限制自定义组件。第三个方案虽然解决了问题,但是确实用户体验不是很好,不太建议。

input在某些输入法时,输入英文时,不会触发input事件

在某些输入法输入英文时,视觉上看起来用户是已经输入进去了,但是当失焦后,输入框内容就清空了。需要用户在输入后,手动点击键盘的文字,才算是真正输入了。这多多少少会降低用户体验。特别如果是用于搜索的输入框,当用户输入了内容,点击搜索按钮,发现自己搜索了空气时,体验感就大大降低了。

解决方案

oninput实时获取输入框value,本来我们常常会将input封装为受控组件,用来实时做一些校验什么的。在失焦时,获取事件源value,和实时获取的value做对比,如果不一致,用失焦的value更新。

import Taro, { useState } from '@tarojs/taro';
import { Input, View } from '@tarojs/components';

import './index.scss';

const InputDemo = () => {
  const [value, setValue] = useState('');

  const onInput = (e) => {
    setValue(e.target.value);
  };

  const onBlur = (e) => {
    if (e.target.value !== value) {
      setValue(e.target.value);
    }
  };

  return (
    <View className='index'>
      <Input
        className='input'
        value={value}
        placeholder='请输入'
        onInput={onInput}
        onBlur={onBlur}
      />
    </View>
  );
};

export default InputDemo;

经过时间推移,后来再在该输入法测试时,发现它的表现和之前不一样了。测试发现,即时用户不在键盘上选中,当失焦后,输入的内容会保留。log发现,在用户输入英文时,依旧不会触发input事件,但是当失焦后,会触发一次oninput事件。如下图所示,只log了一次。这样的话,其实已经能够满足我们的基本需求的。虽然在该输入法,我们做不到实时获取值,当失焦时,也能触发oninput事件,可以将内容保留下来,也足够了。