大家都知道,H5的input框,唤醒手机的键盘在android和ios上展现的不同。需求要求实现我们这边实现一个自己的键盘,无奈需求方还是大佬,搬砖者还是乖乖的想想实现(笔者这边采用的react框架,所以采用了antd mobile组件库)
首先要实现一个键盘,大家的思路是怎么样的呢?
- 用li循环呗,9键出来。
- 用div模拟input + 光标。
第一个很是简单,用组件库的Modal里面嵌套我们自己的循环呗,就这样整呗。
tsx
const numLen = [1, 2, 3, 4, 5, 6, 7, 8, 9]
export default class BcMobileKeyboard extends React.Component<any, any>{
constructor(props) {
super(props)
}
// 获取当前点击的数字,调用父亲的方法传递过去
getCurNum = (num) => {
this.props.getNumber(num)
}
// 删除当前的数字
delNum = () => {
this.props.delNumber()
}
// 点击完成的触发
complate = () => {
this.props.complate()
}
render () {
const { className, visible = true, maskClosable = false, animationType = 'slide-up', popup = true } = this.props
return (
<Modal
className={`key-board-box ${className}`}
popup={popup}
maskClosable={maskClosable}
visible={visible}
animationType={animationType}
>
<div className='board-top'>
<button onClick={this.complate.bind(this)}>完成</button>
</div>
<ul className="board-num">
{
numLen.map(item => {
return <li onClick={this.getCurNum.bind(this, item)}>{item}</li>
})
}
{/* 这个li单纯为了布局,让它占住位置 */}
<li style={{visibility: 'hidden'}}></li>
<li className="zero" onClick={this.getCurNum.bind(this, 0)}>0</li>
{/* 删除的icon, 如果复制需要替换这个成你们自己的icon */}
<li><i className="icon iconfont del-icon" onClick={this.delNum.bind(this)}></i></li>
</ul>
</Modal>
)
}
}
复制代码
键盘的样式
@import 'assets/style/px2rem.scss';
.key-board-box{
overflow: hidden;
-moz-user-select:none;/*火狐*/
-webkit-user-select:none;/*webkit浏览器*/
-ms-user-select:none;/*IE10*/
-khtml-user-select:none;/*早期浏览器*/
user-select:none;
-webkit-touch-callout:none ;
-webkit-text-size-adjust:none ;
-webkit-tap-highlight-color:transparent ;
-webkit-user-select:none ;
.board-top{
height: px2rem(44);
display: flex;
justify-content: flex-end;
align-items: center;
button {
outline: none;
border: 0;
padding-right: px2rem(15);
color: #508CEE;
font-size: px2rem(18);
}
}
.board-num{
background: #D2D5DB;
overflow: hidden;
height: px2rem(216);
padding-left: px2rem(6);
li{
margin-right: px2rem(6);
margin-top: px2rem(6);
width: 31.7%;
float: left;
height: px2rem(46);
background:rgba(255,255,255,1);
box-shadow:0px 1px 0px 0px rgba(132,134,136,1);
border-radius: px2rem(5);
display: flex;
justify-content: center;
align-items: center;
font-size: px2rem(25);
font-family:Helvetica;
color:rgba(0,0,0,1);
}
.zero{
margin-bottom: px2rem(5);
}
li:last-child {
background: none;
border: none;
box-shadow: none;
.del-icon {
color: #3F434A;
}
}
}
}
复制代码
其实主要的就是有了键盘, 那么我们的input框是不是要有啊?那就高仿一个呗?开整
思考问题:
- placeholder的存在?
- 一闪一闪的光标存在?光标具体存在的位置?
- 他的点击事件?(没有拓展失焦、聚焦功能)?
说一下我的具体思路把。
- 初始我在input框里没有任何内容的时候,我用伪类来实现的光标的存在。(css动画)
- 就是input框的内容我任意点击光标的位置改变,采用的是每一个数字我都是一个span,点击会拿取下标,在添加空的span再来实现光标。
- 我用'only'做的唯一标示,让我更有利的判断。
- 关闭弹窗的时候,我这个input的span都要删掉,也就是将数组清空。
tsx
import React from 'react'
import './style.scss'
import { BcMobileKeyboard } from 'container/index'
interface Rules {
className?: string,
placeholder?: string,
isShowBoard?: boolean,
max?: number,
refs?: Function,
notifyComplate?: Function,
onRef?: any
}
export default class BcImitateInput extends React.Component<Rules, any>{
constructor(props) {
super(props)
this.state = {
curNum: [], // 用来记录当前div里面的所有值
selectedInd: null // 当前点击的光标的位置
}
}
// 拿到键盘点击的数字
setNumberHandle = (num) => {
let { curNum, selectedInd } = this.state
let flag = curNum.some((item) => {return item == 'only'}) // 数据存在only,就表示光标在中间存在过
let max = flag ? this.props.max + 1 : this.props.max // 光标只有一次,所有她的最大值也加1
if (curNum.length >= max) return false
if (selectedInd != null) { // 表示光标在中间存在过 增删内容
curNum.splice(selectedInd, 0, num) // 添加内容
this.setState({
curNum,
selectedInd: selectedInd + 1 // 光标位置也随之增加
})
} else { // 表示正常流程走下来。
curNum.push(num)
this.setState({
curNum
})
}
}
// 删除键盘点击的数字
delNumberHandle = () => {
let { curNum, selectedInd } = this.state
if (curNum.length) {
if (selectedInd - 1 >= 0) { // 表示从中间删除内容
curNum.splice(selectedInd - 1, 1)
this.setState({
curNum,
selectedInd: selectedInd - 1 // 当然光标随之减少
})
} else if (selectedInd == null) { // 表示正常的删除内容
curNum.pop()
this.setState({
curNum
})
}
}
}
// 点击键盘中的完成
complateHandle = () => {
let curNum = this.state.curNum
for (let i = 0; i < curNum.length; i++) {
if (curNum[i] == 'only') {
curNum.splice(i, 1)
break;
}
}
this.props.notifyComplate(curNum.join('')) // 准确的把当前的数据通知出去
}
// 高仿的input的点击事件
inputClickHandle = (e) => {
// 这里是为了点击div的任何地方 如果不在数字的中间,那么我们要让光标回到最后的位置
let { curNum, selectedInd } = this.state
if (curNum.length && selectedInd != null) {
for (let i = 0; i < curNum.length; i++) {
if (curNum[i] == 'only') {
curNum.splice(i, 1)
break;
}
}
this.setState({
selectedInd: null
})
}
}
// 点击当前的span获取当前光标的位置
curNumHandle = (ind, e) => {
if (e) { // 为了阻止冒泡
e.stopPropagation();
e.preventDefault();
} else {
window.event.returnValue = false;
window.event.cancelBubble = true;
}
let { curNum } = this.state
for (let i = 0; i < curNum.length; i++) {
if (curNum[i] == 'only') {
curNum.splice(i, 1)
break;
}
}
curNum.splice(ind, 0, 'only')
this.setState({
curNum,
selectedInd: ind
})
}
// 初始化数据 就是为了关闭这个弹窗的时候,数据不可以保留
initCodeNum = () => {
this.setState({
curNum: [],
selectedInd: null
})
}
setClass = () => {
// 这里的class是为了用伪类来实现一些光标
let { curNum, selectedInd } = this.state
if (selectedInd == null && !curNum.length) return 'not'
if (selectedInd != null) return ''
if (selectedInd == null && curNum.length) return 'yes'
}
render() {
const { curNum } = this.state
const { placeholder = '输入验证码', className, isShowBoard } = this.props
return (
<div>
<div className={`${this.setClass()} ${className} imitate-input`} placeholder={placeholder} onClick={this.inputClickHandle}>
{
curNum.length ? curNum.map((ele, index) => {
return <span className={`${ele == 'only' ? 'yes' : ''}`}
onClick={(e) => { this.curNumHandle(index, e) }}>
{ele == 'only' ? '' : ele}
</span>
}) : ''
}
</div>
{/* 就是上文的组件 */}
<BcMobileKeyboard
visible={isShowBoard}
getNumber={this.setNumberHandle}
complate={this.complateHandle}
delNumber={this.delNumberHandle}>
</BcMobileKeyboard>
</div>
)
}
componentWillMount ():void { // 把this 传递父级,可以做更多的事情
this.props.onRef(this)
}
}
复制代码
input的样式
@import 'assets/style/px2rem.scss';
.imitate-input{
display: flex;
align-items: center;
font-size:px2rem(16);
border: px2rem(1) solid #999999;
user-select:text;
-webkit-user-select:text;
.selected::after{
content:'';
display: block;
width:1px;
height:16px;
animation: 1s steps(1, start) 0s normal none infinite running blink;
}
}
.yes::after{
content:'';
display: block;
width:1px;
height:16px;
animation: 1s steps(1, start) 0s normal none infinite running blink;
}
.not:empty::before{
content:'';
display: block;
width:1px;
height:16px;
animation: 1s steps(1, start) 0s normal none infinite running blink;
}
.not:empty::after{
content: attr(placeholder);
color: #999999;
font-size:px2rem(16);
}
@keyframes blink {
0%{
background-color: white;
}
50% {
background-color: #000000;
}
100% {
background-color: white;
}
}
复制代码
具体的代码就是以上的实现,如有错误,请多指正。有更好的想法实现,欢迎评论。大家共勉~