環境

  • React.js
  • TypeScript
  • material-ui

目的

お気持ち:

  • シンプルなwebサイトにしたいが、詳細も載せたい。
  • 別リンクに飛ぶ詳細は面倒。
  • 要素にhoverした時に詳細がpopしてほしい
  • ↑を可能にするPopper(material-ui)はコード量が30行くらいと多い。
  • pop付要素を手軽に実装できるようにラップしよう。

概要

Popper(material-ui)をラップし、手軽な実装を実現。

作成したコンポーネントAddPopper

AddPopperの子要素にhoverすると、props.popに渡したReact要素が表示される。

  • 使用例 Videotogif.gif

Webサイトに表示した、カプースチンのソナタファンタジーいいよねという文字列の「カプースチン」と「ソナタファンタジー」の部分にhoverした時、補足説明が現れる(popする)ようにする。

function App() {
  return (
    <div className="App">
      <AddPopper
        //捕捉説明
        pop={<div>作曲家: Nicolai Kapustin(1937-2020, ウクライナ)</div>}
      >
        {/*このspanにhoverすると捕捉説明①が表示される。*/}
        <span>カプースチン</span>
      </AddPopper>
      
      {/*補足説明②*/}
      <AddPopper pop={<div>Op.39 4楽章構成</div>}>
        {/*このspanにhoverすると捕捉説明②が表示される。*/}
        <span>ソナタファンタジー</span>
      </AddPopper>
      いいよね。
    </div>
  );
}
  • propsの定義
interface Props {
  pop: React.ReactElement<any> //hover時に表示する要素
  type: 'span' | 'div' //被hover要素がinlineかどうかを指定
  children: React.ReactElement<any> //この要素にhoverすると発火
  placement?: PopperPlacementType; //popの位置を決定
}

AddPopperのソースコード

import { Fade, PopperPlacementType } from "@material-ui/core";
import { Popper } from "@material-ui/core";
import React, { useCallback, useState } from "react";

interface Props {
  pop: React.ReactElement<any>;
  children: React.ReactElement<any>;
  placement?: PopperPlacementType;
  type?: "span" | "div";
}

export function AddPopper(props: Props) {
  //被hover要素の位置を受け取る。
  const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);

  //hover時に発火
  const openPop = useCallback((event: React.MouseEvent<HTMLElement>) => {
    setAnchorEl(event.currentTarget);
  }, []);

  //hoverが外れた時に発火
  const closePop = useCallback(() => {
    setAnchorEl(null);
  }, []);

  //hoverの有無をbooleanで持つ
  const open = Boolean(anchorEl);

  //被hover要素に、カーソル設定とhover時背景色設定を追加している。
  const Base = () =>
    React.cloneElement(props.children, {
      style: {
        cursor: "default",
        backgroundColor: open ? "lightgrey" : undefined,
      },
    });

  //被hover要素とPop要素にマウスイベントを付加
  return React.createElement(
    props.type ?? "div",
    { onMouseEnter: openPop, onMouseLeave: closePop },
    [
      <Base key="base" />,
      <Popper
        key="popper"
        open={open}
        anchorEl={anchorEl}
        transition
        placement={props.placement ?? "bottom-start"}
      >
        {({ TransitionProps }) => (
          <Fade {...TransitionProps} timeout={350}>
            {props.pop}
          </Fade>
        )}
      </Popper>,
    ]
  );
}

ソースコードの説明

  • 基本的にはPopper(material-ui)の書き方に倣って書いている。
  • props.childrenとして受け取った要素(AddPopperの子要素)には、hover時のカーソルと、hover時の背景色が自動的に指定される。
  • 被hover要素が、inlineの時はspanを、blockの時はdiv、という使い分けのために、React.CreateElementによってtype(タグ名)をpropsに応じて指定可能にしている。
  • props.placementでpopの位置を調整。defaultはbottom-startとなっている。参照->PopperPlacementTypeの定義