吹き出しコンポーネントを作った時から、SVGで面白い動きのコンポーネントが作ってみたいと思っていて、その習作としてSVGで描画したぽよんと表示されるmodalコンポーネントを作ってみた。
作ったもの
デモ
使い方
インストール
npm i react-elastic-modal
サンプル
以下のように使用する。極力react-modal
に似せたつもり。
<Modal isOpen={ this.state.isOpen } onRequestClose={ () => this.setState({ isOpen: false }) } modal={{ width: '50%', height: '360px', backgroundColor: '#fff', opacity: 0.5, }} overlay={{ background: 'rgba(0, 0, 0, 0.4)', }} > <div>modal example</div> </Modal>
このコンポーネントについて
使用例
@59nagaさんが以下のサイトで使ってくれている。ありがとうござます。
SVGまわり
以下のように書いて、rafでアニメーションしてる。なんかあまりいい方法とは思えない。また、親から例えばサイズを100px×100pxでもらった場合、伸縮を表現するためにSVG領域は110px×110px確保している。アニメーション完了後いサイズを戻せばいいんだろうけど、今は放置している。SVGの機能についても、もっと理解を深める必要がありそう。
modalのコンテンツはSVGとは別レイヤーにしてz-index
で被せている。この辺りも正攻法を把握してない。
<svg width={`${100 + svgMarginRatio * 200}%`} height={`${100 + svgMarginRatio * 200}%`} style={{ position: 'absolute', top: `-${100 * svgMarginRatio}%`, left: `-${100 * svgMarginRatio}%`, transform: `scale3d(${this.state.scale}, ${this.state.scale}, 1)`, opacity: this.props.modal.opacity || 1, }} > <path d={ `M ${x0} ${y0} Q ${cx} ${top} ${x1} ${y0} Q ${right} ${cy} ${x1} ${y1} Q ${cx} ${bottom} ${x0} ${y1} Q ${left} ${cy} ${x0} ${y0}` } fill={ this.props.modal.backgroundColor } /> </svg>
中央寄せの話し
modalを中央寄せする中で最近は以下の方法がかなりお気に入りで使用していたんだけど、要素のwidth/heightが奇数の場合、transform: translate(-50%, -50%)
で座標が小数点になって表示がぼやけるってことに遭遇した。これだったらボックスのサイズを知る必要がなく便利なんだけど、結局古のネガティブマージンで対応することにした。css難しい。
.outer { position: relative; } .inner { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
テストまわり
最近はenzyme
を使っているんだけどまだ理解できていないところが多い。基本shallow
を使ってテスト(この場合はmockは不要?)して、必要に応じてmount
するという方法をとってるんだけどそういうスタンスでいいんだろうか。
たとえば、defaultProps
の値を確認したかったり、componentDidMount
を発火させたかったり、element.clientHeight
を取りたいような場合はどうしいてもmount
する必要がある。その場合は結局mockery
とかproxyquire
とかでmockに置き換える必要があると思う(今回は単一のコンポーネントなので必要ないけど)んだけどそうのようなスタンスでいいのか不明だ。
もう一点ハマった点は、modal
のclinetHeight/Width
が常に0が取れてきていて、なんだろうと思っていたんだけどマウントの仕方が悪かったよう。どうやら以下のようにしているとサイズが出ないようで、
const modal = mount(
<Modal ..... />
);
正しくは以下のようにdivにアタッチする必要があるよう。
const modal = mount( <Modal ..... />, { attachTo: div } );
そのため、beforeとafterで以下のようにしている。
describe('Modal test', () => { let div; beforeEach(() => { div = document.createElement('div'); document.body.appendChild(div); }); afterEach(() => { document.body.innerHTML = ''; }); ...
さいご
SVG
は楽しいんだけど、もう少し、SVG
自身の機能や作法を学ぶ必要がありそう。またモーフィング周りにいいライブラリがあるともっと楽しめそうなんだが、sebmarkbage/art
このあたりとか使えるんだろうか。