PixelaのグラフをGatsby(React)で表示させたい
素直にsvgをobjectタグで読み込むだけだとツールチップが表示されないのでせっかくなら表示させたい
(※前提の参考リンクをよく読めばiframeで良くないか?という話になるが、今回はmswを使って開発しているときはローカルだけで完結させたい、がiframeはmswではモックできないという事情により色々面倒なことをやっている)
前提知識
はてなブログに Pixela グラフを埋め込んで、さらにツールチップを表示させる方法 - えいのうにっき
はてなブログへの埋め込み方法は上記
popoverを表示するためのライブラリとしてtippyjs
を使うことを前提としている
Pixelaが返すSVGの中のrectタグ中にdata-tippy-content
というプロパティがありその中にpopoverで表示されるコンテンツが入っている
tippyjs側では特定の属性の内容をツールチップの内容とする仕様になっている
- popoverの中身
頑張って一瞬でコピーした
<div class="tippy-popper" role="tooltip" id="tippy-92" x-placement="top" style="z-index: 9999; visibility: hidden; transition-duration: 0ms; position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(542px, 6020px, 0px);"><div class="tippy-tooltip dark-theme" data-size="regular" data-animation="shift-away" data-state="hidden" style="transition-duration: 275ms; top: 0px;"><div class="tippy-arrow" style="left: 77px;"></div><div class="tippy-content" data-state="hidden" style="transition-duration: 275ms;"><div>143 views on 2021-09-19</div></div></div></div>
- rect(当日)
<rect class="each-day" rx="2" ry="2" width="10" height="10" x="0" y="0" fill="#d5eaff" data-count="6" data-date="2021-09-19" data-unit="view(s)" data-retina="true" data-retinaday="20210919" data-index="1" tabindex="0"></rect>
一個不具合というかわからないが当日のデータはdata-tippy-content
にデータが入ってこないっぽい
- rect(昨日以前)
<rect data-tippy-content="6 view(s) on 2021-09-18" class="each-day" rx="2" ry="2" width="10" height="10" x="1" y="72" fill="#d5eaff" data-count="6" data-date="2021-09-18" data-unit="view(s)" data-retina="true" data-retinaday="20210918" data-index="2" tabindex="0"></rect>
なるほど
Reactでどうやってツールチップを実現するか
tippyjsを前提としているならreactでtippyjsを使えるようなライブラリがあれば良さそう
tippyjs作者と同じ方がReact用のライブラリも作っているようなのでそれを見にいってREADMEをいくつか試してみた
atomiks/tippyjs-react: React component for Tippy.js (official)
tippyjs単体での使用方法は下記
色々試してみたが力不足のためtippyjs-reactを用いてPixelaのグラフの中のデータをツールチップに表示させるところまで実装できなかった
下記読んで見たissue
Is there a way to use css selectors like with tippy.js? · Issue #170 · atomiks/tippyjs-react
Doesn't accept target property · Issue #39 · atomiks/tippyjs-react
tippy
を呼び出して直接実行することはできそう
ということで、useEffect内でtippyを実行するよう試してみたが
import {tippy} from '@tippyjs/react' ..... ..... useEffect(() => { tippy('.each-day', {arrow: true}) }) ..... ..... return ( <> <div> <div className="each-day" data-tippy-content="aaaa 1">a</div> <div className="each-day" data-tippy-content="bbbb 1">b</div> <div className="each-day" data-tippy-content="cccc 1">c</div> </div> <object type="image/svg+xml" data="https://pixe.la/v1/users/swfz/graphs/til-pv-dev?mode=short" ></object> </> )
SVGで呼び出した各rectにはeach-day
クラスが存在するはずだが反応せず…
同様のCSSクラス名を設定した子要素には反応した
これはobjectで読み込んだSVGがiframeなどと同様に子コンテンツ扱いされているからのよう
Doesn't accept target property · Issue #39 · atomiks/tippyjs-react
CSSセレクタで中身を取得したい場合次のような感じで取得できる
const element = document.querySelector('.selector-in-parent-content').contentWindow.document.querySelector('.selector-in-child-content');
これをCSSセレクタ一発で取得できるか少し調べたが見つからなかったので断念
ということでどうしたもんかなと考えたが次の案くらいしか思い浮かばなかった
案1 fetchとdangerouslySetInnerHTMLでSVGのレスポンスをそのまま突っ込む
- fetchでSVGデータを取得する
- dangerouslySetInnerHTMLでHTMLを入れ込む
- jQueryなどでの使用法と同様に
tippy
を実行する
ツールチップの要素などはtippy
が実行してDOM操作する形になるのでReactの管理対象外になるはず
なので正直気持ちの良いものではない
案2 fetchとcloneElementなどを使ってDOMを書き換えツールチップを動作させる
- fetchでSVGデータを取得する
- cloneElementなどを駆使し、Tippyタグが動作するようにDOMを書き換える
工夫すればできそうだけどsvgの中身まで把握しておかないといけないし結構たいへんそう…
案3 objectタグでレンダリングしているSVGの中でtippyjsを実行する
- そもそもできるのか不明
このへんまで調べてそんなに時間使えないし案1で良いか…ということで
まずは動かすところまで持っていく!!
結局次のような感じになった
案1でやってみた
import React, { useState, useEffect } from "react" import fetch from "node-fetch" import { tippy } from "@tippyjs/react" import "tippy.js/dist/tippy.css" import DOMPurify from "dompurify" const Pixela = () => { const [pixelaSvg, setPixelaSvg] = useState("") useEffect(() => { const fetchPixelaSvg = async () => { const res = await fetch( "https://pixe.la/v1/users/swfz/graphs/til-pageviews?mode=short" ) const html: string = await res.text() setPixelaSvg(DOMPurify.sanitize(html)) tippy(".each-day", { arrow: true }) } fetchPixelaSvg() }, []) return ( <> <div dangerouslySetInnerHTML={{ __html: pixelaSvg, }} ></div> <div style={{ textAlign: `right`, }} > Powered by{" "} <a href="https://pixe.la/" target="_blank"> Pixela </a> </div> </> ) }
まとめ
- PixelaのグラフをGatsby(React)で表示してツールチップまで表示できるようにした
- Reactの中の世界でツールチップを管理することを断念した
- 他案はまた別な機会で挑戦したい