Fix: 修复AI聊天输入框Safari回车发送bug

背景

AI聊天输入框场景下,我们通常需要支持用户按下enter回车的快捷发送,通过键盘事件实现

html 复制代码
<textarea
    @keydown="onInputKeydown"
    placeholder="请输入内容,按 Enter 发送,Shift + Enter 换行"
></textarea>
js 复制代码
// 回车发送
const onInputKeydown = (event) => {
  if(event.key === "Enter" || event.keyCode === '13') {
    if(event.shiftKey) return; // shift + enter 仅换行
    event.preventDefault();
    sendMessage(); // 发送处理
  }
}

问题

但实际上enter作为确认操作,除了确认内容发送动作,还有确认输入法输入的动作;

如用户在输入法输入状态下,按下enter期望确认输入内容到文本框,此时也会触发上面的发送逻辑导致内容内发送出去。

我们可以 onInputKeydown 加一个合成状态(isComposing)判断

js 复制代码
const onInputKeydown = (event) => {
  if(event.key === "Enter" || event.keyCode === '13') {
    if(event.shiftKey) return;
    if(event.isComposing) return; // 此次为输入法状态下的enter(即输入法合成状态),不发送内容
    event.preventDefault();
    sendMessage();
  }
}

这确实有效,但是在Safari下键盘输入文字(如输入英文)且在输入法合成状态,按下enter内容依旧被发送了!原因在于 event.isComposing 在Safari并不可信,导致不可用;

延展

那么解决Safari上这个问题,我们必须Safari上实现可信的isComposing;

尝试通过监听 composotionstart/compositionend 实现自己的 Composing 状态,但事实是不可行(Safari的composition事件与keydown、input等事件执行顺序问题);

但是input事件有 inputType 可判断到输入法状态 MDN inputType

最终实现:额外监听input事件(先于keydown事件),针对Safari使用自己实现的Composing状态判断是否为输入法合成下enter

html 复制代码
<textarea
    @input="onInput"
    @keydown="onInputKeydown"
    placeholder="请输入内容,按 Enter 发送,Shift + Enter 换行"
></textarea>
js 复制代码
// 引入浏览器检测库
import { UAParser } from 'ua-parser-js';
const parser = new UAParser();
const result = parser.getResult();

// 回车发送
const onInputKeydown = (event) => {
  if(event.key === "Enter" || event.keyCode === '13') {
    if(event.shiftKey) return;
    // 针对safari浏览器兼容
    if(result.browser.name === "safari") {
      if(isCompositionInputType) { // 使用自己实现的Composing状态
        isCompositionInputType = false;
        return;
      }
    }else {
      if(event.isComposing) return;
    }
    event.preventDefault();
    sendMessage(); // 发送
  }
}

// 手动实现判断是否为输入法合成状态
let isCompositionInputType = false;
const onInput = (event) => {
  isCompositionInputType = ["insertCompositionText", "insertFromComposition"].includes(event.inputType);
}
相关推荐
杨利杰YJlio14 小时前
ITSK PE 26U5 测试版解读:组件完善、VMD 修复与服务器支持边界
前端·javascript·后端
志尊宝14 小时前
Vue3 零基础每日笔记(021):class 与 style 动态绑定——对象、数组、三元表达式写法汇总
前端·vue.js·笔记·前端框架·html5
夏幻灵14 小时前
Vue / React 列表渲染中的 key:为什么没有 key,Checkbox 可能“对号跑错”?
前端·javascript·vue.js
YWL14 小时前
vue-time-axis-plus进阶:双进度条+跨空白续播+播放指针,打造专业级监控回放体验
前端·javascript·vue·时间轴
mmsx14 小时前
MapLibre 实战 09|Bug 单写着"地图被风刮走了":一个瓦片源工厂,是三个线上事故换来的
android·前端·开源
不羁的木木14 小时前
给鸿蒙 App 增加打开外部网页能力 —— flutter_web_browser 的鸿蒙使用指南
前端·flutter·harmonyos
广州山泉婚姻14 小时前
微服务时代,前后端分离架构该怎样高效协同?
前端·后端
EatFan14 小时前
多租户系统到底怎么设计?从 tenant_id 到 JWT 租户隔离的真实实践
前端·后端·全栈·多租户
计算机魔术师14 小时前
硅谷四大AI巨头联手踩刹车,是真心怕失控还是怕被超越?
前端
岁岁种桃花儿14 小时前
Vue组件第六篇:Vue单文件组件
开发语言·前端·javascript·vue.js