实现一个局部的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...

相关推荐
天天向上102412 分钟前
Vue 配置打包后可编辑的变量
前端·javascript·vue.js
芬兰y27 分钟前
VUE 带有搜索功能的穿梭框(简单demo)
前端·javascript·vue.js
好果不榨汁34 分钟前
qiankun 路由选择不同模式如何书写不同的配置
前端·vue.js
小蜜蜂dry34 分钟前
Fetch 笔记
前端·javascript
拾光拾趣录36 分钟前
列表分页中的快速翻页竞态问题
前端·javascript
小old弟36 分钟前
vue3,你看setup设计详解,也是个人才
前端
Lefan40 分钟前
一文了解什么是Dart
前端·flutter·dart
Patrick_Wilson1 小时前
青苔漫染待客迟
前端·设计模式·架构
vvilkim1 小时前
Nuxt.js 全面测试指南:从单元测试到E2E测试
开发语言·javascript·ecmascript
写不出来就跑路1 小时前
基于 Vue 3 的智能聊天界面实现:从 UI 到流式响应全解析
前端·vue.js·ui