DAILY DOCDAILY DOC
Rust
Node
Notes
Ubuntu
Leetcode
  • it-tools
  • excalidraw
  • linux-command
Rust
Node
Notes
Ubuntu
Leetcode
  • it-tools
  • excalidraw
  • linux-command
  • BFC 块级格式化上下文
  • Note
  • WebAssembly
  • public api
  • 位运算
  • bitwise operator
  • css实现隐藏效果
  • css snippets
  • 抖音点赞
  • js 相等判断
  • fetch ReadableStream
  • git
  • Github Actions 工作流
  • google search
  • RPC vs HTTP
  • gravatar
  • hhkb
  • Init project
  • input 文件上传
  • mac

    • Mac 使用技巧
    • alfred
    • mac shortcuts
    • shortcuts text edit
    • mac 修改host
  • 微前端
  • mock
  • nginx dump
  • nginx
  • NirCmd
  • npm
  • Operator Precedence
  • package.json
  • url query 解析
  • pnpm
  • JavaScript Precise countdown
  • react 模版
  • regexp
  • setup web development
  • telegram

    • telegram bot
  • timeFunction ease
  • 视频裁剪
  • vscode

    • vscode 高级指南
    • bracketPairs
    • jsconfig.json
    • vscode pipe into code
    • social project
    • vscode tasks
  • draggable resizable
  • windows 激活
  • 前端截图实现
  • 文本配音 富文本实现
  • 图片处理
  • 前端坐标
  • 定时任务
  • work efficient
  • 微信小程序动画实现方案
  • 排列组合
  • 数列
  • 语音驱动文字
  • 浏览器
  • 状态管理
  • 移动盒子
  • 移动端开发常用snippets
  • 设计模式
  • web performance

文本配音 富文本实现

背景

文本驱动,需要实现用户输入文本中穿插 自定义的HTML 片段。需要使用富文本来实现。简单的input 输入框满足不了业务需求。项目目前用的还是vue2 的框架

思路

受控富文本比较难处理,异常case 比较多,首先考虑的还是开源的富文本组件。

  • draft (react 项目)
  • slate (react 项目)
  • Quill (实现了自定义embed ,最后还是放弃这种方案)
  • tinyMCE (功能过剩,也不太符合,业务自定义难度大)

富文本参考 https://juejin.cn/post/7084046542994145294

Quill

Quill 实现了受控,光标位置管理,以及数据流model 模型 。刚开始看起来是比较吻合业务需求的。 问题:

  1. 实现了自定义 Embed 块之后。插入自定义embed 块总是换行了,最后没找到解决方案
  2. 统计字数不准,业务涉及到输入文本字符串数量限制,其中有插入新字符但是不做字数统计的需求,但是quill 中没法很好实现

最后还是选择手动实现。通过contenteditable=true 来实现 解决方案 受控组件,则需要把用户输入转化成 json 最后通过json 再渲染到富文本编辑框中 [图片] 获取用户输入文本,渲染到富文本编辑框 需要监听 beforeinput 时间,并且 阻止默认事件

<div :contenteditable="true"  @beforeinput="beforeInput" @input="handleInput"></div>

获取用户输入,转成受控文本

    beforeInput(e) {
      e.preventDefault();
      const { data, inputType } = e;
      this.json.push({
        type: data ? 'text' : 'linebreak',
        text: data,
        uid: Date.now(),
      });
    },

渲染出用户输入文本

 <!-- 受控文本展示 -->
  <template v-for="item in json">
    <span v-if="item.type == 'text'" :key="item.uid">{{ item.text }}</span>
    <div v-else-if="item.type == 'linebreak'" :key="item.uid"></div>
  </template>

受控输入后重新渲染,导致光标跳到第一个字符

//      需要保证渲染后再重制光标位置
      this.$nextTick(() => {
        moveCursorToEnd(e.target);
      });
// element 元素为 富文本根组件 contenteditable 属性的元素
export function moveCursorToEnd(element) {
  const range = document.createRange();
  const selection = window.getSelection();

  range.selectNodeContents(element);
  range.collapse(false); // 将光标定位到节点内容的末尾

  selection.removeAllRanges();
  selection.addRange(range);
}

输入多个空格后,vue中只展示一个,并且光标不会向后移动 渲染的时候增加 pre-wrap 样式

  <span v-if="item.type == 'text'" :key="item.uid" style="white-space: pre-wrap">{{ item.value }}</span>

Js 处理文本的时候,直接空格追加到文本后

 lastEl.value += " ";

删除元素

没实现 ❌❌❌ Beforeinput 里面控制光标位置,找到要删除的字符是哪个,并且手动维护好光标位置???????? 找到要删的受控元素 维护光标位置

字数限制

Input 事件 Input 事件是在用户输入事件完成后触发的,这个时候文本已经输入到富文本中了。只能获取到文本,再重新修改 innerHTML 内容 弊: 富文本中元素比较复杂的话,需要遍历完整个文档去统计字数,再删除不需要的内容,比较繁琐,最好是输入前判断,直接不让输入

    handleInput(e) {
      // console.log(e);
      // e.preventDefault();
      // 输入结束后修改
      editor.innerHTML =      editor.innerHTML.slice(0,100 ) 
    }

beforeinput 事件 beforeinput 事件中 e.preventDefault(); 能阻止输入事件 但是不能阻止输入中文,输入中文会触发 insertCompositionText 事件 insertCompositionText 事件中 preventDefault() 没有效果

   compositionUpdate(e) {
      console.log('compositionUpdate', e);
      this.checkWordCount(e);
    },
    compositionStart(e) {
      console.log('compositionstart', e);
      this.checkWordCount(e);
    },
    beforeInput(e) {
      const { data, inputType } = e;
      console.log('beforeinput', inputType, data);
      switch (inputType) {
        case 'deleteContentBackward':
          break;
        case 'deleteContentForward':
          break;
         case 'insertText':
           if (this.wordCount >= this.maxLength) {
             e.preventDefault();
          }
           break;
         case 'insertCompositionText':  // beforeinput 阻止不了改事件,该事件在 beforeinput 之前触发
           if (this.wordCount >= this.maxLength) {
             e.preventDefault();
           }
           break;
         case 'insertParagraph':
           if (this.wordCount >= this.maxLength) {
             e.preventDefault();
           }
        default:
         e.preventDefault();
          break;
      }
    },

Input 事件获取到dom 转成json 问题: 文字重复,需要清理掉innerHTML 里面原始的内容

Last Updated:
Contributors: rosendo
Prev
前端截图实现
Next
图片处理