前: JavaScript中級者になろう 11章を読んだ (ES5) - t_hazawaの日記
経緯
- JS力が必要
- 楽しい
第十二章 HTML5+JavaScript
十二章第一回 classList
https://uhyohyo.net/javascript/12_1.html
html5の策定に伴ってhtml用のdomにも新しくて便利な仕様が加えられました。
- ここに新しいクラスbbbを追加したいならば、 myDiv.className += " bbb"としてやることでclassNameは"aaa bbb"となります。
- classListが無い時はclass削除は正規表現とかでclassNameをなんとかしていた
classListはいくつかのメソッドを持ち、それらによってclass属性をいじることができます
ちなみに、classListは仕様上はDOMTokenListというオブジェクトのインスタンス
- 他の属性も同じものなので、同じメソッド使えるらしい
element.classList.contains("long")
element.classList.add("foo");
element.classList.remove("foo");
- 確かにこれの前は jQuery でやってたね
- toggle
- item とか length もある
- getElementsByClassName() も HTML5 から
- ES5 じゃないんだな…?
十二章第二回 フォーム
https://uhyohyo.net/javascript/12_2.html (5000字...に対して↓の文章は 2400文字ある)
html5になって、フォームが大きく進化しました どのように進化したかについてはぜひ調べてみましょう。
html5のフォームには、妥当性(validity)という概念があります。各コントロール(inputなどの入力欄)にそれぞれ妥当性があります。 フォームは、その入力内容が全て正しいときのみ送信が可能になります。
- 全然知らなかった
- formNode.checkValidity() // true(妥当) / false
reportValidityの場合、妥当でない場合はユーザーにエラーが表示されます。
実は、イベントハンドラの中ではthisはevent.currentTargetと同じもの、つまりそのイベントハンドラが登録されている要素を指すことになっています。
input要素をはじめとするフォームコントロールはformというプロパティを持っており、これはそのコントロールが属するform要素です(なければnull)
reportvalidityにより表示されるエラーは「送信」ボタンを押したときのエラーと同じはずです。 reportValidityはユーザーが慣れ親しんだui(ブラウザに備え付けのui)でエラーを表示することができるため、独自のエラー表示を作るよりも分かりやすいと考えられます。
- reportValidity は、良い感じの見た目で 入力されてませんみたいに出る(特に文言を設定しなくても出る)
- required="" で 必須要素にできるんだね というか required だけかけばいい
- pattern="\d{7}" で、 input type="text" の適切な入力内容を指定できる
- 知らないことだらけ
- onChange はフォーカスはずれないと発動しない onInput は1も時ずつ
- HTMLの属性(pattern)だと、CSSで色を変えたりもできるとのこと
title要素の内容はエラーメッセージとして表示されます。
- 多分属性かな?
コントロールの妥当性に関してもっと詳細な情報を取得するために、input要素等のノードにはvalidityというプロパティがあります。 ValidityStateというオブジェクトのインスタンス
- valueMissing とかいろんなプロパティがあるので、何が悪いのか分かる
typeMismatch type="email",type="url"の場合に、正しい書式でない場合にtrue。
- すごそう
stepMismatch 数値入力コントロールで、strep属性で指定した単位とあわない場合true。
- なんかすごそう
customError 独自エラー(後述)がある場合true。 i
- つよそう
JavaScript側から「妥当である」とか「妥当でない」ということを決めてやることができる コントロールのノードが持つメソッドsetCustomValidity これは、引数を1つ持ち、それはエラーメッセージです。この関数を呼び出すとエラーメッセージが設定され、その要素は妥当ではなくなります。 "" で同じメソッドを呼ぶと、妥当になる
- form に oninput をつけると、中のどこかがinputされたらイベント発生
- 特に reportValidity を呼ばなくても、妥当でなかったら submit でいい感じにエラー文言が出るようだ
たとえばtype属性が"hidden"とか、あるいは"button"とかの場合はユーザーが入力するものではないですから、妥当であるとか妥当でないとかいう概念が無いのです。 (willValidate で false)
- input type="range" というものがある
これは通常、 number 入力型のようなテキスト入力ボックスではなく、スライダーやダイアルコントロールを用いて表現されます。この種のウィジェットは厳密なものではないので、コントロールの正確な値が重要でない限り、通常は使用するべきではありません。
- type="number" というものもある
。。というinput要素では、値として0,10,20,30,40,…を入力できます。このようなinput要素に対してはstepUp及びstepDownというメソッドが使用できます。引数をnとすると、n段階だけ数値を上げ/下げるメソッドです。
日付入力input 要素から11 valueAsDateは、日付をDateオブジェクトで取得できるプロパティです。Dateオブジェクトはこれまで紹介していませんでしたが、昔からある組み込みオブジェクトで、日付を表すものですす。 またvalueAsNumberは、日付を1970年1月1日の0時からのミリ秒数で取得できます。
十二章第三回 History API
https://uhyohyo.net/javascript/12_3.html
- history.back(); とかは昔からあった
- history.forward(); history.go(3);
HTML5での画期的な新機能は何かというと、履歴の追加です。勝手に自分で履歴を追加できるのです。
- ブラクラに使えそう(20年前から来た人)
- 今のURLも変えられる
history.pushState(null, 'テスト', '/javascript/testtest');
- 第2引数は 履歴のタイトルだが、現状のブラウザは対応してない
- 第一引数 state? でオブジェクトと履歴を結び付けられるらしい
- history.replaceState() だと、追加ではなく上書き
追加した履歴を消す方法はありません。
- SPAでは、実際にはページ異動してないのでとても大事
- 履歴移動の検知でページを書き換える
popstateというイベントを用いることで履歴移動を検知することができます
別のページへ行ってしまう場合は発生しません。pushStateなどによって追加された履歴の間で(すなわち、同じページの中で)履歴を移動する場合に発生してくれるイベントです
window.addEventListener('popstate', function(ev){ }, false);
移動後の履歴に関連付けられたstateオブジェクトがイベントオブジェクトのstateプロパティに入っています。つまり今回の場合はev.stateですね
- WAI-ARIAというアクセサビリティを向上する属性がある
- https://developer.mozilla.org/ja/docs/Learn/Accessibility/WAI-ARIA_basics
history.stateを紹介します。これは「現在の履歴」に関連付けられたstateが入っています。
- ハッシュ以降も、locationで操作できる
locationには、urlの各部分をいじることができるプロパティがあります ハッシュの部分だけを変更するには、location.hashを使います。例えば先程の例だと、location.hashには"#abc"と入っています。
- ハッシュを変更してもページは変わらないがURLは変わるので同じページのままURLを返るのに使えるね
ハッシュを変更すると新しい履歴が追加されます 「戻る」ボタンが押されると同じページの中でハッシュだけが違うurlに移動することになります。同じページ内での履歴移動ということはpopstateイベントも発生します。
popstateと似たイベントとして、履歴移動のうちハッシュだけが変わった場合に発生するhashchangeイベントがあります
hashchangeイベントのイベントオブジェクトにはoldURLとnewURLという2つのプロパティがあり
- hashchange イベントを使うと、openTab() みたいなのを呼ぶ箇所を減らせるとのこと
ハッシュを用いて履歴を管理することには、場合によってはもうひとつ大きな利点があります。それは、ページの状態とurlが対応するという点です urlからページの状態を復元可能
- location.assign URL要素を一個ずついれられる
- location.replace という上書き版もあるらしい
- location.reload 更新
十二章第四回 dataset
https://uhyohyo.net/javascript/12_4.html
- みんなだいすき data-honyarara
- element.dataset.foo で読み書きできるよ
なお、属性名にハイフンが含まれていた場合キャメルケースに変換されます。つまり、例えばdata-foo-barという属性はdataset.fooBarというプロパティに対応します。
十二章第五回 File API
https://uhyohyo.net/javascript/12_5.html (10000字。↓は3500字)
自由にファイルを見られてはセキュリティも何もあったものではないので、ユーザーが認めたファイルのみ見ることができるという安全仕様です。 具体的には、javascriptでどのファイルを読み込めるのかをユーザーに選択してもらう必要があります。
- input type="file" で選択されたファイルはJSから読める
input要素から選択済みファイルを取得するには、このinput要素のfilesプロパティを調べます。このfilesプロパティに入っているのが、FileListオブジェクトです もしかしたら複数ファイルが選択されている可能性がある
- length と item でいい感じに取得できる
- filelist[0] でも取れる
- この中に入ってるのが Fileオブジェクト
FileオブジェクトというのはBlobオブジェクトの一種で(つまり、FileはBlobを継承しています) Blobとは何かというとバイナリデータを表すオブジェクト、要するにファイルの中身そのものを表すオブジェクト
- FileReaderオブジェクトで中身を取れる
これの特徴は非同期読み込みであるということです。JavaScriptで非同期といった場合、意味するところはコールバックで結果を得るということです。
- async/await も今の所マだ謎の要素
- fileReader.readAsText()
同期だと プログラム側は関数の処理が終わるまで待つ このようにプログラムが待たされることをブロックするといいます
今回の場合、readAsTextを「終わったら呼んでね!」と言って呼び出し
- コールバックにイベントはニている イベントハンドラが呼ばれる
- コールバックはだいたい1会呼び出し、 イベントは複数回ありうる
1回だけ発生するイベントがコールバックであるという見方もできます。
var reader=new FileReader();
- var reader=new FileReader();
- reader.onload = function(e){
- console.log("読み込みが終わりました");
- };
- reader.readAsText(file);
- プロパティにコールバック関数を入れると、終わったときとかに読んでくれる
readAsTextは、第一引数にBlob(今回はFileなのでさっき読み込んだFile)を渡します。
人によっては、「readAsTextが一瞬で読み込み終わったらonloadプロパティを設定する前に呼ばれてしまうかもしれない」と思うかもしれませんが、その心配をする必要はありません。詳しくは解説しませんが、これは非同期的にコールバックが呼び出される場合、必ず今まさに実行中の一連のプログラムが最後まで実行されてから呼びだされるからです。
今回のプロパティonloadですが、これはどう見てもイベントloadのイベントハンドラに見えます。上で「コールバックは1回だけ呼ばれるイベントである」というようなことを述べましたが、今回はまさにそうなっています
- イベントハンドラっぽい名前になりがちなコールバック関数設定用プロパティ
実は、おなじみのaddEventListenerメソッドを使ってイベントを登録する方法もあります。
コールバック関数の第1引数eはイベントオブジェクト
- コールバック関数にもイベントオブジェクトが引数で渡ってくる
この場合のe.targetはFileReaderオブジェクト(上の例だとreader)が入っています。
- イベントオブジェクトe にはよく e.target がある
読み込みが終わった時点で、読み込み結果はどこに入っているのかというと、FileReaderオブジェクトのresultプロパティに入っています もちろん、e.target.resultでも同じです
- 起こることが順番に書かれないのでわかりにくいコールバック 読み終わったら起こること→読ませる の順
そのファイルをどんな文字コードで読み込むかは第2引数で文字列で指定します。第2引数がない場合はUTF-8になります。他に"UTF-16"とか、"Shift_JIS"などなどが使えます。
- FileReader.readAsArrayBuffer();
- ArrayBufferとは
ArrayBufferの特徴は、実際にメモリ上に連続する領域が確保されているということです。ファイルをBlobとして得られた段階では実はBlobオブジェクトが作られただけで、まだそのファイルをメモリ上に読み込んでいないかもしれません。それを実際にメモリ上に(文字列やArrayBufferの形で)読み込むのがFileReaderなのだということですね。
このArrayBufferというのは先々また出てくるわりと汎用的なオブジェクトなのでここで慣れ親しんでおきましょう
ArrayBufferはバイナリデータなのですが、実はその中身を読むにはさらに別のオブジェクトが必要なのです。面倒ですね。
- すごくめんどくさそうな話になってきた
それが型つき配列(TypedArray)であり、このオブジェクトによってArrayBufferの中身をどのように(1バイトずつとか、4バイトずつとか)読むかが定まります。
よく使いそうなUint8Arrayから紹介しましょう。Uint8とは、「8ビットで符号なし整数」 1バイトずつ読める(8ビット=1バイトなので)
バイナリデータはバイト単位で区切って表示されることが多いように思いますから、これが最も自然なのではないでしょうか
- UInt32Array (4バイト)とかある Float32Array とか TypedArray
var arr=new Uint8Array(buffer);
- newで作ろう TypedArray
- TypedArray でいじると、もとの ArrayBuffer にも反映されるよ まさにビューワー
- ArrayBufferがメモリに確保して、そのメモリのデータをTypedArrayで操作する
- TypedArray.length 長さ
- ArrayBuffer.byteLength
- arr[0]で読み書きもできるぞ
- readAsDataURLの前まで 2021/12/01 21:10 44m
- data: から始まるURLを読める readAsDataURL
DataURLというのは、データがURL内に全部書いてあり、インターネットアクセスをする代わりにURL内のデータを読むことで結果を得られるものです。
data:text/html,%3c%21doctype%20html%3e%3chtml%3e%3cbody%3e%3ch1%3etest%3c/h1%3e%3c/body%3e%3c/html%3e
- ブラウザに入れると htmlとしてレンダリングされる
このように、ブラウザが読み込んだとき、あたかもどこかからその内容を取ってきたかのように振る舞うのがdataurlです。
- readAsDataURL だと劇ながになることあるので URL.createObjectURL が便利
このメソッドにblobを引数として渡すと、オブジェクトurlと言われる特殊なurlが生成されます このurlはブラウザが発行したurlであり、そのページが閉じられるまで有効です。
このurlは、その内容(blob)をブラウザが覚えておき、それを指し示すものです。 データの本体はurlに書いてあるわけではなくブラウザの内部に保管されています。
- Object URL 知らなかったなあ
var objurl = URL.createObjectURL(file); でつくれる
- URL.revokeObjectURLメソッドを呼び出して(第1引数にオブジェクトURL) すると親切
- なんかTypedArray に自分で色々データを入れてつかうことがあるらしい
- エンディアン バイトを並べる順序のこと このせいで 2バイト以上の TypedArray は使い所が限定されるとのこと
十二章第六回 Drag and Drop API
https://uhyohyo.net/javascript/12_6.html
- こっちも7200文字ある
ドラッグできる要素を作る
- ファイルを投げ込む話しじゃなかった
- html5のdraggable属性
- draggable="true"
- 単にその編の要素にかかけてもドラッグできない
a要素やimg要素はもともとドラッグできる
- なるほど、あの挙動がドラッグできるということか
- dragenterイベント
これは、ドラッグしながら他の要素の上にさしかかったときに発生するイベントです。
dragenterイベントのデフォルトアクションは「ドロップ先をbody要素に変更する」 すなわち、dragenterイベントが発生すると、どこにマウスを持って行っても全部body要素にドロップした扱いになってしまいます。
ですから、どこに置いてもbody要素にドロップした扱いになるのはちょっと困ります。そこで、dragenterイベントのデフォルトアクションを無効にする必要があります(これを、イベントをキャンセルするといいます) - - preventDefault - どこでpreventDefault しないといけないか知識も、JavaScript 難しさ (全部につけられれば楽そう)
さらに面倒なことに、実はもうひとつキャンセルすべきイベントがあります。それはdragoverイベントです
- 驚きのめんどくささ
- ドラッグオペレーション四種類 copy link move none
実際にドロップしたときの動作はやはりJavaScriptで記述するので、ドラッグオペレーションには表示以上の意味はそんなにありません。しかし、値が"none"になっている場合はドラッグ&ドロップ自体が無効になってしまうので、何か他の値にセットする必要があります。
その方法ですが、dragenter,dragoverなどのDnD APIに関係するイベントでは、そのイベントオブジェクトはDragEventといい、dataTransferプロパティを持ちます。これをDataTransferオブジェクトといい、ドラッグ&ドロップに関するさまざまな情報を管理します。 - ???
仕組みとしては、dragenter、dragoverなどのDnD APIに関連するイベントの場合、イベントオブジェクトはDragEventと呼ばれ、dataTransferプロパティを持っています。これはDataTransferオブジェクトと呼ばれ、ドラッグ&ドロップに関する様々な情報を管理しています。
//elmは適当なHTMLElementとする
- elm.addEventListener("dragover",function(e){ e.dataTransfer.dropEffect = "copy"; e.preventDefault(); });
- というようにすればいいらしい
dragoverのデフォルトアクションは「ドラッグオペレーションを"none"にする」ということなのです。
- 確かにポインタが変わってる
- dropイベント
イベントオブジェクトのtargetプロパティはドラッグ先の要素です
- event.target は常連
- dataTransfer には ドラッグされたもののデータを格納しておけるとのこと
ドラッグ&ドロップにおけるデータの流れは、「ドラッグされる側がdataTransferにデータを格納する」→「ドロップされたとき(dropイベント)にdataTransferからデータを取り出す」ということになります。
- dragstart イベントでデータを dataTransfer オブジェクトに格納する
- dataTransfer.setData()
第一引数がフォーマットの文字列、第二引数がデータの文字列です。
- mimeの後ろの方は x- から始まる自由文字列使える
この部分には"x-"から始まる自由な文字列を設定できるので、例えば"text/x-mydata"とかです。
- dataTransferには複数のデータ から
- 2021/12/04 23:11
dataTransferには複数のデータを格納することができますが、同じフォーマットのものは複数格納できません。
document.addEventListener("dragstart",function(ev){ ev.dataTransfer.setData("text/plain",ev.target.textContent); });
同じようにaddEventListenerでイベントを付加してもいいのですが、面倒なのでondrop="drop(event)"のようにしてdropという関数を作ってそれに渡すことにしましょう。
- イベント属性では勝手に event という引数をつけてくれるっぽい
function drop(ev){ var data = ev.dataTransfer.getData("text/plain"); }
dropイベントでも最後にpreventdefaultしているのが分かると思います。これは、(2014年7月現在)firefoxがドロップ後にページ遷移してしまうので、それを防ぐ目的があります。
実は、img要素がドラッグされる場合自動的にdatatransferに画像のurl(具体的にはsrc属性の内容)が格納されます。その際のフォーマットは"text/uri-list" 複数のimg要素が同時にドラッグされている場合もあります。そのようなときは、全てのurlが改行で区切られた文字列が入っています
実はa要素の場合も、そのリンク先のurl(href属性)が"text/uri-list"で入っています。
実は、デフォルトでドロップ可能な要素も存在します。それはtextarea要素およびinput要素(type="text"の場合)です 実は、"text/plain"のデータがdatatransferに入った状態でこれらの要素にドロップされると、その内容が入力されます。
。dragstartをキャンセルした(preventDefaultでデフォルトアプションを無効化)した場合はその要素はドラッグできません。
dragleaveイベント 実は、dragleaveイベントのイベントオブジェクトのrelatedTargetプロパティは移動先の要素を示しています。
ドラッグを続けている間、ドラッグされている要素ではdragイベントが発生し、ドラッグ先の要素ではdragoverイベントが発生します。 ブラウザにもよりますが、1秒に数回程度のペースで発生します。先に述べたようにdragoverはキャンセルしなければドロップを受け入れられません。
dragイベントをキャンセルした場合はドラッグが中断されます。ドラッグを強制的に止めたい場合はdragイベントをキャンセルしましょう。
dropeffectが"none"の場合など、ドロップが受け入れられない状況でドラッグが終了した場合はドロップ扱いにならず、dropイベントは発生しません。
dragendイベントがドラッグされている要素で発生します dragstart→drag→drag→……→drag→dragend dragenter→dragover→dragover→……→dragover→dragleave 最終的に要素が話された地点ではドロップ先の要素でdropイベントが発生
dataTransfer内のデータにアクセスできるのはdragstartイベントとdropイベント
十二章第七回 Drag and Drop API 2
https://uhyohyo.net/javascript/12_7.html
- 4900文字
実はページの外から何かがドラッグされてくるという場合もあります
- 僕が最初考えてたDnD
実は、ファイルをjavascriptから読み込む方法はもう1つあります。それが、ブラウザ内へファイルをドラッグ&ドロップしてもらうことです。
- 言われてみればこの二種類しかないね
実は、ページの外からファイルがドラッグ&ドロップされてきた場合も、ドロップ先の要素ではdragenter、dragoverその他のイベントが発生します。 実は、dataTransferはfilesというプロパティを持っており、これはFileListオブジェクトです
p.textContent= file.name;
ev.target.appendChild(p);
ちなみに、紹介しそびれたdataTransferのメソッドで、clearDataというものがあります。これはフォーマット文字列を引数にとって呼び出すことで、そのフォーマットのデータをdataTransferから削除できます。
dataTransferを扱うメソッド(setData, getData, clearData)は実はちょっと古い方法で、最新の仕様では新しい方法が用意されています
- なんか先に廃止サれそう
というか、現在(2019年7月)でもSafariというブラウザが未だに未対応となっています。そのため、以下の内容は今すぐ実用できないかもしれません。
新しい方法では、DataTransfer内のデータはdataTransferが持つitemsプロパティに集約されます DataTransferItemListオブジェクト
この新しい方法の特徴は、addメソッドの第1引数に別の方法で生成したFileオブジェクトを渡すことでdataTransferにファイルを追加することができる
古い方法では、ブラウザウィンドウの外からファイルをドラッグしてきた場合しかDnD APIでファイルを扱う機会はありませんでしたが、新しい方法ではこのようなパターンもあるのです
しかも、dataTransfer.items[0]のように取得したデータは生のデータではありません。ここで得られるのはDataTransferItemオブジェクトというもので、このオブジェクトからさらにデータを引き出すのが少し面倒 文字列の場合はgetAsStringメソッドで、ファイルの場合はgetAsFileで生のデータを取得します
- 面倒そう
getAsStringメソッドの場合はさらに面倒です。このメソッドには引数でコールバック関数を渡す必要があり、その関数にデータが渡されます。
- 普及しなさそう
特に、この方法によって中にどのようなデータ(データのフォーマットや種類)が入っているかを知ることができます。前回dragstartイベントとdropイベント以外ではdataTransfer内のデータを見ることができないと解説しましたが、実は実際のデータを見ることはできないもののデータの種類などの情報は見ることができます。これにより、例えばdragenterイベントで、データの種類によって受け入れるかどうかを決めるというような挙動が可能になります。
dataTransfer.items.addとdataTransfer.setDataにはひとつ違いがあります。既に存在するタイプのデータを追加しようとしたとき、前者はエラーとなるのに対し後者は上書きします。
- ちょっとふみそう
実は、DataTransferには、setDragImageというメソッドがあり、ドラッグ中に表示する画像を設定できます。例えばdragstartでこれを呼び出すと、ドラッグ中のマウス等の画像表示を変更できる dataTransfer.setDragImage( element, x,y); elementというのは、画像として使用する要素です。一般的にはimg要素ですが、他にも任意の要素を指定可能です。x,yは数値で指定します。これは画像のつかむ場所を示す座標です。
ドラッグオペレーション(copy・link・move)
- moveしかしらないような…
- ググッテュ情報でてこなかった
例えばwindowsでは、普通にドラッグすると移動(move)だけどctrlキーを押しながら移動するとコピー(copy)になります。気が利くブラウザならばそういった情報を与えてくれるでしょう。しかし、あまり期待するべきではありません。 uninitializedの場合は何をドラッグしているかによってさらに気を利かせてくれます
- ドラッグに種類があるのね
感想
- Form の 妥当性は全然知らなかった
時間まとめ
- 2021/11/29 - 2021/12/5
- 38 + 48 + 45 + 39 + 34 = 204分
- ブログ 25分(入力システム不具合で6分つまった) 合計 229 分
- 累計1343分 (22.4時間)