본문 바로가기
프로그래밍/웹 개발

[React, TypeScript] 브라우저 화면에서 특정 단어 검색 기능 구현

by 제이콥J 2023. 4. 20.

카톡방에서 '대화 내용 검색'기능과 유사한 기능을 React로 브라우저에서 구현했습니다.

보통은 브라우저에 표시된 화면에서 Ctrl(Command) + F 를 사용하면 됩니다.

그러나 특정 컴포넌트 내에서만 이 기능이 동작하도록 코드로 직접 구현했습니다.

 

 

1. 검색하고 싶은 단어가 포함된 Node를 HTML DOM으로 불러와 텍스트만 추출하기

검색하고 싶은 단어들이 포함된 Node를 HTML DOM으로 불러옵니다.

DOM문법을 사용해도 되고 React를 사용하고 있다면 useRef를 통해 불러옵니다.

저의 경우 useRef를 통해 HTML DOM을 불러왔지만, 그 안에서 특정 클래스에 있는 텍스트만 추출했습니다.

 

그리고 검색된 키워드를 포함하는 엘리먼트만 배열에 담아 state에 저장해줍니다.

 

const ElementNodeList = mainRef.current?.querySelectorAll(".specific-class");
const ElementArr: Element[] = []; // scroll 이동용

ElementNodeList?.forEach((Element) => {
    // 검색어 일치하면 바로 블록설정 진행
    if (Element.textContent) {
    if (Element.textContent.includes(searchKeyword)) {
        ElementArr.push(Element);
    }
    }
});

setSearchedElements(ElementArr);

 

 

2. 키워드 검색 버튼 클릭 시 단어들을 검색하고 index를 변경시켜 화면에 띄울 단어를 이동시키기

단어 검색 버튼 클릭 시 state에 키워드를 저장하는 작업을 수행합니다.

이 경우 useEffect를 통해 1번의 과정이 실행되도독 합니다.

 

그 상태에서 단어 검색 버튼을 반복해서 클릭하면, 검색된 단어를 순차적으로 이동시키며 화면에 보여줍니다.

이 경우 엘리먼트를 담은 배열의 index가 변경되면서 작동됩니다.

index 또한 state로 관리합니다.

 

const SearchKeyword = () => {
  if (searchKeyword && searchedElements) {
    setIndex((index + 1) % searchedElements.length);
  } else {
    setSearchKeyword(inputRef?.current?.value);
  }
};

 

 

3. 검색된 키워드에 블록을 설정하여 화면에 표시하기

특정 index에 해당하는 엘리먼트의 텍스트를 추출합니다. (검색 키워드가 포함된 텍스트)

해당 텍스트에서 검색 키워드의 시작 인덱스와 마지막 인덱스를 구합니다. (indexOf 메소드 사용)

 

document.createRange()를 통해 range 객체를 생성합니다.

그리고 엘리먼트 객체를 활용해 시작 인덱스와 마지막 인덱스의 범위를 구합니다.

(range.setStart와 range.setEnd 활용)

 

그리고 window.getSelection()을 통해 selection 객체를 생성합니다.

이는 현재 선택된 범위를 나타내줍니다.

selection.removeAllRanges()를 통해 선택 범위를 초기화시켜줍니다.

그리고 selection.addRange를 통해 range 객체를 전달하여 화면상으로 블록 설정을 해줍니다.

 

  const selectText = () => {
    if (searchedElements) {
      const element = searchedElements[index];

      if (
        element.firstChild &&
        element.textContent &&
        searchKeyword &&
        !element.firstChild.firstChild
      ) {
        const selection = window.getSelection();
        selection?.removeAllRanges();
        const range = document.createRange();

        const startIndex = element.textContent.indexOf(searchKeyword);
        const endIndex = startIndex + searchKeyword.length;

        range.setStart(element.firstChild, startIndex);
        range.setEnd(element.firstChild, endIndex);

        selection?.addRange(range);
      }
    }
  };

 

 

4. 스크롤을 통해 자동으로 블록 설정된 단어로 화면을 이동시키기

해당 엘리먼트로 화면 스크롤을 이동합니다.

block: "center" 옵션을 통해 블록 지정된 텍스트가 화면의 중간에 오도록 합니다.

    if (searchedElements) {
      searchedElements[index].scrollIntoView({ block: "center" });
    }

 

 

5. return 문을 제외한 전체 코드

const [isSearchOpened, setIsSearchOpened] = useState(false);
  const [searchKeyword, setSearchKeyword] = useState<string>();
  const [index, setIndex] = useState(0);
  const [searchedElements, setSearchedElements] = useState<Element[]>();

  const mainRef = useRef<HTMLElement>(null);
  const inputRef = useRef<HTMLInputElement>(null);

  const SearchKeyword = () => {
    if (searchKeyword && searchedElements) {
      setIndex((index + 1) % searchedElements.length);
    } else {
      setSearchKeyword(inputRef?.current?.value);
    }
  };

  const selectText = () => {
    if (searchedElements) {
      const element = searchedElements[index];

      if (
        element.firstChild &&
        element.textContent &&
        searchKeyword &&
        !element.firstChild.firstChild
      ) {
        const selection = window.getSelection();
        selection?.removeAllRanges();
        const range = document.createRange();

        const startIndex = element.textContent.indexOf(searchKeyword);
        const endIndex = startIndex + searchKeyword.length;

        range.setStart(element.firstChild, startIndex);
        range.setEnd(element.firstChild, endIndex);

        selection?.addRange(range);
      }
    }
  };

  useEffect(() => {
    if (searchKeyword) {
      const ElementNodeList = mainRef.current?.querySelectorAll(".message");
      const ElementArr: Element[] = []; // scroll 이동용

      ElementNodeList?.forEach((Element) => {
        // 검색어 일치하면 바로 블록설정 진행
        if (Element.textContent) {
          if (Element.textContent.includes(searchKeyword)) {
            ElementArr.push(Element);
          }
        }
      });

      setSearchedElements(ElementArr);

      if (index === 0) {
        selectText();
      }
    }
  }, [searchKeyword]);

  useEffect(() => {
    selectText();
    if (searchedElements) {
      searchedElements[index].scrollIntoView({ block: "center" });
    }
  }, [index]);

 

반응형

댓글