浏览器可以通过ctrl + f
来实现,这个功能真的很不错,但是如何实现类似的功能呢?想了很久,感觉可以基于文本选中来实现
复制时的效果是这样的
搜索时的效果
是不是除了颜色不一样,其他都一样呢
其实文本选中的样式是可以被自定义的,可以通过CSS3的伪类选择器::selection
来设置文本被选中时的状态。比如:
::selection {
background: #ff9632;
}
这样是不是就根搜索时的样式一样了
参考:
https://developer.mozilla.org/zh-CN/docs/Web/API/Range
获取
//获取选中的文字
document.getElementById("get").onclick = function () {
var txt = window.getSelection ? window.getSelection() : document.selection.createRange().text;
alert(txt);//alert默认调用了toString()
}
清除
//清除选中的文字
document.getElementById("set").onclick = function () {
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
}
全部选中
const selectAll = () => {
// 创建range对象
const range = document.createRange();
// 获取要选中的文本
const node = document.getElementsByClassName('container')[0];
range.selectNodeContents(node);
const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);
};
Range.selectNodeContents()
方法用于设置 Range
,使其包含一个 Node 的内容。Window.getSelection
返回一个 Selection
对象,表示用户选择的文本范围或光标的当前位置。Selection.removeAllRanges()
方法会从当前 selection
对象中移除所有的 range
对象,取消所有的选择只 留下anchorNode
和focusNode
属性并将其设置为 null
。addRange
:向选区添加一个区域设置开始截止位置选中
<template>
<el-button type="primary" @click="selectAll">全部选中</el-button>
<div class="container">
<p>这是一段默认有开始截止位置的文本</p>
</div>
</template>
<script setup lang="ts">
const selectAll = () => {
// 创建range对象
const range = document.createRange();
// 获取开始节点
const startNode = document.getElementsByTagName('p').item(0)?.firstChild;
if (startNode) {
range.setStart(startNode, 2);
range.setEnd(startNode, 5);
const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);
}
};
</script>
如果起始节点类型是 Text
, Comment
, or CDATASection
之一, 那么startOffset
指的是从起始节点算起字符的偏移量。
跨段选中
<template>
<el-button type="primary" @click="selectAll">跨行选中</el-button>
<div class="container">
<span>太阳当空照,<br></span>
<span>花儿对我笑。<br></span>
<span>小鸟说:“早,早,早,<br></span>
<span>你为什么背上小书包?”<br></span>
<span>我去上学校,<br></span>
<span>天天不迟到。<br></span>
<span>爱学习,爱劳动,<br></span>
<span>长大要为祖国立功劳。<br></span>
</div>
</template>
<script setup lang="ts">
const selectAll = () => {
// 创建range对象
const range = document.createRange();
// 获取开始节点
const startNode = document.getElementsByClassName('container')[0];
// childNode会将html换行加进去
for (var i = 0; i < startNode.childNodes.length; i++) {
console.log(`节点:${i + 1}`, startNode.childNodes[i], startNode.childNodes[i].nodeName);
if (startNode.childNodes[i].nodeName == '#text' && !/\s/.test(startNode.childNodes.nodeValue)) {
startNode.removeChild(startNode.childNodes[i]);
}
}
var startOffset = 1;
range.setStart(startNode, startOffset);
var endOffset = startNode.childNodes.length - 2;
range.setEnd(startNode, endOffset);
const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);
};
</script>
跨段文字特定位置选中
<template>
<el-button type="primary" @click="selectAll">跨行选中</el-button>
<div class="container">
<span>太阳当空照,<br></span>
<span>花儿对我笑。<br></span>
<span>小鸟说:“早,早,早,<br></span>
<span>你为什么背上小书包?”<br></span>
<span>我去上学校,<br></span>
<span>天天不迟到。<br></span>
<span>爱学习,爱劳动,<br></span>
<span>长大要为祖国立功劳。<br></span>
</div>
</template>
<script setup lang="ts">
const selectAll = () => {
// 创建range对象
const range = document.createRange();
// 获取开始节点
const startNode = document.getElementsByClassName('container')[0];
// childNode会将html换行加进去
for (var i = 0; i < startNode.childNodes.length; i++) {
console.log(`节点:${i + 1}`, startNode.childNodes[i], startNode.childNodes[i].nodeName);
if (startNode.childNodes[i].nodeName == '#text' && !/\s/.test(startNode.childNodes.nodeValue)) {
startNode.removeChild(startNode.childNodes[i]);
}
}
range.setStart(startNode.childNodes[5].firstChild, 2);
range.setEnd(startNode.childNodes[7].firstChild, 8);
const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);
};
</script>
<template>
<div>
<el-input v-model="inputValue" placeholder="请输入" />
<el-button type="primary" @click="search">搜索</el-button>
<el-button type="primary" @click="previous">上一个</el-button>
<el-button type="primary" @click="next">下一个</el-button>
<div class="container" id="container">
<p> 床前明月光,疑是地上霜。</p>
<p>举头望明月,低头思故乡。</p>
</div>
</div>
</template>
<script setup lang="ts">
import { ElMessage } from 'element-plus';
import { ref } from 'vue';
const inputValue = ref('');
const findNode = ref();
const index = ref(-1);
const search = () => {
// 获取容器
const container = document.getElementById('container');
// 获取所有文本
const allText = container?.innerText;
if (allText && allText.includes(inputValue.value)) {
// 获取所有节点
const containerAllNode = container.childNodes;
console.log(containerAllNode);
// 用于保存找到的节点
findNode.value = [];
for (let i = 0; i < containerAllNode.length; i++) {
// 遍历查询节点
if (containerAllNode[i].textContent?.includes(inputValue.value)) {
findNode.value.push(containerAllNode[i]);
}
}
// 默认选中第一个
if (findNode.value && findNode.value.length > 0) {
index.value = 0;
setSelect(0);
}
} else {
ElMessage.warning('未匹配到');
}
};
const previous = () => {
if (index.value > 0) {
index.value -= 1;
} else {
index.value = findNode.value.length - 1;
}
setSelect(index.value);
};
const next = () => {
if (index.value < findNode.value.length - 1) {
index.value += 1;
} else {
index.value = 0;
}
setSelect(index.value);
};
const setSelect = (index) => {
const range = document.createRange();
range.selectNodeContents(findNode.value[index]);
const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);
};
</script>
<style lang="scss" scoped>
.container {
width: 400px;
height: 200px;
margin-left: 100px;
border: 1px solid red;
::selection {
background: #ff9632;
}
}
</style>
改进版
<template>
<div>
<el-input v-model="inputValue" placeholder="请输入" />
<el-button type="primary" @click="search">搜索</el-button>
<el-button type="primary" @click="previous">上一个</el-button>
<el-button type="primary" @click="next">下一个</el-button>
<div class="container" id="container">
<p> 床前明月光,疑是地上霜。</p>
<p>举头望明月,低头思故乡。</p>
</div>
</div>
</template>
<script setup lang="ts">
import { ElMessage } from 'element-plus';
import { ref } from 'vue';
const inputValue = ref('');
const findNode = ref();
const index = ref(-1);
const search = () => {
// 获取容器
const container = document.getElementById('container');
// 获取所有文本
const allText = container?.innerText;
if (allText && allText.includes(inputValue.value)) {
// 获取所有节点
const containerAllNode = container.childNodes;
console.log(containerAllNode);
// 用于保存找到的节点
findNode.value = [];
for (let i = 0; i < containerAllNode.length; i++) {
// 遍历查询节点
if (containerAllNode[i].textContent?.includes(inputValue.value)) {
findNode.value.push(containerAllNode[i]);
}
}
// 默认选中第一个
if (findNode.value && findNode.value.length > 0) {
index.value = 0;
for (let j = 0; j < findNode.value.length; j++) {
findNode.value[j].classList.add('abc');
}
setSelect(0);
}
} else {
ElMessage.warning('未匹配到');
}
};
const previous = () => {
if (index.value > 0) {
index.value -= 1;
} else {
index.value = findNode.value.length - 1;
}
setSelect(index.value);
};
const next = () => {
if (index.value < findNode.value.length - 1) {
index.value += 1;
} else {
index.value = 0;
}
setSelect(index.value);
};
const setSelect = (index) => {
const range = document.createRange();
console.log(findNode.value[index]);
range.selectNodeContents(findNode.value[index]);
const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);
};
</script>
<style lang="scss" scoped>
.container {
width: 400px;
height: 200px;
margin-left: 100px;
border: 1px solid red;
::selection {
background: #ff9632;
}
}
.abc {
background-color: #ff0;
}
</style>
如果想实现只选中输入的文字,可以看 设置开始截止位置选中 这块内容,找到文字对应的起始位置和结束位置
局限
最近突然有了些想法,浏览器的搜索功能应该不是通过正则替换来实现的。但是如果只是具体的需求,完全可以通过正则来实现
比如:a是关键词,b是加有样式的关键词。当搜索时将a都替换成b;当清空时再由b替换后a;如果是change事件的话,每次改变时先由b提换回a,再更新a和b,更新后再将a都替换成b。