ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

浏览器文本补全脚本

浏览器文本补全脚本 一、功能展示如何在网页里面实现文本补全呢在油猴里面粘贴并保存下面的完整代码然后在网页文本框里面输入索引词就会弹出待选项按下按钮即可自动替换。补全功能如图所示。二、添加地方在下面代码区域添加你想要补全的内容。例打印: console.log();冒号前面为索引词冒号后面为待选项使用中括号就是有多个待选项。const SNIPPETS { 打印: console.log();, 报错: console.error();, log: [ console.log();, console.log(DEBUG:, ...);, console.log(JSON.stringify(obj, null, 2));, console.table(data); ], 循环: [ for (let i 0; i arr.length; i) {\n const item arr[i];\n \n}, arr.forEach((item, index) {\n \n});, for (const key in obj) {\n if (obj.hasOwnProperty(key)) {\n \n }\n} ], 函数: [ function name() {\n \n}, const name () {\n \n};, async function name() {\n try {\n \n } catch (err) {\n console.error(err);\n }\n} ], 判断: if (condition) {\n \n}, 试试: try {\n \n} catch (err) {\n console.error(err);\n}, 框架: !DOCTYPE html\nhtml\nhead\n meta charsetUTF-8\n title文档/title\n/head\nbody\n \n/body\n/html, 请求: axios.get(/api).then(res {\n console.log(res.data);\n}); };三、完整代码// UserScript // name 智能代码补全自动定位 可拖拽 // namespace http://tampermonkey.net/ // version 4.0 // description 下拉菜单自动防遮挡支持鼠标拖拽移动兼容React/Vue // author You // match *://*/* // grant none // /UserScript (function() { use strict; // // ★★★ 配置区域 ★★★ // const SNIPPETS { 打印: console.log();, 报错: console.error();, log: [ console.log();, console.log(DEBUG:, ...);, console.log(JSON.stringify(obj, null, 2));, console.table(data); ], 循环: [ for (let i 0; i arr.length; i) {\n const item arr[i];\n \n}, arr.forEach((item, index) {\n \n});, for (const key in obj) {\n if (obj.hasOwnProperty(key)) {\n \n }\n} ], 函数: [ function name() {\n \n}, const name () {\n \n};, async function name() {\n try {\n \n } catch (err) {\n console.error(err);\n }\n} ], 判断: if (condition) {\n \n}, 试试: try {\n \n} catch (err) {\n console.error(err);\n}, 框架: !DOCTYPE html\nhtml\nhead\n meta charsetUTF-8\n title文档/title\n/head\nbody\n \n/body\n/html, 请求: axios.get(/api).then(res {\n console.log(res.data);\n}); }; // let menuDiv null; let isComposing false; let activeIndex -1; let currentMatches []; let currentWord ; let currentInput null; let currentPage 0; const PAGE_SIZE 9; // ---- 拖拽相关 ---- let isDragging false; let dragOffsetX 0; let dragOffsetY 0; let dragHandle null; // ---- 创建菜单 ---- function createMenu() { if (menuDiv) return; menuDiv document.createElement(div); menuDiv.style.cssText position: fixed; background: #ffffff; border: 1px solid #d1d5db; border-radius: 8px; box-shadow: 0 6px 16px rgba(0,0,0,0.15); max-height: 300px; width: 320px; overflow-y: auto; font-family: Segoe UI, Consolas, monospace; z-index: 999999; display: none; padding: 0 0 4px 0; user-select: none; ; document.body.appendChild(menuDiv); // 创建拖拽手柄顶部灰色条 dragHandle document.createElement(div); dragHandle.style.cssText height: 12px; background: #f3f4f6; cursor: grab; border-radius: 8px 8px 0 0; display: flex; align-items: center; justify-content: center; font-size: 10px; color: #9ca3af; letter-spacing: 4px; border-bottom: 1px solid #e5e7eb; ; dragHandle.textContent ⋮⋮⋮; // 拖拽点阵 dragHandle.addEventListener(mousedown, startDrag); menuDiv.appendChild(dragHandle); } // ---- 拖拽逻辑 ---- function startDrag(e) { e.preventDefault(); e.stopPropagation(); isDragging true; const rect menuDiv.getBoundingClientRect(); dragOffsetX e.clientX - rect.left; dragOffsetY e.clientY - rect.top; menuDiv.style.cursor grabbing; document.addEventListener(mousemove, onDrag); document.addEventListener(mouseup, endDrag); } function onDrag(e) { if (!isDragging) return; e.preventDefault(); let left e.clientX - dragOffsetX; let top e.clientY - dragOffsetY; // 防止拖出屏幕边界 left Math.max(0, Math.min(left, window.innerWidth - menuDiv.offsetWidth)); top Math.max(0, Math.min(top, window.innerHeight - menuDiv.offsetHeight)); menuDiv.style.left left px; menuDiv.style.top top px; menuDiv.style.right auto; menuDiv.style.bottom auto; } function endDrag(e) { isDragging false; menuDiv.style.cursor default; document.removeEventListener(mousemove, onDrag); document.removeEventListener(mouseup, endDrag); } // ---- 智能定位 ---- function positionMenu() { if (!menuDiv || !currentInput) return; const rect currentInput.getBoundingClientRect(); const menuWidth menuDiv.scrollWidth || 320; const menuHeight menuDiv.scrollHeight || 300; // 先计算初始位置默认在输入框下方 let left rect.left; let top rect.bottom 4; // 1. 水平方向修正 if (left menuWidth window.innerWidth) { left window.innerWidth - menuWidth - 10; } if (left 0) left 10; // 2. 垂直方向修正优先在下方下方不够就跳到上方 if (top menuHeight window.innerHeight) { // 检查上方空间是否足够 if (rect.top menuHeight 10) { top rect.top - menuHeight - 4; } else { // 上下都不够就紧贴屏幕底部 top window.innerHeight - menuHeight - 10; } } if (top 0) top 10; menuDiv.style.left left px; menuDiv.style.top top px; menuDiv.style.right auto; menuDiv.style.bottom auto; } // ---- 渲染页面 ---- function renderPage() { if (!menuDiv) return; const start currentPage * PAGE_SIZE; const end start PAGE_SIZE; const pageItems currentMatches.slice(start, end); const totalPages Math.ceil(currentMatches.length / PAGE_SIZE); // 保留拖拽手柄清除其余内容 while (menuDiv.childNodes.length 0) { if (menuDiv.childNodes[0] dragHandle) break; menuDiv.removeChild(menuDiv.childNodes[0]); } // 确保手柄在第一位 if (menuDiv.firstChild ! dragHandle) { menuDiv.insertBefore(dragHandle, menuDiv.firstChild); } // 删除手柄之后的所有内容 while (menuDiv.childNodes.length 1) { menuDiv.removeChild(menuDiv.childNodes[1]); } if (pageItems.length 0 currentPage 0) { currentPage 0; renderPage(); return; } // 1. 渲染代码选项 pageItems.forEach((item, idx) { const num idx 1; const div document.createElement(div); div.style.cssText padding: 6px 12px; cursor: pointer; color: #333; border-bottom: 1px solid #f3f4f6; font-size: 12px; line-height: 1.5; display: flex; align-items: flex-start; white-space: pre-wrap; word-break: break-all; user-select: none; ; const numSpan document.createElement(span); numSpan.style.cssText display: inline-block; width: 24px; color: #2563eb; font-weight: bold; margin-right: 8px; flex-shrink: 0; ; numSpan.textContent num; const codeSpan document.createElement(span); codeSpan.textContent item.snippet; div.appendChild(numSpan); div.appendChild(codeSpan); div.addEventListener(mousedown, (e) { e.preventDefault(); e.stopPropagation(); }); div.addEventListener(click, (e) { e.stopPropagation(); const globalIdx currentPage * PAGE_SIZE idx; applySnippet(globalIdx); hideMenu(); }); div.addEventListener(mouseenter, () { activeIndex idx; highlightItem(activeIndex); }); menuDiv.appendChild(div); }); // 2. 底部翻页栏 if (totalPages 1) { const footer document.createElement(div); footer.style.cssText display: flex; justify-content: space-between; align-items: center; padding: 6px 12px; border-top: 1px solid #e5e7eb; background: #f9fafb; font-size: 12px; color: #6b7280; position: sticky; bottom: 0; user-select: none; ; const prev document.createElement(span); prev.textContent ‹ 上一页; prev.style.cssText cursor: pointer; padding: 2px 6px; border-radius: 4px;; prev.addEventListener(click, (e) { e.stopPropagation(); if (currentPage 0) { currentPage--; renderPage(); } }); if (currentPage 0) prev.style.opacity 0.5; const info document.createElement(span); info.textContent ${currentPage 1} / ${totalPages}; const next document.createElement(span); next.textContent 下一页 ›; next.style.cssText cursor: pointer; padding: 2px 6px; border-radius: 4px;; next.addEventListener(click, (e) { e.stopPropagation(); if (currentPage totalPages - 1) { currentPage; renderPage(); } }); if (currentPage totalPages - 1) next.style.opacity 0.5; footer.appendChild(prev); footer.appendChild(info); footer.appendChild(next); menuDiv.appendChild(footer); } // 先显示再定位防止获取高度为0 menuDiv.style.display block; // 使用 requestAnimationFrame 确保DOM渲染完成后再计算位置 requestAnimationFrame(() { positionMenu(); }); activeIndex -1; } function highlightItem(index) { // 跳过第一个手柄只高亮代码项 const items menuDiv.querySelectorAll(div:not(:first-child):not(:last-child)); items.forEach((el, i) { el.style.background i index ? #e3f2fd : transparent; }); } function showMenu(inputEl, word, matches) { if (!menuDiv) createMenu(); currentInput inputEl; currentWord word; currentMatches matches; currentPage 0; if (!matches || matches.length 0) { hideMenu(); return; } renderPage(); } function hideMenu() { if (menuDiv) menuDiv.style.display none; currentMatches []; activeIndex -1; currentPage 0; // 如果拖拽状态未结束强制清除 if (isDragging) endDrag(); } // ---- 核心插入逻辑保留 ---- function applySnippet(globalIdx) { if (!currentInput || !currentWord) return; if (globalIdx 0 || globalIdx currentMatches.length) return; const replacement currentMatches[globalIdx].snippet; if (!replacement) return; const isTextarea currentInput.tagName TEXTAREA || currentInput.tagName INPUT; const isEditable currentInput.isContentEditable; if (isTextarea) { const start currentInput.selectionStart; const text currentInput.value; const before text.substring(0, start); const regex /([a-zA-Z0-9_\u4e00-\u9fa5])$/; const match before.match(regex); if (match match[1] currentWord) { const newText text.substring(0, start - currentWord.length) replacement text.substring(start); const newPos start - currentWord.length replacement.length; let valueSetter null; let proto Object.getPrototypeOf(currentInput); while (proto !valueSetter) { const desc Object.getOwnPropertyDescriptor(proto, value); if (desc desc.set) valueSetter desc.set; proto Object.getPrototypeOf(proto); } if (valueSetter) { valueSetter.call(currentInput, newText); } else { currentInput.value newText; } currentInput.selectionStart currentInput.selectionEnd newPos; currentInput.dispatchEvent(new Event(input, { bubbles: true })); currentInput.dispatchEvent(new Event(change, { bubbles: true })); setTimeout(() { if (!currentInput) return; if (currentInput.value ! newText) { if (valueSetter) { valueSetter.call(currentInput, newText); } else { currentInput.value newText; } currentInput.selectionStart currentInput.selectionEnd newPos; currentInput.dispatchEvent(new Event(input, { bubbles: true })); } }, 0); } } else if (isEditable) { const sel window.getSelection(); if (!sel.rangeCount) return; const range sel.getRangeAt(0); const node range.startContainer; if (node.nodeType Node.TEXT_NODE) { const text node.textContent; const offset range.startOffset; const before text.substring(0, offset); const regex /([a-zA-Z0-9_\u4e00-\u9fa5])$/; const match before.match(regex); if (match match[1] currentWord) { range.setStart(node, offset - currentWord.length); range.setEnd(node, offset); sel.removeAllRanges(); sel.addRange(range); document.execCommand(insertText, false, replacement); } } } } // ---- 事件监听保持原有逻辑 ---- document.addEventListener(input, function(e) { if (isComposing) return; const el document.activeElement; if (!el) return; const isInput el.tagName TEXTAREA || el.tagName INPUT || el.isContentEditable; if (!isInput) return; let text, start; if (el.tagName TEXTAREA || el.tagName INPUT) { text el.value; start el.selectionStart; } else { const sel window.getSelection(); if (!sel.rangeCount) return; const range sel.getRangeAt(0); const node range.startContainer; if (node.nodeType ! Node.TEXT_NODE) return; text node.textContent; start range.startOffset; } const before text.substring(0, start); const regex /([a-zA-Z0-9_\u4e00-\u9fa5])$/; const match before.match(regex); if (match) { const word match[1]; if (word.length 1) { hideMenu(); return; } const expanded []; Object.keys(SNIPPETS).forEach(key { if (key.includes(word)) { const val SNIPPETS[key]; if (Array.isArray(val)) { val.forEach(snippet expanded.push({ key, snippet })); } else { expanded.push({ key, snippet: val }); } } }); if (expanded.length 0) { showMenu(el, word, expanded); } else { hideMenu(); } } else { hideMenu(); } }); document.addEventListener(keydown, function(e) { if (!menuDiv || menuDiv.style.display none) return; if (!isComposing e.key 1 e.key 9) { e.preventDefault(); const num parseInt(e.key) - 1; const globalIdx currentPage * PAGE_SIZE num; if (globalIdx currentMatches.length) { applySnippet(globalIdx); hideMenu(); } return; } if (e.key PageDown || e.key ArrowRight) { e.preventDefault(); const total Math.ceil(currentMatches.length / PAGE_SIZE); if (currentPage total - 1) { currentPage; renderPage(); } return; } if (e.key PageUp || e.key ArrowLeft) { e.preventDefault(); if (currentPage 0) { currentPage--; renderPage(); } return; } if (e.key ArrowDown) { e.preventDefault(); const pageItems currentMatches.slice(currentPage * PAGE_SIZE, (currentPage 1) * PAGE_SIZE); if (pageItems.length 0) return; activeIndex (activeIndex 1) % pageItems.length; highlightItem(activeIndex); } else if (e.key ArrowUp) { e.preventDefault(); const pageItems currentMatches.slice(currentPage * PAGE_SIZE, (currentPage 1) * PAGE_SIZE); if (pageItems.length 0) return; activeIndex (activeIndex - 1 pageItems.length) % pageItems.length; highlightItem(activeIndex); } else if (e.key Enter) { e.preventDefault(); if (activeIndex 0) { const pageItems currentMatches.slice(currentPage * PAGE_SIZE, (currentPage 1) * PAGE_SIZE); if (activeIndex pageItems.length) { const globalIdx currentPage * PAGE_SIZE activeIndex; applySnippet(globalIdx); hideMenu(); } } } else if (e.key Escape) { hideMenu(); } }); document.addEventListener(mousedown, function(e) { if (menuDiv !menuDiv.contains(e.target)) { hideMenu(); } }); document.addEventListener(compositionstart, function() { isComposing true; }); document.addEventListener(compositionend, function() { isComposing false; const el document.activeElement; if (el) el.dispatchEvent(new Event(input, { bubbles: true })); }); })();
返回列表