移動端自定義輸入框和鍵盤的實現。(借鑑antd mobile源碼)

近期,大把時間花費在浪費生命的手機鍵盤中,最後無奈被各大機型的各類兼容性戰勝~css

你覺得我這樣就輸了嗎? No No No !html

來自產品方的需求:(驗證碼頁面)react

1. 拉起數字鍵盤瀏覽器

2. 只容許用戶輸入6位數字bash

哎吆喂,就2個需求嘛,so easy!開始咱們的input的type爲number,給它來個最大的可輸入長度。盤它!你會發現無論是在安卓手機仍是蘋果手機,系統自帶的輸入法,用戶裝的輸入法,各式各樣的奇葩都在裏面。再加上在各類瀏覽器裏面也會有所不一樣,再加上樣式展示的不一樣,與其折磨本身,不如去參考別人的實現。巧了,思路由此展開。antd

根據上面異樣,給出了實現的答案。dom

1. 本身用div去模仿假的input,用li循環鍵盤。svg

2. 在安卓端用本身寫的高仿的,在蘋果端仍是用自帶的鍵盤。學習

以上解析你可能會有疑問?優化

  1. 本身模擬的input,能夠在數字的中間刪除數字,光標的位置能夠改變嗎?

  2. 爲何要在安卓端和蘋果端寫入的鍵盤要不一致?

第一個問題,咱們針對這個沒有作處理,若有須要,請查看筆者的文章,用div模仿的input,裏面有思路如何改變光標。

第二個問題是由於蘋果端有提供咱們更加便捷的方式,利大於弊。點擊驗證碼直接進入輸入框,減小用戶的操做。

開始步入正題,參考antd mobile的自定義鍵盤,開發咱們本身的鍵盤。

本身高仿鍵盤的組件

import React from 'react';
import TouchFeedback from 'rmc-feedback'; // 點擊數字高亮的插件
import IconSvg from './iconSvg' // 這個是x號的svg,你們能夠替換本身的
import './style.scss'
const numArray = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '', '0']
interface KeyboardItemProps {
    children?: any,
    className?: string,
    onClick?: () => void
}
class KeyboardItem extends React.Component<KeyboardItemProps, any> {
    render() {
        const { children, onClick, className = '' } = this.props
        let value = (className == 'delete' ? className : children)
        return (
            <TouchFeedback activeClassName={`active`}>
                <li
                    onClick={e => onClick(e, value as string)}
                    className={`${children == '' ? 'hidden' : className}`}
                >
                    {children}
                </li>
            </TouchFeedback>
        )
    }
}
class CustomKeyboard extends React.Component<any, any> {
    // 每個數字的點擊事件
    onKeyboardClick = (e?, value?: string) => {
        e.nativeEvent.stopImmediatePropagation();
        this.props.onKeyboardClick(value)
    }
    // 每個鍵盤的render
    renderKeyboardItem = (item: string, index: number) => {
        return (
            <KeyboardItem key={`item-${item}-${index}`} onClick={this.onKeyboardClick}>{item}</KeyboardItem>
        )
    }
    render() {
        const { className } = this.props
        return (
            <div className={`keyboard-box ${className}`}>
                <div className="custom-header">
                    <div className="complete" onClick={(e) => this.onKeyboardClick(e, 'complete')}>完成</div>
                </div>
                <ul className={`number-keyboard`}>
                    {
                        numArray.map((item, index) => this.renderKeyboardItem(item, index))
                    }
                    <KeyboardItem className="delete" key={`item-${10}`} onClick={this.onKeyboardClick}>
                        <i><IconSvg color="#3F434A;"/></i>
                    </KeyboardItem>
                </ul>
            </div>
        )
    }
}
export default CustomKeyboard
複製代碼

本身高仿的input,用僞類作光標。

import React from 'react'
import CustomKeyboard from '../CustomKeyboard'
import './style.scss'

interface NumberInputProps {
    placeholder?: string,
    editInput?: boolean,
    disabled?: boolean,
    value?: any,
    className?: string,
    maxLength?: number,
    fakeInputClassName?: string,
    children?: any,
    labelNumber?: number,
    extra?: any,
    onChange?: (value: string) => void,
    onBlur?: (value: string) => void,
    onExtraClick?: () => void,
}

class YzmInput extends React.Component<NumberInputProps, any>{
    constructor(props) {
        super(props)
        this.state = {
            focus: false, // 默認不聚焦
            value: props.value || '',
        }
    }
    fakeInput = null
    // 僞造的input的點擊事件
    onFakeInputClick = () => {
        this.focus()
    }
    focus = () => {
        this.removeBlurListener()
        const { focus } = this.state
        if (!focus) {
            this.onInputFocus()
        }
        setTimeout(() => {
            this.addBlurListener()
        }, 50)
    }
    onChange = (value: any) => {
        this.setState({ value })
        this.props.onChange(value)
    }
    onInputFocus = () => {
        this.setState({
            focus: true
        })
    }
    onInputBlur = (value: string) => {
        const { focus } = this.state
        if (focus) {
            this.setState({
                focus: false
            })
            this.props.onBlur && this.props.onBlur(value)
        }
    }
    doBlur = (ev: MouseEvent) => {
        const { value } = this.state;
        if (ev.target !== this.fakeInput) {
            this.onInputBlur(value);
        }
    }
    // addBlurListener,removeBlurListener就是爲了處理blur&focus事件
    addBlurListener = () => {
        document.addEventListener('click', this.doBlur, false);
      }
    removeBlurListener = () => {
        document.removeEventListener('click', this.doBlur, false);
    }
    // 是點擊鍵盤的每個dom的處理方式
    handleKeyboard = (keyboardVal: string) => {
        const { maxLength } = this.props
        const { value } = this.state
        const { onChange } = this
        let valueAfterChange
        if (keyboardVal === 'delete') {
            valueAfterChange = value.substring(0, value.length - 1)
            onChange(valueAfterChange)
        } else if (keyboardVal === 'complete') {
            valueAfterChange = value
            onChange(valueAfterChange)
            this.setState({
                focus: false
            })
        } else {
            if (maxLength !== undefined && maxLength >= 0 && (value + keyboardVal).length > maxLength) {
                valueAfterChange = (value + keyboardVal).substr(0, maxLength)
                onChange(valueAfterChange)
            } else {
                valueAfterChange = value + keyboardVal
                onChange(valueAfterChange)
            }
        }
    }
    render() {
        let { value, focus } = this.state
        const {
            placeholder = '輸入驗證碼',
            editInput = true,
            disabled = false,
            className = '',
            fakeInputClassName = '',
            labelNumber = 1,
            children,
            extra,
            onExtraClick
        } = this.props
        const preventKeyboard = disabled || !editInput
        return (
            <>
                <div className={`${className} fake-input-box`}>
                    {children ? (<div style={{ width: 16 * labelNumber + 'px' }} className="label">{children}</div>) : ''}
                    <div className='fake-input-content' onClick={preventKeyboard ? () => { } : this.onFakeInputClick}>
                        {value == '' && (<div className="fake-input-placeholder">{placeholder}</div>)}
                        <div
                            className={`${fakeInputClassName} fake-input ${focus ? 'focus' : ''}`}
                            role="textbox"
                            aria-label={value || placeholder}
                            ref={el => this.fakeInput = el}
                        >
                            {value}
                        </div>
                    </div>
                    {extra && <div className="extra" dangerouslySetInnerHTML={{ __html: extra }} onClick={onExtraClick}></div>}
                </div>
                <CustomKeyboard className={focus ? '' : 'none'} onKeyboardClick={this.handleKeyboard} />
            </>
        )
    }
}
export default YzmInput
複製代碼

實際在頁面中的使用。

<div className="yzm-box">
        <BcNumberInput
            className="yzm-input"
            labelNumber={4}
            maxLength={6}
            onChange={(value) => change(value, flagTime)}
            placeholder="輸入驗證碼"
            extra={`|<div id=${flagTime ? 'timer' : 'retrieve'}>${flagTime ? time + '秒後重發' : '從新獲取'}</div>`}
            onExtraClick={flagTime ? () => {} : () => click(this.actionCountDown.bind(this))}
        >驗證碼</BcNumberInput>
    </div>
複製代碼

以上代碼不足的地方還有不少。基於如今的實現已經知足我方這邊的需求,後續還會在優化~

問題。(antd mobile的自定義鍵盤已經實現的很完美,能夠參考下)

假設個人input在底部的位置,那麼我鍵盤擡起的時候,是否是須要將滾動條往上移,讓用戶輸入的同時可看見輸入裏面的內容。

還有咱們的鍵盤是否是能夠作成用戶可配置,可配置鍵盤的內容,可配置鍵盤的頭部。

等等一系列,還須要咱們學習的地方,小夥伴們,Come on ! 有不足的地方,歡迎指正。

相關文章
相關標籤/搜索