ReactでModal windowを実装する
汎用コンポーネントも存在しますが、自作のミニマル実装コードです。
ひとまずコード(スタイルなどは簡略化しています)
import React from 'react'; import PropTypes from 'prop-types'; class ModalWindow extends React.Component { constructor(props) { super(props); this.eventListener = this._handleEscKey.bind(this); this.state = {}; } componentDidMount() { document.body.classList.add('openModal'); document.addEventListener('keydown', this.eventListener); } componentWillUnmount() { document.body.classList.remove('openModal'); document.removeEventListener('keydown', this.eventListener); } _handleEscKey(e) { const keyCode = parseInt(e.keyCode, 10); if (keyCode !== 27) { return false; } this.props.onClose(); } render() { const { children, onClose } = this.props; return ( <div className="modalWin_wrapper" onClick={(e) => e.target === e.currentTarget ? onClose() : null}> <div className="modalWin_inner"> <button className="modalWin_close" onClick={() => onClose()}> × </button> {children} </div> </div> ); } } ModalWindow.propTypes = { children: PropTypes.node.isRequired, onClose: PropTypes.func.isRequired } export default ModalWindow;
.modalWin_wrapper { display: block; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, .5); z-index: 10000; } .modalWin_inner { position: relative; width: 1000px; height: 100%; margin: 0 auto; padding: 0 0 20px; background: #FFF; overflow: scroll; box-sizing: border-box; box-shadow: 0 0 10px 0 rgba(0, 0, 0, .5); } .modalWin_close { display: block; position: absolute; width: 40px; height: 40px; top: 0; right: 0; border: 0; background: #666; font-size: 40px; line-height: 1; color: #FFF; text-align: center; outline: none; } body.openModal { overflow: hidden; }
使うときは、
<ModalWindow onClose={this.onClose.bind(this)}> <div>Content</div> </ModalWindow>
のようにする。
最低限、
- モーダルウィンドウの外側(ここではmodalWin_wrapperクラスのdiv要素)をクリックしたら閉じる
- Escキーを押したら閉じる
- モーダルウィンドウ内にも閉じるボタン(ここでは.modalWin_closeクラスのbutton要素)
- 閉じる時にActionの発行をしたいので、閉じるための関数は外部から渡す
を実装しています。
Escキーが押されたことを検知するにはイベントリスナーの登録が必要なので、componentDidMount()
内でdocument.addEventListener('keydown', this.eventListener);
しています。そしてcomponentWillUnmount()
で削除します。削除する時に同じ関数を引数にする必要があるので、関数はコンストラクターで定義してしまっています。
もう一点、モーダルウィンドウを開いている状態でスクロールをすると、後ろに隠れているコンテンツもスクロールしてしまいます。これではあまり使い勝手がよろしくないので、モーダルウィンドウが開いている間はbody要素にoverflow: hidden
を設定しています。
body要素はReactのマウントポイントの外側にあるため、直接DOMツリーからアクセスしています。この部分:document.body.classList.add('openModal');
。
videoタグのダウンロードオプション
HTMLのvideoタグで動画を表示すると、Chromeで右下のメニューをクリックすると「ダウンロード」という項目があります。(他のブラウザでは確認できませんでした。)
これは、controlslist属性に"nodownload"というパラメータを指定すると非表示になります。
controlslist属性には他にも様々な値が指定できますが、HTML標準にはなってないようです。
<video>: 動画埋め込み要素 - HTML: HyperText Markup Language | MDN
[HTMLMediaElement] Add a controlsList/controlslist attribute. by avayvod · Pull Request #2426 · whatwg/html · GitHub
(MDNのページには実験段階を示すアイコンがついているし、WHATWGの提案はマージされていない様子。)
<video class="mediaWrapper_video" src="video.mp4" type="video/mp4" controls controlslist="nodownload" > </video>
これでChromeでも「ダウンロード」というメニューは表示されなくなりますが、動画の上で右クリックするとコンテキストメニューに「名前をつけて動画を保存」という項目は表示されたままです。できれば保存されたくない、という要望があった場合、やはり右クリックも無効化するしかなさそうです。
document.querySelector('.mediaWrapper').addEventListener('contextmenu', function(e) { e.preventDefault(); return false; });
ユーザビリティの面からは、コンテキストメニューの非表示はあまりやりたくないので、せめて動画の部分(ここではmediaWrapperクラス)だけを無効化。)
もちろんソースコード上では動画のURLが見えていてダウンロードできてしまうので、厳密にやるのであればストリーミング配信するしかないですが、「なるべくなら保存されたくない」というような場合は、現状はこれがベターでしょうか。
GETで見られたくないものを送信するときはヘッダーに入れる
HTTPSで通信していても、GETのクエリパラメータは暗号化されないので、見られたくないものを送信するときは、POSTで送るか、HTTPのリクエストヘッダーに入れるようにしています。
クエリパラメータはURLの一部なので、https://example.com?page=1みたいな平文が公衆Wi-Fiの通信に乗ったり、中間サーバのログに残ったりする可能性があります。ページ番号であればそれでも構いませんが、例えばログインリクエストの場合にはhttps://example.com?user=xxxxx&password=yyyyyのような情報を送ることになるので、これは見られては困ります。
単純にPOSTリクエストのリクエストボディに入れても良いのですが、リクエスト先がRESTfulなAPIで、セマンティクス的にGETがふさわしい場合(サーバのリソースを書き換えないリクエスト)には、HTTPのカスタムヘッダーを使うようにしています。
例:
https://example.com/api/login のようなURLのエンドポイントにGETリクエストを送るときに、
MyApp-User-Authorization:{ユーザー認証のための文字列}
のようなカスタムヘッダーを追加します。
「ユーザー認証のための文字列」には、「ログイン名:パスワード」をbase64エンコードしたものを入れて、サーバ側でデコード・パースして認証を行います。
このやり方は、cybozuさんのAPIを勝手に参考にさせていただいたものです:
kintone REST APIの共通仕様 – cybozu developer network
上記のAPI仕様書によると、cybozuさんのカスタムヘッダーは「X-Cybozu-Authorization」という名前ですが、カスタムなヘッダーの場合に接頭辞を「X-」とする、という習慣は、現在は非推奨とのことなので、シンプルにプロダクトのサービス名から始めるようにしています。
「X-」接頭辞についての参考資料:
HTTP ヘッダー - HTTP | MDN
RFC 6648 - Deprecating the "X-" Prefix and Similar Constructs in Application Protocols
PHPがセッションIDを発行しなくなった
突然PHPのsession_id()
が空白を返すようになってしまった
もちろん本当に「突然」なのではなく、何かいじってしまったからですが。
PHPのセッション情報は、デフォルトではファイルに保存される
ので、そのファイルの読み書きができなければセッションIDも発行されません。
phpinfo();
でセッションの項目を確認。
session.save_handler
がfiles
の場合、
session.save_path
に設定されているディレクトリに読み書きの権限があるかを確認。
なぜそんなことになったのかの推測(自分メモ)
対象ディレクトリのユーザーがroot、グループがapacheになっていた。
当該サーバではApacheではなくNginxを使っているので、グループがapacheなのはおかしいようにも思える。
おかしくなる直前に、PHPの追加モジュールを入れたりしていたので、そこでパーミッションをいじってしまったか、グループがapacheに変わってしまったのかもしれない。
letter-spacingとtext-align: center(とcalc())
letter-spacingとテキストの中央揃えを併用すると、左右の空白がずれて見えるのでpaddingで調整することがよくある。
例えばこんな見た目のボタンを作るのに、
padding: 5px; font-size: 16px; letter-spacing: 1em; text-align: center; /*〜略〜*/
とする。
(全角スペースで表現されたデザイン画を受け取ることもあるけど、HTMLに「検 索」と入れるのはなしですよね。ぱっと思いつくところだとアクセシビリティの問題とか。)
そうすると、letter-spacingは各文字の後ろに入るので、右側の余白が大きく見えてしまう。
これをpaddingで調整するのに、
padding: 5px 5px 5px 21px; /* 5 + 16 = 21 */
でも良いのだが、ここ数年はモダンブラウザであれば「calc()」が問題なく使えるので*1、
padding: 5px 5px 5px calc(5px + 1em);
としておくと、コードの意図がより分かりやすくなって余白の調整もやりやすい。