基于vue封裝一個安全鍵盤組件
為什么需要安全鍵盤
大部分中文應用彈出的默認鍵盤是簡體中文輸入法鍵盤,在輸入用戶名和密碼的時候,如果使用簡體中文輸入法鍵盤,輸入英文字符和數(shù)字字符的用戶名和密碼時,會自動啟動系統(tǒng)輸入法自動更正提示,然后用戶的輸入記錄會被緩存下來。
系統(tǒng)鍵盤緩存最方便拿到的就是利用系統(tǒng)輸入法自動更正的字符串輸入記錄。 緩存文件的地址是:
/private/var/mobile/Library/Keyboard/dynamic-text.dat
導出該緩存文件,查看內(nèi)容,欣喜的發(fā)現(xiàn)一切輸入記錄都是明文存儲的。因為系統(tǒng)不會把所有的用戶輸入記錄都當作密碼等敏感信息來處理。 一般情況下,一個常規(guī) iPhone 用戶的 dynamic-text.dat 文件,高頻率出現(xiàn)的字符串就是用戶名和密碼。
使用自己定制的安全鍵盤的原因主要有:
- 避免第三方讀取系統(tǒng)鍵盤緩存
- 防止屏幕錄制 (自己定制的鍵盤按鍵不加按下效果)
實現(xiàn)方案
封裝組件
首先建一個文件safeKeyboard.vue安全鍵盤子組件.
<template> <div class="keyboard"> <div class="key_title"> <p><img src="../../../../static/img/ic_logo@2x.png"><span>小猴子的安全鍵盤</span></p> </div> <p v-for="keys in keyList" :style="(keys.length<10&&keys.indexOf('ABC')<1&&keys.indexOf('del')<1&&keys.indexOf('suc')<1)?'padding: 0px 20px;':''"> <template v-for="key in keys"> <i v-if="key === 'top'" @click.stop="clickKey" @touchend.stop="clickKey" class="tab-top"><img class="top" :src='top_img'></i> <i v-else-if="key === 'del'" @click.stop="clickKey" @touchend.stop="clickKey" class="key-delete"><img class="delete" src='刪除圖標路徑'></i> <i v-else-if="key === 'blank'" @click.stop="clickBlank" class="tab-blank">空格</i> <i v-else-if="key === 'suc'" @click.stop="success" @touchend.stop="success" class="tab-suc">確定</i> <i v-else-if="key === '.?123' || key === 'ABC'" @click.stop="symbol" class="tab-sym">{{(status==0||status==1)?'.?123':'ABC'}}</i> <i v-else-if="key === '123' || key === '#+='" @click.stop="number" class="tab-num">{{status==3?'123':'#+='}}</i> <i v-else @click.stop="clickKey" @touchend.stop="clickKey">{{key}}</i> </template> </p> </div> </template> <script> export default { data () { return { keyList: [], status: 0, // 0 小寫 1 大寫 2 數(shù)字 3 符號 topStatus: 0, // 0 小寫 1 大寫 top_img: require('小寫圖片路徑'), lowercase: [ ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'], ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'], ['top', 'z', 'x', 'c', 'v', 'b', 'n', 'm', 'del'], ['.?123', 'blank', 'suc'] ], numbercase: [ ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'], ['-', '/', ':', ';', '(', ')', '$', '&', '@', '"'], ['#+=', '.', ',', '?', '!', "'", 'del'], ['ABC', 'blank', 'suc'] ], symbolcase: [ ['[', ']', '{', '}', '#', '%', '^', '*', '+', '='], ['_', '\\', '|', '~', '<', '>', '€', '`', '¥', '·'], ['123', '.', ',', '?', '!', "'", 'del'], ['ABC', 'blank', 'suc'] ], uppercase: [ ['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'], ['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L'], ['top', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', 'del'], ['.?123', 'blank', 'suc'] ], equip: !!navigator.userAgent.toLocaleLowerCase().match(/ipad|mobile/i)// 是否是移動設備 } }, props: { option: { type: Object } }, mounted () { this.keyList = this.lowercase }, methods: { tabHandle ({value = ''}) { if (value.indexOf('tab-num') > -1) { if (this.status === 3) { this.status = 2 this.keyList = this.numbercase } else { this.status = 3 this.keyList = this.symbolcase } // 數(shù)字鍵盤數(shù)據(jù) } else if (value.indexOf('delete') > -1) { this.emitValue('delete') } else if (value.indexOf('tab-blank') > -1) { this.emitValue(' ') } else if (value.indexOf('tab-point') > -1) { this.emitValue('.') } else if (value.indexOf('tab-sym') > -1) { if (this.status === 0) { this.topStatus = 0 this.status = 2 this.keyList = this.numbercase } else if (this.status === 1) { this.topStatus = 1 this.status = 2 this.keyList = this.numbercase } else { if (this.topStatus == 0) { this.status = 0 this.top_img = require('小寫圖片路徑') this.keyList = this.lowercase }else{ this.status = 1 this.keyList = this.uppercase this.top_img = require('大寫圖片路徑') } } // 符號鍵盤數(shù)據(jù) } else if (value.indexOf('top') > -1) { if (this.status === 0) { this.status = 1 this.keyList = this.uppercase this.top_img = require('大寫圖片路徑') } else { this.status = 0 this.keyList = this.lowercase this.top_img = require('小寫圖片路徑') } } else if (value.indexOf('tab-suc') > -1) { this.$emit('closeHandle', this.option) // 關閉鍵盤 } }, number (event) { this.tabHandle(event.srcElement.classList) }, clickBlank (event) { this.tabHandle(event.srcElement.classList) }, symbol (event) { this.tabHandle(event.srcElement.classList) }, success (event) { this.tabHandle(event.srcElement.classList) }, english (event) { this.tabHandle(event.srcElement.classList) }, clickKey (event) { if (event.type === 'click' && this.equip) return let value = event.srcElement.innerText value ? this.emitValue(value) : this.tabHandle(event.srcElement.classList) }, emitValue (key) { this.$emit('keyVal', key) // 向父組件傳值 }, closeModal (e) { if (e.target !== this.option.sourceDom) { this.$emit('closeHandle', this.option) this.keyList = this.lowercase } }, } } </script> <style scoped lang="scss"> .keyboard { width: 100%; margin: 0 auto; font-size: 18px; border-radius: 2px; background-color: #fff; box-shadow: 0 -2px 2px 0 rgba(89,108,132,0.20); user-select: none; position: fixed; bottom: 0; left: 0; right: 0; z-index: 999; pointer-events: auto; .key_title{ height: 84px; font-size: 32px; color: #0B0B0B; overflow: hidden; margin-bottom: 16px; p{ display: flex; justify-content: center; align-items: center; min-width: 302px; height: 32px; margin: 32px auto 0px; img{ width: 32px; height: 32px; margin-right: 10px; } } } p { width: 99%; margin: 0 auto; height: 84px; margin-bottom: 24px; display: flex; display: -webkit-box; flex-direction: row; flex-wrap: nowrap; justify-content: center; box-sizing: border-box; i { position: relative; display: block; margin: 0px 5px; height: 84px; line-height: 84px; font-style: normal; font-size: 48px; border-radius: 8px; width: 64px; background-color: #F2F4F5; box-shadow: 0 2px 0 0 rgba(0,0,0,0.25); text-align: center; flex-grow: 1; flex-shrink: 1; flex-basis: 0; -webkit-box-flex: 1; img{ width: 48px; height: 48px; } } i:first-child{ margin-left: 0px } i:last-child{ margin-right: 0px } i:active { background-color: #A9A9A9; } .tab-top, .key-delete, .tab-num, .tab-eng, .tab-sym{ background-color: #CED6E0; } .tab-top,.key-delete { display: flex; justify-content: center; align-items: center; width: 84px; height: 84px; } .tab-top{ margin-right: 30px; font-size: 32px; } .key-delete{ margin-left: 30px; } .tab-num, .tab-eng, .tab-sym{ font-size: 32px; } .tab-point { width: 70px; } .tab-blank, .tab-suc{ text-align: center; line-height: 84px; font-size: 32px; color: #000; } .tab-blank{ flex: 2.5; } .tab-suc{ background-color: #CFA46A; } } p:last-child{ margin-bottom: 8px; } } </style>
但是,鍵盤的特性是,點擊除鍵盤和輸入框以外的地方,鍵盤收起。
所以還需要一個clickoutside.js文件,用來自定義一個指令,實現(xiàn)需求:
代碼如下:
export default { bind(el, binding, vnode) { function documentHandler(e) { if (el.contains(e.target)) { return false; } if (binding.expression) { binding.value(e); } } el.__vueClickOutside__ = documentHandler; document.addEventListener('click', documentHandler); }, unbind(el, binding) { document.removeEventListener('click', el.__vueClickOutside__); delete el.__vueClickOutside__; } };
然后在safeKeyboard.vue中引入:
import clickoutside from './clickoutside'
并注冊局部指令:
directives: { clickoutside }
然后綁定方法:
<div class="keyboard" v-clickoutside="closeModal">
聲明方法:
closeModal (e) { if (e.target !== this.option.sourceDom) { this.$emit('closeHandle', this.option) this.keyList = this.lowercase } },
安全鍵盤組件就構(gòu)建完成了,接下來是在需要用到安全鍵盤的頁面引入使用了。
使用組件
引入組件
import Keyboard from './safeKeyboard' components: { Keyboard }
使用范例
<input type="password" ref="setPwd" v-model='password'/> <Keyboard v-if="option.show" :option="option" @keyVal="getInputValue" @closeHandle="onLeave"></Keyboard>
鍵盤相關數(shù)據(jù)對象及方法
- option
option: { show: false, // 鍵盤是否顯示 sourceDom: '', // 鍵盤綁定的Input元素 _type: '' // 鍵盤綁定的input元素ref },
- getInputValue
getInputValue(val)會接收鍵盤錄入的數(shù)據(jù),val是輸入的單個字符或者是刪除操作,由于是單個字符,所以需在方法中手動拼接成字符串。在方法中根據(jù)option._type區(qū)分是哪個輸入框的數(shù)據(jù)。
- onLeave
onLeave()相當于blur,這是由于在移動端H5項目中,input獲取焦點時會調(diào)起手機軟鍵盤,所以需要禁止軟鍵盤被調(diào)起來,辦法是:
javascript
復制代碼
document.activeElement.blur() // ios隱藏鍵盤 this.$refs.setPwd.blur() // android隱藏鍵盤
就相當于強制使input元素處于blur狀態(tài),那么軟鍵盤就不會被調(diào)起,所以如果要做blur監(jiān)聽,就需要onLeave()。
但是這樣出現(xiàn)了一個新的問題,輸入框里面沒有光標??!雖然不影響業(yè)務邏輯,但是用戶用起來會很不舒服。
所以,只能和input元素說再見了,自己手寫一個吧:
輸入框組件
再來一個子組件cursorBlink.vue
<template> <div class="cursor-blink" @click.stop="isShow"> <span v-if="pwd.length>0" :style="options.show?'':'border:0;animation:none;'" class="blink">{{passwordShow}}</span> <span v-else style="color: #ddd" :style="options.show?'':'border:0;animation:none;'" class="blink_left">{{options.desc}}</span> </div> </template> <script> export default { props: { pwd: { type: String }, options: { type: Object }, }, data(){ return { passwordShow: '', } }, mounted() { if(this.pwd.length > 0){ for (let i = 0; i < this.pwd.length; i++) { this.passwordShow += '*' // 顯示為掩碼 } } }, watch: { pwd(curVal, oldVal){ if (oldVal.length < curVal.length) { // 輸入密碼時 this.passwordShow += '*' } else if (oldVal.length > curVal.length) { // 刪除密碼時 this.passwordShow = this.passwordShow.slice(0, this.passwordShow.length - 1) } } }, methods: { isShow(){ this.$emit('cursor') } }, } </script> <style lang="scss" scoped> .cursor-blink{ display: inline-block; width: 500px; height: 43px; letter-spacing: 0px; word-spacing: 0px; padding: 2px 0px; font-size: 28px; overflow: hidden; .blink,.blink_left{ display: inline; margin: 0px; } .blink{ // 輸入密碼后 border-right: 2px solid #000; animation: blink 1s infinite steps(1, start); } .blink_left{ // 輸入密碼前 border-left: 2px solid #000; animation: blinkLeft 1s infinite steps(1, start); } } @keyframes blink { 0%, 100% { border-right: 2px solid #fff; } 50% { border-right: 2px solid #000; } } @keyframes blinkLeft { 0%, 100% { border-left: 2px solid #fff; } 50% { border-left: 2px solid #000; } } </style>
引入之后光榮的接替input的位置:
<CursorBlink :pwd='password' ref="setPwd" :options='option2' @cursor="onFocus"></CursorBlink>
數(shù)據(jù)方法說明:
option2: { show: false, // 區(qū)分輸入前輸入后 desc: '請重復輸入密碼' // 相當于placeholder }, onFocus() 相當于input標簽的focus
這樣一個完美的安全鍵盤就做好了。
以上就是基于vue封裝一個安全鍵盤組件的詳細內(nèi)容,更多關于vue封裝鍵盤組件的資料請關注腳本之家其它相關文章!
相關文章
Vue Cli3 打包配置并自動忽略console.log語句的方法
這篇文章主要介紹了Vue Cli3 打包配置并自動忽略console.log語句的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-04-04前端數(shù)據(jù)存儲常用工具Vuex、Pinia、Redux詳解
Redux、Vuex 和 Pinia 都是用于狀態(tài)管理的流行框架,這篇文章主要介紹了前端數(shù)據(jù)存儲常用工具Vuex、Pinia、Redux的相關資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2025-04-04