软件教程 2025年08月6日
0 收藏 0 点赞 563 浏览 4844 个字
摘要 :

文章目录 一、需求的由来 实现效果 二、技术实现过程 (一)用contenteditable实现富文本编辑 (二)光标选区保存与恢复 (三)变量元素DOM设计 (四)双向数据绑定 ……




  • 一、需求的由来
  • 二、技术实现过程
    • (一)用contenteditable实现富文本编辑
    • (二)光标选区保存与恢复
    • (三)变量元素DOM设计
    • (四)双向数据绑定
    • (五)配置式参数传递
  • 三、总结

最近遇到个挺有意思的开发需求,今天来给大伙说说。在开发短信模板编辑器的时候,得实现一个变量插入功能,也就是在输入框里能插入变量,而且还得是富文本输入框,支持各种格式的编辑。这听起来有点复杂,不过咱们一步步拆解,其实也没那么难。

一、需求的由来

那天我正捣鼓一个日期选择器的bug呢,公司新来的前端小妹林小夕找我帮忙,说有个需求搞不定。我一看,是在短信模板编辑器里实现变量插入功能。嘿,这可太熟悉了,我刚入行那会,也被这需求折磨得够呛。想着这需求虽然看着简单,但里面弯弯绕绕还挺多的,就跟小夕说,咱们试试用contenteditable这个方案来实现吧。如何使用contenteditable实现富文本输入框支持插入变量

实现效果

如何使用contenteditable实现富文本输入框支持插入变量

二、技术实现过程

(一)用contenteditable实现富文本编辑

这个需求的关键就是得有个能编辑的输入框,既能输入普通文本,还得能插入不可编辑的元素。但Vue 3本身没有直接绑定contenteditable的机制,所以得手动监听@input事件,再解析innerHTML,才能实现富文本编辑。

咱们先在模板里定义好这个输入框:

<div 
    ref=\"editorRef\" 
    class=\"content flex-1\" 
    contenteditable 
    @input=\"handleInput\" 
    @paste=\"handlePaste\" 
    @blur=\"saveSelection\"
></div>

这里给div加上了contenteditable属性,让它变成可编辑的。同时监听了input、paste和blur这几个事件,每个事件都有不同的作用。

接着看看处理输入事件的代码:

// 更新字符统计
const updateCharCount = () => {
  const text = editorRef.value?.textContent || \'\';
  currentCharCount.value = text.length;
};

// 发送更新事件
const emitUpdate = () => {
  const content = editorRef.value?.textContent?.replace(/\\n/g, \'\') || \'\';
  emit(\"update:modelValue\", content);
};

// 处理输入事件
const handleInput = () => {
  updateCharCount();
  emitUpdate();
};

在handleInput方法里,调用了updateCharCount函数更新输入框的字数统计,再通过emitUpdate把输入框的文本内容传递给父组件保存起来。

还有处理粘贴事件的代码也很重要,因为普通的粘贴可能会带来格式问题,甚至有XSS攻击的风险:

const handlePaste = (event: { preventDefault: () => void; clipboardData: any; }) => {
  // 阻止默认行为,即不允许粘贴任何格式的内容
  event.preventDefault();
  // 获取剪贴板数据
  const text = event.clipboardData?.getData(\"text/plain\") || \"\";
  // 使用document.execCommand(\'insertText\', false, text)插入纯文本
  document.execCommand(\"insertText\", false, text);
};

在handlePaste方法里,先阻止了默认的粘贴行为,然后获取剪贴板里的纯文本内容,再用document.execCommand把纯文本插入到输入框里,这样就能保证插入的内容安全又规范。

(二)光标选区保存与恢复

小夕在测试的时候发现,每次点击插入按钮,变量老是出现在输入框末尾,这可不行。这时候就得靠Range和Selection这两个“神器”来解决问题了。

为了能把变量准确插入到用户想要的位置,得先保存当前的光标位置,也就是选区Range对象,等插入完变量再恢复它。

先看看保存光标的代码:

const saveCaretPosition = () => {
  const selection = window.getSelection();
  if (!selection?.rangeCount) return;
  lastSelection = selection.getRangeAt(0).cloneRange();
  editorRef.value?.focus();
};

在saveCaretPosition函数里,通过window.getSelection获取当前的选区,如果有选区,就克隆一份保存起来,方便后续恢复。

再看看插入变量的代码:

const insertVariable = (varName: string) => {
  if (!varName) return;

  editorRef.value?.focus();
  const selection = window.getSelection();

  if (lastSelection && editorRef.value?.contains(lastSelection.startContainer)) {
    selection!.removeAllRanges();
    selection!.addRange(lastSelection);
  }

  const varElement = createVarElement(varName);
  const range = selection!.getRangeAt(0);
  range.insertNode(varElement);

  requestAnimationFrame(() => {
    const newRange = document.createRange();
    newRange.setStartAfter(varElement);
    newRange.collapse(true);
    selection!.removeAllRanges();
    selection!.addRange(newRange);
    saveCaretPosition();
  });
};

在insertVariable函数里,先判断变量名是否存在,存在的话就获取选区。如果之前保存过选区,就先把当前选区清空,再恢复之前保存的选区,然后创建变量元素插入到选区里。插入完之后,还得重新设置选区,把光标定位到变量后面,最后再保存一下新的光标位置。

还有在输入框失焦的时候,也得保存选区状态,防止下次插入变量时光标位置不对:

// 常规光标位置保存(用于blur事件)
const saveSelection = () => {
  const selection = window.getSelection();
  if (selection?.rangeCount) {
    const range = selection.getRangeAt(0);
    if (editorRef.value?.contains(range.commonAncestorContainer)) {
      lastSelection = range.cloneRange();
    }
  }
};

(三)变量元素DOM设计

为了让变量不能被编辑,还能整体删除,咱们创建一个span标签,把变量包起来,然后设置contentEditable=false。

const createVarElement = (varName: string) => {
  const span = document.createElement(\"span\");
  span.className = \"variable\";
  span.contentEditable = \"false\";
  span.dataset.var = varName;
  span.textContent = `${${varName}}`;
  return span;
};

在这个createVarElement函数里,创建了一个span标签,给它加上了variable类名,设置不可编辑,还在dataset里存了变量名,最后把变量的显示内容设置好。

光有标签还不够,还得在CSS里给变量设置个独特的样式,和普通文本区分开:

.variable {
  background-color: #f0f2f5;
  border-radius: 3px;
  padding: 0 4px;
  color: #409eff;
  display: inline-block;
}

这样,用户在编辑的时候就能很清楚地知道哪些是变量,哪些是普通文本了。

(四)双向数据绑定

在Vue组件里,为了能正确存储和展示变量,咱们得在组件挂载的时候解析modelValue,把变量转换成span元素。

const initHtml = (value: string) => {
  return value.replace(/${(\\w+)}/g, (_, varName) => {
    return createVarElement(varName).outerHTML;
  });
};

onMounted(() => {
  editorRef.value!.innerHTML = initHtml(props.modelValue);
  updateCharCount();
});

在initHtml函数里,用正则表达式把字符串里的变量替换成对应的span元素。在onMounted钩子函数里,把解析后的内容设置到输入框里,再更新一下字数统计。

除了初始化,还得监听modelValue的变化,保证输入框内容和外部数据始终一致:

watch(() => props.modelValue, (newVal) => {
  const parsedHTML = initHtml(newVal);
  if (parsedHTML !== editorRef.value?.innerHTML) {
    editorRef.value!.innerHTML = parsedHTML;
    updateCharCount();
  }
});

在这个watch函数里,只要modelValue有变化,就重新解析内容,如果和输入框当前的innerHTML不一样,就更新输入框内容,同时更新字数统计。

(五)配置式参数传递

为了让这个组件用起来更灵活,咱们定义了一个variableList,用来配置变量选项,用户可以根据自己的需求自由设置。

const props = defineProps({
  variableList: {
    type: Array<{ label: string; value: string }>,
    default: () => [],
  },
});

这样,在使用这个组件的时候,只需要传递不同的variableList,就能轻松切换变量选项了。

三、总结

折腾了这么一大圈,这个短信模板编辑器的富文本输入框功能总算是实现了。这里面用到的几个技术点还挺关键的:

  • 利用contenteditable实现富文本编辑:借助这个属性,再配合一些事件监听,咱们就打造出了一个比传统textarea功能更强大的富文本输入框,能满足各种编辑需求。
  • 光标选区保存与恢复机制:靠Range和Selection对象,成功解决了变量插入位置不对的问题,大大提升了用户体验,编辑起来更顺手。
  • 变量元素DOM设计:用不可编辑的span标签封装变量,再加上独特的CSS样式,让变量在编辑过程中既不会被误改,又能方便地整体删除。
  • 双向数据绑定实现内容同步:通过Vue的响应式机制,不管是初始化还是数据更新,输入框内容和外部数据都能保持一致,后续处理和存储数据也更方便。
  • 配置式参数传递实现组件灵活集成:把变量列表这些配置参数通过Props传递,组件复用性大大提高,以后遇到类似需求,稍微改改配置就能用,开发效率杠杠的!

大伙要是在开发中也遇到类似需求,不妨参考参考这篇文章,希望能帮到你们!

微信扫一扫

支付宝扫一扫

版权: 转载请注明出处:https://www.zuozi.net/6832.html

管理员

相关推荐
2025-08-06

文章目录 一、Promise基础回顾 二、Promise 与 axios 结合使用场景及方法 (一)直接返回 axios …

269
2025-08-06

文章目录 一、模块初始化时的内部机制 二、常见导出写法的差异分析 (一)写法一:module.exports…

107
2025-08-06

文章目录 一、ResizeObserver详解 (一)ResizeObserver是什么 (二)ResizeObserver的基本用法 …

683
2025-08-06

文章目录 一、前期准备工作 (一)下载相关文件 (二)安装必要工具 二、处理扣子空间生成的文件…

338
2025-08-06

文章目录 一、官方文档 二、自动解包的数据类型 ref对象:无需.value即可访问 reactive对象:保持…

371
2025-08-06

文章目录 一、Hooks的工作原理 二、在if语句中使用Hook会出什么岔子? 三、React官方的Hook使用规…

843
发表评论
暂无评论

还没有评论呢,快来抢沙发~

助力内容变现

将您的收入提升到一个新的水平

点击联系客服

在线时间:08:00-23:00

客服QQ

122325244

客服电话

400-888-8888

客服邮箱

122325244@qq.com

扫描二维码

关注微信客服号