移动端 uniapp 写一个可自由拖拽的小键盘

发布于:2025-06-25 ⋅ 阅读:(18) ⋅ 点赞:(0)

写之前要考虑:

  1. 键盘展开后,不能超过手机边缘
  2. 在底部展开键盘,键盘应出现在展开按钮上方;以此类推
  3. 重复点击展开按钮,关闭键盘

效果:
在这里插入图片描述

在这里插入图片描述
代码如下,有些按键逻辑还需要优化

<template>
  <view class="container">
    <!-- 可拖动按钮 -->
    <view
      class="drag-btn"
      :style="{left: btnLeft + 'px', top: btnTop + 'px'}"
      @touchstart="onTouchStart"
      @touchmove="onTouchMove"
      @touchend="onTouchEnd"
      @click="showKeyboard"
    >
      <text>键盘</text>
    </view>

    <!-- 小键盘 -->
    <view
      v-if="isShowKeyBoard"
      class="keyboard"
      :style="keyboardStyle"
    >
      <view v-for="(row, rowIndex) in keyboardLayout" :key="rowIndex" class="keyboard-row">
        <view
          v-for="(key, keyIndex) in row"
          :key="keyIndex"
          class="key"
          :class="key.className || ''"
          @click="onKeyPress($event,key.value)"
        >
          {{ key.name }}
        </view>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data () {
    return {
      btnLeft: 100, // 按钮初始位置
      btnTop: 100,
      startX: 0, // 触摸起始位置
      startY: 0,
      isDragging: false, // 是否正在拖动
      isShowKeyBoard: false, // 是否显示键盘
      screenWidth: 375, // 屏幕宽度,会在onLoad中获取实际值
      screenHeight: 667, // 屏幕高度
      keyboardWidth: 330, // 键盘宽度
      keyboardHeight: 170, // 键盘高度
      // 键盘布局
      keyboardLayout: [
        // 第一行
        [
          {name: 'Esc', value: 0x1},
          {name: '1', value: 0x2},
          {name: '2', value: 0x3},
          {name: '3', value: 0x4},
          {name: '4', value: 0x5},
          {name: '5', value: 0x6},
          {name: '6', value: 0x7},
          {name: '7', value: 0x8},
          {name: '8', value: 0x9},
          {name: '9', value: 0xa},
          {name: '0', value: 0xb},
          {name: '-', value: 0xc},
          {name: '=', value: 0xd},
          {name: '←', value: 0xe, className: 'wide-key'}
        ],
        // 第二行
        [
          {name: 'Tab', value: 0xf, className: 'wide-key'},
          {name: 'q', value: 0x10},
          {name: 'w', value: 0x11},
          {name: 'e', value: 0x12},
          {name: 'r', value: 0x13},
          {name: 't', value: 0x14},
          {name: 'y', value: 0x15},
          {name: 'u', value: 0x16},
          {name: 'i', value: 0x17},
          {name: 'o', value: 0x18},
          {name: 'p', value: 0x19},
          {name: '[', value: 0x1a},
          {name: ']', value: 0x1b},
          {name: '\\', value: 0x2b}
        ],
        // 第三行
        [
          {name: 'Caps', value: 0x3a, className: 'wide-key'},
          {name: 'a', value: 0x1e},
          {name: 's', value: 0x1f},
          {name: 'd', value: 0x20},
          {name: 'f', value: 0x21},
          {name: 'g', value: 0x22},
          {name: 'h', value: 0x23},
          {name: 'j', value: 0x24},
          {name: 'k', value: 0x25},
          {name: 'l', value: 0x26},
          {name: ';', value: 0x27},
          {name: '\'', value: 0x28},
          {name: 'Enter', value: 0x1c, className: 'wide-key'}
        ],
        // 第四行
        [
          {name: 'Shift', value: 0x2a, className: 'extra-wide-key'},
          {name: 'z', value: 0x2c},
          {name: 'x', value: 0x2d},
          {name: 'c', value: 0x2e},
          {name: 'v', value: 0x2f},
          {name: 'b', value: 0x30},
          {name: 'n', value: 0x31},
          {name: 'm', value: 0x32},
          {name: ',', value: 0x33},
          {name: '.', value: 0x34},
          {name: '/', value: 0x35},
          {name: 'Shift', value: 0x36, className: 'extra-wide-key'}
        ],
        // 第五行
        [
          {name: 'Ctrl', value: 0x1d, className: 'wide-key'},
          {name: 'Alt', value: 0x38, className: 'wide-key'},
          {name: '空格', value: 0x39, className: 'space-key'},
          {name: 'Alt', value: 0x138, className: 'wide-key'},
          {name: 'Ctrl', value: 0x11d, className: 'wide-key'}
        ]
      ]
    }
  },
  computed: {
    // 计算键盘位置样式
    keyboardStyle () {
      let left = this.btnLeft
      let top = this.btnTop + 50 // 默认在按钮下方

      // 如果键盘会超出右边界,则放在左侧
      if (left + this.keyboardWidth > this.screenWidth) {
        left = this.btnLeft - this.keyboardWidth
      }

      // 如果键盘会超出下边界,则放在上方
      if (top + this.keyboardHeight > this.screenHeight) {
        top = this.btnTop - this.keyboardHeight
      }

      // 确保不超出左边界和上边界
      left = Math.max(0, left)
      top = Math.max(0, top)

      return {
        left: left + 'px',
        top: top + 'px',
        width: this.keyboardWidth + 'px',
        height: this.keyboardHeight + 'px'
      }
    }
  },
  onLoad () {
    // 获取屏幕尺寸
    uni.getSystemInfo({
      success: (res) => {
        this.screenWidth = res.windowWidth
        this.screenHeight = res.windowHeight
        // 初始位置设置为屏幕中间
        this.btnLeft = (res.windowWidth - 50) / 2
        this.btnTop = (res.windowHeight - 50) / 2
      }
    })
  },
  methods: {
    onTouchStart (e) {
      this.startX = e.touches[0].clientX
      this.startY = e.touches[0].clientY
      this.isDragging = false
      // 隐藏键盘
      // this.isShowKeyBoard = false
    },
    onTouchMove (e) {
      const moveX = e.touches[0].clientX - this.startX
      const moveY = e.touches[0].clientY - this.startY

      // 移动距离大于5px才认为是拖动
      if (Math.abs(moveX) > 5 || Math.abs(moveY) > 5) {
        this.isDragging = true
      }

      // 计算新位置
      let newLeft = this.btnLeft + moveX
      let newTop = this.btnTop + moveY

      // 限制不超出屏幕边界
      newLeft = Math.max(0, Math.min(newLeft, this.screenWidth - 50))
      newTop = Math.max(0, Math.min(newTop, this.screenHeight - 50))

      // 更新位置
      this.btnLeft = newLeft
      this.btnTop = newTop

      // 更新起始位置,实现连续拖动
      this.startX = e.touches[0].clientX
      this.startY = e.touches[0].clientY

      // 阻止默认行为和冒泡
      e.preventDefault()
      e.stopPropagation()
    },
    onTouchEnd (e) {
      if (this.isDragging) {
        // 如果是拖动操作,阻止点击事件
        e.preventDefault()
        e.stopPropagation()
      }
      this.isDragging = false
    },
    showKeyboard () {
      if (!this.isDragging) {
        this.isShowKeyBoard = !this.isShowKeyBoard
      }
    },
    onKeyPress (e, key) {
      this.$emit('onKeyPress', e, key)
      // spiceInterface.onKeyUpdate(Number(key), true)
      // spiceInterface.onKeyUpdate(Number(key), false)
      // 这里可以添加按键处理逻辑
      // 例如:uni.vibrateShort() 震动反馈
    }
  }
}
</script>

<style scoped>
.container {
  position: relative;
 /* width: 100%;
  height: 100vh; */
}

.drag-btn {
  position: fixed;
  width: 45px;
  height: 45px;
  background-color: #007AFF;
  border-radius: 25px;
  display: flex;
  justify-content: center;
  align-items: center;
  color: white;
  font-size: 12px;
  z-index: 999;
  touch-action: none; /* 防止触摸事件的默认行为 */
}

.keyboard {
  position: fixed;
  background-color: #f0f0f0;
  border-radius: 8px;
  padding: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
  z-index: 998;
}

.keyboard-row {
  display: flex;
  justify-content: center;
  margin-bottom: 5px;
}

.key {
  width: 25px;
  height: 30px;
  margin: 0 2px;
  background-color: white;
  border-radius: 4px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 14px;
  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
}
.wide-key {
  width: 45px !important; /* 比普通键宽 */
}

.extra-wide-key {
  width: 80px !important; /* 更宽的键 */
}

.space-key {
  width: 200px !important; /* 空格键特别宽 */
}
</style>


网站公告

今日签到

点亮在社区的每一天
去签到