touchstartをイベントハンドラとした時のpageX,pageYプロパティへのアクセス方法
イベントハンドラがmousedown
のときとtouchstart
の時で引数に渡されるオブジェクトが異なるというメモ。
mousedownのときに渡されるオブジェクト
MouseEvent
オブジェクトが渡される。
目的のpageX
、pageY
プロパティは直下にあるので
e.pageX e.pageY
でアクセスできる。
MouseEvent - Web API インターフェイス | MDN
touchstartのときに渡されるオブジェクト
TouchEvent
オブジェクトが渡される。
TouchEvent - Web API インターフェイス | MDN
場合によってはMouseEvent
も渡されるっぽいが、あらゆる場合で渡されるのかは未調査。
MouseEvent
オブジェクトとは違い、pageX
、pageY
プロパティは直下には存在しない。
ではどこにあるのかというと、TouchEvent
オブジェクトのchangedTouches
プロパティの中だ。
TouchEvent.changedTouches - Web API インターフェイス | MDN
ここにtouchList
というオブジェクトが格納されている。
TouchList - Web API インターフェイス | MDN
touchList
オブジェクトの中には0
というプロパティ名でTouch
オブジェクトが格納されている。
この中にpageX
やpageY
が入っている。
Touch - Web API インターフェイス | MDN
そのため、アクセスするには以下のように記述すればOK。
e.changedTouches[0].pageX e.changedTouches[0].pageY
オブジェクトのプロパティへアクセスする際の注意事項
JavaSctiptでは、ブラケット表記法でオブジェクトのプロパティにアクセスする際に数値をいれても文字列として扱われる。
ブラケット表記法には式の評価を行うプロセスがあり、その際にtoString
メソッドを経由するので、強制的に文字列型への変換がかかるためである。
一方、ドット表記法はそのプロセスは存在しない。
したがってe.changedTouches.0
のような表記は使えないので注意。
これらについては別記事でまとめてあるのできになる場合はそちらで確認してほしい。
変数をキーとしてオブジェクトのプロパティを参照する際はブラケット表記法を使う。 - nayucolony
関連
今回のエントリの内容は以下のサンプルコードに登場します。
SVGを触ってみる【path要素/d属性/直線/ベジェ曲線】
SVGのことを実は全く知らないので少し調べてみた。
とりあえず、簡単なパスを引いて図形をつくる。
まず、基本事項としてsvg
要素が存在し、子要素としてpath
要素を作る。
そしてpath
要素にd
属性を指定することでパスを引いていく。d
はdraw
= 線を引くという意味。
パスの内側を塗りつぶす時はfill
属性を使用する。
直線を引く場合
始点を決めたら、あとは座標をどかどかうっていくと直線をどんどん引いていける。
Mで始点の座標を決定し、L以降に指定した座標に直線を引いていく。LはLineto
で〜に直線を引く
以下の例では、0,0
を支店に、順番に320,0
=>320,160
=>0,160
と線を引いた場合。
0,160
=> 0,0
のように始点までの直線を指定しなくても、最後の座標から自動的に始点にまでのパスを引いてクローズドパスになる。
<svg width="1000" height="1000"> <path d="M0,0 L320,0 320,160 0,160" fill="#3F51B5"></path> </svg>
座標は書いた順番にパスが惹かれていくので、入れ替えると見た目は変わる。
<svg width="1000" height="1000"> <path d="M0,0 L320,160 320,0 0,160" fill="#3F51B5"></path> </svg>
曲線を引く場合
曲線を引く場合はQを使う。 曲線はベジ絵曲線で、クアドラティックベジエとキュービックベジエの二種類がある。Qだと「クアドラティックベジエ」が惹かれる。 名前が違えば当然処理も違うが、詳しいことはまだ調べきれていない。
以下は、一番最初の図形を少し変更したもので
320,160
=> 160,320
と 160,320
=> 0,160
が曲線になる。
<svg width="1000" height="1000"> <path d="M0,0 L320,0 320,160 Q160,320 0,160" fill="#3F51B5"></path> </svg>
注意
この図形描画は、svg
要素がその直線を引くのに必要なだけの領域を確保できていることが前提となる。
上記までのサンプルは、しれっと1000px
* 1000px
を確保していたが
たとえば2つ目のサンプルは、描画に320px * 160pxを必要とする。
ここで、svg
要素の領域を160px
* 80px
にしてみる。
<svg width="160" height="80"> <path d="M0,0 L320,160 320,0 0,160" fill="#3F51B5"></path> </svg>
すると、こうなる。
path
要素はちゃんと描画領域を確保しているものの、svg
が領域を確保していないためにクロップされている、と表現するのが正しそう。
参考
インスタンス内において、アロー関数の「this」はインスタンスを参照しない
Vue.jsに限った話ではないが、Vue.jsを触っていて湧いたちょっとした疑問を調べたメモ。
TL;DR
- アロー関数内の
this
は「Vueインスタンスを参照したい」という文脈での使用に適さないのでつかわない。 function
による関数宣言では「インスタンス内のthis
はインスタンスそのもの」なのでVueインスタンスを参照できる。- アロー関数では「呼ばれた場所が
this
」。 - 例えば
computed
のメソッド内のthis
は「computed
のオブジェクト」であり、インスタンスではないので不適。
経緯
Vue.jsに書いてある
算出プロパティ(例 aDouble: () => this.a * 2) を定義するためにアロー関数を使用すべきではないことに注意してください。アロー関数は、this が期待する Vue インスタンスではなく、this.a が undefined になるため、親コンテキストに束縛できないことが理由です。
とか
data プロパティ(例 data: () => { return { a: this.myProp }}) でアロー関数を使用すべきではないことに注意してください。アロー関数は、this が期待する Vue インスタンスではなく、this.myProp が undefined になるため、親コンテキストに束縛できないことが理由です。
これが気になってちゃんと調べた。
解説
Vue.jsを使用する際、「Vueインスタンスのdata
オプションのオブジェクトに入ってる値」を参照して計算することが多々ある。
例えばこんな感じ(Vue.jsより)
var vm = new Vue({ data: { a: 1 }, computed: { // get のみ。必要なのは関数一つだけ aDouble: function () { return this.a * 2 }, // get と set 両方 aPlus: { get: function () { return this.a + 1 }, set: function (v) { this.a = v - 1 } } } }) vm.aPlus // -> 2 vm.aPlus = 3 vm.a // -> 2 vm.aDouble // -> 4
このとき、this.a
は「data
オプションのオブジェクトのプロパティ名a
」をさしている。
これは、「コンストラクタを呼び出してインスタンスを生成したとき、インスタンス中のthis
はインスタンスそのものをさす」ことによる。
data
、method
などのオプションのオブジェクトのプロパティは、Vueインスタンス直下に格納されるので、this.a
のようにして参照できる。
ちなみに、所属するオプションを明示的にしてアクセスをする場合、this._data.a
だったりthis.$data.a
だったりでアクセスできる。
(ただし、アクセスできるものの、「完全に代替できる」とはいえないっぽいということがオプション / データ - Vue.jsに書いてある。
使用する際は一読することを推奨する。)
では、アロー関数でのthis
はどうなのか。
アロー関数のthis
は、function
の時のように「インスタンス内ではそのインスタンスをさす」という動きはしない。
以下は先ほどの例をただアロー関数に変えたもの。
var vm = new Vue({ data: { a: 1 }, computed: { // get のみ。必要なのは関数一つだけ aDouble: () => { return this.a * 2 }, // get と set 両方 aPlus: { get: () => { return this.a + 1 }, set: (v) => { this.a = v - 1 } } } })
ここで、一部分に着目
var vm = new Vue({ data: { a: 1 }, computed: { aDouble: () => { return this.a * 2 }, } })
このとき、funciton
ではthis.a
はdata
オプションのa
を参照できていた。
しかし、アロー関数を用いるとそうはできない。なぜなら、アロー関数は「呼び出された場所をthisとする」という動きをするからだ。
「呼び出された場所」は今回のケースだと、「computed
オプションのオブジェクト」にあたる。
そのオブジェクトの中にa
をプロパティ名として持つ要素は存在しないので、undefined
となる。
これが、
アロー関数は、this が期待する Vue インスタンスではなく
という文章の意味だった。たしかにVueインスタンスを参照できていないので不適だ。
this
使ってない場合はアロー関数でも問題はないが、混合させるくらいならfunction
で統一すべきだろう。
関連
アロー関数がどうのこうのと言う前に、そもそもthisは結構文脈によってさすものが違うので注意。 JavaScriptの「this」は「4種類」?? - Qiita
変数をキーとしてオブジェクトのプロパティを参照する際はブラケット表記法を使う。
結論
- JavaScriptにおいて、変数をキーとしてオブジェクトのプロパティを参照する際はブラケット表記法を使う。
- ブラケット表記法には、ドット表記法にはない「式の評価」処理があるため、変数を解決できる。
経緯
for文の中にfor文を配置して二次元配列的にループしてさせようとしていた。
<div id="demo"> <tr v-for="entry in gridData"> <td v-for="key in gridColumns">{{ entry.key }}</td> </tr> </div>
const demo = new Vue({ el: '#demo', data: { gridColumns: ['name', 'power'], gridData: [ { name: 'Chuck Norris', power: Infinity }, { name: 'Bruce Lee', power: 9000 }, { name: 'Jackie Chan', power: 7000 }, { name: 'Jet Li', power: 8000 } ], }, });
するとこうなった。
ちゃんとループはしているのだが、データがバインドできていないという状態だ。
解決方法
オブジェクトのデータ参照方法をドット表記法でなくブラケット表記法に変更した。
それぞれの表記法については下記の通り。 メンバー演算子 - JavaScript | MDN
<div id="demo"> <tr v-for="entry in gridData"> <td v-for="key in gridColumns">{{ entry[key] }}</td> </tr> </div>
チャックノリスもにっこり。
なぜなのか
ブラケット表記法とドット表記法は、単純に「ふた通りの表記法があります」という話ではない。
たとえば、今回のケースではkey
が変数であるということが大きなポイント。ドット表記法では、プロパティ名に変数をもちいることができない
ここに書いてあった。 ECMAScript 2015 Language Specification – ECMA-262 6th Edition
12.3.2.1 Runtime Semantics: Evaluation
MemberExpression : MemberExpression [ Expression ]
1.Let baseReference be the result of evaluating MemberExpression.
2.Let baseValue be GetValue(baseReference).
3.ReturnIfAbrupt(baseValue).
4.Let propertyNameReference be the result of evaluating Expression.
5.Let propertyNameValue be GetValue(propertyNameReference).
6.ReturnIfAbrupt(propertyNameValue).
7.Let bv be RequireObjectCoercible(baseValue).
8.ReturnIfAbrupt(bv).
9.Let propertyKey be ToPropertyKey(propertyNameValue).
10.ReturnIfAbrupt(propertyKey).
11.If the code matched by the syntactic production that is being evaluated is strict mode code, let strict be true, else let strict be false.
12.Return a value of type Reference whose base value is bv and whose referenced name is propertyKey, and whose strict reference flag is strict.
MemberExpression : MemberExpression . IdentifierName
1.Let baseReference be the result of evaluating MemberExpression.
2.Let baseValue be GetValue(baseReference).
3.ReturnIfAbrupt(baseValue).
4.Let bv be RequireObjectCoercible(baseValue).
5.ReturnIfAbrupt(bv).
6.Let propertyNameString be StringValue of IdentifierName
7.If the code matched by the syntactic production that is being evaluated is strict mode code, let strict be true, else let strict be false.
8.Return a value of type Reference whose base value is bv and whose referenced name is propertyNameString, and whose strict reference flag is strict.
どうみてもプロセスの数が違う。ブラケット表記法の方がプロセスが多い。
私の解釈が間違っていなければ、「式の評価」というプロセスがあるかないかという差分がある。
(そもそも、MemberExpression [ Expression ]
とMemberExpression . IdentifierName
という違いがある。前者は式、後者は識別子と明確に別物)
ブラケット表記法では、変数を解決するプロセスをしっかりもっているが、ドット表記法にはそれがない。
ということで、これはべつにバグではなくかっちり定義されている仕様。二つの表記法は好みで使い分けていいものだと思っていたのでいい勉強になった。
関連
今回の現象は以下のサンプルをいじっていたときに発生したものでした。