なぽろぐ

気ままに感じたことを記事にまとめます。

DIVでBlurっぽいことをやる(React)

DIVでもInput要素のblur的なことがしたい!

InputElementは別のところを触るとBlurイベントを発火してフォーカスが外れます.

developer.mozilla.org

MDNのサイトで動作は確認できるかと思います.

これをDIV Elementでもやりたいなぁと言うのが今回の議題です.

Image from Gyazo

今回はBlur的なことをしたくなったSelectorUIを用意しました.https://naporin0624.github.io/napoblog-assets/#/div-blur

  • 上のセレクターを出すボタンを押すとセレクターを表示する
  • 要素を選択するところを触っている時はセレクターを消さない
  • 背後の要素はスクロールできる
  • それ以外のところを触るとセレクターが閉じる

ようなUIを考えます.

方針

セレクターUIはdivの中に存在するようになっていて,次のようなDOM構成になっています.

  • ContainerDIV
    • ButtonDIV
    • MenuDIV
      • OptionGroupDIV
        • OptionDIV
        • OptionDIV
        • ...

ButtonDIVもOptionGroupDIVもOptionDIV触っても親の要素は常にContainerDIVになるので

developer.mozilla.org

こちらのclosestメソッドを使ってクリックされた要素の親にContainerがあるかどうかでフォーカスがはずれたかどうかを検知するようにしていきます!関係ないところをクリックしてもその親にはContainerはいないのでフォーカスは外れる仕組みです!

実装

こんな感じでできるかと思います!InputElementが自分でblurイベントを発火してくれるところを自分でclosestメソッドを使ってblurイベント相当のものを生成しています.

function Selector() {
  // closestにSelectorをわたすためにContainerのrefをとります
  const ref = useRef<HTMLDivElement>(null);
  useEffect(() => {
    const handler = (e: Event) => {
      // Blurを制御したい親Elementのselector(クラス)refからを作る
      const selector = ref.current?.className
        .split(" ")
        .map(s => `.${s}`)
        .join();

      // eはクリックしたところから発火されるイベント
      // eをclosestで比較し,eの親にrefが含まれるか確認する
      const blur = !(e.target as HTMLElement).closest(selector || "");

      // 親にrefが含まれていなければフォーカスを外す
      if (blur) setIsFocus(false);
    };

    document.addEventListener("click", handler);
    return () => document.addEventListener("click", handler);
  }, []);

  return (
    <Container ref={ref}>
      <Button></Button>
      <Menu>
        <OptionGroup>
          <Option>hoge</Option>
          <Option>huga</Option>
          <Option>nyan</Option>
        </OptionGroup>
      </Menu>
   </Container>
  )
}

ここなんですが

const selector = ref.current?.className
        .split(" ")
        .map(s => `.${s}`)
        .join();

javascriptだと

const selector = [...ref.current.classList].map(s => `.${s}`).join()

みたいなことができたんですけど,TSだと怒られましたw

最後に

今回使ったプログラムはここに置いてあります!必要であれば使ってください. github.com

自作のUI(DIVで作ったやつ)でフォーカス制御してみたいなことがあって困っていろいろ模索した結果こんな感じの解にたどり着きました.MDNじろじろ見る癖が少しづつついてきたかなぁって感想と,CSS筋が前よりついてきた気がします.

もっとこうしたらいいよとかあれば@naporin24690に教えてください〜!