前端——deepseek一分钟帮我实现富文本编辑选取输入判断变量(contenteditable+selection监听)

如何使用 DeepSeek 帮助自己的工作? 10w+人浏览 588人参与

⭐前言

大家好,我是yma16,本文分享 前端——富文本编辑实现选取输入判断变量(contenteditable+selection监听)。
contenteditable
全局属性 contenteditable 是一个枚举属性,表示元素是否可被用户编辑。如果可以,浏览器会修改元素的组件以允许编辑。

Selection
Selection 对象表示用户选择的文本范围或插入符号的当前位置。它代表页面中的文本选区,可能横跨多个元素。文本选区由用户拖拽鼠标经过文字而产生。

node系列往期文章
node_windows环境变量配置
node_npm发布包
linux_配置node
node_nvm安装配置
node笔记_http服务搭建(渲染html、json)
node笔记_读文件
node笔记_写文件
node笔记_连接mysql实现crud
node笔记_formidable实现前后端联调的文件上传
node笔记_koa框架介绍
node_koa路由
node_生成目录
node_读写excel
node笔记_读取目录的文件
node笔记——调用免费qq的smtp发送html格式邮箱
node实战——搭建带swagger接口文档的后端koa项目(node后端就业储备知识)
node实战——后端koa结合jwt连接mysql实现权限登录(node后端就业储备知识)
node实战——koa给邮件发送验证码并缓存到redis服务(node后端储备知识)

koa系列项目文章
前端vite+vue3结合后端node+koa——实现代码模板展示平台(支持模糊搜索+分页查询)
node+vue3+mysql前后分离开发范式——实现对数据库表的增删改查
node+vue3+mysql前后分离开发范式——实现视频文件上传并渲染

koa-vue性能监控到封装sdk系列文章
性能监控系统搭建——node_koa实现性能监控数据上报(第一章)
性能监控系统搭建——vue3实现性能监控数据展示(第二章)
性能监控计算——封装带性能计算并上报的npm包(第三章)
canvas系列文章
web canvas系列——快速入门上手绘制二维空间点、线、面
webgl canvas系列——快速加背景、抠图、加水印并下载图片
webgl canvas系列——animation中基本旋转、平移、缩放(模拟冒泡排序过程)
前端vue系列文章
vue3 + fastapi 实现选择目录所有文件自定义上传到服务器
前端vue2、vue3去掉url路由“ # ”号——nginx配置
csdn新星计划vue3+ts+antd赛道——利用inscode搭建vue3(ts)+antd前端模板
认识vite_vue3 初始化项目到打包
python_selenuim获取csdn新星赛道选手所在城市用echarts地图显示
让大模型分析csdn文章质量 —— 提取csdn博客评论在文心一言分析评论区内容
前端vue3——html2canvas给网站截图生成宣传海报
前端——html拖拽原理
前端 富文本编辑器原理——从javascript、html、css开始入门
前端老古董execCommand——操作 选中文本 样式
前端如何在30秒内实现吸管拾色器?
前端——原生Selection api操作选中文本 样式、取消样式(解决标签的无限嵌套问题)
前端 ——xml转json json转xml 实现 mjml 邮件内容转json,json转mjml
前端 ——youtube、tiktok视频封面获取并使用canvas合并封面和自定义播放按钮生成图片
前端gmail邮件加载动态样式——动态评分交互邮件可提交api
react_flow自定义节点、边——使用darg布局树状结构

⭐从deepseek获取思路

提示词:采用contenteditable+selection实现实现选取监听 当光标上输入 selection输入光标前存在{{ 输入光标后两个字符存在}} 就判断 是变量,弹出一个下拉选择框选择变量

变量输入检测与下拉选择实现
我将实现一个智能文本输入系统,当用户输入{{var}}格式的变量时,自动弹出下拉选择框供用户选择预定义变量。

思路分析
监听输入框的输入事件

检测光标位置前后是否有{{}}

当条件满足时,显示变量选择下拉框

用户选择变量后,自动替换当前输入的变量占位符

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>智能变量输入系统</title>
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        
        body {
            background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
        }
        
        .container {
            width: 100%;
            max-width: 800px;
            background: white;
            border-radius: 12px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
            overflow: hidden;
        }
        
        .header {
            background: linear-gradient(90deg, #4b6cb7 0%, #182848 100%);
            color: white;
            padding: 20px 30px;
            text-align: center;
        }
        
        .header h1 {
            font-size: 24px;
            margin-bottom: 8px;
        }
        
        .header p {
            opacity: 0.8;
            font-size: 14px;
        }
        
        .content {
            padding: 30px;
        }
        
        .input-container {
            position: relative;
            margin-bottom: 20px;
        }
        
        #editableDiv {
            width: 100%;
            min-height: 200px;
            padding: 15px;
            border: 2px solid #e1e5ee;
            border-radius: 8px;
            font-size: 16px;
            line-height: 1.5;
            overflow-y: auto;
            outline: none;
            transition: border-color 0.3s;
        }
        
        #editableDiv:focus {
            border-color: #4b6cb7;
        }
        
        .variable {
            background-color: #e3f2fd;
            color: #1565c0;
            padding: 2px 6px;
            border-radius: 4px;
            font-weight: 500;
        }
        
        .variables-panel {
            background: #f8f9fa;
            border-radius: 8px;
            padding: 20px;
            margin-top: 20px;
        }
        
        .variables-panel h3 {
            margin-bottom: 15px;
            color: #2c3e50;
            font-size: 18px;
        }
        
        .variable-list {
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
        }
        
        .variable-tag {
            background: #e3f2fd;
            color: #1565c0;
            padding: 6px 12px;
            border-radius: 20px;
            font-size: 14px;
            cursor: pointer;
            transition: all 0.2s;
        }
        
        .variable-tag:hover {
            background: #bbdefb;
            transform: translateY(-2px);
        }
        
        .dropdown {
            position: fixed;
            background: white;
            border: 1px solid #e1e5ee;
            border-radius: 6px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
            max-height: 200px;
            overflow-y: auto;
            z-index: 1000;
            display: none;
            min-width: 200px;
        }
        
        .dropdown-item {
            padding: 10px 15px;
            cursor: pointer;
            border-bottom: 1px solid #f0f0f0;
            transition: background 0.2s;
        }
        
        .dropdown-item:hover {
            background: #f5f7fa;
        }
        
        .dropdown-item.active {
            background: #e3f2fd;
        }
        
        .dropdown-item:last-child {
            border-bottom: none;
        }
        
        .instructions {
            background: #e8f4fd;
            border-left: 4px solid #4b6cb7;
            padding: 15px;
            margin-top: 25px;
            border-radius: 0 8px 8px 0;
        }
        
        .instructions h3 {
            color: #2c3e50;
            margin-bottom: 10px;
        }
        
        .instructions ul {
            padding-left: 20px;
        }
        
        .instructions li {
            margin-bottom: 8px;
            color: #555;
        }
        
        .highlight {
            background-color: #fff9c4;
            padding: 0 2px;
            border-radius: 2px;
        }
        
        .status {
            margin-top: 10px;
            font-size: 14px;
            color: #666;
            min-height: 20px;
        }
        
        .debug {
            margin-top: 10px;
            padding: 10px;
            background: #f8f9fa;
            border-radius: 4px;
            font-size: 12px;
            color: #666;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>智能变量输入系统</h1>
            <p>基于contenteditable和Selection API的实时变量检测</p>
        </div>
        
        <div class="content">
            <div class="input-container">
                <div id="editableDiv" contenteditable="true">请输入文本,例如:我的用户名是{{username}}</div>
                <div id="dropdown" class="dropdown"></div>
                <div id="status" class="status">等待输入...</div>
                <div id="debug" class="debug"></div>
            </div>
            
            <div class="variables-panel">
                <h3>可用变量</h3>
                <div class="variable-list">
                    <div class="variable-tag" data-variable="username">用户名</div>
                    <div class="variable-tag" data-variable="email">邮箱地址</div>
                    <div class="variable-tag" data-variable="phone">手机号码</div>
                    <div class="variable-tag" data-variable="date">当前日期</div>
                    <div class="variable-tag" data-variable="time">当前时间</div>
                    <div class="variable-tag" data-variable="company">公司名称</div>
                    <div class="variable-tag" data-variable="department">部门</div>
                    <div class="variable-tag" data-variable="position">职位</div>
                </div>
            </div>
            
            <div class="instructions">
                <h3>使用说明</h3>
                <ul>
                    <li>在编辑区域输入 <span class="highlight">{{变量名}}</span> 格式</li>
                    <li>当光标位于 <span class="highlight">{{</span><span class="highlight">}}</span> 之间时,会自动弹出变量选择框</li>
                    <li>使用鼠标或键盘选择变量</li>
                    <li>变量会被高亮显示</li>
                </ul>
            </div>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const editableDiv = document.getElementById('editableDiv');
            const dropdown = document.getElementById('dropdown');
            const status = document.getElementById('status');
            const debug = document.getElementById('debug');
            const variableTags = document.querySelectorAll('.variable-tag');
            
            // 预定义变量
            const variables = {
                'username': '用户名',
                'email': '邮箱地址', 
                'phone': '手机号码',
                'date': '当前日期',
                'time': '当前时间',
                'company': '公司名称',
                'department': '部门',
                'position': '职位'
            };
            
            let isChecking = false;
            
            // 监听所有可能改变内容或光标位置的事件
            editableDiv.addEventListener('input', checkForVariable);
            editableDiv.addEventListener('keyup', checkForVariable);
            editableDiv.addEventListener('click', checkForVariable);
            document.addEventListener('selectionchange', checkForVariable);
            
            // 键盘事件处理
            editableDiv.addEventListener('keydown', function(e) {
                if (dropdown.style.display === 'block') {
                    handleDropdownKeyboard(e);
                }
            });
            
            // 点击变量标签
            variableTags.forEach(tag => {
                tag.addEventListener('click', function() {
                    insertVariable(this.dataset.variable);
                });
            });
            
            // 点击其他地方隐藏下拉框
            document.addEventListener('click', function(e) {
                if (!dropdown.contains(e.target) && e.target !== editableDiv) {
                    hideDropdown();
                }
            });
            
            // 检查变量格式
            function checkForVariable() {
                if (isChecking) return;
                isChecking = true;
                
                setTimeout(() => {
                    const sel = window.getSelection();
                    if (sel.rangeCount === 0) {
                        isChecking = false;
                        return;
                    }
                    
                    const range = sel.getRangeAt(0);
                    const container = range.commonAncestorContainer;
                    
                    if (!editableDiv.contains(container)) {
                        isChecking = false;
                        return;
                    }
                    
                    // 获取光标位置
                    const cursorPos = range.startOffset;
                    let textNode = container;
                    
                    // 确保是文本节点
                    if (textNode.nodeType !== Node.TEXT_NODE) {
                        textNode = findTextNodeAtCursor(range);
                        if (!textNode) {
                            hideDropdown();
                            isChecking = false;
                            return;
                        }
                    }
                    
                    const fullText = textNode.textContent;
                    
                    // 检查光标前2个字符是否有 {{
                    const beforeCursor = fullText.substring(0, cursorPos);
                    const hasOpenBrace = beforeCursor.endsWith('{{');
                    
                    // 检查光标后2个字符是否有 }}
                    const afterCursor = fullText.substring(cursorPos);
                    const hasCloseBrace = afterCursor.startsWith('}}');
                    
                    updateDebugInfo(fullText, cursorPos, hasOpenBrace, hasCloseBrace);
                    
                    // 如果同时满足条件,显示下拉框
                    if (hasOpenBrace && hasCloseBrace) {
                        // 获取变量名(光标位置的内容)
                        const varStart = beforeCursor.lastIndexOf('{{') + 2;
                        const varEnd = cursorPos + 2; // }} 的位置
                        const variableName = fullText.substring(varStart, cursorPos);
                        
                        showDropdown(variableName, textNode, varStart, varEnd);
                        status.textContent = `检测到变量: {{${variableName}}}`;
                    } else {
                        hideDropdown();
                        status.textContent = '正常输入模式';
                    }
                    
                    isChecking = false;
                }, 10);
            }
            
            // 处理下拉框键盘事件
            function handleDropdownKeyboard(e) {
                const items = dropdown.querySelectorAll('.dropdown-item');
                const activeItem = dropdown.querySelector('.dropdown-item.active');
                
                switch(e.key) {
                    case 'ArrowDown':
                        e.preventDefault();
                        if (!activeItem) {
                            items[0]?.classList.add('active');
                        } else {
                            const next = activeItem.nextElementSibling;
                            if (next) {
                                activeItem.classList.remove('active');
                                next.classList.add('active');
                            }
                        }
                        break;
                    case 'ArrowUp':
                        e.preventDefault();
                        if (activeItem) {
                            const prev = activeItem.previousElementSibling;
                            if (prev) {
                                activeItem.classList.remove('active');
                                prev.classList.add('active');
                            }
                        }
                        break;
                    case 'Enter':
                        e.preventDefault();
                        if (activeItem) {
                            selectVariable(activeItem.dataset.variable);
                        }
                        break;
                    case 'Escape':
                        e.preventDefault();
                        hideDropdown();
                        break;
                }
            }
            
            // 查找光标所在的文本节点
            function findTextNodeAtCursor(range) {
                let node = range.startContainer;
                
                if (node.nodeType === Node.TEXT_NODE) {
                    return node;
                }
                
                // 在元素中查找文本节点
                const walker = document.createTreeWalker(
                    node,
                    NodeFilter.SHOW_TEXT,
                    null,
                    false
                );
                
                let textNode = null;
                while (walker.nextNode()) {
                    const currentNode = walker.currentNode;
                    const nodeRange = document.createRange();
                    nodeRange.selectNodeContents(currentNode);
                    
                    if (range.compareBoundaryPoints(Range.START_TO_START, nodeRange) >= 0 &&
                        range.compareBoundaryPoints(Range.END_TO_END, nodeRange) <= 0) {
                        textNode = currentNode;
                        break;
                    }
                }
                
                return textNode;
            }
            
            // 显示下拉框
            function showDropdown(variableName, textNode, startPos, endPos) {
                dropdown.innerHTML = '';
                
                // 过滤匹配的变量
                const matchedVars = Object.keys(variables).filter(v => 
                    v.toLowerCase().includes(variableName.toLowerCase())
                );
                
                // 如果没有匹配项,显示所有变量
                const displayVars = matchedVars.length > 0 ? matchedVars : Object.keys(variables);
                
                // 创建下拉选项
                displayVars.forEach(variable => {
                    const item = document.createElement('div');
                    item.className = 'dropdown-item';
                    item.textContent = `${variables[variable]} (${variable})`;
                    item.dataset.variable = variable;
                    
                    item.addEventListener('click', () => selectVariable(variable));
                    dropdown.appendChild(item);
                });
                
                // 保存当前变量范围信息
                dropdown._currentVariable = {
                    textNode,
                    startPos,
                    endPos,
                    originalText: textNode.textContent
                };
                
                // 定位下拉框
                positionDropdown();
                dropdown.style.display = 'block';
                
                // 高亮第一个选项
                const firstItem = dropdown.querySelector('.dropdown-item');
                if (firstItem) firstItem.classList.add('active');
            }
            
            // 定位下拉框
            function positionDropdown() {
                const sel = window.getSelection();
                if (sel.rangeCount === 0) return;
                
                const range = sel.getRangeAt(0);
                const rect = range.getBoundingClientRect();
                
                if (rect.width === 0 && rect.height === 0) {
                    const tempRange = range.cloneRange();
                    tempRange.collapse(false);
                    const tempRect = tempRange.getBoundingClientRect();
                    
                    dropdown.style.top = `${tempRect.bottom + window.scrollY + 5}px`;
                    dropdown.style.left = `${tempRect.left + window.scrollX}px`;
                } else {
                    dropdown.style.top = `${rect.bottom + window.scrollY + 5}px`;
                    dropdown.style.left = `${rect.left + window.scrollX}px`;
                }
            }
            
            // 隐藏下拉框
            function hideDropdown() {
                dropdown.style.display = 'none';
                dropdown._currentVariable = null;
            }
            
            // 选择变量
            function selectVariable(variable) {
                if (!dropdown._currentVariable) return;
                
                const { textNode, startPos, endPos, originalText } = dropdown._currentVariable;
                
                // 创建变量元素
                const variableElement = document.createElement('span');
                variableElement.className = 'variable';
                variableElement.textContent = `{{${variable}}}`;
                variableElement.dataset.variable = variable;
                
                // 替换文本
                const beforeText = originalText.substring(0, startPos);
                const afterText = originalText.substring(endPos);
                
                // 创建文档片段
                const fragment = document.createDocumentFragment();
                if (beforeText) fragment.appendChild(document.createTextNode(beforeText));
                fragment.appendChild(variableElement);
                if (afterText) fragment.appendChild(document.createTextNode(afterText));
                
                // 替换内容
                textNode.parentNode.replaceChild(fragment, textNode);
                
                // 移动光标到变量后
                const range = document.createRange();
                range.setStartAfter(variableElement);
                range.collapse(true);
                
                const sel = window.getSelection();
                sel.removeAllRanges();
                sel.addRange(range);
                
                hideDropdown();
                status.textContent = `已插入变量: {{${variable}}}`;
                editableDiv.focus();
            }
            
            // 插入变量(从标签点击)
            function insertVariable(variable) {
                const sel = window.getSelection();
                if (sel.rangeCount === 0) return;
                
                const range = sel.getRangeAt(0);
                
                // 创建变量元素
                const variableElement = document.createElement('span');
                variableElement.className = 'variable';
                variableElement.textContent = `{{${variable}}}`;
                variableElement.dataset.variable = variable;
                
                // 插入变量
                range.insertNode(variableElement);
                
                // 移动光标到变量后
                const newRange = document.createRange();
                newRange.setStartAfter(variableElement);
                newRange.collapse(true);
                
                sel.removeAllRanges();
                sel.addRange(newRange);
                
                editableDiv.focus();
                status.textContent = `已插入变量: {{${variable}}}`;
            }
            
            // 更新调试信息
            function updateDebugInfo(fullText, cursorPos, hasOpenBrace, hasCloseBrace) {
                const before = fullText.substring(0, cursorPos);
                const after = fullText.substring(cursorPos);
                
                let debugText = `全文: "${fullText}"<br>`;
                debugText += `光标位置: ${cursorPos}<br>`;
                debugText += `光标前: "${before}" → 有{{: ${hasOpenBrace}<br>`;
                debugText += `光标后: "${after}" → 有}}: ${hasCloseBrace}<br>`;
                debugText += `满足条件: ${hasOpenBrace && hasCloseBrace ? '是' : '否'}`;
                
                debug.innerHTML = debugText;
            }
        });
    </script>
</body>
</html>

运行效果

在这里插入图片描述
在这里插入图片描述

⭐结束

本文分享到这结束,如有错误或者不足之处欢迎指出!

scene

👍 点赞,是我创作的动力!

⭐️ 收藏,是我努力的方向!

✏️ 评论,是我进步的财富!

💖 最后,感谢你的阅读!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yma16

感谢支持!共勉!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值