1. 问题效果图
2. 穿透原因
2.1 原生组件
2.2 层级限制
2.3 原生组件同层渲染
3. 解决办法
- 使用 alwaysEmbed 属性,强制 input 处于同层状态,默认 focus 时 input 会切到非同层状态 (仅在 iOS 下生效)。
此属性设置在安卓手机无效,使用的华为 P40 测试,依然存在穿透!
- 使用 input 标签和 view 标签切换,就是聚焦时使用 input,失去焦点时使用 view。
- 使用 input 进行定位到屏幕外,然后在当前位置使用 view 进行模拟 input 的效果。
- 将 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. 总结
- 使用 alwaysEmbed 在安卓没有效果;
- 使用 input 标签和 view 标签切换,存在抖动问题;
- 使用 visibility: hidden 不能对 input 进行聚焦;
- 使用 position: fixed 不能实现【键盘弹起时,是否自动上推页面】;
- 使用 position: absolute 上边的问题都能解决,但是由于光标是使用 css 模拟的,因此高度和颜色不能根据字体颜色改变。