ReactMarkdown の img
タグのカスタムコンポーネントでの課題と解決策を書いてみました。
ニッチな内容ですが、公式にもイシューによく上がっているにも関わらずあまり記事がなかったのでまとめてみました。
1. 背景
ReactMarkdown
を利用して Markdown を HTML に変換する際、画像(img
タグ)をカスタマイズしたい場面があります。
マウスをかざしたとき、グレーのオーバーレイをさせるときの対応をimgタグに実装したかった。
このときにdivをつかってオーバーレイを実装していました。
しかし、ReactMarkdown
はデフォルトで img
を <p>
タグでラップする仕様があり、これが原因で以下の問題が発生しました。
- 画像の上に
hover
のオーバーレイを表示したいが、p
の影響を受ける ::after
や::before
を使ったhover
のオーバーレイが適用されないp
タグ内にdiv
を追加すると HTML 構造が壊れるp
タグを変更せずにimg
のみをカスタマイズする方法が不明
この問題を ReactMarkdown
のカスタムコンポーネントのみで解決する方法を検討しました。
2. 問題点
① ReactMarkdown
のデフォルト動作
ReactMarkdown
は以下のように、img
タグを <p>
の内部に配置します。
import ReactMarkdown from 'react-markdown';
const markdown = ``;
<ReactMarkdown>{markdown}</ReactMarkdown>;
➡ 生成される HTML
<p>
<img src="https://example.com/sample.jpg" alt="サンプル画像" />
</p>
p
タグの影響を受けるため、自由に img
を加工できない問題が発生しました。
公式やイシューを見ると、MDでの写真は段落として表示するため、pタグで囲む思想のようでした。
② ::before
や ::after
を使った hover
オーバーレイが機能しない
通常の div
なら ::before
を使ってオーバーレイを表示できますが、img
タグは 空要素 のため、::before
や ::after
は適用されません。
const StyledImage = styled.img`
position: relative;
&:hover::before {
content: 'クリックして拡大';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 16px;
background: rgba(0, 0, 0, 0.5);
padding: 8px 12px;
border-radius: 4px;
}
`;
➡ img
タグでは ::before
や ::after
が無視されるため、オーバーレイが表示されない
③ div
を追加すると HTML 構造が壊れる
img
を div
でラップすれば hover
のオーバーレイを実装できますが、ReactMarkdown
は p
タグを変更しないため、HTML が壊れてしまいます。
const CustomImage = ({ src, alt }: { src?: string; alt?: string }) => {
return (
<div>
<img src={src} alt={alt} />
<span>クリックして拡大</span>
</div>
);
};
<ReactMarkdown components={{ img: CustomImage }}>{markdown}</ReactMarkdown>;
➡ p
タグの中に div
が入るため、不正な HTML 構造になる
3. 解決策
box-shadow
を使って hover
時にオーバーレイを適用
img
自体に box-shadow
を適用し、hover
した際に擬似オーバーレイを表示する方法が最適でした。
const StyledImage = styled.img`
max-width: 100%;
height: auto;
display: block;
cursor: pointer;
transition: filter 0.3s ease-in-out, box-shadow 0.3s ease-in-out;
&:hover {
filter: brightness(50%);
box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.5);
}
`;
➡ filter: brightness(50%)
で画像を暗くし、box-shadow
でオーバーレイを再現
span
を使って hover
テキストを表示
div
ではなく span
を使い、img
の隣にテキストを配置することで、p
の影響を受けずに hover
オーバーレイを適用できます。
const OverlayText = styled.span`
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 16px;
font-weight: bold;
background: rgba(0, 0, 0, 0.5);
padding: 8px 12px;
border-radius: 4px;
opacity: 0;
transition: opacity 0.3s ease-in-out;
pointer-events: none;
`;
const ImageWrapper = styled.span`
position: relative;
display: inline-block;
cursor: pointer;
&:hover ${OverlayText} {
opacity: 1;
}
`;
➡ p
の影響を受けず、img
の hover
に対応
4. まとめ
ReactMarkdown
の img
カスタマイズの問題
-
img
タグが デフォルトでp
にラップされる -
::before
や::after
がimg
では使えない -
div
を追加すると HTML 構造が崩れる -
hover
のオーバーレイが 表示されない
解決策
box-shadow
と filter
を利用し、img
単体で hover
を適用
span
を利用し、テキストオーバーレイを hover
時に表示
p
タグを変更せず、HTML の構造を維持
この方法を採用することで、ReactMarkdown
の img
タグを hover
可能にし、HTML の構造を壊さずにオーバーレイを適用することが可能になった。