本文介绍如何在 textarea 中监听右键事件,仅当光标位于预设关键词(如 'hello'、'hi' 等)上时,才显示自定义上下文菜单;其他位置或非关键词区域不响应,确保交互精准可控。
本文介绍如何在 textarea 中监听右键事件,仅当光标位于预设关键词(如 'hello'、'hi' 等)上时,才显示自定义上下文菜单;其他位置或非关键词区域不响应,确保交互精准可控。
实现「仅对特定词触发右键菜单」的核心难点在于:不能仅依赖整个 textarea 的 value 是否包含关键词,而需判断用户右键点击时,光标实际悬停/选中的单词是否匹配目标词。原始方案中简单 split(' ') 并全局过滤,会误判(例如 "hello!" 或 "some-text" 不应匹配 'hello' 或 'some'),且未考虑光标位置——用户可能右键在空白处或标点旁。
✅ 正确思路是:
- 捕获右键事件坐标 → 定位当前光标/选区位置;
- 从 selectionStart 向前后扩展,提取完整单词(按空白符和常见标点分隔);
- 清洗单词(去除首尾标点,如 hello! → hello),再比对关键词数组;
- 匹配成功则定位并显示自定义菜单;否则 preventDefault() 后静默退出。
以下是健壮、可直接运行的实现:
<style>
.custom-context-menu {
z-index: 1100;
display: none;
position: absolute;
list-style: none;
margin: 0;
padding: 0;
border: 1px solid #ccc;
background: #fff;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
border-radius: 4px;
min-width: 160px;
}
.custom-context-menu li {
padding: 8px 12px;
cursor: pointer;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
.custom-context-menu li:hover {
background-color: #f5f5f5;
}
.custom-context-menu li:not(:last-child) {
border-bottom: 1px solid #eee;
}
</style><textarea rows="4" cols="50">hello This is some text. Say hi to yo! Howdy there.</textarea>
<script>
const specificWords = ['hi', 'hello', 'yo', 'hey', 'some', 'howdy'];
const textarea = document.querySelector('textarea');
const contextMenu = document.querySelector('.custom-context-menu');
// 提取光标位置附近的完整单词(支持标点剥离)
function getWordAtPosition(text, pos) {
if (pos < 0 || pos > text.length) return '';
// 向左找单词起点(跳过空白和标点)
let start = pos;
while (start > 0 && /w/.test(text[start - 1])) start--;
// 向右找单词终点
let end = pos;
while (end < text.length && /w/.test(text[end])) end++;
return text.slice(start, end).toLowerCase();
}
textarea.addEventListener('contextmenu', function(e) {
e.preventDefault();
const pos = this.selectionStart;
const word = getWordAtPosition(this.value, pos);
// 严格匹配:仅当提取的单词在 specificWords 中才显示菜单
if (specificWords.includes(word)) {
contextMenu.style.left = (e.pageX + 10) + 'px'; // 避免紧贴鼠标
contextMenu.style.top = (e.pageY + 10) + 'px';
contextMenu.style.display = 'block';
contextMenu.dataset.activeWord = word; // 存储当前词供后续操作
} else {
contextMenu.style.display = 'none';
}
});
// 点击页面其他区域隐藏菜单
document.addEventListener('click', function() {
contextMenu.style.display = 'none';
});
// 菜单项点击处理
contextMenu.addEventListener('click', function(e) {
if (e.target.tagName === 'LI') {
const action = e.target.dataset.action;
const word = contextMenu.dataset.activeWord || '';
switch(action) {
case 'copy-word':
navigator.clipboard.writeText(word);
break;
case 'highlight':
const regex = new RegExp(`\b${word}\b`, 'gi');
textarea.value = textarea.value.replace(regex, `[${word.toUpperCase()}]`);
break;
case 'translate':
alert(`Translating "${word}"... (integrate with API here)`);
break;
}
contextMenu.style.display = 'none';
}
});
</script>⚠️ 关键注意事项:
- 不要用 split(' ') 全局检测:它无法反映用户实际点击位置,且无法处理标点(如 "hello!")、连字符、换行等边界情况;
- getWordAtPosition 是核心:基于正则 w(字母/数字/下划线)精准提取“视觉单词”,兼容大多数自然文本场景;
- 菜单定位偏移 +10px:避免鼠标遮挡菜单第一项,提升可用性;
- dataset.activeWord 缓存匹配词:确保菜单操作能准确作用于被点击的词,而非最后一次匹配结果;
- 务必绑定 document.click 隐藏逻辑:否则菜单可能常驻,破坏用户体验。
此方案兼顾准确性、健壮性与可维护性,可无缝集成至富文本编辑器、代码高亮工具或协作平台中,作为精细化上下文交互的基础能力。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











