【taro react】 ---- 解决 input 、textarea 层级穿透

0 阅读3分钟

1. 问题效果图

输入图片说明

2. 穿透原因

2.1 原生组件

输入图片说明

2.2 层级限制

输入图片说明

2.3 原生组件同层渲染

输入图片说明

3. 解决办法

  1. 使用 alwaysEmbed 属性,强制 input 处于同层状态,默认 focus 时 input 会切到非同层状态 (仅在 iOS 下生效)。 输入图片说明

此属性设置在安卓手机无效,使用的华为 P40 测试,依然存在穿透!

  1. 使用 input 标签和 view 标签切换,就是聚焦时使用 input,失去焦点时使用 view。
  2. 使用 input 进行定位到屏幕外,然后在当前位置使用 view 进行模拟 input 的效果。
  3. 将 input 和 view 放在同一位置,但是 input 使用 visibility: hidden 不显示。

4. alwaysEmbed 实现

4.1 代码

    return <Input
      alwaysEmbed={true}
      value={value}
      onInput={onInput} 
      onBlur={onBlur} />

4.2 效果

输入图片说明

5. input 和 view 切换

5.1 代码

import { View, Input } from '@tarojs/components';
import { useReactive } from '@utils/hooks';
import api from '@utils/api';
import './index.scss';

const RuiInput = (props) => {
  let {  value, className, placeholderClass, placeholder } = props;
  // 是否聚焦
  const state = useReactive({
    value: value || '',
    isFocus: false
  })
  // 聚焦
  const handleClick = () => {
    state.isFocus = true;
  }
  // 失焦
  const onBlur = (e) => {
    state.isFocus = false;
    props?.onBlur?.(e);
  }
  // 获取输入值
  const onInput = (e) => {
    state.value = e.detail.value;
    props?.onInput?.(e);
  }
  // 判断返回
  const renderInputHtml = () => {
    if(state.isFocus){
      return <Input 
      {...props}
      focus={state.isFocus}
      value={state.value}
      onInput={onInput} 
      onBlur={onBlur} />
    } else {
      return <View 
      onClick={handleClick}
      style={{whiteSpace: 'nowrap'}}
      className={api.classNames({
        'rui-current-input': true,
        [className]: className,
        [placeholderClass]: !value
      })}>{value || placeholder}</View>
    }
  }

  return renderInputHtml()
}
export default RuiInput;

5.2 效果

输入图片说明

5.3 添加模拟 input 样式

.rui-current-input{
  cursor: auto;
  display: block;
  font-family: UICTFontTextStyleBody;
  height: 1.4rem;
  line-height: 1.4rem;
  overflow: hidden;
  text-overflow: clip;
  white-space: nowrap;
}

5.4 效果

输入图片说明

5.5 注意

可以看出在 input 和 view 切换的时候,会出现轻微的抖动现象,即便是使用 css 模拟到和 input 差不多,依然存在抖动。

6. 使用 visibility: hidden

visibility: hidden 是CSS的一个属性,它用于隐藏元素,但隐藏的元素仍然占据页面上的空间。隐藏的元素不能获取焦点的原因是,即使元素不可见,它仍然占据着布局空间,所以用户界面(如浏览器的tab顺序)仍然将其视为可交互元素的一部分。

7. 使用 position: fixed

7.1 代码

import { View, Input, Text } from '@tarojs/components';
import { useReactive } from '@utils/hooks';
import api from '@utils/api';
import './index.scss';

const RuiInput = (props) => {
  let {  value, className, placeholderClass, placeholder } = props;
  // 是否聚焦
  const state = useReactive({
    value: value || '',
    isFocus: false
  })
  // 聚焦
  const handleClick = () => {
    state.isFocus = true;
    console.log(state.isFocus)
  }
  // 失焦
  const onBlur = (e) => {
    state.isFocus = false;
    props?.onBlur?.(e);
  }
  // 获取输入值
  const onInput = (e) => {
    state.value = e.detail.value;
    console.log(state.value)
    props?.onInput?.(e);
  }
  // 判断返回
  const renderInputHtml = () => {
    return <View 
    onClick={handleClick}
    className={api.classNames({
      'rui-pr': true,
      [className]: className
    })}>
      <View
      style={{whiteSpace: 'nowrap'}}
      className={api.classNames({
        'rui-flex-ac rui-pr rui-current-input-content': true,
        [className]: className,
        [placeholderClass]: !state.value
      })}>
        <Text>{state.value}</Text>
        {
          state.isFocus ? <Text className='rui-flicker-li'></Text> : <></>
        }
        {
          state.value ? <></> : <Text className='rui-current-input-placeholder'>{placeholder}</Text>
        }
        <Text className='rui-hidden'>{placeholder}</Text>
      </View>
      <Input 
        {...props}
        className={api.classNames({
          'rui-current-input': true,
          [className]: className
        })}
        focus={state.isFocus}
        value={state.value}
        onInput={onInput} 
        onBlur={onBlur} />
    </View>
  }

  return renderInputHtml()
}
export default RuiInput;

7.2 样式

.rui-current-input{
  position: fixed;
  top: -300vh;
  left: -300vw;
}
.rui-current-input-content{
  line-height: 1.4;
}
.rui-current-input-placeholder{
  position: absolute;
  top: 50%;
  left: 0;
  transform: translateY(-50%);
}
.rui-flicker-li{
  flex: none;
  width: 2px;
  height: 40px;
  background-color: #D8D8D8;
  animation: flicker 1000ms linear infinite;
}
@keyframes flicker{
  0%{opacity: 1;}
  50% { opacity: 1; }
  50.01% { opacity: 0; }
  100%{opacity: 0;}
}

7.3 问题

当聚焦时,输入的键盘不会实现【键盘弹起时,是否自动上推页面】。

8. 使用 position: absolute

8.1 代码

import { View, Input, Text } from '@tarojs/components';
import { useReactive } from '@utils/hooks';
import api from '@utils/api';
import './index.scss';

const RuiInput = (props) => {
  let {  value, className, placeholderClass, placeholder } = props;
  // 是否聚焦
  const state = useReactive({
    value: value || '',
    isFocus: false
  })
  // 聚焦
  const handleClick = () => {
    state.isFocus = true;
    console.log(state.isFocus)
  }
  // 失焦
  const onBlur = (e) => {
    state.isFocus = false;
    props?.onBlur?.(e);
  }
  // 获取输入值
  const onInput = (e) => {
    state.value = e.detail.value;
    console.log(state.value)
    props?.onInput?.(e);
  }
  // 判断返回
  const renderInputHtml = () => {
    return <View 
    onClick={handleClick}
    className={api.classNames({
      'rui-pr': true,
      [className]: className
    })}>
      <View
      style={{whiteSpace: 'nowrap'}}
      className={api.classNames({
        'rui-flex-ac rui-pr rui-current-input-content': true,
        [className]: className,
        [placeholderClass]: !state.value
      })}>
        <Text>{state.value}</Text>
        {
          state.isFocus ? <Text className='rui-flicker-li'></Text> : <></>
        }
        {
          state.value ? <></> : <Text className='rui-current-input-placeholder'>{placeholder}</Text>
        }
        <Text className='rui-hidden'>{placeholder}</Text>
      </View>
      <Input 
        {...props}
        className={api.classNames({
          'rui-current-input': true,
          [className]: className
        })}
        focus={state.isFocus}
        value={state.value}
        onInput={onInput} 
        onBlur={onBlur} />
    </View>
  }

  return renderInputHtml()
}
export default RuiInput;

8.2 样式

.rui-current-input{
  position: absolute;
  bottom: -10px;
  left: -300vw;
}
.rui-current-input-content{
  line-height: 1.4;
}
.rui-current-input-placeholder{
  position: absolute;
  top: 50%;
  left: 0;
  transform: translateY(-50%);
}
.rui-flicker-li{
  flex: none;
  width: 2px;
  height: 40px;
  background-color: #D8D8D8;
  animation: flicker 1000ms linear infinite;
}
@keyframes flicker{
  0%{opacity: 1;}
  50% { opacity: 1; }
  50.01% { opacity: 0; }
  100%{opacity: 0;}
}

8.3 效果

输入图片说明

9. 总结

  1. 使用 alwaysEmbed 在安卓没有效果;
  2. 使用 input 标签和 view 标签切换,存在抖动问题;
  3. 使用 visibility: hidden 不能对 input 进行聚焦;
  4. 使用 position: fixed 不能实现【键盘弹起时,是否自动上推页面】;
  5. 使用 position: absolute 上边的问题都能解决,但是由于光标是使用 css 模拟的,因此高度和颜色不能根据字体颜色改变。