实现一个局部的CTRL+F

需求来源

一天产品经理找到我,问我实现一个页面局部搜索能做吗?需要把所有相同的关键词高亮展示出来,并可以上下选中其他关键词。我一听好家伙这个不就是浏览器自带的Ctrl+F的全局搜索吗。于是我给他展示了一下:

产品经理一看好家伙还真是这样的功能,于是马上去给业务方教学了,但是很快又找到了我,说实现是实现了,但是用起来业务方感觉和实际需求还是有点偏颇:

  1. 这个是页面全局的搜索,但是实际需求页面上还会有其他的信息总数也会被统计进去,分析就不准确了。
  2. 希望每次进入页面都会给默认的关键词高亮,这样操作路径就短了。

这么一看就是要实现一个局部的搜索了,能不能做?当然能,毕竟存在即合理。

需求点拆分

实际体验过这个功能的话,主要功能可以拆分为以下几个点

  1. 高亮词匹配高亮。
  2. 选中单个高亮词。
  3. 选中的高亮词需要位于页面中。

实现

实现的效果

我们先来看下最终实现的效果:

可以看到我们的页面一共两个部分,一部分是在我们的搜索范围内的,一开始会有一个默认的关键词选中在我们的搜索框中,这里我是在请求中获得的,一开始是经常 ,后面我给改成了面试,同时在选中的高亮词超出可视区域的时候会自动滚动到可视区域中,至此整个的需求算是完整的实现了。

实现思路

关键词高亮:只需要匹配对应的关键词,然后进行替换成带样式的dom元素即可。

js 复制代码
const genContent = (text: string) => {
        try {
            return (
                <div dangerouslySetInnerHTML={{
                    __html: text.replace(new RegExp(inputValue, 'g'), `<span class=${hightLightWordClassName} style="background-color: yellow">${inputValue}</span>`)
                }} />
            )
        } catch (error) {

        }
    }

关键词选择改变:借助在关键词高亮的时候dom元素上的class属性,我们就可以获得所有的高亮dom,那么接下来我们只需要,维护一个当前位于第几个dom的状态即可。

js 复制代码
     /**
     * 从第几个到第几个dom
     */
    const changeDomBgc = (from: number, to: number) => {
        const doms = document.getElementsByClassName(hightLightWordClassName);
        if (!scrollRef.current || !doms) return;
        doms[to] && scrollToView(doms[to]);
        doms[to].setAttribute('style', 'background-color: orange');
        doms[from].setAttribute('style', 'background-color: yellow');
    }
    /**
     * 
     * 上一步或者下一步
     */
    const changeStep = (step: -1 | 1) => {
        if (step === 1) {
            if (curHeightLightDom < domListLength - 1) {
                changeDomBgc(curHeightLightDom, curHeightLightDom + 1);
                setCurHeightLightDom(curHeightLightDom + 1);
            }
        } else if (curHeightLightDom > 0) {
            changeDomBgc(curHeightLightDom, curHeightLightDom - 1);
            setCurHeightLightDom(curHeightLightDom - 1);
        }
    }

选词超出可视区域滚动到可视区域: 主要借助dom的scrollTo方法。

js 复制代码
    const scrollToView = (element: Element) => {
        if (!scrollRef.current) return;
        const { top: elementRectTop, bottom: elementRectBottom } = element.getBoundingClientRect();
        const { top: parentRectTop, bottom: parentRectBottom } = scrollRef.current.getBoundingClientRect();
        console.log(elementRectTop,elementRectBottom,parentRectTop,parentRectBottom)
        if (elementRectTop - parentRectTop > scrollContentHeight) {
            scrollRef.current.scrollTo({
                top: elementRectTop - parentRectTop + scrollRef.current.scrollTop + 20
            })
        }
        if (parentRectBottom - elementRectBottom > scrollContentHeight) {
            scrollRef.current.scrollTo({
                top: scrollRef.current.scrollTop - (parentRectTop  - elementRectBottom) - 20
            })
        }

    }
分为两种情况,一种是往下一个的时候,下一个在视图外,那么这个时候就要向上滚动, getBoundingClientRect() 可以获得元素的宽高和其相对于视口的上下左右,那么只要是父元素的底的高度减去子元素的底的差值已经大于父元素的高度的时候,说明元素元素已经滚到父元素的上面去了,这里可以看途中的1例子就是这种情况;如果子元素的顶减去父元素的顶已经大于父元素的顶了,那么说明此时已经滚动到父元素的下面去了,这里就是对应的第二种情况了,搞清楚了两种情况下面就简单了。
如果对应的第一种情况滚动到父元素的上面,那么我们的元素就要向下滚动一定的距离,滚动多少呢?我们只需要滚动子元素的底相对于父元素的顶的距离就行,这里需要注意的是要在页面的滚动基础上进行,因为我们的整个页面也是可以滚动的,同时还要让元素看的到所以要再往下滚动元素的高度这里我就写了20。同理如果是第二种我们只需要滚动父元素的顶到子元素的顶这样的距离,同样要在页面的滚动基础上,再加上元素的本身高度即可。

正文

至此我们的功能就已经实现了,有人就要问了,这么简单的功能为啥还要分享?没错正文才刚刚开始,细心的同学肯定发现了,在例子展示的时候我们请求改变响应体的时候用的是一个浏览器插件,对的没错这个就是我开发的一个浏览器mock插件,非常的实用,它可以帮助我们快速的获取浏览器的请求并进行更改,这样我们在开始的时候接口还没有好的阶段就可以用这个插件来进行请求的mock。详情可以看这个视频:www.bilibili.com/video/BV1Cw...

文章中案例的代码仓库:github.com/caixiaocai1...

相关推荐
程序员shen1616112 分钟前
抖音短视频saas矩阵源码系统开发所需掌握的技术
java·前端·数据库·python·算法
Ling_suu30 分钟前
SpringBoot3——Web开发
java·服务器·前端
Yvemil737 分钟前
《开启微服务之旅:Spring Boot Web开发》(二)
前端·spring boot·微服务
hanglove_lucky39 分钟前
本地摄像头视频流在html中打开
前端·后端·html
维李设论42 分钟前
Node.js的Web服务在Nacos中的实践
前端·spring cloud·微服务·eureka·nacos·node.js·express
2401_857600951 小时前
基于 SSM 框架 Vue 电脑测评系统:赋能电脑品质鉴定
前端·javascript·vue.js
天之涯上上1 小时前
Pinia 是一个专为 Vue.js 3 设计的状态管理库
前端·javascript·vue.js
@大迁世界1 小时前
摆脱 `<div>`!7 种更语义化的 HTML 标签替代方案
前端·html
高山我梦口香糖2 小时前
[react] <NavLink>自带激活属性
前端·javascript·react.js
撸码到无法自拔2 小时前
React:组件、状态与事件处理的完整指南
前端·javascript·react.js·前端框架·ecmascript