原文地址:https://www.zhangxinxu.com/wordpress/?p=10635
结果是:
<div id="a">
<div id="b">
<button id="button">点击我</button>
</div>
</div>
<p>结果是:<output id="output">?</output></p>
Element.prototype.farthest = function (selector) {
if (typeof selector != 'string') {
return null;
}
let eleMatch = this.closest(selector);
let eleReturn = null;
while (eleMatch) {
eleReturn = eleMatch;
eleMatch = eleMatch.parentElement.closest(selector);
}
return eleReturn;
};
button.onclick = function () {
output.textContent = this.farthest('div').id;
};
我喜欢坐在夜晚空无一人的大街上,听着“他们”的窃窃私语,享受着“他们”的喧嚣。
沈飞穿越平行世界,绑定了直播系统。能让他在直播的时候,看到连麦水友的过去与未来。并且将他们未来往好的方向引导,就能获得奖励。刚开播,沈飞便说哭了一位女主播,让她从此发愤图强,积极向上。一次,有一逃犯来到直播间与沈飞连麦。沈飞:“我拷,刑啊,大哥你早点自首吧,或许还能少判几年。”才刚讲,视频那头的逃犯就被当场抓获。结果,全国网友都震惊了。
<ui-list id="list" style="--ui-space: 1rem;">
<a href="//m.qidian.com/book/1030870265.html" class="ui-list-item"><img src="//bookcover.yuewen.com/qdbimg/349573/1030870265/150" class="book-cover" alt="明克街13号"><div class="book-cell"><h4 class="book-title">明克街13号</h4><p class="book-desc">我喜欢坐在夜晚空无一人的大街上,听着“他们”的窃窃私语,享受着“他们”的喧嚣。</p><div class="book-meta"><div class="book-meta-l"><span class="book-author" role="option"><aria>作者:</aria>纯洁滴小龙</span></div><div class="book-meta-r"><span class="tag-small-group origin-right"><em class="tag-small gray">都市</em><em class="tag-small red">连载中</em><em class="tag-small blue">87.4万字</em></span></div></div></div></a>
<a href="//m.qidian.com/book/1030867097.html" class="ui-list-item"><img src="//bookcover.yuewen.com/qdbimg/349573/1030867097/150" class="book-cover" alt="直播:我能看见过去与未来"><div class="book-cell"><h4 class="book-title">直播:我能看见过去与未来</h4><p class="book-desc">沈飞穿越平行世界,绑定了直播系统。能让他在直播的时候,看到连麦水友的过去与未来。并且将他们未来往好的方向引导,就能获得奖励。刚开播,沈飞便说哭了一位女主播,让她从此发愤图强,积极向上。一次,有一逃犯来到直播间与沈飞连麦。沈飞:“我拷,刑啊,大哥你早点自首吧,或许还能少判几年。”才刚讲,视频那头的逃犯就被当场抓获。结果,全国网友都震惊了。</p><div class="book-meta"><div class="book-meta-l"><span class="book-author" role="option"><aria>作者:</aria>吟诗摇人</span></div><div class="book-meta-r"><span class="tag-small-group origin-right"><em class="tag-small gray">都市</em><em class="tag-small red">连载中</em><em class="tag-small blue">60.54万字</em></span></div></div></div></a>
</ui-list>
复用上面案例的HTML元素,直接看输出结果:
// 获取所有的文本节点
Object.defineProperty(Element.prototype, 'childTextNodes', {
get: function () {
// 获取所有的文本节点
const nodeIterator = document.createNodeIterator(this, NodeFilter.SHOW_TEXT, (node) => {
return node.textContent.trim() ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
});
// 节点迭代器转为数组并返回
const arrNodes = [];
let node = nodeIterator.nextNode();
while (node) {
arrNodes.push(node);
node = nodeIterator.nextNode();
}
return arrNodes;
}
});
<h4>文本节点依次是:</h4>
<ul id="allTextNodes"></ul>
// 所有文本节点显示
allTextNodes.innerHTML = list.childTextNodes.map(node => '<li>' + node.textContent + '</li>').join('');
const getNodesInRange = function (range) {
var start = range.startContainer;
var end = range.endContainer;
var commonAncestor = range.commonAncestorContainer;
var nodes = [];
var node;
// 使用公用的祖先进行节点遍历
for (node = start.parentNode; node; node = node.parentNode) {
nodes.push(node);
if (node == commonAncestor) {
break;
}
}
nodes.reverse();
const getNextNode = function (node) {
if (node.firstChild) {
return node.firstChild;
}
while (node) {
if (node.nextSibling) {
return node.nextSibling;
}
node = node.parentNode;
}
}
// 遍历子元素和兄弟元素
for (node = start; node; node = getNextNode(node)) {
nodes.push(node);
if (node == end) {
break;
}
}
return nodes;
}
// 获取选区内的所有文本节点
const getTextNodesInRange = function (range) {
return getNodesInRange(range).filter(node => node.nodeType == 3 && node.textContent.trim());
};
<h4>选区内文本节点依次是:</h4>
<ul id="allRangeNodes"></ul>
// 选区内文本节点显示
document.addEventListener('mouseup', function () {
const selection = document.getSelection();
if (selection.rangeCount && !selection.isCollapsed) {
allRangeNodes.innerHTML = getTextNodesInRange(selection.getRangeAt(0)).map(node => '<li>' + node.textContent + '</li>').join('');
} else {
allRangeNodes.innerHTML = '<li class="gray">并无框选内容</li>';
}
});
// 改变元素的标签方法
const propsTagName= Object.getOwnPropertyDescriptor(Element.prototype, 'tagName');
Object.defineProperty(Element.prototype, 'tagName', {
...propsTagName,
set: function (name) {
if (typeof name != 'string' || name.toUpperCase() == this.tagName) {
return;
}
const eleNew = document.createElement(name.toLowerCase());
eleNew.append.apply(eleNew, [...this.childNodes]);
this.replaceWith(eleNew);
return name;
}
});
<h4>预览效果:</h4>
<b>钓鱼可否?<a href="https://item.jd.com/13356308.html">《CSS新世界》</a></b>
<hr>
<button id="btnConvert">点击我转换</button>
<h4>现在的 HTML 为:</h4>
<div id="currentHtml"></div>
// 标签类型转换
btnConvert.onclick = function () {
const ele = document.querySelector('b');
const eleParent = ele.parentElement;
// 改变标签
ele.tagName = 'strong';
// 显示此时的 HTML 元素内容
currentHtml.innerHTML = eleParent.querySelector('strong').outerHTML.replaceAll('<', '<');
};
保佑今天钓鱼爆护!
// 部分文字使用 HTML 包装
// selector 表示希望包裹的HTML元素选择器
// 仅支持标签和类名选择器
// text 表示希望包裹的字符,不设置表示整个文本节点
Text.prototype.surround = function (selector, text) {
const textContent = this.nodeValue;
if (!textContent || !selector) {
return null;
}
text = text || textContent;
// 包装的元素标签和类名
const arrClass = selector.split('.');
const tagName = arrClass[0] || 'span';
const className = arrClass.slice(1).join(' ');
// 索引范围
const startIndex = textContent.indexOf(text);
if (startIndex == -1) {
return null;
}
const range = document.createRange();
range.setStart(this, startIndex);
range.setEnd(this, startIndex + text.length);
// 元素创建
const eleSurround = document.createElement(tagName);
if (className) {
eleSurround.className = className;
}
// 执行最后一击
range.surroundContents(eleSurround);
return eleSurround;
};
<p id="wrap">保佑今天钓鱼爆护!</p>
wrap.firstChild.surround('strong', '钓鱼');
我是加粗1加粗2,我是蓝色蓝色红色 红色,只会合并蓝色。
// 合并等同元素
Element.prototype.merge = function (selector) {
if (!selector) {
return;
}
[...this.querySelectorAll(selector)].some(ele => {
// 如果和前面的节点类型一致,合并
let nodePrev = ele.previousSibling;
let elePrev = ele.previousElementSibling;
if (!nodePrev || !elePrev) {
return false;
}
// 非内联元素换行符忽略
const display = getComputedStyle(ele).display;
if (nodePrev.nodeType == 3 && (nodePrev.nodeValue === '' || (!/inline/.test(display) && !nodePrev.nodeValue.trim()))) {
nodePrev.remove();
// 递归处理
this.merge(selector);
return true;
}
// 如果前面的节点也是元素,同时类名一致
if (nodePrev == elePrev && ele.cloneNode().isEqualNode(nodePrev.cloneNode())) {
elePrev.append.apply(elePrev, [...ele.childNodes]);
ele.remove();
// 递归处理
this.merge(selector);
return true;
}
});
};
// 测试代码
mergeP.merge('strong, span[class]');
// 测试代码
document.body.merge('ol');