自定义右键菜单:在项目里实现“选中文字即刻生成新提示”

一个真正丝滑的项目,交互不能只停留在点击按钮。 "选中即触发" (Selection-to-Action)是生产力工具的标配。

实现这个功能看似简单,实则藏着不少关于 Selection API视口坐标计算的深坑。


1. 核心流程:监听、获取、定位

第一步:捕捉用户的"选中时刻"

虽然有 selectionchange 事件,但在做浮窗时,mouseup 通常更稳,因为它能确保是在用户完成拖拽动作后触发。

JavaScript

ini 复制代码
document.addEventListener('mouseup', handleSelection);

function handleSelection(e) {
  const selection = window.getSelection();
  const selectedText = selection.toString().trim();

  if (selectedText.length > 0) {
    const range = selection.getRangeAt(0);
    // 关键点:获取选中文字在视口中的精确几何位置
    const rect = range.getBoundingClientRect();
    
    showFloatingMenu(rect, selectedText);
  } else {
    hideFloatingMenu();
  }
}

2. 坐标计算:让浮窗"如影随形"

这是最容易翻车的地方。getBoundingClientRect() 返回的是相对于**视口(Viewport)**的坐标。如果你的页面有滚动条,或者容器是 position: relative,直接赋值 top/left 会让浮窗飞到九霄云外。

正确的绝对定位公式:

L e f t = r e c t . l e f t + w i n d o w . s c r o l l X + ( r e c t . w i d t h / 2 ) − ( m e n u W i d t h / 2 ) Left = rect.left + window.scrollX + (rect.width / 2) - (menuWidth / 2) Left=rect.left+window.scrollX+(rect.width/2)−(menuWidth/2)

T o p = r e c t . t o p + w i n d o w . s c r o l l Y − m e n u H e i g h t − o f f s e t Top = rect.top + window.scrollY - menuHeight - offset Top=rect.top+window.scrollY−menuHeight−offset

JavaScript

javascript 复制代码
function showFloatingMenu(rect, text) {
  const menu = document.getElementById('floating-menu');
  const offset = 10; // 距离文字上方的间距

  // 计算位置:居中显示在选中文字上方
  const left = rect.left + window.scrollX + (rect.width / 2);
  const top = rect.top + window.scrollY - offset;

  Object.assign(menu.style, {
    display: 'flex',
    left: `${left}px`,
    top: `${top}px`,
    transform: 'translate(-50%, -100%)' // 利用 transform 实现水平对齐
  });

  menu.dataset.selectedText = text; // 暂存文字供后续使用
}

3. 避坑指南

① 避免"点一下就弹"

如果用户只是单纯点击了一下(没有选中任何字),mouseup 也会触发。

  • 解决 :除了判断 selectedText.length > 0,还可以记录 mousedown 的位置,如果 mouseup 的位置没变,说明是点击而非选择。

② 浮窗点击冲突:onmousedown 陷阱

当你点击浮窗上的"复制"按钮时,浏览器默认会清除当前页面的文字选中状态,导致 mouseup 再次触发把浮窗关掉。

  • 解决 :在浮窗容器上使用 onmousedown={(e) => e.preventDefault()}。这样点击浮窗时,焦点不会离开原来的文字。

③ 边界检测(Viewport Boundary)

如果选中的文字在屏幕最顶端,浮窗会超出屏幕。

  • 对策 :判断 rect.top 是否小于浮窗高度。如果是,则将浮窗显示在文字下方

4. 功能实现:一键复制与翻译

JavaScript

ini 复制代码
// 复制逻辑(复用我们上一篇文中的 safeCopy)
menu.querySelector('.copy-btn').onclick = async () => {
  const text = menu.dataset.selectedText;
  await safeCopy(text);
  showToast('已复制!');
  hideFloatingMenu();
};

// 翻译逻辑:调用 AI 接口
menu.querySelector('.translate-btn').onclick = async () => {
  const text = menu.dataset.selectedText;
  // 直接跳转到 AI 对话框并自动输入 Prompt
  router.push(`/chat?prompt=请翻译以下文字:${text}`);
};

5. 交互进阶:移动端长按适配

在移动端,用户习惯长按选择文字。

  • 方案 :现代移动浏览器会自动弹出系统菜单。如果你想覆盖它,需要监听 contextmenu 事件,或者通过 CSS 属性 -webkit-touch-callout: none; 禁用系统菜单,再手写一套长按逻辑(touchstart + setTimeout)。

相关推荐
计算机魔术师6 小时前
纽约时报版权诉讼披露:微软高管内部称训练 AI 是人类历史上最大规模劳动窃取
前端
去伪存真6 小时前
大模型的参数量为什么那么大?
前端·人工智能
IT_陈寒6 小时前
Java空指针这次真把我坑惨了
前端·人工智能·后端
高级程序源7 小时前
django大学生创新创业项目管理系统94923-计算机课程设计、毕业设计
javascript·vue.js·spring boot·后端·python·django·课程设计
八荒启·交互动画7 小时前
# Web特效020—让 Web 特效真正动起来:时间循环应该怎么接
前端·webgl·网页特效·八荒启-交互动画·八荒启
动恰客流统计8 小时前
线下零售数字化浪潮下,客流统计的3个核心发展趋势
大数据·前端·人工智能
lizz22768 小时前
Babel插件手册
javascript
用户938515635078 小时前
从小米前端面试题,彻底搞懂闭包、作用域链与 useState 惰性初始化
前端·面试
粥里有勺糖9 小时前
视野修炼第133期 | Native 回春了?
前端·github·agent
aichitang20249 小时前
前端小skill
前端·人工智能·算法·ai·前端框架