键盘方向键移动当前选中的table单元格,并可以输入内容

有类似于这样的表格,用的<table>标签。原本要在单元格的文本框里面输入内容,需要用鼠标一个一个去点以获取焦点,现在需要不用鼠标选中,直接用键盘的上下左右来移动当前正在输入的单元格文本框。

复制代码
 const currentCell = React.useRef<HTMLElement | null>() // 储存当前选中的单元格

 const  handleArrowKeys = (event) => { // 当按下键盘方向键做的事情
    if (!currentCell || !currentCell.current) return;
    const cellIndex = currentCell?.current?.cellIndex;
    let newCell;

    switch (event.key) {
      case 'ArrowUp':
        newCell = currentCell.current?.parentElement?.previousElementSibling?.cells[cellIndex];
        break;
      case 'ArrowDown':
        newCell = currentCell.current?.parentElement?.nextElementSibling?.cells[cellIndex];
        break;
      case 'ArrowLeft':
        newCell = currentCell?.current?.previousElementSibling;
        break;
      case 'ArrowRight':
        newCell = currentCell?.current?.nextElementSibling;
        break;
      default:
        break;
    }

    if (newCell) {
      if(currentCell?.current){
        currentCell.current.style.border = 'solid 2px black'
        // currentCell.current.style.boxShadow = 'none'
      }
      currentCell.current = newCell
      newCell.style.border = '3px solid #1890ff'
      // newCell.style.borderColor = '#1890ff'
      // newCell.style.boxShadow = '0 0 10px 5px #1890ff'
    }
  }

  useEffect(()=>{
      // 鼠标点击事件,因为第一次选中单元格肯定是要点击
      document.addEventListener("click", (e: MouseEvent) => {
      const target = e.target as HTMLElement

      // console.log(target.tagName, 'target')
      // 这里要判断被点击的对象是不是你需要监听的表格的单元格
      const isActive = (target.tagName === 'TD' || target.tagName === 'TH') && ...

      if (isActive) {
        if(currentCell?.current){ // 将原本被选中的单元格样式改为正常样式
          currentCell.current.style.border = 'solid 2px black'
        }
        // 新的单元格存起来,并高亮显示
        currentCell.current = target
        target.style.border = '3px solid #1890ff'
      } else {
        // 如果被点击的不是需要监听的地方,则整个表格"失去焦点"
        if(currentCell?.current){
          currentCell.current.style.border = 'solid 2px black'
          currentCell.current = null
        }
      }
    })
    document.addEventListener('keydown', function(e) {
      // console.log(e, 'e')
      if (e.ctrlKey || e.altKey){
        // 这是按ctrl+  alt+的情况,很多快捷键方式不知道怎么模仿。
      } else if (e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
        handleArrowKeys(e);
      } else if (
        e.key === 'Insert' ||  // 按下的是插入键
        e.key === 'Home' ||  // 按下的是Home键
        e.key === 'End'  // 按下的是End键
        // 其他需要处理的按键
      ) {
        return
      } else{
        if(!currentCell || !currentCell.current) return
        let childNodes = currentCell.current.childNodes
        let inputIndex: any = null, textAreaIndex: any = null
        childNodes.forEach((node, index) => {
          if(node.tagName === 'INPUT') inputIndex = index
          if(node.tagName === 'TEXTAREA') textAreaIndex = index
        })
        if(inputIndex !== null){
          if (
            e.key === 'Backspace' ||  // 按下的是退格键
            e.key === 'Delete'
            // 其他需要处理的按键
          ) {
            childNodes[inputIndex].value = ''
          } else if(e.key.length === 1) {
            childNodes[inputIndex].value = childNodes[inputIndex].value + e.key
          }
        }else if(textAreaIndex !== null){
          if (
            e.key === 'Backspace' ||  // 按下的是退格键
            e.key === 'Delete'
            // 其他需要处理的按键
          ) {
            childNodes[textAreaIndex].value = ''
          } else if(e.key.length === 1) {
            childNodes[textAreaIndex].value = childNodes[inputIndex].value + e.key
          }
        }
      }
    });
  },[])

这种方式,实现的功能就是点击单元格,注意不能点击到格里的文本框(因为我觉得文本框都是单击它就获取了焦点,键盘方向键也是用来控制光标位置的,这里没有过多的去纠结去探究,也许可以做到),然后键盘的上下左右就能控制当前选中的单元格,输入,就能改变单元格的文本框的值。其实这样我觉得就和excel单击单元格选中,输入就是覆盖整个内容,方向键控制选中单元格;双击单元格才是继续编辑单元格内容,方向键控制光标差不多,不过我这个变成了单击单元格是选中,然后输入覆盖,单击文本框是继续输入。

但是,这样是有弊端的,代码中也能看出来,对于ctrl+,alt+这些快捷键的功能我没有模仿出来,可能跟个人能力有关,而且就算有办法我觉得可能也太复杂了(不想折腾),还有就是很重要的一点,他没办法输入中文,因为我是监听键盘按下的事件,然后获得它的key,那用户想输入中文,我也只能获取到一个一个的英文字母(本人也想过偷懒,因为这个系统这里的表格大多数是不用输入中文,少数有中文,后面闲着没事,就问了chat gpt得到一些灵感)。

复制代码
 const currentCell = React.useRef<HTMLElement | null>() // 储存当前选中的单元格

 const  handleArrowKeys = (event) => { // 当按下键盘方向键做的事情
    if (!currentCell || !currentCell.current) return;
    const cellIndex = currentCell?.current?.cellIndex;
    let newCell;

    switch (event.key) {
      case 'ArrowUp':
        newCell = 
 currentCell.current?.parentElement?.previousElementSibling?.cells[cellIndex];
        break;
      case 'ArrowDown':
        newCell = 
 currentCell.current?.parentElement?.nextElementSibling?.cells[cellIndex];
        break;
      case 'ArrowLeft':
        newCell = currentCell?.current?.previousElementSibling;
        break;
      case 'ArrowRight':
        newCell = currentCell?.current?.nextElementSibling;
        break;
      default:
        break;
    }

     if (newCell) {
      if(currentCell?.current){
        currentCell.current.style.border = 'solid 2px black'
        let input = document.getElementById("dynamicInput");
        if (input) {
          input.remove();
        }
      }
      currentCell.current = newCell
      newCell.style.border = '3px solid #1890ff'
      let input = document.createElement("input");
      input.type = "text";
      input.style.position = "absolute";
      input.style.left = "-9999px";
      input.id = "dynamicInput";
      newCell.appendChild(input);
      input.addEventListener("input", handleInput);
      input.focus();
    }
  }
 const handleInput = (e) => {
    if(!currentCell || !currentCell.current) return
    let childNodes = currentCell.current.childNodes
    let inputIndex: any = null, textAreaIndex: any = null
    childNodes.forEach((node, index) => {
      console.log(node, 'node')
      if(node.tagName === 'INPUT' && !node.id) inputIndex = index
      if(node.tagName === 'TEXTAREA') textAreaIndex = index
    })
    console.log(e, 'e')

     if(inputIndex !== null){
       childNodes[inputIndex].value = e.target.value
     }else if(textAreaIndex !== null){
       childNodes[textAreaIndex].value = e.target.value
     }
  }
  useEffect(()=>{
      // 鼠标点击事件,因为第一次选中单元格肯定是要点击
      document.addEventListener("click", (e: MouseEvent) => {
      const target = e.target as HTMLElement

      // console.log(target.tagName, 'target')
      // 这里要判断被点击的对象是不是你需要监听的表格的单元格
      const isActive = (target.tagName === 'TD' || target.tagName === 'TH') && ...

      if (isActive) {
        if(currentCell?.current){ // 将原本被选中的单元格样式改为正常样式
          currentCell.current.style.border = 'solid 2px black'
          let input = document.getElementById("dynamicInput");
          if (input) {
            input.remove();
          }
        }
        // 新的单元格存起来,并高亮显示
        currentCell.current = target
        target.style.border = '3px solid #1890ff'
        let input = document.createElement("input");
        input.type = "text";
        input.id = "dynamicInput";
        input.style.position = "absolute";
        input.style.left = "-9999px";
        target.appendChild(input);
        input.addEventListener("input", handleInput);
        input.focus();
      } else {
        // 如果被点击的不是需要监听的地方,则整个表格"失去焦点"
        if(currentCell?.current){
          currentCell.current.style.border = 'solid 2px black'
          currentCell.current = null
          let input = document.getElementById("dynamicInput");
          if (input) {
            input.remove();
          }
        }
      }
    })
    document.addEventListener('keydown', function(e) {
      // console.log(e, 'e')
      if (e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
        handleArrowKeys(e);
      }
    });
  },[])

后面这种方法就改成了给当前选中的单元格插入一个用户看不到的自动获取焦点的input,然后监听这个文本框的input事件,并实时将这个文本框的内容更新到对应的文本框。才刚实现这个,没有经过大量操作的测试,不知道会不会有什么bug,目前没有什么大问题。

相关推荐
A_nanda8 分钟前
根据AI提示排查vue前端项目
前端·javascript·vue.js
happymaker062632 分钟前
web前端学习日记——DAY05(定位、浮动、视频音频播放)
前端·学习·音视频
~无忧花开~43 分钟前
React状态管理完全指南
开发语言·前端·javascript·react.js·前端框架
LegendNoTitle1 小时前
计算机三级等级考试 网络技术 选择题考点详细梳理
服务器·前端·经验分享·笔记·php
@大迁世界1 小时前
1.什么是 ReactJS?
前端·javascript·react.js·前端框架·ecmascript
BJ-Giser2 小时前
Cesium 基于EZ-Tree的植被效果
前端·可视化·cesium
王码码20353 小时前
Flutter for OpenHarmony:Flutter 三方库 algoliasearch 毫秒级云端搜索体验(云原生搜索引擎)
android·前端·git·flutter·搜索引擎·云原生·harmonyos
发现一只大呆瓜3 小时前
深入浅出 AST:解密 Vite、Babel编译的底层“黑盒”
前端·面试·vite
天天鸭3 小时前
前端仔写了个 AI Agent,才发现大模型只干了 10% 的活
前端·python·ai编程
发现一只大呆瓜4 小时前
前端模块化:CommonJS、AMD、ES Module三大规范全解析
前端·面试·vite