Use VUE to make a custom soft keyboard + input box component
The preview address is GitHub
The effect
Let’s take a look at the effect
demand
For this component, WHEN I was working on the project “Macao Parking Lot Payment”, I needed to make a soft keyboard with only English and numbers, and it had a default beginning, so I could not delete or modify the default beginning. For China, you may need to make a key to switch between Chinese characters and English characters, such as Cantonese Axxxx, which requires users to press Cantonese characters on the soft keyboard. However, for my project, because it is not in China, the license plate number only has English and numbers, such as Mo-00-00, so I did not make the function of switching the input method.
Implementation method
Received parameter
props: {
// Text data source
text: {
type: String.default: "",},// Fix the beginning
defaultVal: {
type: String.default: "",},// Enter the number of boxes
length: {
type: Number.default: 6,},Copy the code
Built-in data
data(){
return {
// Keyboard data is used to render the keyboard
keys: [[0.1.2.3.4.5.6.7.8.9],
["Q"."W"."E"."R"."T"."Y"."U"."I"."O"."P"],
["A"."S"."D"."F"."G"."H"."J"."K"."L"],
["Z"."X"."C"."V"."B"."N"."M"]],showKeyboard: false.// Control keyboard display
isFocus: false.// Whether to focus}}Copy the code
The current input position requires computed to return the length of the string
computed: {
/** * gets the subscript * of the current input box@param {void}
* @return {void}* /
currentInput: function() {
// Returns the length of current text
const length = this.text.length || 0;
returnlength; }},Copy the code
Input box
First of all, let’s look at the input box. The input box number can be controlled by the external incoming length. Of course, the number should be in a controllable range, otherwise you have to think of your own way
html
<! -- Input box display area -->
<div class="input">
<div
v-for="(item, index) in length"
:key="index"
:class="['box-item', isFocus && currentInput === index ? 'active' : '', text[index] ? 'hight-light' : '']"
>
<span>{{ text[index] }}</span>
</div>
</div>
Copy the code
- Generate a corresponding number of boxes based on the length passed in
- Determine the active box based on whether the focus identifier isFocus and the length of the character entered
- Boxes with content are always highlighted in the Ight -light style
- The box displays the text corresponding to the subscript of the input text
The transparent background enables you to click the area outside the keyboard to fold up the keyboard
<! -- Click the area outside the keyboard to hide the keyboard -->
<div @click.stop="hide" v-if="showKeyboard" class="bg"></div>
Copy the code
This will cause the background to block a click on the submit button on the page. Clicking once will put the keyboard away and wait for the button to be clicked again. If you want to make it possible to click a page button even if the keyboard exists, you need to do a listener on the root element of the parent component, then remove the transparent background DOM, and then do your own logic on the click event of the root element of the parent component. I was lazy, and I didn’t have to think about it, so I didn’t do it.
The keyboard area
<! -- Keyboard area -->
<div ref="cusBoard" v-if="showKeyboard" class="cus-board">
<div v-for="(line, index) in keys" :key="'line' + index" class="letter-line">
<! Put the keyboard away -->
<div v-if="index === keys.length - 1" @click.stop="hide" class="action">
<img :src="require('@/assets/keyboard.png')" />
</div>
<div @touchstart="touchStart" @touchend="touchEnd" v-for="key in line" :key="key" :data-text="key" class="item">{{ key }}</div>
<! - delete - >
<div v-if="index === keys.length - 1" @click.stop="handleDel" class="action">
<img :src="require('@/assets/delete.png')" />
</div>
</div>
</div>
Copy the code
The dom of keys is cyclically generated based on the keys data. Note that the last line needs to be preceded and followed by a button to remove the keyboard and a button to delete the keyboard. You don’t have to give the user that experience. It’s up to you. Keyboard. PNG and delete. PNG are keyboard ICONS and delete ICONS.
Display keyboard method
/** * display keyboard *@param {void}
* @return {void}* /
show() {
this.isFocus = true;
this.showKeyboard = true;
// The dom cannot be found unless the timer is executed
setTimeout(() = > {
// Raise the keyboard
this.$refs.cusBoard.style.transform = `translateY(0)`;
}, 20);
},
Copy the code
Here it needs to be noted that the implementation of the pop-up process, the need to delay the execution of the timer, otherwise only a flash of embarrassment.
Hide the keyboard
/** * hide keyboard *@param {void}
* @return {void}* /
hide() {
// Lose focus
this.isFocus = false;
// Drop the keyboard
this.$refs.cusBoard.style.transform = `translateY(100%)`;
// The timer must be executed otherwise there will be no overanimation
setTimeout(() = > {
this.showKeyboard = false;
}, 500);
},
Copy the code
Here also need to pay attention to, realize the process of folding, need to delay through the timer to hide the keyboard, otherwise before the animation is over, you will directly hide the keyboard, only a flash of embarrassment.
Press the
/** ** Press *@param {object} El Click event *@return {void}* /
touchStart(el) {
// Click the target
const { target } = el;
let text = this.text;
// No processing is performed when the text reaches the upper limit
if (text.length >= this.length) return;
// Concatenate click values
const content = target.innerText;
text += content;
// Update the text data source
this.$emit("update:text", text);
// Background color changes
target.style.background = "rgb(228, 229, 228)";
// Add activation className to display feedback
target.classList.add("active");
},
Copy the code
When I press here, I’m going to judge the length of the text, and if it goes beyond that, I’m not going to respond. It is necessary to change the background color when pressing and add animation of press feedback to improve user experience
In my case, for example, I did a quick background switch and then undo, and I did a zooming in and out animation on top of the button with the micro elements to tell the user what to press.
Press the feedback animation
.active{&::after {
position: absolute;
top: -40px;
left: 0;
width: 32px;
height: 40px;
background-color: #ffffff;
content: attr(data-text);
animation: itemActive 0.5 sinfinite; }}@keyframes itemActive {
0% {
transform: scale(1);
}
50% {
transform: scale(2);
}
100% {
transform: scale(1); }}Copy the code
Let go of the keys
touchEnd(el) {
// Click the target
const { target } = el;
// Use timer to achieve transition effect
setTimeout(() = > {
// Background color changes
target.style.background = "#fff";
/ / remove the className
target.classList.remove("active");
}, 100);
},
Copy the code
Remember to clear the style when the button is lifted
Click the delete
/** * click delete *@param {void}
* @return {void}* /
handleDel() {
if (this.defaultVal && this.text.length === this.defaultVal.length && this.text.indexOf(this.defaultVal) === 0) {
If the text only has a fixed beginning and no input, click Delete and do nothing
return;
}
// Delete a text from the back
let text = this.text;
text = text.slice(0, text.length - 1);
this.$emit("update:text", text);
},
Copy the code
Note here, if there is a fixed default start, and delete the default start here, do not do any feedback, to avoid deleting the fixed default start
Component complete code
<template> <div class="keyboard"> <! <div @click.stop="show" v-for="(item, index) in length" :key="index" :class="['box-item', isFocus && currentInput === index ? 'active' : '', text[index] ? 'hight-light' : '']" > <span>{{ text[index] }}</span> </div> </div> <! Div @click.stop="hide" v-if="showKeyboard" class="bg"></div> <! <div v ="cusBoard" v-if="showKeyboard" class="cus-board"> index) in keys" :key="'line' + index" class="letter-line"> <! <div v-if="index === keys.length - 1" @click.stop="hide" class="action"> <img :src="require('@/assets/keyboard.png')" /> </div> <div @touchstart="touchStart" @touchend="touchEnd" v-for="key in line" :key="key" :data-text="key" class="item">{{ key }}</div> <! <div v-if="index === keys.length - 1" @click.stop="handleDel" class="action"> <img :src="require('@/assets/delete.png')" /> </div> </div> </div> </div> </template> <script> export default { name: "Keyboard", props: {// Text data source text: {type: String, default: ",}, // Fixed starting defaultVal: {type: String, default: Length: {type: Number, default: 6,},}, data() {return {keys: [ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"], ["A", "S", "D", "F", "G", "H", "J", "K", "L"], [" Z ", "X", "C", "V", "B", "N", "M"],], showKeyboard: false, / / control keyboard show hidden isFocus: false, / / whether the focus}; }, computed: {/** * get the currentInput subscript * @param {void} * @return {void} */ currentInput: The function () {/ / returns the length of the current text const length = this. Text. Length | | 0; return length; },}, mounted() {this.$emit("update:text", this.defaultVal); }, methods: {/** * display keyboard * @param {void} * @return {void} */ show() {this.isfocus = true; this.showKeyboard = true; / / timer implementation Otherwise you will find the dom setTimeout () = > {/ / rises this keyboard. $refs. CusBoard. The style.css. Transform = ` translateY ` (0); }, 20); }, /** * hide keyboard * @param {void} * @return {void} */ hide() {// lose focus this.isfocus = false; / / fall this keyboard. $refs. CusBoard. Style.css. Transform = ` translateY ` (100%); SetTimeout (() => {this.showKeyboard = false; }, 500); }, /** * press * @param {object} el click event * @return {void} */ touchStart(el) {// Click target const {target} = el; let text = this.text; If (text.length >= this.length) return; // Concatenate click value const Content = target.innerText; text += content; $emit("update:text", text); Background-color = "RGB (228, 229, 228)"; background-color =" RGB (228, 229, 228)"; ClassName displays feedback target.classlist. add("active"); }, touchEnd(el) {const {target} = el; SetTimeout (() => {// Change the background color target.style.background = "# FFF "; Remove ("active"); // Remove className target.classlist. remove("active"); }, 100); }, * @param {void} * @return {void} */ handleDel() {if (this.defaultVal && this.text.length === This.defaultval. Length && this.text.indexof (this.defaultVal) === 0) {// Have a default start if the text has a fixed start and no input hit Delete without doing anything return; } // Delete a text from the back let text = this.text; text = text.slice(0, text.length - 1); this.$emit("update:text", text); ,}}}; </script> <style lang="scss" scoped> .keyboard { user-select: none; } .bg { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 1; background: rgba(255, 255, 255, 0); } .input { display: flex; align-items: center; justify-content: space-between; margin: 0 auto; } .box-item { flex-basis: 35px; height: 40px; border: 1px solid #bfbfbf; border-radius: 2px; display: flex; align-items: center; justify-content: center; &.active { position: relative; border-color: #348fec; &::after { position: absolute; content: ""; width: 1px; height: 50%; background-color: #333333; left: 50%; top: 50%; transform: translate(-50%, -50%); animation: inputFocusLine 1s infinite; } } &.hight-light { border-color: #348fec; } } @keyframes inputFocusLine { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 1; } } .cus-board { font-size: 15px; width: 375px; background: rgb(246, 247, 246); padding: 15px 5px 30px 5px; position: fixed; z-index: 9999; bottom: 0; left: 0; right: 0; transform: translateY(100%); The transition: all 0.5 s; .active { &::after { position: absolute; top: -40px; left: 0; width: 32px; height: 40px; background-color: #ffffff; content: attr(data-text); Animation: itemActive 0.5 s infinite; } } } .item, .action { width: 32px; height: 40px; border-radius: 5px; background-color: white; line-height: 40px; text-align: center; position: relative; img { display: inline-block; width: 16px; height: 16px; } } @keyframes itemActive { 0% { transform: scale(1); } 50% { transform: scale(2); } 100% { transform: scale(1); } } .line { display: flex; align-items: center; justify-content: space-between; font-size: 15px; } .letter-line { width: 100%; display: flex; align-items: center; justify-content: center; .item, .action { margin: 3px; } } </style>Copy the code
Component usage code
<template> <div @dblclick="() => {return false; }" id="app"> <div class="cus-keyboard"> <Keyboard :length="length" :defaultVal="defaultVal" :text.sync="value"></Keyboard> </div> </div> </template> <script> import Keyboard from "@/components/keyboard/index.vue"; export default { name: "App", components: { Keyboard, }, data() { return { value: "", defaultVal: "MO", length: 6, }; }}; </script> <style lang="scss"> body, html { width: 100%; height: 100%; } #app { width: 100%; height: 100%; min-height: 100%; margin: 0 auto; overflow-x: hidden; overflow-y: scroll; } .cus-keyboard { width: 100%; padding: 50px 0; margin: 0 auto; width: 280px; } </style>Copy the code
@dblclick=”() => {return false; }” prevents double click scaling
conclusion
If you don’t need to make your own keyboard, you need to use input, hide the input, click the homemade verification code input box, JS focus on input, and then assign value to the input box by listening to the text of the input, to achieve the native keyboard with a custom verification code input box. Train of thought is such a train of thought, can do a variety of verification code input box and license plate keyboard, id card keyboard, numeric keyboard. Of course, if you have a better idea of implementation, but also hope to give advice.