需求场景
平板的弹窗需要自带一个键盘弹窗输入
实现
基于 Vue 3 和 Simple Keyboard 库
<template>
<div
ref="draggableDiv"
class="draggable"
@mousedown="onMouseDown"
@touchstart="onTouchStart"
>
<div v-show="visible" class="keyboard-content">
<div :class="keyboardClass"></div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch, onMounted, onUnmounted } from 'vue'
import Keyboard from 'simple-keyboard'
import 'simple-keyboard/build/css/index.css'
import useVisible from '@/hooks/visible'
const props = defineProps({
modalValue: {
type: String,
},
keyboardClass: {
type: String,
default: 'simple-keyboard',
},
input: {
type: String,
},
maxLength: {
type: Number,
default: 100,
},
})
const keyboard = ref()
const inputValue = ref()
const emit = defineEmits(['change', 'enter'])
/** hooks */
const { visible, setVisible } = useVisible(false)
const onChange = (value: string) => {
emit('change', value)
}
const displayDefault = ref({
'{bksp}': 'backspace',
'{lock}': 'caps',
'{enter}': 'enter',
'{tab}': 'tab',
'{shift}': 'shift',
'{change}': 'en',
'{space}': 'space',
'{clear}': 'del',
'{close}': '退出',
'{arrowleft}': '←',
'{arrowright}': '→',
})
const open = () => {
visible.value = !visible.value
}
const handleShift = () => {
const currentLayout = keyboard.value.options.layoutName
const shiftToggle = currentLayout === 'default' ? 'shift' : 'default'
keyboard.value.setOptions({
layoutName: shiftToggle,
})
}
const clean = () => {
keyboard.value.clearInput()
setVisible(false)
}
const onKeyPress = (button: any) => {
/**
* 如果你想操作shift键和大写锁定键
*/
if (button === '{shift}' || button === '{lock}') handleShift()
if (button === '{enter}') {
emit('enter')
}
if (button === '{clear}') {
keyboard.value.clearInput()
}
if (button === '{close}') {
setVisible(false)
}
}
watch(
() => props.modalValue,
(newvalue) => {
if (newvalue !== inputValue.value) {
keyboard.value.setInput(newvalue)
}
}
)
/**
* 实现键盘的拖拽
*/
// 记录鼠标或触摸初始位置和元素初始位置
let startX = 0
let startY = 0
let elementX = 0
let elementY = 0
const draggableDiv = ref()
const onTouchMove = (event: any) => {
const touch = event.touches[0]
const deltaX = touch.clientX - startX
const deltaY = touch.clientY - startY
draggableDiv.value.style.left = `${elementX + deltaX}px`
draggableDiv.value.style.top = `${elementY + deltaY}px`
}
const onTouchEnd = () => {
document.removeEventListener('touchmove', onTouchMove)
document.removeEventListener('touchend', onTouchEnd)
}
const onMouseMove = (event: any) => {
const deltaX = event.clientX - startX
const deltaY = event.clientY - startY
draggableDiv.value.style.left = `${elementX + deltaX}px`
draggableDiv.value.style.top = `${elementY + deltaY}px`
}
const onMouseUp = () => {
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
}
const onMouseDown = (event: any) => {
startX = event.clientX
startY = event.clientY
elementX = draggableDiv.value.offsetLeft
elementY = draggableDiv.value.offsetTop
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', onMouseUp)
}
const onTouchStart = (event: any) => {
const touch = event.touches[0]
startX = touch.clientX
startY = touch.clientY
elementX = draggableDiv.value.offsetLeft
elementY = draggableDiv.value.offsetTop
document.addEventListener('touchmove', onTouchMove)
document.addEventListener('touchend', onTouchEnd)
}
defineExpose({
open,
clean,
})
onMounted(() => {
keyboard.value = new Keyboard(props.keyboardClass, {
onChange,
onKeyPress,
useMouseEvents: true,
maxLength: {
default: props.maxLength,
},
preventMouseDownDefault: false,
layout: {
// 默认布局
default: [
'` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
'{close} q w e r t y u i o p [ ] \\',
"{lock} a s d f g h j k l ; ' {enter}",
'{change} z x c v b n m , . / {clear}',
'{space}',
],
// 大小写
shift: [
'~ ! @ # $ % ^ & * ( ) _ + {bksp}',
'{close} Q W E R T Y U I O P { } |',
'{lock} A S D F G H J K L : " {enter}',
'{change} Z X C V B N M < > ? {clear}',
'{space}',
],
// 数字布局
number: [
'7 8 9',
'4 5 6',
'1 2 3',
'. 0 {bksp}',
'{arrowleft} {arrowright} {clear} {close}',
],
},
layoutName: 'default',
display: displayDefault.value,
})
})
onUnmounted(() => {
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
document.removeEventListener('touchmove', onTouchMove)
document.removeEventListener('touchend', onTouchEnd)
})
</script>
<style scoped lang="less">
.draggable {
position: absolute;
cursor: move;
bottom: calc(12vh);
z-index: 1001;
.keyboard-content {
width: 1200px;
font-size: 32px;
font-weight: 500;
:deep(.hg-theme-default .hg-button) {
height: 55px;
}
:deep(.hg-theme-default .hg-button.hg-standardBtn:active) {
background-color: rgb(var(--gray-3));
}
}
}
</style>
介绍
主要功能
-
虚拟键盘输入:提供全功能的虚拟键盘输入,支持字母、数字、符号等
-
拖拽功能:整个键盘区域可以通过鼠标或触摸屏拖拽移动位置
-
布局切换:支持默认布局、大写布局和数字布局三种键盘布局
-
自定义按键:提供清除、关闭、回车等特殊功能按键
核心实现
键盘功能
-
使用
simple-keyboard
库实现核心键盘功能 -
通过
onChange
和onKeyPress
事件处理输入和特殊按键 -
支持通过
handleShift
方法切换大小写布局 -
提供
displayDefault
对象自定义按键显示文本
拖拽功能
-
使用
mousedown
/touchstart
事件开始拖拽 -
通过
mousemove
/touchmove
计算移动距离并更新位置 -
在
mouseup
/touchend
时移除事件监听 -
使用绝对定位 (
position: absolute
) 实现自由移动
组件通信
-
通过
props
接收初始值 (modalValue
)、键盘类名 (keyboardClass
) 等 -
通过
emit
发送change
和enter
事件 -
使用
defineExpose
暴露open
和clean
方法供外部调用
使用方式
<simple-keyboard
ref="keyboardRef"
:max-length="20"
:modal-value="state.lockScreenPassword"
@change="getInput"
@enter="unlockScreen"
/>
keyboardRef.value.open()