レスポンシブデザインのために resize イベントを使うのはやめて matchMedia メソッドを使おう
レスポンシブデザインのために CSS メディアクエリを使うことが多いと思います。 CSS 側だけで完結したらいいのですが、JavaScript 側でも画面サイズの変更を検知したかったり、画面サイズ以外のメディアクエリ相当のことをしたくなったりすることはありますよね。
画面サイズの変更自体は window
に発生する resize
イベント (window.onresize
イベントハンドラ) で検知できますが、CSS メディアクエリとこれを組み合わせてレスポンシブ対応しようとすると以下の問題がでてきます。
- ウィンドウサイズ変更時に
resize
イベントが高頻度で発生するので、resize
イベントのリスナでコストのかかる処理を行うのはよくないとされる。 (Throttling することが推奨される。)- 参考 : resize | MDN
- CSS メディアクエリと完全に対応するものではないので、CSS 側との対応を取りづらい。
上の方はまあ throttling すればいいのですが、下の問題はどうしようもないですね。
window.matchMedia
メソッドと MediaQueryList
そこで別の方法としておすすめしたいのが window.matchMedia
メソッドとその返り値の MediaQueryList
オブジェクトです。 標準化についてはまだ完了しておらず、CSSOM View Module で作業されているようです。 とはいえ最近のブラウザだとどのブラウザでも使えるみたいなので、実用的に使っていける状況になっているかと思います。 (IE 9 とか少し古めの Android のブラウザとかでは使えないので、そこら辺のサポートが必要ならまだ使えませんが><)
window.matchMedia
メソッドの引数としてメディアクエリのリスト (media query list: メディアクエリをカンマ区切りで繋げたもの) を渡すと、それらのメディアクエリのリストを表す MediaQueryList
オブジェクトが返されます。 MediaQueryList#matches
プロパティを使うことで、リスト中のメディアクエリのうち少なくとも 1 つ以上が真になっているか、もしくはすべてが偽であるかを判別できます。
また、MediaQueryList#addListenr
メソッドでイベントリスナを設定することで、matches
の値の変化を検知することができます。
// TypeScript です。 // 縦 600px 以下、または横 600px 以下の場合に matches state が真になるメディアクエリリスト。 var mql = window.matchMedia("(max-width: 600px), (max-height: 600px)"); // メディアクエリリストの matches state に応じた処理を行う関数。 function handleMediaQueryListMatchesState(matches: boolean) { if (matches) { // メディアクエリリストの matches state が真の場合の処理。 } else { // 偽の場合の処理。 } } // イベントリスナを設定して matches state の変化を検知。 mql.addListener((evt) => handleMediaQueryListMatchesState(evt.matches)); // 初期化。 handleMediaQueryListMatchesState(mql.matches);
便利ですね。 非対応ブラウザを切っていいようでしたらどんどん使っていきましょう。
歴史的経緯?
ところで MediaQueryList
は addEventListener
メソッドを持っているはずなのに、それとは別に addListener
メソッドも持っていてどうなってるんだろう、と思いますよね。 私も思いました。 どうやら昔は MediaQueryList
は独自のコールバックの仕組みを使っていて、addEventListener
を持っていなかったようです。 また、コールバックメソッドに引数として渡される値も MediaQueryListEvent
ではなく、MediaQueryList
オブジェクトがそのまま渡されていたようです。
Note: This specification initially had a custom callback mechanism with
CSSOM View Module, 4.2. The MediaQueryList InterfaceaddListener()
andremoveListener()
, and the callback was invoked with the associated media query list as argument. Now the normal event mechanism is used instead. For backwards compatibility, theaddListener()
andremoveListener()
methods are basically aliases foraddEventListener()
andremoveEventListener()
, respectively, and the change event masquerades as aMediaQueryList
.
実際に試したところ、Firefox 49.0.1 や Edge 38.14393.0.0 では古い挙動になっていました。 Chrome 53.0.2785.116m は最新の CSSOM View Module にあった実装になっていました。 Firefox や Edge の実装はまだ最新の CSSOM View Module にあった実装になっていないので、しばらくは addListener
メソッドを使っていくようにするのが良さそうです。
他の方法
CSS 側でメディアクエリを使って特定要素のプロパティを変更するなどして、JS 側からはそのプロパティを見ることでどのメディアクエリが有効になってるのか検査するのが今のところは安定、という話も。 IE 9 や古めの Android 端末をサポートするならそういう方法が良さそうです。 (ということですよね? 他の理由があるなら教えてください!)
レスポンシブデザインのために resize イベントを使うのはやめて matchMedia メソッドを使おう - ひだまりソケットは壊れない今はまだ画面サイズ検知用の要素作って、font-familyとかをメディアクエリで変更するのが一番楽かな。font-family: "sp";とか。resizeイベント側はfont名を見るだけ。ただイベントで変化を検知できるのはいいなぁ
2016/10/02 14:57
matchMedia使うよりも対象メディアクエリ内で特定要素のdisplsyプロパティを検査した方が安全で簡単な場合が多いかな。https://t.co/WZ3VhfpJfQ
— h. kitago (@hkitago) October 2, 2016
参考
- CSSOM View Module : 標準化作業中の文書。
- 本記事記述のために参考にしたバージョンは こちら。
- スクリプトからのメディアクエリの使用 - ウェブデベロッパーガイド | MDN : MDN 上の
matchMedia
メソッドとMediaQueryList
の説明。 - Responsive Web Design Basics | Web | Google Developers : レスポンシブデザインの全体的な話。 (JS 上でのメディアクエリの話はないけど参考に。)
- Media Queries : メディアクエリについての W3C 勧告。
- GitHub - paulirish/matchMedia.js: matchMedia polyfill for testing media queries in JS :
matchMedia
の polyfil。 - ブログ記事もいくつかあります。
- JavaScript でメディアクエリを行う window.matchMedia の使い方 | Mozilla Developer Street (modest)
- やるやんwindow.matchmedia - FICC Workbook
- $(window).resize()は使わず、window.matchMediaを使って、jsでのメディアクエリー的な事をしてみよう的なお話 ~ 適当な感じでプログラミングとか! : サンプルコードで、毎回
matchMedia
メソッドを呼んでるのが微妙。 - レスポンシブWeb制作時に便利なmatchMediaメソッド | while(isプログラマ) : サンプルコード中に 「イベントリスナで実行された場合は、必ずtrue」 って書かれてるけど実際はそんなことはない。
- matchMediaを使ってみる | cly7796.net :
resize
イベントのリスナの中でmatchMedia
メソッドを使っていて微妙。
PowerShell (Windows) で Docker コンテナにホストディレクトリをデータボリュームとしてマウントする際に pwd 相当のことをしたい
試した環境
- Windows 10 Home (Anniversary Update; 64-bit 版)
- Docker Toolbox 1.12.0
前提知識
docker run -v /path/of/host/dir:/path/of/container ...
という感じでホストディレクトリをデータボリュームとしてマウントできる。- Windows で Docker Engine を使う場合、ホストディレクトリのパスは 「/c/Users/.../...」 という形式で記述する。
- バージョン 1.10 のドキュメント 参照。
- 最新のドキュメント では 「c:\Users\...\...」 という表記になっているが、これがあってるのか間違ってるのかは不明。 Windows で Dockcer Toolbox 1.12.0 を試した限りではだめだった。
- ホストディレクトリのパスは絶対パスで記述する必要がある。
絶対パスで記述するためにどうするかが問題
Linux では以下のように pwd
コマンドを使うのが一般的かと思います。
docker run -v $(pwd)/path/to/target:/container/path ...
じゃあ Windows (PowerShell) でどうするのか、というのが問題です。 PowerShell にも pwd
コマンド相当の Get-Location
コマンドレット があるのですが、これをそのまま使うとパスの形式が通常の Windows のパス表記の形式になるので、docker
コマンドの -v
オプションに渡せません。
Write-Output "$(Get-Location)/path/to/target" # => C:\Users\userName\Documents\project/path/to/target # 本当は /c/Users/userName/Cocuments/project/path/to/target という形式で欲しい # 実際にやってみると以下のようなエラーが出る。 docker run -v "$(Get-Location)/path/to/target:/container/path" ... C:\Program Files\Docker Toolbox\docker.exe: Error response from daemon: Invalid bind mount spec "C:\\Users\\userName\\Documents\\project/path/to/target:/container/path": invalid mode: /container/path. See 'C:\Program Files\Docker Toolbox\docker.exe run --help'.
雑に対応する
そんなわけで、以下のように Windows の通常のパスの表記を Linux ぽい感じに変換する関数を定義して使っています。
function pwd_as_linux { "/$((pwd).Drive.Name.ToLowerInvariant())/$((pwd).Path.Replace('\', '/').Substring(3))" } # 実際使う場合は以下のような感じ。 docker run -v "$(pwd_as_linux)/path/to/target:/container/path" ...
関数を定義するまでもなく変数に格納して使いたいって感じなら以下のようにもできます。
# Linux 風のパス形式。 $pwd_as_linux = "/$((pwd).Drive.Name.ToLowerInvariant())/$((pwd).Path.Replace('\', '/').Substring(3))" # 使う。 docker run -v ${pwd_as_linux}/path/to/target:/container/path ...
Docker Engine 側で通常の Windows のパス形式を受け付けてくれたらいいのですが、コロンが区切りに使われてるから難しいんだろうなぁと思ったり。
参考ページ
- Manage data in containers : 最新 (?) の公式ドキュメント。 コンテナ内のデータの管理について。
- Manage data in containers : バージョン 1.10 の公式ドキュメント。 基本的には上と同じはず。
- Dockerにホストのフォルダをマウントしたい! - Qiita : Windows でホストディレクトリをデータボリュームとしてマウントする話。
- Data Volume と Data Volume Container - Qiita : データボリューム全般に関して。
- Dockerコンテナからのディレクトリアクセスやボリューム共有 | Think IT(シンクイット) : データボリューム全般に関して。
株式会社はてなを退職しました
私事で恐縮ですが、2016 年 8 月 31 日付で株式会社はてなを退職しました。 はてなユーザーの皆様や一緒に仕事をした皆様、また、仕事以外でも勉強会などで一緒になった皆様、大変お世話になりました。 ありがとうございました!
入社したのは 2012 年 4 月で、当時は本格的な web サービス開発について全然知らないという状態でした。 そこから約 4 年間、Perl での web アプリケーションの開発から JavaScript や TypeScript でのフロントエンド開発、Windows ストアアプリや Android アプリといったクライアントアプリ、開発環境周りなどを経験し、社内の人にも社外の人にもいろいろと教えてもらいながら web サービスのエンジニアとして成長することができました。 本当にありがとうございました。 まだまだはてなでやってみたいことはありましたし、貢献できる部分もあるだろうとは思ったのですが、私的な事情により今回退職することを決めました。
退職することになりはしましたが、私自身ははてなで働けて良かったと思っておりますし、今年上場するなど勢いもありますので、はてなのサービス開発に興味がある方やインターネットで人の生活をよりよくしていきたいという方はぜひぜひはてなの採用に応募してみてはいかがでしょうか! (辞めた人の目線で話を聞いてみたいという方がいらっしゃいましたら *1 お声がけくださいー。)
さて、はてなについてはおいておいて私自身のことを言いますと、今後も web サービス開発に携わっていくつもりですし、勉強会などにも参加すると思いますので、今後ともどうぞよろしくお願いいたします!
ところで
退職する少し前からインターネット上での活動を控えていたせいか、何名かの方から 「生きてますか?」 というようなメッセージを頂きました。 心配してくださってありがとうございます。 活動を控えている理由は私的な事情で、本当に単に控えているだけですのでご安心ください。 *2
(ここに書かれていた内容については、当事者との相談をもって削除しました。)
おわりに
皆様が平穏無事な日々を過ごせますよう祈っております。
Android の Instrumented Test で指定のサイズのテストだけ実行する (@SmallTest とか @LargeTest とか)
Android Testing Support Library (ATSL) の話。 バージョン 0.5 時点での情報です。
ライブラリの準備方法などはドキュメントを読んでください。
テストサイズを表すアノテーション
android.support.test.filters
というパッケージがあって、この中にはテストをフィルタするのに使用できるアノテーションが入っています。 その中に、テストのサイズを表すためのアノテーションが 3 つ入っています。
@SmallTest
アノテーション : 200 ms 未満で終わるようなテスト。 ほぼユニットテスト用。 各種リソース (ファイルや DB、ネットワーク) は使用しない。@MediumTest
アノテーション : 1,000 ms 程度で終わるようなテスト。 コンポーネント単体やいくつかのコンポーネントを結合してテストする場合に使用する。 ファイルアクセスや DB アクセス、コンテキストなどは使っていいけど、ネットワークアクセスなどのそこそこ時間がかかる非同期処理はテストに含めない。@LargeTest
アノテーション : アプリケーション全体のコンポーネントを統合してテストするようなもの。
これらはテストクラス自体に付けることもできますし、メソッド単体に付けることもできます。 実装を見たところ、メソッドとクラスの両方にアノテーションが付けられている場合はメソッドのアノテーションが優先されるようです。
ちなみに、Android Testing Support Library じゃなくて android.test.suitebuilder.annotation
パッケージの方にも同名のアノテーションがありますが、そっちは deprecated ぽいし AndroidJUnitRunner と組み合わせて使うことはできないぽいので注意しましょう。
AndroidJUnitRunner
とテストサイズによるフィルタリング
AndroidJUnitRunner
でテストを実行する場合、テストサイズによるフィルタリングが可能です。 下記 Javadoc にいろいろ書かれています。
adb
でテスト実行する場合
上のドキュメントでは、adb
コマンドでテストを実行する際にどういうオプションを渡せばいいかが主に書かれています。 例えば、small サイズのテストのみを実行する場合は、以下のようになります。
adb shell am instrument -w -e size small your.test.target/android.support.test.runner.AndroidJUnitRunner
Gradle の connectedAndroidTest
タスクで実行する場合
AndroidManifest.xml で指定する (ただし現在はバグで動かない)
上のドキュメントには All arguments can also be specified in the in the AndroidManifest via a meta-data tag
ということが書かれています。 すなわち、テスト用のアプリパッケージの AndroidManifest.xml ファイル (app/src/androidTest/AndroidManifest.xml ファイル) に以下のような記述をすると、small サイズのテストのみが実行されるはず、ということです。
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" android:targetPackage="..."> <meta-data android:name="size" android:value="small"/> </instrumentation>
ただし現在はバグで動きません。 (指定が無視されます。) バグ報告を上げたので、対応状況は下記ページをご覧ください。
バグがなければ、上のような記述をしておけば ./gradlew :app:connectedAndroidTest
という感じで Gradle タスクでテストを実行したときにテストサイズのフィルタリングが効きます。
Gradle のビルドスクリプトで指定する
AndroidManifest.xml に書かずに Gradle のビルドスクリプトにオプションを指定することができます。 上の方法の代わりにこの方法を使うことで、上のバグを回避できます。 sumio さんに教えて頂きました。
@nobuoka もしコマンドラインからの実行で良いのであれば
— TOYAMA Sumio (@sumio_tym) May 26, 2016
build.gradle側で、
testInstrumentationRunnerArguments
を指定するので回避できたりしないでしょうか。こちらならManifestではなくargs扱いになるような気がします。
android { defaultConfig { testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunnerArguments size: 'small' } }
上のような感じで書きます。
Android Studio 上でテストを実行する場合
Android Studio 上でテストを実行する場合は、build.gradle での記述が有効にならないので、configurations を弄る必要があります。 下図のような感じで、Extra options に 「-e size small」 と入力すれば良いです。
おわり
@SmallTest
アノテーションとか @LargeTest
アノテーションとかがあって、テスト実行時にそれらのフィルタリングができるという情報を過去に見たのですが、いざやってみるとあまり情報がなくてうまくいかなかったのでまとめてみました。
コミットごとのテストは @SmallTest
だけにして、ある程度開発が終わってレビューに出す前の段階になったら @MediumTest
と @LargeTest
のテストも行うようにする、とかにしたらテストにかかる時間が短くなっていいかもしれませんね。
関連ページ
- Androidオールスターズでテストの話をしました | RECRUIT TECHNOLOGIES Member's blog : テストサイズごとのテストのアプローチなどが書かれていて、思想的な部分も実際の実装の部分でも参考になります。
- GitHub - googlesamples/android-testing-templates : Google による Android Testing Support Library を使ったテスト記述のサンプルプロジェクト。 参考になります。
- Is there a way to only run a specific set of tests in an Android Gradle project? - Stack Overflow : sumio さんに教えてもらった後に気付きましたが、Stack Overflow でも同じ方法が紹介されていました。
Rust 入門してる #3 : オブジェクト指向っぽい部分?
#2 に続いて Rust のドキュメントをぼちぼち読んでます。 このブログ記事はメモと感想程度のものですので、詳細は各見出しの下のリンク先を見てください。
今回は構造体とか列挙型とかメソッドとかトレイトとかそこら辺のオブジェクト指向っぽい部分 (の途中まで) を読みました。 パターンマッチとか文字列とかも出てきました。
構造体 (Structs)
// こういう感じで定義して、 struct Point { x: i32, y: i32, } // こういう感じでインスタンスを生成。 let origin = Point { x: 0, y: 0 }; // `..` に続けて別のインスタンスを指定すると、残りのフィールドにそのインスタンスの値を渡せるぽい。 let x1 = Point { x: 1, .. origin };
Tuple に名前を付けたような Tuple Structs というものもある。 型としては Tuple 型らしい。
struct Color(i32, i32, i32); let black = Color(0, 0, 0);
Unit-like Structs というものもある。 これはフィールドを持たない構造体で、Unit-like Structs を定義すると、同時にその構造体と同名の定数も定義される。
// Unit-like Structs
struct Cookie;
上の定義は、下のコードと同等。
struct Cookie {}
const Cookie: Cookie = Cookie {};
単に構造体を定義してフィールドの値を読み書きする、というぐらいであれば簡単っぽいけど、実践的に使うためにはトレイトと組み合わせたりしないとだめそうな気がする。 (まだよくわかってない。)
列挙型 (Enums)
列挙型は、複数の変種 (variant) の中の 1 つのデータを表す型。 列挙型の中のそれぞれの変種は、それに関連するデータを保持することもできる。 列挙型の値としては、その列挙型の任意の変種を取りうる。 代数的データ型でいうところの直和型 (sum type) に相当するっぽい。
enum Message { Quit, ChangeColor(i32, i32, i32), Move { x: i32, y: i32 }, Write(String), }
上の例を見るとわかるように、各変種を定義するための構文は、構造体を定義するための構文に似ている。
それぞれの変種は列挙型の名前でスコープ化されているので、::
を使って各変種の名前を使用する。
let message: Message = Message::Move { x: 10, y: 5 };
列挙型のコンストラクタは、関数としても扱えるらしい。 例えば、上の例の Message::Write
のコンストラクタは fn (String) -> Message
という型の値として扱える。 クロージャとかと同様に他の関数に渡したりして使う場合に便利っぽい。
Match
match
式はパターンによって分岐するものである。 単純な例だと、以下のように if
/else
の連なりを書きかえることができる。
let x = 3; let val = match x { 1 => "one", 2 => "two", 3 => "three", _ => "else", };
match
には、「網羅的なチェック」 を強制するという利点がある。 例えば上の例で 「_
」 を除くとコンパイルエラーになる。
パターン (Patterns)
変数束縛や match
式、他の場所にもパターンは現れる。 下記のものを組み合わせてパターンが表現される。
- 複式パターン :
1 | 2
- 範囲 :
1 ... 5
- 変数への束縛 :
x
- 値の型に応じてコピーか移動になる。
- 参照を束縛 :
ref x
(ref mut x
) - サブパターンで束縛 :
e @ 1 ... 5
- デストラクチャリング :
Point { x, y }
- フィールドの値に別名を付けて束縛 :
Point { x: x1, y: ref y1 }
- フィールドの値に別名を付けて束縛 :
- ワイルドカード (
..
) :Point { x: x1, .. }
- プレイスホルダ (
_
) :Err(_)
パターンガードもある。
let message = match val { Some(x) if x < 5 => "Less than five", Some(x) => "Else", None => "None", };
メソッド
impl
キーワードである型に対するメソッドを定義できる。 第 1 引数は self
か &self
か &mut self
。 impl
は同じ型に対していくつも定義できるらしい。
struct Point { x: i32, y: i32 } impl Point { fn p(&self) { println!("({}, {})", self.x, self.y); } } let point = Point { x: 1, y: 2 }; point.p();
関連する関数 (static メソッドぽいもの) も定義できる。 第 1 引数が self
(または &self
、&mut self
) でない場合は自動的にそうなる。
impl Point { fn new(x: i32, y: i32) -> Point { Point { x: x, y: y } } } let point = Point::new(10, 2);
ここら辺は Perl とかやってると馴染みのある感じ。 既存の構造体に対して新しいメソッドを定義することもできそうだし、そこら辺は便利そうな気がする。 (動的型付けだとどこでメソッドが定義されたのかわからなくて困りそうだけど、Rust だとまあ大丈夫そう?)
Vectors
以下のようなイテレーションの例があるけど、vector の指定方法に応じて i
の型も変わるのが慣れるまで大変そう。
for i in &v { println!("A reference to {}", i); } for i in &mut v { println!("A mutable reference to {}", i); } for i in v { println!("Take ownership of the vector and its element {}", i); }
文字列
Rust の文字列は UTF-8 エンコードされた Unicode スカラ値の連なり。 全ての文字列は UTF-8 列として正しい。
型としては主に 2 種類。
&str
: 文字列スライス (string slices)- サイズ固定で変更不可。
String
: ヒープ割り当てされた文字列。- 変更可能。
文字列リテラルは static にメモリ割り当てされるので、それへの参照として &str
を使用できる。
&str
の値のto_string()
メソッドを呼ぶことでString
の値を得られる。 (メモリ割り当てするのでコストがかかる。)String
の値の参照を取ることで&str
として扱える。 (&String
から&str
への自動的な型強制がかかるため。 コストはかからない。)
バイトごとや文字ごとのイテレーションをしたい場合は as_bytes()
メソッドや chars()
メソッドを使うぽい。
String
の後ろに &str
を結合できる。
let s1: String = "こんにちは".to_string(); let s2: &str = "世界"; println!("{}", s1 + s2);
こういう結合をしたときの所有権の動きがいまいちよくわかってない。
トレイト
Rust のトレイトは、メソッドを宣言するだけで実装は含まないから *1 トレイトというよりインターフェイスなのでは? と思ったけど、impl
キーワードでトレイトに対する実装を提供する、という感じだからインターフェイスじゃなくてトレイトなのか。 impl HasArea for Circle { ... }
という感じ。
Java なんかの Circle
が HasArea
を実装する、というのとは逆で、Circle
に対する HasArea
の実装を提供する、という感じなのが面白い。 こういう風になっているおかげで、既存の構造体に対して追加でトレイトの実装を提供できて便利ぽい。 (と思ったけど、トレイトと実装対象の型のどちらかは自分で実装したものではないとだめっぽいから、自由になんでもできるわけではなさそう。)
ジェネリック関数の境界にトレイトを使用できる。
fn print_area<T: HasArea>(shape: T) { ... }
複数の境界を指定することもできる。 Java 8 の intersection types っぽい。
fn foo<T: Clone + Debug>(x: T) { ... }
ジェネリック型の実装のジェネリックパラメータの境界にトレイトを使用した場合は、その実装はその条件を満たすジェネリックパラメータの場合のみにその実装が提供される。
impl<T: PartialEq> Rectangle<T> { fn is_square(&self) -> bool { self.width == self.height } }
上の例だと、ジェネリックパラメータ T
に対して PartialEq
の実装が提供されている場合にのみ Rectangle<T>
に is_square
メソッドが生える。
ジェネリックパラメータの宣言場所での境界の定義だけでなく、where
句での定義も可能。
fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) { ... } // 上と同じ。 fn foo<T, K>(x: T, y: K) where T: Clone, K: Clone + Debug { ... }
トレイトはメソッドのデフォルト実装を持てる。 また、継承も可能。
一部のトレイトは、属性を使って自動的に実装できる。
#[derive(Debug)] struct Foo;
*1:後ろの方に書いたけどデフォルト実装を持てます。 suzak さん指摘ありがとうございます!