块级格式:标题与列表的块节点操作
目标
加粗是"行内格式"(包一层标签);标题/列表是"块级格式"——整块节点换标签。本节课手写 toggleHeading(),把光标所在块换成 <h2>。
运行环境:浏览器。运行方式:在 l1 的 index.html 里,把 <script> 换成下面内容,把光标放进某段,点"转标题"按钮。
<script>
const editor = document.getElementById('editor')
function toggleHeading() {
const sel = window.getSelection()
if (!sel.rangeCount) return
let node = sel.getRangeAt(0).startContainer
if (node.nodeType === 3) node = node.parentElement // 文本节点取其父元素
const block = node.closest('p, div, h1, h2, h3')
if (!block) return
const h2 = document.createElement('h2')
h2.innerHTML = block.innerHTML // 保留块内文字与行内格式
block.replaceWith(h2) // 整块替换
// 把光标移回新标题内
const range = document.createRange()
range.selectNodeContents(h2)
sel.removeAllRanges()
sel.addRange(range)
}
document.getElementById('h2').addEventListener('click', toggleHeading)
</script>配套加按钮 <button id="h2">转标题</button>。closest(...) 向上找最近的块级祖先,是块级操作的关键。
名词解释
块级格式(Block Format):作用在一整个"块"(段落/标题/列表项)上的格式,如标题、引用、列表。区别于"行内格式"(只作用于一段连续文字)。
closest(selector):从当前元素向上查找最近的、匹配选择器的祖先(含自身)。做块级操作时常用来定位"光标在哪一段"。
replaceWith(node):用新节点替换自身,是"整块换标签"的标准做法。
课后练习
练习:写 toggleList(),把光标所在块变成 <ul><li>...</li></ul> 无序列表。
答案:
function toggleList() {
const sel = window.getSelection()
if (!sel.rangeCount) return
let node = sel.getRangeAt(0).startContainer
if (node.nodeType === 3) node = node.parentElement
const block = node.closest('p, div, h1, h2, h3')
if (!block) return
const li = document.createElement('li')
li.innerHTML = block.innerHTML
const ul = document.createElement('ul')
ul.appendChild(li)
block.replaceWith(ul)
}总结
块级格式和行内格式是两类不同的操作,对应模型里两类不同的节点:行内节点(加粗标记)挂在某段文字上,块级节点(段落/标题)是独立的结构单元。直接 replaceWith 换标签简单直观,但同样有边界问题:列表项里可能嵌套别的结构、跨多块的选择要逐块处理。这也是为什么框架用"块节点 + 行内 mark"分离建模——toggleHeading 在框架里变成"把选中块节点的 type 从 paragraph 改成 heading",视图层再去渲染成 <h2>。你手写的版本抓住了本质:块级格式 = 找到块、换它的类型。剩下的复杂度,交给数据模型去统一管理。