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

一个真正丝滑的项目,交互不能只停留在点击按钮。 "选中即触发" (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 会让浮窗飞到九霄云外。

正确的绝对定位公式:

<math xmlns="http://www.w3.org/1998/Math/MathML"> 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) </math>Left=rect.left+window.scrollX+(rect.width/2)−(menuWidth/2)

<math xmlns="http://www.w3.org/1998/Math/MathML"> 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 </math>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)。

相关推荐
明月_清风1 小时前
告别后端转换:高质量批量导出实战
前端·javascript
刘发财6 小时前
弃用html2pdf.js,这个html转pdf方案能力是它的几十倍
前端·javascript·github
牛奶8 小时前
2026年大模型怎么选?前端人实用对比
前端·人工智能·ai编程
牛奶8 小时前
前端人为什么要学AI?
前端·人工智能·ai编程
Kagol11 小时前
🎉OpenTiny NEXT-SDK 重磅发布:四步把你的前端应用变成智能应用!
前端·开源·agent
GIS之路12 小时前
ArcGIS Pro 中的 notebook 初识
前端
JavaGuide12 小时前
7 道 RAG 基础概念知识点/面试题总结
前端·后端
ssshooter13 小时前
看完就懂 useSyncExternalStore
前端·javascript·react.js
格砸13 小时前
从入门到辞职|从ChatGPT到OpenClaw,跟上智能时代的进化
前端·人工智能·后端