在线demo预览,实现原理如下:

1)获取选区内容,如果没有选区则给整段文本设置款式。2)新增span标签包裹选区内容,为span标签减少style款式。3)优化DOM构造。比方demo中删除了空白节点、合并父子层级款式缩小层级嵌套。

第一步:通过 www.getSelection() 获取选区,sel.isCollapsed判断选区的起始点和终止点是否位于一个地位

//返回一个 Selection 对象,示意用户抉择的文本范畴或光标的以后地位let sel = window.getSelection()let range = sel.getRangeAt(0) //返回一个蕴含以后选区内容的区域对象。if (sel.isCollapsed) { //没有框选设置整段文本款式   container.style[key] = val   return}

第二步:新增span标签包裹选区内容

const f = range.extractContents() //获取选取内容,并删除let span = document.createElement('span')span.style.setProperty(key, val)span.appendChild(f) //span标签包裹选取内容

第三步:优化dom构造,替换选取节点内容

const keys = ['font-size', 'letter-spacing', 'font-weight', 'color']function getNodeStyle(n) {    const style = n.style    let ret = {}    keys.filter(k => style[k] != null && style[k] != '').forEach(k => {      ret[k] = style[k]    })    return ret}function appendStyle(n, style) {  for (let key in style) {    n.style.setProperty(key, style[key])  }}function isSame(s1, s2) {    return JSON.stringify(s1) == JSON.stringify(s2)}//找到文本,向上获取父节点的款式附加到本身,缩小span嵌套层级function flatNode(node) {    let list = []    let pStyle = getNodeStyle(node)    let last = null;    function _each(n, s) {      for (let i = 0; i < n.childNodes.length; i++) {        let child = n.childNodes[i]        if (child.nodeType == 3) {          let newStyle = Object.assign({}, s, pStyle)          if (last && isSame(newStyle, getNodeStyle(last))) {            last.appendChild(child.cloneNode())            last.normalize() //相邻文本节点合并          } else {            let span = document.createElement('span')            span.appendChild(child.cloneNode())            appendStyle(span, newStyle)            list.push(span)            last = span;          }        } else {          _each(child, Object.assign({}, s, getNodeStyle(child), pStyle))        }      }    }    _each(node, {})    return list;  }const nodeList = flatNode(span)let newF = document.createDocumentFragment()newF.replaceChildren(...nodeList)newF.normalize()range.insertNode(newF) //选区地位插入新节点