共计 1618 个字符,预计需要花费 5 分钟才能阅读完成。
在线 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) // 选区地位插入新节点
正文完