目录

前端原生Selection-api操作选中文本-样式取消样式解决标签的无限嵌套问题

前端——原生Selection api操作选中文本 样式、取消样式(解决标签的无限嵌套问题)

文章目录

⭐前言

大家好,我是yma16,本文分享 前端——Selection api操作选中文本 样式、取消样式(解决标签的无限嵌套问题)

Selection API

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

node系列往期文章

koa系列项目文章

koa-vue性能监控到封装sdk系列文章

canvas系列文章

前端vue系列文章

⭐Selection获取选区

通过document.getSelection()获取选中的选区

通过onselectionchange 监听 选区变化

  document.onselectionchange = function () {
    console.log('New selection made');
    selection = document.getSelection();
    console.log('selection', selection);
    console.log('type', selection.type);
    console.log('content', selection.toString())
  };

在控制台打印选区的内容

https://i-blog.csdnimg.cn/direct/4a2c9c2f5c7546febeb86e63286ccc24.png

✏️EyeDropper拾色器api给选区加颜色

使用EyeDropper对象获取唤起拾色器api

    if (!window.EyeDropper) {
      message.warning("你的浏览器不支持 EyeDropper API")
      return;
    }
    const eyeDropper = new EyeDropper();

    eyeDropper
      .open()
      .then((result) => {
        console.log('result.sRGBHex',result.sRGBHex)
      })
      .catch((e) => {
        console.error(e)
      });

✏️selection insertNode 插入html字符串并渲染

使用insertNode api

function insertHTML(value) {
  const sel = document.getSelection();

  if (sel && sel.rangeCount) {
    const node = document.createElement('div');
    const range = sel.getRangeAt(0);
    range.deleteContents();

    if (value) {
      node.innerHTML = value;
    } else if (value) {
      node.appendChild(value);
    }

    Array.prototype.slice.call(node.childNodes).forEach(nd => {
      range.insertNode(nd);
    });

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

添加成功

https://i-blog.csdnimg.cn/direct/7a06733d3b1e44c0a95a2a856f14d026.png

查看div下的span节点

https://i-blog.csdnimg.cn/direct/2cfe82a4e6034499b28643b2fdd33740.png

✏️selection insertNode清空内容再写入内容再清除样式

先删除选区再添加选区

function clearStyle() {
  const sel = document.getSelection();
  const text=sel.toString()
  console.log('text',text)
  if (sel && sel.rangeCount) {
    const node = document.createElement('span');
    node.value=text
    const range = sel.getRangeAt(0);
    range.deleteContents();
    node.innerHTML=text

    Array.prototype.slice.call(node.childNodes).forEach(nd => {
      console.log('nd',nd)
      range.insertNode(nd);
    });

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

带来的问题:遗留空节点标签

https://i-blog.csdnimg.cn/direct/8c12751087304d2bb1dfe20426b5f481.png

✏️selection insertNode带来的空节点和冗余父级节点问题

使用selection 插入 节点和样式带来,如下无限嵌套问题

<span style='red'><span style='red'><span style='red'>修改文字</span></span></span>

空节点问题

<span style='red'></span><span style='red'>修改文字</span>

思路:

  1. 每次修改选区时刻进行 整体向下 内容校验,合并嵌套的标签,删除无效标签(父节点查找子节点)
  2. 每次修改选区时刻进行 从选区内容往父级节点查找 内容校验,合并嵌套的标签,删除无效标签(子节点查找父节点)

整体修改内容清除冗余嵌套标签

逻辑实现(参考)

    /**
     * 
     * @param node 合并节点样式
     * @returns {*}
     */
    function mergeTag(node){
        console.log('node',node)
        console.log('node.textContent',node.textContent)
        if (node.nodeType === Node.ELEMENT_NODE) {
            const nodeStyle=node.getAttribute('style')
            // 如果是 span 标签,递归处理其子节点
            for (let child of node.childNodes) {
                console.log('child',child)
                console.log('node.innerText',node.textContent)
                console.log('child.innerText',child.textContent)
                if(child.textContent ===node.textContent ){
                    // 内容一样的子节点忽略 获取style
                    const childStyle=child.getAttribute('style')
                    if(childStyle){
                        const nodeStyleConfig={}
                        if(nodeStyle){
                            nodeStyle.split(';').map(item=>{
                                const key=item.split(':')[0]
                                const value=item.split(':')[1]
                                if(key){
                                    nodeStyleConfig[key]=value
                                }
                            })
                        }
                        // 子节点样式覆盖
                        childStyle.split(';').map(item=>{
                            const key=item.split(':')[0]
                            const value=item.split(':')[1]
                            if(key){
                                nodeStyleConfig[key]=value
                            }
                        })
                        console.log('nodeStyleConfig',nodeStyleConfig)
                        // console.log('nodeStyleConfig.entries()',nodeStyleConfig.entries())
                        let style=[]
                        Object.keys(nodeStyleConfig).forEach(key=>{
                            style.push(key+':'+nodeStyleConfig[key])
                        })
                        console.log('style',style)
                        node.setAttribute('style',style.join(';'))
                        // 合并 nodeStyle 和  childStyle (childStyle 优先级高)
                    }
                    //  节点变成文本 创建一个新的文本节点并替换它
                    const textNode = document.createTextNode(child.textContent);
                    child.parentNode.replaceChild(textNode, child);
                }
                else if(!child.textContent){
                    // 删除 空节点
                    child.remove()
                }
                else{
                    mergeTag(child);
                }

            }
        }
        return node
    }

⭐结束

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

https://i-blog.csdnimg.cn/direct/43ba8e75529c476da572ac0993e56362.png

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

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

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

💖 最后,感谢你的阅读!