插入图片与链接:在光标处注入节点
目标
图片和链接是"行内媒体节点"。本节课用 Range 在光标处插入 <img> 与 <a>,并强调"先校验 URL、再插入"的安全习惯。
运行环境:浏览器。运行方式:在 l1 的 index.html 里,把 <script> 换成下面内容,把光标放进编辑器,点按钮插入。
<script>
const editor = document.getElementById('editor')
function insertImage(url) {
if (!/^https?:\/\//i.test(url)) { alert('仅允许 http(s) 图片'); return }
const sel = window.getSelection()
if (!sel.rangeCount) return
const img = document.createElement('img')
img.src = url; img.alt = 'image'
sel.getRangeAt(0).insertNode(img)
}
function insertLink(url, text) {
if (!/^https?:\/\//i.test(url)) { alert('仅允许 http(s) 链接'); return }
const sel = window.getSelection()
if (!sel.rangeCount) return
const range = sel.getRangeAt(0)
const a = document.createElement('a')
a.href = url
a.target = '_blank'
a.textContent = range.toString() || text
try { range.surroundContents(a) } catch (e) { range.insertNode(a) }
}
document.getElementById('img').addEventListener('click', () => insertImage(prompt('图片URL')))
document.getElementById('link').addEventListener('click', () => insertLink(prompt('链接URL'), '链接'))
</script>配套按钮 <button id="img">插图</button><button id="link">插链</button>。插入前用正则校验 http(s) 协议,是不让 javascript: 等危险 URL 进来的一道防线。
名词解释
行内媒体节点(Inline Media):嵌在文字流里的非文本节点,如图片、链接、@提及、公式。它们和"加粗"一样是行内级别的。
surroundContents(node):把 Range 选中的内容用 node 包裹起来(如用 <a> 包住选中文字)。选区跨元素时会抛错,需用 insertNode 兜底。
URL 校验:插入外部资源前检查协议(只允许 http/https),防止 javascript: 等脚本 URL 造成 XSS。
课后练习
练习:改进 insertImage,让插入后光标自动跳到图片之后,方便继续输入。
答案:插入后用 Range 把光标移到图片后面:
function insertImage(url) {
if (!/^https?:\/\//i.test(url)) return
const sel = window.getSelection()
if (!sel.rangeCount) return
const img = document.createElement('img')
img.src = url; img.alt = 'image'
const range = sel.getRangeAt(0)
range.insertNode(img)
range.setStartAfter(img)
range.collapse(true)
sel.removeAllRanges()
sel.addRange(range)
}总结
插入图片/链接是"在光标处注入节点"的典型场景,技术上就是 Range 的 insertNode / surroundContents。本课更想强调的是安全:富文本编辑器是 XSS 高发地,因为用户内容最终要进 DOM。插入任何外部 URL 前校验协议、渲染时对 href/src 做白名单,是底线操作。生产框架(如 TipTap)把图片/链接做成"带属性的节点",URL 作为节点属性存进模型、渲染时统一做转义,比直接拼 DOM 安全得多。你手写的版本点出了关键两步:定位光标(Range)+ 注入节点,以及"先校验、再插入"的安全意识。