前言
浏览器可以通过ctrl + f
来实现,这个功能真的很不错,但是如何实现类似的功能呢?想了很久,感觉可以基于文本选中来实现
复制时的效果是这样的
搜索时的效果
是不是除了颜色不一样,其他都一样呢
文本选中样式设置
其实文本选中的样式是可以被自定义的,可以通过CSS3的伪类选择器::selection
来设置文本被选中时的状态。比如:
::selection {background: #ff9632;
}
这样是不是就根搜索时的样式一样了
通过js来实现文本选中
参考:
https://developer.mozilla.org/zh-CN/docs/Web/API/Range
javascript里文字选中/选中文字
获取、清除选中的文字
获取
//获取选中的文字
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>
ctrl + f 简单demo
<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>
如果想实现只选中输入的文字,可以看 设置开始截止位置选中 这块内容,找到文字对应的起始位置和结束位置
局限
- ctrl + f 搜索不是基于文本选中实现的,从下面的图片可以看出来
- 如果真要实现类似浏览器的搜索功能是很复杂的,需要判断节点的类型,文字在哪一个节点里面;但是如果节点类型固定的话,还是可以简单试试