- 投稿日:2020-10-20T23:26:43+09:00
TypeScript サーバー 向けの プロジェクトジェネレーター を作ってみた
- 取り敢えずコマンド一発で TS のプロジェクトを作ってくれる便利なやつです
- 個人的に必要最低限と思うプロジェクトを生成するようにしてます
- 元々は MS が配布してる TypeScript-Node-Starter が express 向けの上に重厚にセットアップされていて、何が必要で、何が不要かわからなかったので、もっとスリムにならないかな?と勉強がてら作ってみたという側面があります
- TypeScript って環境構築が地味に面倒で nodemon はしょっちゅうクラッシュするし、 ESLint と Prettier の喧嘩も面倒!なんか一発でプロジェクト作りたいなという思いもありました
- 何もかも初めての経験だったので、良い経験になりました
- ちなみに実装そのものは JS でしてます(理由は後述)
まずは Boilerplate の作成から
まずはプロジェクトの雛形ということで ts-server-boilerplate という Boilerplate を作ってみました
このプロジェクトには nodemon を使用しておらず tsc-watch を使っています
- 理由としては tsc-watch は
tsc
を叩きビルドが完了した後に node を再起動するという振る舞いを容易に実現できたからです
- これを nodemon でやろうとすると酷く大変で諦めました
- ただ tsc-watch を使っていても node inspector プロセスのゾンビが残っている可能性があるため、node inspector のプロセスを殺すためにkill-node-inspectorという、npm module を自作しました
- 内容は inspector で使ってる port を listen してるプロセスを殺してるだけです
しかし Boilerplate を
git clone
して使おうとすると余計なファイルが入っていて毎回消したり編集するのが面倒…なんかこう
ng new my-best-app
みたいに行きませんかね!?というのでプロジェクトジェネレーターの作成に進みますプロジェクトジェネレーターの作成
- 先の kill-node-inspector の実装により Node CLI の実装方法と npm module の公開方法を学べたので、
ng new my-best-app
みたいなことできるじゃん!というわけで作成を決意- という訳で ts-boilerplate-generator-cli として
tsg [project-name]
をコンソールから叩くだけで TypeScript プロジェクト一式を勝手に作ってくれるのを作りました- 内容としては先程の ts-server-boilerplate を
git clone
してきて、いい感じに調整してるだけです(npm install
やgit init
もします)おまけ
npm にはバグがある
- これを作っている過程で Windows 版の npm にバグがあることを知ったのでついでに書いておきます
npm i --prefix foo
とすると Windows では npm が上手く動きません
- 具体的には親フォルダに
npm i
しにいき、package.json が存在しないとエラーを吐いて死にます。まぁこんなオプション私くらいしか使わないでしょうけど…- [BUG] Npm install with --prefix does not work on Windows #1290
JS で実装した理由
- 実は JavaScript で実装してます。TypeScript のプロジェクトを作るスクリプトなのに。
- 理由は単純で TS はビルドに時間がかかって面倒なのに対して、JS にはそれがないので気楽に開発できたという部分があります。
@ts-check
をかけておけば実質的に TS で開発できるというところもあります
- 投稿日:2020-10-20T22:16:24+09:00
【Vue】The template root disallows 'v-for' directives. 解決法
テンプレートルートは、'v-for'ディレクティブを許可しません。
分割したコンポーネントで
v-for
を使ったところ、このようなエラーが、、、The template root disallows ‘v-for’ directives.
(翻訳にかけると「テンプレートルートは、'v-for'ディレクティブを許可しません」と言う意味)なんじゃこりゃ??
コードはこんな感じ
<template> <app-stock v-for="stock in stocks" :key="stock.id"></app-stock> </template> <script> import Stock from './Stock.vue'; export default { data() { return { stocks: [ { id: 1, name: 'BMW', price: 110 }, { id: 2, name: 'Google', price: 210 }, { id: 3, name: 'Amazon', price: 250 }, { id: 4, name: 'Sony', price: 260 }, ] } }, components: { appStock: Stock, } } </script>Componentの中の処理はすべてsingle root elementで囲わなければならない
なんだか難しそうな言葉ですが、要するに「何か別のタグで囲え」という事
<template> <!-- 適当にdivタグで囲った --> <div> <app-stock v-for="stock in stocks" :key="stock.id"></app-stock> </div> </template> <script> import Stock from './Stock.vue'; export default { data() { return { stocks: [ { id: 1, name: 'BMW', price: 110 }, { id: 2, name: 'Google', price: 210 }, { id: 3, name: 'Amazon', price: 250 }, { id: 4, name: 'Sony', price: 260 }, ] } }, components: { appStock: Stock, } } </script>というわけで、
<div>
でv-for
を囲いました。。
- 投稿日:2020-10-20T20:21:33+09:00
【初心者向け】select要素にプレースホルダーを付ける方法と付けられない理由の考察
どうも7noteです。select要素のプレースホルダーについて。
ちょっと変な矛盾しているタイトルですが、ご了承ください。
先日、デザインに合わせてコーディングをしていた時のことなのですが、select要素にplaceholderのような見た目を付ける場面に出会いました。
私もいろいろ調べてみたのですが、厳密にはselect要素にplaceholderを指定できません(指定しても何もなりません)。
- とりあえず無理やりな方法でもいいからselect要素にplaceholderを指定したい方は①から
- select要素にplaceholderを選択できない理由が知りたい方は②から
①select要素でplaceholderっぽく見せる方法
その1:入力後、サンプルは選択肢から消す方法
<select> <option value="" disabled selected style="display:none;">選択してください</option> <option>A</option> <option>B</option> <option>C</option> <option>D</option> </select>こちらは、optionの1つ目をplaceholderっぽくみせておき、選択肢からは非表示にする方法です。
selectedがついているので、ページ読み込み時は「選択してください」の文字がselect内にでますが、"display=none;"
が指定されているため選択肢には表示されません。そのため、placeholderっぽい役割を果たします。この方法の注意点としては、placeholderのように色を薄く出来ない事と、
デフォルトが「全て」という選択肢ではないことに気を付けなければなりません。その2:1つ目だけplaceholderのように色を薄くする方法(javascript)
<select> <option value="placeholder">選択してください</option> <option>A</option> <option>B</option> <option>C</option> <option>D</option> </select> <style> select { color: #ddd; } option { color: #333; } </style> <script> $(function(){ $('select').on('change', function(){ if($(this).val() == "placeholder"){ $(this).css('color','#ddd') } else { $(this).css('color','#333') } }); }); </script>javascript(&jQuery)を使い、特定の選択肢が指定された時のみ色を変更する書き方ができます。
この方法が一番textboxなどのplaceholderに近い書き方になるかなと思います。②なぜselect要素にはplaceholderが存在しないのかの考察
そもそも、デフォルトの属性としてplaceholderが存在しないという事はそのような使い方を推奨していないということだと考えます。
「必ず選択肢のうちのどれかを選択してもらう時に使うもの」という認識を持って使う方が正しいのではないか。
例でエリア検索するためのselect要素を用意した場合、以下のような書き方はよろしくないのではないかという考えです。<!-- よくない例 ---> <select> <option>エリアを選択</option> <!-- ↑これが選択されている場合というのは意味が存在しない。 これが選択されているときに全国を出すのって「エリアを選択」という日本語と矛盾してるのでは??--> <option>東京</option> <option>大阪</option> <option>名古屋</option> ・ ・ ・ </select> <button>検索する</button>もし、初期値の「エリアを選択してください」のまま検索するボタンが押されたとき、おそらく全国の検索結果を出すと思います。
では、選択肢と表示内容を合わせるのであれば本来selectの値は「全国」となっているのが正しいのではないでしょうか。もちろん申込フォームなどでは「選択無し」とする場合もあるかもしれませんし、生年月日の入力では「年を選択」などが入っている方が使いやすいと思います。
ただ、そのような場合以外では、基本的には必ずどれかの選択肢を選べるような作りにすべきと私は考えています。
生年月日なども、デフォルトは「年を選択」などを入れずに、サイトに適した中央値あたりの数字を入れておく方がいいと思いますし、むしろそのように使うものだと思います。賛否両論あるかもしれませんが、事実select要素にはplaceholderの指定ができないので、あながち間違いじゃないのかも。と思っています。
ご意見お考えある方是非コメントください!お待ちしています!
まとめ
select要素でplaceholderっぽいことはできるが、必ずどれかが選択されているという考えで作成する方が正しいのではないか。
おそまつ!
~ Qiitaで毎日投稿中!! ~
【初心者向け】HTML・CSSのちょいテク詰め合わせ
- 投稿日:2020-10-20T20:13:06+09:00
ActiveXObjectを利用してJavaScriptでのファイル出力
このご時世にまだそんなことしているんだと突っ込まれそうですが、
IE8(互換モード)でJavaScriptからファイル出力をする必要がありそこでめちゃくちゃ嵌ってしまったので備忘録を兼ねて記事書きます。ActiveXObjectを利用してファイル出力できますというのは色々な方が記載されていますが、いちお記載すると以下の感じ。
sample1.jsvar fso = new ActiveXObject('Scripting.FileSystemObject'); // fileName,addTextは適宜設定 var output = fso.createTextFile(fileName, true, false); output.Write(addText); output.close();またActiveXを利用するために、
IEの設定でインターネットオプション→セキュリティタグ→レベルのカスタマイズから
「スクリプトを実行しても安全だとマークされていないActiveXコントロールの初期化とスクリプトの実行」のラジオボタンを有効に設定。
ここが有効になっていないと「オートメ―ションサーバはオブジェクトを作成できません」のエラーがでます。
ここまでは良かったのですが、上記の通り記載してもファイル作成ができず、
「ファイルが見つかりません。」のエラーメッセージがでました。
エラーメッセージで検索かけても全くヒットせず、解決策が全く見つからなかったのですが、
同じことをやってファイル出力が上手くいっている人がいたので環境の差分チェックしたら、
保護モードがオンになっていることが不具合の原因でした。
- 投稿日:2020-10-20T19:29:13+09:00
【JavaScript】クラス・コンストラクタを使いたい!
こんにちは、本日もJavaScriptについて学習したことをメモしていきます。
今回はクラス・コンストラクタの基本的な使い方について記述しました。
クラス・コンストラクタって何?と言った方の参考に少しでもなれたら幸いです。また至らない点がありましたら、ご指摘いただけると幸いです。
クラスとは
オブジェクトを作り出す為の「特別な関数」になります。
よく使われるイメージとしては、オブジェクトの「設計図」です。オブジェクトは名前(プロパティ)と値をセットにして格納することができますが、このクラス(設計図)を使えば、共通のプロパティやメソッドを保持したオブジェクトを必要に応じて生成することができます。
また、クラスからオブジェクト生成することを「インスタンス化」といい、生成されたオブジェクトは「インスタンス」と言います。
コンストラクタとは
クラス内で使える、プロパティの設定・初期化を行うメソッドです。
作られたインスタンスオブジェクトは、そのままでは空なので、共通するプロパティを設定し、値を代入する役割を担っています。と、ざっくりとした概要はここまでにして早速コードを見ていきましょう。
クラスの活用場面
例えば、商品情報を持った複数のオブジェクトを作りたいとします。
クラスを使わずに、オブジェクトを用意するとした場合、以下のようなコードになります。const product1 = { name: '商品A', price: 300, display(){ console.log(`商品名:${this.name} 値段:${this.price}円`); } } const product2 = { name: '商品B', price: 400, display(){ console.log(`商品名:${this.name} 値段:${this.price}円`); } } const product3 = { name: '商品C', price: 1000, display(){ console.log(`商品名:${this.name} 値段:${this.price}円`); } }このように各オブジェクトには、共通してname、priceプロパティや商品情報を表示するメソッドが定義されていますが、
以下のような問題があります。・同じ記述が多いので、商品が増えればコードが長くなる。
・displayメソッドは同じ処理だが、オブジェクトごとに定義しているので、無駄なメモリ消費をしている。
・修正箇所が出た場合、変更箇所が多くて大変。これらの問題点を解決してくれるのが、「クラス」です。
この商品の例をクラスを使って実装すると、以下のようになります。//class関数を使って、Productコンストラクタを作る class Product{ constructor(name,price){ //プロパティの初期化 this.name = name; this.price = price; } //各インスタンスから参照されるメソッドを定義 display(){ console.log(`商品名:${this.name} 値段:${this.price}円`); } } //以下、インスタンスオブジェクトの生成 const product1 = new Product("商品A",300); const product2 = new Product("商品B",400); const product3 = new Product("商品C",1000);同じ記述が減り、先ほどよりすっきりしましたね。
では、具体的にどのような構造になっているか、上から順番に見ていきます。クラスの構造
① class
まず、「class」についてです。
class Product{...}class(クラス)とは冒頭に述べたように、インスタンスを作る為の「特別な関数」「設計図」になります。
なのでコード上のclassは、これからclass関数を使ってインスタンス定義を行いますよ!と宣言しているわけです。
そして、クラス名は他の関数と区別できるよう頭文字を大文字にします。
② constructorメソッド
冒頭で述べたコンストラクタの説明は、このメソッドのことを指します。
また、constructorメソッドはクラスが呼び出された際、最初に実行されます。
具体的に見ていきましょう。class Product{ constructor(name,price){ //thisの設定、プロパティの初期化 this.name = name; this.price = price; } }実行された際の処理は以下になります。
①thisの設定
②プロパティの初期化まずthisの設定ですが、
new Product(引数);(インスタンス化)
をすることで作られる空オブジェクト(インスタンス)をthisに設定します。
この処理によって、product1のnameプロパティ、product2のnameプロパティ、といった具合に各インスタンスごとのプロパティを準備することができます。続いて、プロパティの初期化です。
準備した各プロパティに、インスタンス化で受け取った引数を代入していきます。このようにconstructorメソッドは、インスタンス化された際に最初に実行され、インスタンスのプロパティの設定と初期化を行います。
③ メソッド
続いて、メソッドの定義です。
インスタンスには、「dispaly」という商品情報を表示するメソッドを持たせたいので、
constructorメソッドと同じ階層に、dispalyメソッドを定義してあげます。class Product{ constructor(name,price){...} //省略 //各インスタンスが参照するメソッドを定義 display(){ console.log(`商品名:${this.name} 値段:${this.price}円`); } }このように定義することで、生成されたインスタンスはdisplayメソッドを使えるようになります。
ではここで、「class内でメソッドを定義する方法」と、最初の例にあげた「オブジェクトごとにメソッド定義する方法」の違いを見ていきましょう。
簡単に言うと、1つのメソッドを共有して参照するか、各メソッドをそれぞれ参照するかという違いがあります。
コードではイメージしづらいので、下の図を見てみましょう。
オブジェクトごとにdisplayを定義した場合、実行時はそれぞのオブジェクト内のdisplayメソッドを参照して実行することになります。メソッド内の処理は同じですが、メソッドはそれぞれ独立して定義されています。
その為、オブジェクトが増えればdisplayメソッドも増えていくので、
結果的に、無駄なメモリ消費をしていることになります。対して、class内でメソッドを定義した場合は以下になります。
このようにclassで定義して実行すると、全てのインスタンスはclass内で定義したdispalyメソッドを参照します。つまり、メソッドの定義は1つのだけで、インスタンスはそのメソッドへ参照を保持している形になるので、結果としてメモリの節約に繋がります。
④ new演算子
クラスは関数ですが、一般的な関数とは異なり実行する際は、クラス名の前に「new」をつけます。
new ClassName(arguments);このようにしてあげることで、インスタンス化が行われます。
コード上では見えませんが、インスタンス化の処理の流れを簡単に説明すると、
dispalyメソッドの参照先を保持した新しい空オブジェクト(インスタンス)が作られ、constructorメソッドでプロパティを初期化し、最終的にそのオブジェクトが返されます。const product1 = new Product("商品A",300);そして帰ってきたオブジェクトは、今回の場合product1変数に代入されます。
ということで、まとめると「new」はインスタンス化に使用するキーワードで、新しいオブジェクトを生成し、クラス内でメソッドの参照やプロパティを設定し、最終的にオブジェクトを返すことができる、ということですね。
長くなりましたが、クラスの定義から実行までの流れは以上になります。
classのホイスティングについて
classは関数ですが、一般的な関数とは異なり、ホイスティングは発生しません。
なのでclass宣言よりも前に呼び出しをするとエラーになります。const product1 = new Prodcut(); // ReferenceError class Product {}ホイスティングってなんだっけ?という方は、ホイスティングとは?をご覧ください。
まとめ
ここまでお読みいただきありがとうございます。
今回はクラスの基本的な部分について記述しました。
JavaScriptのクラスは他の言語と比べると癖があるようなので、何度も記述して使いこなせるようになりたいと思います。
また至らない点がありましたら、ご指摘いただけると幸いです。参考
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Classes
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Classes/constructor
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/new
https://qiita.com/daisu_yamazaki/items/109e3044645c4fe2925d
https://qiita.com/takeharu/items/809114f943208aaf55b3
https://qiita.com/JPNYKW/items/248f99c94c00c3d1aa27
- 投稿日:2020-10-20T18:58:27+09:00
P5.js 日本語リファレンス(specularMaterial)
このページでは「P5.js 日本語リファレンス」 の specularMaterial関数を説明します。
specularMaterial()
説明文
オブジェクトに鏡面素材を設定します。鏡面素材は光沢のある反射素材です。ambientMaterial と同様に環境光下でオブジェクトが反射する色を定義します。たとえば、
環境光に赤が含まれているとき、鏡面素材に赤が含まれていればオブジェクトは光を反射します。逆に鏡面素材に赤が含まれていなければオブジェクトは光を反射しません。
点光源(pointLight)や指向性ライト(directionalLight)など他のすべてのタイプのライトのとき、鏡面素材は光源の色をビューアに反映します。構文
specularMaterial(gray, [alpha])
specularMaterial(v1, [v2], [v3], [alpha])
specularMaterial(color)
パラメタ
v1
gray:グレー値alpha
Number:不透明度(オプション)RGB または HSB を指定するとき
v1
Number:赤または色相値(Hue)v2
Number:緑または彩度値(Saturation)v3
Number:青または明度値(Brightness)color
Number[] | 文字列 | p5.color:color、color Array または CSS color 文字列例1
function setup() { createCanvas(100, 100, WEBGL); } function draw() { bacground(0); ambientLight(50); pointLight(250, 250, 250, 100, 100, 30); specularMaterial(250); sphere(40); }実行結果
https://editor.p5js.org/bit0101/sketches/OqM18NeZF
著作権
p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.
ライセンス
Creative Commons(CC BY-NC-SA 4.0) に従います。
- 投稿日:2020-10-20T18:58:16+09:00
P5.js 日本語リファレンス(emissiveMaterial)
このページでは「P5.js 日本語リファレンス」 の emissiveMaterial関数を説明します。
emissiveMaterial()
説明文
オブジェクトに使用するマテリアル(素材)の放射色を設定します。放射光があると反射光がなくてもオブジェクトは光っているように見えます。
構文
emissiveMaterial(v1, [v2], [v3], [a])
emissiveMaterial(color)
パラメタ
gray 値を指定するとき
- v1
Number:グレー値RGB または HSB を指定するとき
v1
Number:赤または色相値(Hue)v2
Number:緑または彩度値(Saturation)v3
Number:青または明度値(Brightness)a
Number:不透明度(オプション)color
Number[] | 文字列 | p5.color:color、color Array または CSS color 文字列例1
// 環境光などライトは無くても光っています。 function setup() { createCanvas(300, 300, WEBGL); } function draw() { background(0); noStroke() ; // ambientLight(100, 0, 0); translate(-30, 0); emissiveMaterial(255, 0, 0); box(50); translate(60, 0); emissiveMaterial(0, 255, 0); box(50); }実行結果
https://editor.p5js.org/bit0101/sketches/smoxhzTr4
著作権
p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.
ライセンス
Creative Commons(CC BY-NC-SA 4.0) に従います。
- 投稿日:2020-10-20T18:58:04+09:00
P5.js 日本語リファレンス(ambientMaterial)
このページでは「P5.js 日本語リファレンス」 の ambientMaterial関数を説明します。
ambientMaterial()
説明文
オブジェクトに使用するマテリアル(素材)の反射色を定義します。環境光(ambientLight)によって反射状況が変わります。たとえば、環境光に赤が含まれているとき、マテリアルに赤が含まれていればオブジェクトは光を反射します。逆にマテリアルに赤が含まれていなければオブジェクトは光を反射しません。
構文
ambientMaterial(v1, [v2], [v3])
ambientMaterial(color)
パラメタ
gray 値を指定するとき
- v1
Number:グレー値RGB または HSB を指定するとき
v1
Number:赤または色相値(Hue)v2
Number:緑または彩度値(Saturation)v3
Number:青または明度値(Brightness)color
Number[] | 文字列 | p5.color:color、color Array または CSS color 文字列例1
// 環境光(ambientLight)は緑です。左側の box は緑を含ま // ないので光を反射しませんが、右の box は緑を含むので // 光を反射します。 function setup() { createCanvas(300, 300, WEBGL); // colorMode(RGB); } function draw() { background(100); ambientLight(0, 255, 0); // 緑の環境光 translate(-30, 0); ambientMaterial(255, 0, 255); //緑色を含まない box(50, 50, 50); // 左側のbox translate(60, 0); ambientMaterial(255, 200, 255); //緑色を含む box(50, 50, 50); // 右側のbox }実行結果
https://editor.p5js.org/bit0101/sketches/b195ihKaL
著作権
p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.
ライセンス
Creative Commons(CC BY-NC-SA 4.0) に従います。
- 投稿日:2020-10-20T18:57:50+09:00
P5.js 日本語リファレンス(textureMode)
このページでは「P5.js 日本語リファレンス」 の textureMode関数を説明します。
textureMode()
説明文
テクスチャマッピングの座標空間を設定します。デフォルトのモードは IMAGE で画像の実際の座標を参照します。 NORMAL は 0 〜 1 の範囲の値の正規化された空間を指します。この関数は WEBGLモードでのみ機能します。
IMAGE では画像が 100 x 200 ピクセルの場合、画像を四角形の全体のサイズにマッピングするにはポイント (0,0)(100, 0)(100,200)(0,200) になります。 NORMAL での同じマッピングは(0,0)(1,0)(1,1)(0,1) になります。構文
textureMode(mode)
パラメタ
- mode
定数:IMAGE または NORMAL例1
let img; function preload() { //画像 img = loadImage('image.png'); } function setup() { createCanvas(400, 400, WEBGL); } function draw() { // 画像空間を(0, 0)-(1, 1) で指定する texture(img); textureMode(NORMAL); beginShape() ; vertex(-150, -200, 0, 0); vertex( 150, -200, 1, 0); vertex( 150, 0, 1, 1); vertex(-150, 0, 0, 1); endShape() ; // 画像空間を(0, 0)-(300, 200) で指定する texture(img); textureMode(IMAGE); beginShape() ; vertex(-150, 0, 0, 0); vertex( 150, 0, 300, 0); vertex( 150, 200, 300, 200); vertex(-150, 200, 0, 200); endShape() ; }実行結果
https://editor.p5js.org/bit0101/sketches/S3csBXQtt
著作権
p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.
ライセンス
Creative Commons(CC BY-NC-SA 4.0) に従います。
- 投稿日:2020-10-20T17:15:41+09:00
Resource Hints を知る
Resource Hintsとは?
まずResource Hintsってなんですか。
Resource Hints | W3C を見ると、This specification defines the dns-prefetch, preconnect, prefetch, and prerender relationships of the HTML Link Element (). These primitives enable the developer, and the server generating or delivering the resources, to assist the user agent in the decision process of which origins it should connect to, and which resources it should fetch and preprocess to improve page performance.
(DeepLの日本語訳)
この仕様では、HTMLリンク要素(<link>)のdns-prefetch、preconnect、prefetch、prinerの関係を定義します。これらのプリミティブは、開発者と、リソースを生成または配信するサーバが、どのオリジンに接続すべきか、どのリソースをフェッチしてページのパフォーマンスを向上させるために前処理すべきかの決定プロセスにおいて、ユーザエージェントを支援することを可能にします。つまりは、
Webページでこれから必要になるリソースを、ブラウザに事前に教えてあげる
ものです。
ブラウザはそのヒントから、次に発生するリクエストに対して準備をします。もっと簡単に言うと、これから使うリソースを事前に読み込むものです。
ブラウザのリソース取得までのフロー
Resource Hintsを使う前に、リソースをリクエストしてから評価するまでの一連の流れを知っておく必要があります。
ブラウザがリソースを取得する際、以下のようなフローで取得しています。
- DNSルックアップ
- URLからホスト名を解決し、IPアドレスを取得する
- TCP接続
- 指定されたURLに対して、コネクションを張る
- リソースの取得
- リソースのダウンロードを行う
- リソースの評価
- HTMLやJavaScript, CSSのパースを行う
この4つのプロセスそれぞれに対して、Resource Hintsで事前に準備をすることができます。
Resource Hintsを使う
Resource Hintsは以下のような
link
タグを仕込んでおくことで、使うことができます。DNS Prefetch
DNS Prefetch
は、「ブラウザのリソース取得までのフロー」のDNSルックアップまで事前に行います。dns-prefetch<link rel="dns-prefetch" href="https://example.com">Preconnect
Preconnect
は、「ブラウザのリソース取得までのフロー」のTCP接続まで事前に行います。preconnect<link rel="preconnect" href="https://example.com">Prefetch
Prefetch
は、「ブラウザのリソース取得までのフロー」のリソースの取得まで事前に行います。prefetch<link rel="prefetch" href="main.js" as="script"> <link rel="prefetch" href="image.png" as="image">
as
属性は必須ではないですが、指定しておくことで、ブラウザがリソースの解析をするのを手助けすることができます。
他に、media
,style
,font
,document
などあります。Prerender
Prerender
は、「ブラウザのリソース取得までのフロー」のリソースの評価まで事前に行います。Prerender<link rel="prerender" href="https://example.com/next_page.html">バックグラウンドで、指定したページのレンダリングを行うため、ページ遷移後のレンダリングがとても速くなります。
ただし、指定できるページは1つのみであるため、遷移確率の低いページを指定してしまうと、ユーザーに無駄なレンダリングをさせてしまう恐れがあります。実際に使うならこんなふうに使うのかな?
実際に使う場合、多くのユーザーが遷移するだろうというリソースがある場合以外は、動的に事前読み込みをする対象を決めることになると思います。
Resource Hintsの
link
タグは、動的に追加や削除を行っても良いため、動的にlinkを追加するサンプル.jsconst hint = document.createElement("link"); hint.rel = "prefetch"; hint.as = "document"; hint.href = "/article/part3.html"; document.head.appendChild(hint);のように書くことができます。
これをクリックやホバー、スクロールなどのイベントで実行することで、適切なリソースの事前読み込みができるのではないか?と思っています。最後に
Resource Hintsはサイトの改善にとても強力なものだと思います。
ただ、「なんでもかんでも事前読み込みしちゃえ」のように使うと、ユーザーのネットワーク帯域やCPUなどを占領してしまう恐れがあるので、適切に使っていく必要があるものです。
また、SafariやFirefoxでは一部使うことでできないため、https://caniuse.com/?search=resource%20hints を確認しておくべきです。もし、記事に間違いなどありましたら、教えていただけると勉強になります。
リンク
- 投稿日:2020-10-20T17:12:04+09:00
Qiita開発チームがReactの開発で default export を使わなくなった理由
はじめに
こんにちは、Qiita開発チームの@ohakutsuです。
現在、QiitaのフロントエンドはReact + Atomic Designで実装されています。
Atomic Designの特徴として、再利用可能な単位で分割されたコンポーネントをつくる必要があります。
しかし、分割されたコンポーネントの数は日々大きくなっていってしまい、コンポーネントの管理が難しくなってしまいます。
そこで、Qiita開発チームでは、default export
を使い実装をしていたのをやめ、named export
を使い実装を行うようになりました。
今回は、Qiita開発チームがなぜdefault export
を使わなくなったのかについてお話します。なぜ default export を使わなくなったのか
Qiita開発チームが
default export
を使わなくなった理由は、default exportだとimportする側で自由に名前を決められてしまうためです。default exportを使った場合
.jsxexport default () => { ... }index.jsximport Button from './Button' // ↑↓自由に名前をつけることができてしまう import SubmitButton from './SubmitButton' ...このように、default exportにしてしまうと、import側で自由に名前をつけることができてしまい、コンポーネント名が統一されなくなってしまいます。
また、例に出した
SubmitButton
を抽象化し、Button
にした場合、ファイルパスはVSCodeなどで自動的に修正をしてくれますが、SubmitButton
→Button
にはなりません。ここで、実際の実装と定義名が異なってしまいます。また、それだけではなく、異なっていたとしても動いてしまうため、発見が難しくなるといったこともあります。named exportを使った場合
SubmitButton.jsxexport const SubmitButton = () => { ... }index.jsximport { SubmitButton } from './SubmitButton' ...named exportの場合、importする側ではexport側で定義された名前をそのまま使うことになります。
また、以下のようにコンポーネント名を変更した場合、Button.jsxexport const Button = () => { ... }
Submitbutton
をインポートしている側でエラーが起きます。index.jsximport { SubmitButton } from './Button' // ←エラーになる ...エラーが起きるため、インポート側でのコンポーネント名の変更忘れに気づくことができます。
冒頭でもお話しましたが、Atomic Designでは、部品ごとにパーツを作り、さらに組み立てたコンポーネントをつくっていくため、どうしてもコンポーネントの数が多くなってしまいます。そこで
default export
を使うと、コードを書く人によってコンポーネント名が変わる可能性があり、コンポーネントの管理が難しくなるといった問題が考えられます。
そのため、named export
でインポートするコンポーネント名に制限をし、コンポーネントの管理をしやすくしたというのも1つの理由です。named exportにしたときの問題点
React.lazy
はdefault exportでないと使えないCode-Splitting – React によると、
React.lazy
はnamed exportには対応しておらず、ManyComponents.jsexport const MyComponent = /* ... */; export const MyUnusedComponent = /* ... */;MyComponent.jsexport { MyComponent as default } from "./ManyComponents.js";MyApp.jsimport React, { lazy } from 'react'; const MyComponent = lazy(() => import("./MyComponent.js"));のように、
named export
->default export
にするための中間コンポーネントが必要になります。
React.lazy
を使う必要のあるコンポーネントの場合は、例外としてディレクトリを分けるなどして、default export
で実装するのもアリだと思います。さいごに
Qiita開発チームでAtomic Designを行う際に、
default export
を使わなくなった理由について書いてきました。
僕はまだQiita開発チームに来て4ヶ月程度しかたっておらず、まだまだ力不足ですが、これからもQiitaをよりよいものにしていくために、開発・改善を行っていきます。また、Qiitaの質問に
default export
/named export
についての意見交換を投稿しているので、ぜひ、ご意見をお聞かせください。
[意見交換] React開発で使うならnamed export?default export? - Qiita
- 投稿日:2020-10-20T15:15:43+09:00
[Swiper]スライド数が足りない場合はスライダーを無効にする
初めに
サイトによってはスライダー表示のコンテンツが運用していくなかで増えたり減ったりすることも考慮する必要がでてくると思います。
そんな時にSwiperを使用して、表示数よりも記事数が多い場合はスライダー表示をして、そうでない場合はスライダーを表示しない実装を紹介します。バージョン
ライブラリ バージョン Swiper 4.5.0 jQuery 3.3.1 Swiperとは
SwiperはjQueryに依存しないスライダー実装ライブラリで、レスポンシブ対応、スマホ対応、画像遅延読み込み、複数行のスライドレイアウト、などなどリッチな機能のスライダーを簡単に実装することができます。
公式サイト:https://swiperjs.com/手順
HTML、CSSの実装
公式のドキュメントを参考に、必要な要素を記述します。
今回はスライドのコンテンツの他に、ページネーションとスライドを動かすためのボタンを記述しています。<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="lib/swiper/css/swiper.min.css"> <link rel="stylesheet" href="css/common.css"> <script src="lib/jquery/jquery-3.3.1.min.js"></script> <script src="lib/swiper/js/swiper.min.js"></script> <script src="js/script.js"></script> <style> .slider.swiper-container { margin: 0 auto; width: 980px; } .slider .swiper-slide { display: flex; justify-content: center; align-items: center; height: 200px; background-color: #FFF1EA; color: #555; } </style> </head> <body> <!-- Slider main container --> <div class="swiper-container slider"> <!-- Additional required wrapper --> <div class="swiper-wrapper"> <!-- Slides --> <div class="swiper-slide">Slide 1</div> <div class="swiper-slide">Slide 2</div> <div class="swiper-slide">Slide 3</div> <!-- <div class="swiper-slide">Slide 4</div> --> </div> <!-- If we need pagination --> <div class="swiper-pagination"></div> <!-- If we need navigation buttons --> <div class="swiper-button-prev"></div> <div class="swiper-button-next"></div> </div> </body> </html>JSの実装
jsではswiperの設定を記述します。
$(window).on('load', function() { slider(); }); function slider() { var sliderSelector = '.slider'; // スライダーのコンテナーとなる要素のセレクタ var slidesPerView = 3; // スライド表示数 var $slide = $(sliderSelector + ' .swiper-slide'); var slidable = $slide.length > slidesPerView; var containerModifierClass = slidable ? 'is-active swiper-container-' : 'swiper-container-'; // スライドがアクティブな場合は、コンテナー要素にクラス名「is-active」を設定 // スライド数が足りない場合はloop: false, watchOverflow: true でスライド無効に設定 slider = new Swiper('.slider.swiper-container', { containerModifierClass: containerModifierClass, speed: 400, spaceBetween: 40, slidesPerView: slidesPerView, loop: slidable, pagination: { el: '.slider .swiper-pagination', type: 'bullets', clickable: true, }, navigation: { nextEl: '.slider .swiper-button-next', prevEl: '.slider .swiper-button-prev', }, watchOverflow: true, }) }オプションを設定
swiperには
watchOverflow
というオプションが用意されていて、
有効にするとスワイプが無効になり、スライドするスライドが足りない場合はナビゲーションボタンが非表示になるというオプションです。本来ならばこのオプションを設定すればスライド数に応じたスライダーの実行が可能になるのですが、
loop
オプションと併用することによりこのオプションが無効になってします。
なのでスライダーをループさせたい場合は、スライド数が足りない時はloop
オプションにfalse
を設定して、スライド数が表示数より多い時はtrue
を設定するようにします。
containerModifierClass
はswiperが実行されたときにコンテナー要素に追記されるクラス名を変更するオプションでデフォルトでは'swiper-container-'
が設定されています。
実際に付与されるクラス名はswiper-container-initialized swiper-container-horizontal
のようになります。
swiperが用意しているcssはこのデフォルトのクラス名に対してデフォルトのスタイルを適用しているので、基本的に変更する必要はないのですが、
今回はスライダーの有効・無効を判断してスライダーの子要素のスタイルを変えたい場合を考慮して、スライダーが有効な場合は'is-active swiper-container-'
を設定しています。これにより、下記のようにスライダーがアクティブの場合はスタイルを適用できるようになります。
.slider.is-active .swiper-slide { ... }終わりに
説明は以上になります。間違えていることや、こうしたほうがいい等ありましたらコメントお願いします。
- 投稿日:2020-10-20T14:20:09+09:00
input要素に対してダイヤル/リングの回転操作で数値入力するUI
前回の針を直接回して時刻入力するタイムピッカーのつまんで回転させる動作を流用して作成した、数値を扱えるタイプのinput要素に対して回転動作で入力するためのUIです。
操作感覚は映像編集関連入力機器のジョグシャトルをイメージしています。実物のジョグシャトル(ジョグダイヤル及びシャトルリング)は主に映像編集関連機器でよく使われる、編集ポイントに移動するための送り戻しを自在に行うための入力装置です。
設定方法
script
タグでjogshuttle.js
を読み込み、適用させたいinputタグにdata-jogshuttle
属性を追記します。基本設定
<script src='jogshuttle.js'></script> <input type='text' data-jogshuttle>設定項目
inputタグ自体の属性
適用させるinputタグに
min
、max
、step
属性があれば、UI側でもその値を使用します。data-jogshuttle属性で設定できる項目
data-jogshuttle
には、以下の項目が指定できます。
size UIのサイズ(px) デフォルトは200px
jog-color ジョグダイヤルの枠色
jog-background ジョグダイヤルの背景色
shuttle-color シャトルリングの枠色
shuttle-background シャトルリングの背景色
jog-ratio UI全体に対するジョグダイヤルのサイズ比(0~1) デフォルト 0.7
0にすると実質シャトルリングのみ、1にするとジョグダイヤルのみとして扱うことができます。
oninput 適用元要素にoninputイベントが存在する場合、UIの数値が変化するたびに適用元要素のoninput()
を発火させます。
onchange 適用元要素にonchangeイベントが存在する場合、UIの数値を変化させ離したタイミングで適用元要素のonchange()
を発火させます。
対象要素にonchange
やoninput
が設定されていてもその要素のvalueをJavaScriptで変更しただけでは発火しないため、元要素に対してonchange()
やoninput()
メソッドを実行することで発火させています。スクリプト
jogshuttle.js"use strict"; { const jsParam = { styles: {}, ids: {}, attr: {}, displayFlg: 0, }, d = {}, jsHandler = (function() { const events = {}; let key = 1; return { add: function(target, type, listener, capture) { target.addEventListener(type, listener, capture); events[key] = { target: target, type: type, listener: listener, capture: capture }; return key++; }, remove: function(key) { if(key in events) { const e = events[key]; e.target.removeEventListener(e.type, e.listener, e.capture); delete(events[key]); } } }; }()); function jsSetStyle(size, styles) { if(size === undefined) size = 200; if(styles === undefined) styles = {}; const jogRatio = styles.jogRatio !== undefined ? styles.jogRatio : 0.7; // 外枠 let ds = d.jogshuttle.style; ds.width = size + 'px'; ds.height = size + 'px'; ds.position = 'absolute'; ds.left = '0px'; ds.top = '0px'; ds.overflow = 'hidden'; // shuttle ring ds = d.shuttle.style; ds.position = 'absolute'; ds.width = size + 'px'; ds.height = size + 'px'; ds.left = '0px'; ds.top = '0px'; ds.border = '1px solid ' + (jsParam.attr['shuttle-color'] !== undefined ? jsParam.attr['shuttle-color'] : '#888'); ds.background = (jsParam.attr['shuttle-background'] !== undefined ? jsParam.attr['shuttle-background'] : '#ddd'); ds.cursor = 'pointer'; ds.borderRadius = '50%'; ds.transform = 'rotate(180deg)'; ds.boxSizing = 'border-box'; ds = d.shuttle.querySelector('.jsRefPosition').style; ds.position = 'absolute'; ds.width = (size * 0.01) + 'px'; ds.height = (size * 0.25) + 'px'; ds.left = (size / 2 - size * 0.005 / 2) + 'px'; ds.bottom = '0px'; ds.background = (jsParam.attr['shuttle-color'] !== undefined ? jsParam.attr['shuttle-color'] : '#888'); ds.boxSizing = 'border-box'; // jog dial ds = d.jog.style; ds.position = 'absolute'; ds.display = (jogRatio === 0) ? 'none' : 'block'; ds.width = (size * jogRatio) + 'px'; ds.height = (size * jogRatio) + 'px'; ds.left = (size / 2 - size * jogRatio / 2) + 'px'; ds.top = (size / 2 - size * jogRatio / 2) + 'px'; ds.border = '1px solid ' + (jsParam.attr['jog-color'] !== undefined ? jsParam.attr['jog-color'] : '#888'); ds.background = (jsParam.attr['jog-background'] !== undefined ? jsParam.attr['jog-background'] : '#ddd'); ds.cursor = 'pointer'; ds.borderRadius = '50%'; ds.transform = 'rotate(0deg)'; ds.boxSizing = 'border-box'; ds = d.jog.querySelector('.jsRefPosition').style; ds.position = 'absolute'; ds.width = '20%'; ds.height = '20%'; ds.left = '40%'; ds.top = '10%'; ds.border = '1px solid ' + (jsParam.attr['jog-color'] !== undefined ? jsParam.attr['jog-color'] : '#888'); ds.borderRadius = '50%'; ds.boxSizing = 'border-box'; // フォーカス制御用ダミー ds = d.dummy.style; ds.position = 'absolute'; ds.top = '-100px'; if(jsParam.touchFlg) d.dummy.type = 'checkbox'; } window.addEventListener('load', function() { const html = "<div id='jogshuttle'>" + " <input type='text' id='jsDummy'>" + " <div id='jsShuttle'>"+ " <div class='jsRefPosition'></div>"+ " </div>" + " <div id='jsJog'>" + " <div class='jsRefPosition'></div>"+ " </div>" + "</div>"; document.querySelector('body').innerHTML += html; d.jogshuttle = document.querySelector('#jogshuttle'); d.dummy = document.querySelector('#jogshuttle #jsDummy'); d.shuttle = document.querySelector('#jogshuttle #jsShuttle'); d.jog = document.querySelector('#jogshuttle #jsJog'); d.jogshuttle.style.display = 'none'; jsParam.touchFlg = window.ontouchstart !== undefined ? true : false; jsSetStyle(); let r, r_, x, y; let shuttleFlg = false, jogFlg = false; let start; window.addEventListener(jsParam.touchFlg ? 'touchstart' : 'mousedown', function(e) { jsParam.clickStart = true; }); // shuttle クリック d.shuttle.addEventListener(jsParam.touchFlg ? 'touchstart' : 'mousedown', function(e) { if(!shuttleFlg) { shuttleFlg = true; jsParam.shuttleValue = 0; jsStartSendShuttle(); const st = d.shuttle.style.transform; r_ = r = (parseInt(st.replace(/^[^\d-]+/g, '')) + 360 ) % 360; const js = d.jogshuttle; const ex = jsParam.touchFlg ? e.touches[0].clientX : e.clientX; const ey = jsParam.touchFlg ? e.touches[0].clientY : e.clientY; x = ex - (js.offsetLeft - window.pageXOffset) - (js.clientWidth / 2); y = ey - (js.offsetTop - window.pageYOffset) - (js.clientHeight / 2); const at = r_ = Math.atan2(y, x); start = ((at * (360 / (Math.PI * 2))) + 270) % 360; if(jsParam.touchFlg) jsAddHandler(); } }); // jog クリック d.jog.addEventListener(jsParam.touchFlg ? 'touchstart' : 'mousedown', function(e) { if(!jogFlg) { jogFlg = true; const st = d.jog.style.transform; r_ = r = (parseInt(st.replace(/^[^\d-]+/g, '')) + 360 ) % 360; const js = d.jogshuttle; const ex = jsParam.touchFlg ? e.touches[0].clientX : e.clientX; const ey = jsParam.touchFlg ? e.touches[0].clientY : e.clientY; x = ex - (js.offsetLeft - window.pageXOffset) - (js.clientWidth / 2); y = ey - (js.offsetTop - window.pageYOffset) - (js.clientHeight / 2); const at = Math.atan2(y, x); start = ((at * (360 / (Math.PI * 2))) + 450 - r) % 360; if(jsParam.touchFlg) jsAddHandler(); jsParam.ids['inertia'] = 0; jsParam.attr.diff = 0; } }); // ドラッグ window.addEventListener(jsParam.touchFlg ? 'touchmove' : 'mousemove', function(e) { const js = d.jogshuttle; jsParam.clickStart = false; if(shuttleFlg || jogFlg) { const ex = jsParam.touchFlg ? e.touches[0].clientX : e.clientX; const ey = jsParam.touchFlg ? e.touches[0].clientY : e.clientY; x = ex - (js.offsetLeft - window.pageXOffset) - (js.clientWidth / 2); y = ey - (js.offsetTop - window.pageYOffset) - (js.clientHeight / 2); const at = r = Math.atan2(y, x); let f = (r - r_) > 0 ? 1 : -1; if(r - r_ > Math.PI) r_ += Math.PI * 2; else if(r - r_ < -Math.PI) r_ -= Math.PI * 2; if(shuttleFlg) { const diff = Math.abs(r - r_) * f; const dd = diff * (360 / (Math.PI * 2)); let n = (parseFloat(d.shuttle.style.transform.replace(/^[^\d-]+/g, '')) + dd); n = (n > 330) ? 330 : (n < 30) ? 30 : n; d.shuttle.style.transform = 'rotate(' + n + 'deg)'; jsParam.shuttleValue = (n - 180) / 150; } else { const k = ((at * (360 / (Math.PI * 2))) + 810 - start) % 360; d.jog.style.transform = 'rotate(' + k + 'deg)'; jsParam.attr.diff = k - jsParam.k_; if(jsParam.attr.diff > 180) jsParam.attr.diff -= 360; else if(jsParam.attr.diff < -180) jsParam.attr.diff += 360; jsParam.valueOrigin = Number(jsParam.valueOrigin) + Number(jsParam.attr.diff / (360 / 20)) * jsParam.attr.step; jsRangeCheck(); jsParam.k_ = k; } r_ = r; } }); // リリース window.addEventListener(jsParam.touchFlg ? 'touchend' : 'mouseup', function(e) { if(shuttleFlg) { shuttleFlg = false; jsParam.shuttleValue = 0; d.shuttle.style.transform = 'rotate(180deg)'; jsStopSendShuttle(); } if(jogFlg) { jogFlg = false; if(Math.abs(jsParam.attr.diff) > 10) { jsParam.attr.diff *= 10; jsParam.ids['inertia'] = 1; jsJogInertia(); } } if(d.jogshuttle.style.display !== 'none') { d.dummy.focus(); if(jsParam.attr.onchange !== undefined && jsParam.o.onchange && jsParam.attr.onchange) jsParam.o.onchange(); } if(jsParam.touchFlg) jsRemoveHandler(); if(d.jogshuttle.style.display !== 'none') { setTimeout(function() { if(jsParam.clickStart === true) { d.jogshuttle.style.display = 'none'; jsRemoveHandler(); jsParam.attr.diff = 0; } }, 50); } }); // マウスホイール window.addEventListener('wheel', function(e) { if(!jsParam.ids['wheel']) return; const delta = e.wheelDelta !== undefined ? e.deltaY / 40 : e.deltaY; jsParam.valueOrigin = jsParam.valueOrigin + (delta / 4) * jsParam.attr.step; jsRangeCheck(); const n = parseFloat(d.jog.style.transform.replace(/^[^\d-]+/g, '')) + delta * 4; d.jog.style.transform = 'rotate(' + (360 + n) % 360 + 'deg)'; }); // dataset(data-jogshuttle)指定のinput要素にclickイベントを追加 const inputElements = document.querySelectorAll('input'); for(let i = 0; i < inputElements.length; i++) { const dataset = inputElements[i].dataset.jogshuttle; if(dataset === undefined) continue; const params = dataset.split(';'); let size = jsParam.styles.size; let jogRatio = 0.7; for(let l = 0; l < params.length; l++) { const param = params[l].split(':'); const key = param[0].trim().toLowerCase(); const value = param[1]; if(key === 'size') { size = Math.abs(parseInt(value)); } else if(key === 'jog-ratio') { jogRatio = Math.abs(parseFloat(value)); jogRatio = jogRatio > 1 ? 1 : jogRatio < 0 ? 0 : jogRatio; } } inputElements[i].addEventListener('click', function() { jsJogshuttle(this, size, jogRatio); }); } }); function jsAddHandler() { jsParam.ids['touchmove'] = jsHandler.add(window, 'touchmove', function(e) { e.preventDefault(); }, { passive: false }); jsParam.ids['wheel'] = jsHandler.add(window, 'wheel', function(e) { e.preventDefault(); }, { passive: false }); } function jsRemoveHandler() { if(jsParam.ids['touchmove'] > 0) { jsHandler.remove(jsParam.ids['touchmove']); jsParam.ids['touchmove'] = 0; } if(jsParam.ids['wheel'] > 0) { jsHandler.remove(jsParam.ids['wheel']); jsParam.ids['wheel'] = 0; } } function jsStartSendShuttle() { if(jsParam.ids['send'] === undefined || jsParam.ids['send'] === 0) { jsParam.ids['send'] = 1; jsSendShuttleValue(); } } function jsStopSendShuttle() { if(jsParam.ids['send'] !== undefined && jsParam.ids['send'] !== 0) jsParam.ids['send'] = 0; } function jsSendShuttleValue() { jsParam.valueOrigin = Number(jsParam.valueOrigin) + Math.pow(jsParam.shuttleValue * 9, 3) * jsParam.attr.step; jsRangeCheck(); if(jsParam.ids['send']) requestAnimationFrame(jsSendShuttleValue); } function jsRangeCheck() { if(jsParam.attr.min !== undefined) { if(jsParam.valueOrigin < jsParam.attr.min) jsParam.valueOrigin = jsParam.attr.min; } const rs = 1 / jsParam.attr.step; jsParam.o.value = Math.floor(jsParam.valueOrigin * rs) / rs; if(jsParam.attr.min !== undefined) jsParam.o.value -= -(jsParam.attr.min % jsParam.attr.step); if(jsParam.attr.max !== undefined) { if(jsParam.o.value > jsParam.attr.max) jsParam.o.value = jsParam.attr.max; if(jsParam.valueOrigin > jsParam.attr.max + jsParam.attr.step) jsParam.valueOrigin = jsParam.attr.max + jsParam.attr.step; } if(jsParam.attr.stepDecDigit) { jsParam.o.value = Number(jsParam.o.value).toFixed(jsParam.attr.stepDecDigit) .replace(/0*$/, '').replace(/\.$/, ''); } else { jsParam.o.value = Number(jsParam.o.value).toFixed(0); } if(jsParam.attr.min !== undefined && jsParam.o.value < jsParam.attr.min) jsParam.o.value = jsParam.attr.min; if(jsParam.attr.oninput !== undefined && jsParam.o.oninput && jsParam.attr.oninput) jsParam.o.oninput(); } function jsJogInertia() { if(!jsParam.ids['inertia']) return; requestAnimationFrame(jsJogInertia); console.log(jsParam.attr.diff); jsParam.attr.diff /= 1.1 if(Math.abs(jsParam.attr.diff) < 0.1) { jsParam.ids['inertia'] = 0; return; } jsParam.valueOrigin = Number(jsParam.valueOrigin) + Number(jsParam.attr.diff / (360 / 20)) * jsParam.attr.step; jsRangeCheck(); const n = parseFloat(d.jog.style.transform.replace(/^[^\d-]+/g, '')) + jsParam.attr.diff; d.jog.style.transform = 'rotate(' + (360 + n) % 360 + 'deg)'; } function jsJogshuttle(e, size, jogRatio) { jsParam.clickStart = false; const js = d.jogshuttle; if(js.style.display !== 'none' && jsParam.o === e) { jsParam.displayFlg ^= 1; if(!jsParam.displayFlg) { e.focus(); js.style.display = 'none'; jsRemoveHandler(); jsParam.attr.diff = 0; return; } } jsParam.displayFlg = 1; jsParam.attr = {}; const attr = e.attributes; for(let i = 0; i < attr.length; i++) { if(['min', 'max', 'step'].indexOf(attr[i].name) >= 0) { jsParam.attr[attr[i].name] = parseFloat(attr[i].value); } } const dataset = e.dataset.jogshuttle; if(dataset !== undefined) { const params = dataset.split(';'); for(let l = 0; l < params.length; l++) { const param = params[l].split(':'); if(param.length > 2) param[1] = param.slice(1).join(':'); const key = param[0].trim().toLowerCase(); let value = param[1] !== undefined ? param[1].trim() : ''; if(['jog-color', 'jog-background', 'shuttle-color', 'shuttle-background', 'oninput', 'onchange'].indexOf(key) >= 0) { if(value.toLowerCase() === 'true') value = true; else if(value.toLowerCase() === 'false') value = false; else if(!isNaN(value)) value = Number(value); jsParam.attr[key] = value; } } } jsSetStyle(size, {jogRatio:jogRatio}); if(jsParam.attr.step === undefined || !jsParam.attr.step) jsParam.attr.step = 1; jsParam.attr.stepDecDigit = String(jsParam.attr.step).replace(/^-?\d*\.?(\d*)0*?$/, "$1").length; if(jsParam.attr.step !== 1 && jsParam.attr.max) { const m = jsParam.attr.min !== undefined ? jsParam.attr.min : 0; jsParam.attr.max = Math.floor((jsParam.attr.max - m) / jsParam.attr.step) * jsParam.attr.step + m; } js.style.left = (e.offsetLeft) + 'px'; js.style.top = (4 + e.offsetTop + e.clientHeight) + 'px'; jsParam.o = e; jsParam.valueOrigin = isNaN(e.value) ? 0 : Number(e.value); jsParam.ids['inertia'] = 0; jsParam.k_ = 0; js.style.display = 'block'; d.dummy.focus(); jsRemoveHandler(); if(!jsParam.touchFlg) jsAddHandler(); } }
- 投稿日:2020-10-20T13:56:34+09:00
JavaScriptを使って要素を表示、非表示にする方法
JavaScriptを使って要素を表示、非表示にする方法
完成形はこちらです
このような実装をできるようにする為に必要なコードです。
2段階に分けて説明していきます。header.html<button id="button">表示 非表示ボタン</button> <input id="input" placeholder="表示させたい要素">header-script$("#input").toggle(); $("#button").click(function() { $("#input").toggle(); });1.表示させたいボタンと要素を作る
・
button
タグを作り、idを指定する(このidはJavaScriptで使うため指定しておく)
・input
タグで表示させたい要素を作る、これもidを指定しておく
・placeholder
は文字や数字を表示することができますheader.html<button id="button">表示 非表示ボタン</button> <input id="input" placeholder="表示させたい要素">2.要素を表示、非表示にするJavaScriptを書く
header-script$("#input").toggle(); $("#button").click(function() { $("#input").toggle(); });・
("#input")
ここには表示、非表示にしたい要素を書く
・("#button")
どこをクリックした時にイベント処理が始まるかを指定する、idを指定したbuttonを指定する
・toggle
とは要素がついていれば外す、要素が付いていなければ付けてくれる。toggle
と書くだけで2つのことをしてくれるのでとても便利です。
・$("#input").toggle();
1行目にこの1行を足すことで最初の表示を非表示にしてくれますこちらの記述で実装が完成します。
ボタンを押すと表示、非表示にすることができました。
まとめ
toggleを使うことでクリックなどの操作によって、
2つの状態を交互に切り替える
ことができるという仕組みなので、皆さんも是非toggle
を使ってみてください!
最後に、これからも日々のアウトプットとして投稿していきます。
よろしくお願いします。参考にした記事
https://techacademy.jp/magazine/9540
https://itsakura.com/jquery-toggle
https://qiita.com/sunnyG/items/faf1821ae09820e30ce8
30分で理解!jQueryのtoggle()と3種のメソッド活用術!
https://www.sejuku.net/blog/40705
- 投稿日:2020-10-20T13:05:58+09:00
User-Agent が使えなくなる世界線で何を考えたら良いだろう
※ この記事は 2020/10 時点での情報になり、今後大きく変動する可能性があります。
User-Agent が Google Chrome で凍結・非推奨になる という話題が年明け以降一部の界隈で盛り上がっています。
また、Chrome に限らず、 Firefox, Safari など Chromium 以外のベンダでも User-Agent(以下 UA) の凍結・非推奨が予想されており、近い将来 UA を使わずにデバイスの判定をする世界線は避けて通れなくなりそうです。
UA の代替となる指標
Google は UA を凍結・非推奨とする代わりに User-Agent Client Hints (以下 UA-CH) を新たなデバイス等の判定の指標として提案しています。
※ UA-CH は W3C の Draft Community なので、今後仕様が変わる可能性があります。
例えば Chromeから任意の URI にリクエストを行う場合、Webサーバ側では以下のような値が Request Header に含まれて送信されます(
chrome://flags
のExperimental Web Platform features
を有効することで試すことが可能です)。sec-ch-ua: "Chromium";v="86", "\"Not\\A;Brand";v="99", "Google Chrome";v="86" sec-ch-ua-mobile: ?0 # ?1 が true (mobile である), ?0 が false (mobile でない) を指す # デフォルトはこの2つの Key/Value 値になります # 他にもプロパティはあるので詳しくは Document を参照下さいまた、Apache 等の Middleware 側でも Accept-CH などを setHeader することで、自身の開発している Webサーバ からのレスポンスに上記以外の UA-CH を含めることも可能です。
UA-CH で何ができる
UA の代替として使えることが考えられます。
現行だと Chromium ベースに限っての話になりますが、Client, Web Server 側両方とも検知が可能です。Client 側で UA-CH を取得する
Web API で提供されている Navigator インターフェースより行うことができます。
現在、navigator.userAgentData
が UA-CH のインターフェースとして活用できます。公式より/** @see https://github.com/WICG/ua-client-hints#for-example */ const uaData = navigator.userAgentData; const brands = uaData.brands; // [ {brand: "Google Chrome", version: "84"}, {brand: "Chromium", version: "84"} ] const mobileness = uaData.mobile; // false (async ()=>{ // `getHighEntropyValues()` returns a Promise, so needs to be `await`ed on. const highEntropyValues = await uaData.getHighEntropyValues( ["platform", "platformVersion", "architecture", "model", "uaFullVersion"]); const platform = highEntropyValues.platform; // "Mac OS X" const platformVersion = highEntropyValues.platformVersion; //"10_15_4" const architecture = highEntropyValues.architecture; // "x86" const model = highEntropyValues.model; // "" const uaFullVersion = highEntropyValues.uaFullVersion; // "84.0.4113.0" })();これにより、ブラウザの特定 及び デバイス判定(Desktop か Mobile) を行うことができます。
UA で文字列を正規表現で舐めて判定していた時代より随分に楽になりそうです。ただ、
userAgentData
の実装は Chromium ベースのブラウザなの(このインターフェースの仕様も半年で微妙に変わっている...exbrands
が3ヶ月前はuaList
だったり)で、他のベンダ(Firefox, Safari)が UA を廃止した時の代替実装がまだどういうものなのか、また、userAgentData を実装するかさえもわからないため、Client 側の判定方針は今後も注視する必要がありそうです。Ref ) Chromium ベースのブラウザは検証している方がいるので参考までに
Web Server 側で UA-CH を取得する
先程のコードブロックに記載通り、Web Server 側では Request Header に UA-CH の情報が含まれているので、
Client のように Web API を介した情報の取得をせずとも対応が可能なので、Server 側のほうが現行だと対応しやすいかもしれません。Server はリクエストを受ける側ですので、UA-CH と UA で判定処理をかき分けておけばある程度の状態は把握することができるでしょう。
例えば、「モバイルかどうか」の判定は以下のようなコードで表現できたりします。const judgeIsMobile = ( legacyUA: string = '', CHUAM: string = '' ): boolean => { /** Legacy User-Agent judgment */ const isIOS = /iP(hone|(o|a)d)/.test(legacyUA); const isAndroid = /Android/.test(legacyUA); const isLegacyMobile = isIOS || isAndroid; /** * Client Hints judgment for Chromium * CHUAM (sec-ch-ua-mobile) has `?0 (false)` or `?1 (true)` judgment value. */ const capture = /^\?(?<mobile>\d{1})$/.exec(CHUAM); const hasMobile = capture ? (capture.groups ? capture.groups.mobile : '0') : '0'; const isCHMobile = Boolean(parseInt(hasMobile)); return (isLegacyMobile || isCHMobile); }; /** Usage */ const legacyUA = req.headers['user-agent']; const CHUAM = req.headers['sec-ch-ua-mobile']; const isMobile = judgeIsMobile(legacyUA, CHUAM); /** Use isMobile logics */ // ...UA-CH を効率良く使う
これまでは UA-CH の現行の仕様で何ができるかを考えてみました。
ただ、UA-CH はデフォルトの値だけではなく、Viewport
などにも対応しています。例えば、HTML に
<meta httpEquiv="Accept-CH" content="Viewport-Width, Width" />
を設置して、Web Server 側でリクエストを受け付けるとします。
Request Header には
viewport-width
の値が反映された状態でリクエストが来ます。これは便利ですね。デバイス判定はできないにしても、CSSレイアウトなど
Viewport
で振り分けていることって意外と多いので、デバイスの Width などで判定をするなどのビジネルルールでソフトウェアを開発する場合などは、こちらのほうが自分たちで制御しやすいのでいいのでは?とも考えられます。UA-CH がカバーしているプロパティは、chromium 内のこのソースコードで確認できます。
まとめ
現行の世界線とこれからの世界線がどういうことになりそうかまとめてみます。
Chromium (Chrome など) Webkit (Safari) Gecko (Firefox) Client
- UA 凍結・廃止の意向
- UA-CH / navigator.userAgentData を使って判定が可能(になるかもしれない)
- GAに関しての指摘 もされており、UA と代替となる技術で GA は運用されることが示唆されています(viewport-width などはそれも睨んでいるのかな?など思ってみたり)
- 一部の Web API の実装を拒否する意向
- navigator などが無くなるわけではないが注視は必要か
- 現行もUAを固定(iPad 周りの判定しづらい)していたり、今後も予告なく凍結などする可能性あり(予告がないため)
- UA-CH の実装予定はあり(優先度低い)
- 現行は UA 対応か (UA-CH が実装されれば userAgentData ワンちゃんあり?)
- Safari 同様一部の Web API の実装をしない傾向にある
Web Server
- Request Header 内の UA / UA-CH での判定
- setHeader などで UA-CH の対応も可能
- Viewport などでのデバイス判定利用をすることもできるたけ柔軟な対応が可能
- 現行は UA
- Client 側の実装が突然されなくなる可能性あり、Safari は他のベンダに比べて情報を取得できなくなる可能性が高い
- 現行は UA
- Client で UA-CH の実装が行われればこちらは問題なさそう
現行だと、Chrome はなんとかなりそうですが、Safari, Firefox は注視していく必要がありそうです。ただ、今年は感染症の影響もあり、Chrome 側も UA の本格的な廃止は来年にずれ込むようです。
Firefox はこちらのようなポジションを取っており、また、Tor ブラウザも Gecko ベースなので 慎重になっているのかなという考察しています。
フロントエンドは
navigator.userAgentData
の I/F が結構な頻度で変わっているのもあり、現行プロダクションレベルでの対応は中々骨が折れそうですが、Web Server 側での分岐処理などは一旦事前に検討しておくことが可能な印象です。BFF など上手く活用できるとクライアントの煩雑な処理はある程度避けられるのかなと考えたりしています。数年で UA 周りの景色はだいぶ変わるかもしれませんね。
- 投稿日:2020-10-20T11:50:05+09:00
クイズアプリを作ってみた
クイズアプリを作りました
https://siruba75.github.io/Rabbit_Quiz/ドットインストールのクイズアプリを作る講座を見て、一度選択肢をクリックすると回答が出てきてそこでクイズが終わってしまうのが残念に感じ、選択肢を選べるようにするにはどうすればいいのかを自分なりに考えました
こちらがソースコードです
https://github.com/siruba75/Rabbit_Quiz是非ご覧ください
- 投稿日:2020-10-20T10:18:53+09:00
スクロールダウンしたときにヌルッとでてくるヘッダーの作り方
はじめに
Web制作をしていくなかで、スクロールダウンをしたときにヘッダーとは異なるヘッダーを常時だしておきたいときに、こちらの方法で実装しました。
備忘録として、メモを残しておきます。
コードの書きかた
class名はjsが適用されるときはプリフィックスとして、jsを付けています。
index.html<div id="js-header"> <div class="js-header__menu"> <ul class="js-header__list"> <li><a href="#">メニューA</a></li> <li><a href="#">メニューB</a></li> <li><a href="#">メニューC</a></li> <li><a href="#">メニューD</a></li> <li><a href="#">メニューE</a></li> </ul> </div> </div>style.css#js-header { line-height: 66px; top: -66px; /* 上に隠す高さ */ left: 0; width: 100%; margin: 0 auto; overflow: scroll; position: fixed; opacity: 0; z-index: 1000; }script.jsjQuery(function () { "use strict"; var flag = "view"; $(window).on("scroll", function () { if ($(this).scrollTop() > 200) { if (flag === "view") { $("#js-header").stop().css({opacity: '1.0'}).animate({ top: 0 }, 500); flag = "hide"; } } else { if (flag === "hide") { $("#js-header").stop().animate({top:"-66px",opacity: 0}, 500); flag = "view"; } } }); });どっかからひろってきたコードです。
理解はできていないくても普通に使えたんですが、せっかくなので1行ずつみていきますか。jQuery(function () { "use strict"; var flag = "view";falg変数のデフォルト値として「view」を指定しています。変数flagを使わないと、ページをスクロールしていると常に同じ処理が繰り返されてしまうようです。
$(window).on("scroll", function () { if ($(this).scrollTop() > 200) { if (flag === "view") { $("#js-header").stop().css({opacity: '1.0'}).animate({ top: 0 }, 500); // flag変数の値を「hide」に変更 flag = "hide"; }scrollが200に到達したら#js-headerにopacity:1.0を与える指示をしています。
もともとのCSSには、opacity:0;を指定しておいて、スクロールしたときにopacity:1.0にすることで表示させているんですね。
ちなみにこの200の数字の単位は、pxなはず。top:0というのはcssでtop:-66pxで上に隠していたのを下におろして表示させるための指示です。
書いていて思いましたが、opacityでも非表示にして、高さでも隠してなぜダブルでやっているんだろうか、、。また、500(0.5秒)という数字を変えると表示される速度が変わります。好みに合わせてください。
} else { if (flag === "hide") { $("#js-header").stop().animate({top:"-66px",opacity: 0}, 500); flag = "view"; } }今はhideのフラッグがたっています。
こちらでは、スクロールアップしていったときに通常のヘッダーの表示に切り替える指示をだしています。
※ヌルッとでてきたヘッダーを隠す指示同様に、top:-66pxとopacity:0を500(0.5秒)で行っています。
そして、flagをviewに戻しています。まとめ
今までコピペで対応してきたんですが、応用力を高めるためにコピペ内容を自分なりにまとめてみました。
非常に勉強になったので今後も続けていきます。たぶん。
■参考にした記事
マークダウンの書き方
https://qiita.com/shizuma/items/8616bbe3ebe8ab0b6ca1ヘッダーの作成方法
https://naughties.net/2018/02/05/scroll-fixed-header/
https://mae.chab.in/archives/2699
- 投稿日:2020-10-20T09:51:30+09:00
【JavaScript】ちゃんと勉強してみた
今回は、JavaScriptちゃんと勉強してみた。
個人で作成したポートフォリオなどで使っていたものの、今迄は、ググってコピペして動いたから
とりあえずオッケーっと言う感じにしていたので、今回基礎からちゃんと勉強してみた。ほぼ自分の勉強メモです。
過度な期待はしないでください。ES2015(ES6)
2015年、JavaScriptの使用が大きく改定され、名前「ECMAScript 2015」(通称ES2015 または ES6)と
呼ばれるようになった。従来のJSより効率的にコードが書けるよう大幅に機能が拡張された。
console.log()
-console.log() は丸括弧 () 内に入力された文字をコンソールに出力する。
-文字列を出力する場合、シングルクォーテーション(')かダブルクォーテーション(")で囲む必要がある。
-文の最後はセミコロン;
→ なくても問題は無いが、思わぬ不具合が生じる可能性もある。
-文頭に//
を書くと、その行はコメントとみなされる。なので、プログラムとしては無視され実行されない。例console.log('Hello World'); console.log("Hello World"); // 文の最後には、セミコロン // 出力結果 → Hello World
-数値と計算
数値は文字列と違いクォーテーションで囲まない。
クォーテーションで囲ってしまえば、例え計算式でも文字列として出力される。例console.log(7 + 7); // 出力結果 → 14 console.log("7 + 7"); // 出力結果 → 7 + 7
変数
-変数は
「let 変数名 = 値」
として定義
出力する時、変数はクォーテーションで囲まない、囲むと文字列となってしまう。
-変数の最後にもセミコロン;
→ なくても問題は無いが、思わぬ不具合が生じる可能性もある。例let name = 'Aki'; console.log(name); // 出力結果 → Aki console.log('name'); // 出力結果 → name
-変数の命名ルールの例
付けて良い変数名 備考 _style アンダースコアは使える $ ダラーマークは使える name1 1文字目で、なければ数字は使える
付けてはいけない変数名 備考 1name 1文字目が数字 css-style - は使用出来ない &name & は使用出来ない
-変数の更新
「let」は必要なく、「変数名 = 新しい値」
と書けば値が変更される。例let name = 'Aki'; console.log(name); // 出力結果 → Aki name = 'John'; console.log('name'); // 出力結果 → John
定数
-変数は
「const 変数名 = 値」
として定義
変数は1度代入した値を更新することができるが、定数は値を更新することはできない。-定数のメリットは、「後から値を更新できない」ところ、
コードの量が増えるにつれて、より安全なコードを書くと言うことに適している。例const name = 'Aki'; console.log(name); // 出力結果 → Aki name = 'John'; console.log('name'); // 出力結果 → Aki 定数は一度代入した値は更新出来ない
テンプレートリテラル
-文字列や定数の連結には、「+」記号を用いてきたが、それ以外に
文字列の中に「${定数}」
とすることで、文字列の中に定数や変数を含めることが出来る。
この時、文字列全体をバッククォーテーション(`)で囲む必要がある。例const name = "Aki"; console.log(`ぼくの名前は${name}です`); // 出力結果 → 私の名前はAkiです
if文
-ifの後ろに
( )
の中に条件式を書き、それが成り立つ場合の処理を{ }
の中に書く例const level = 12; if (level > 10){ console.log('レベルが10より大きいです'); } // 出力結果 → レベルが10より大きいです
-比較演算子
左と右の値が等しいか異なるかを調べるもの
「a === b」 → a と b が等しい
「a !== b」 → a と b は異なる例const password = "JavaScript"; if (password==='JavaScript'){ console.log('ログインに成功しました!'); } if (password!=='JavaScript'){ console.log('ログインに失敗しました!'); } // 出力結果 → ログインに成功しました!
-else を使って一つに纏める
if文に「else」を組み合わせると「もし〇〇なら●●を行う、そうでなければ■■を行う」
という処理が行われる。例const password = "Python"; if (password==='JavaScript'){ console.log('ログインに成功しました!'); }else{ console.log('ログインに失敗しました!'); } // 出力結果 → ログインに失敗しました!
-else if
ifとelseだけでなく、さらに条件を追加したい場合に使用する。
「else if (条件)」を追加
例const number = "12"; if (number >= 20){ console.log('数字は20以上です'); }else if (number >= 10){ console.log('数字は10以上です'); }else{ console.log('数字は10より小さいです'); } // 出力結果 → 数字は10以上です
-複数の条件式
「かつ」 - 「&&」 → 複数の条件がすべてtrueならtrueになる
「または」 - 「| |」 → 複数の条件のうち1つでもtrueならtrueになる例const number = 25; if (number >= 20 && number <= 30){ console.log('数字は2桁です'); } // 出力結果 → 数字は2桁です
switch文
-
switch(条件の値){ 処理 }
でとすることでswitch文を書くことが出来る。
条件の値とは、変数や定数のこと
値によって処理を分岐する場合にswitch文を使用。
switch文の中にcase
を追加することで処理を分けることができ、分岐の数だけcaseを追加する。
switch文の中でbreak
は、switch文を終了する命令例const ranking = 2; switch (ranking) { case 1: console.log('1位です!'); break; case 2: console.log('2位です!'); break; case 3: console.log('3位です!'); break; } // 出力結果 → 2位です!
-breakが無いと、その次のcaseの処理も実行してしまう。
例const ranking = 2; switch (ranking) { case 1: console.log('1位です!'); case 2: console.log('2位です!'); break; } // 出力結果 → 1位です! 2位です!
-caseのどれにも一致しなかった時、
default
のブロックが実行される
defaultはif文のelseに似たようなもの例const ranking = 5; switch (ranking) { case 1: console.log('1位です!'); break; case 2: console.log('2位です!'); break; case 3: console.log('3位です!'); break; default: console.log('4位以下です'); break; } // 出力結果 → 4位以下です
- 投稿日:2020-10-20T07:43:40+09:00
初心者のプログラミング2
勉強の記録
勉強した内容
- 前回の復習
- ツイッターなどでよくある診断ツールの作成。
何をベースに勉強してるか
内容の詳細
わかったことについて
- 前回に引き続き同じ内容でしたが、プラスして分かった事。 今まではconsole.log で一つ一つのエラー確認をしていましたが、 consolo.assert()を使ってのエラー確認を学びました。 ただ自身の認識では、A=Aでない場合はエラーメッセージを出すという認識ですが 間違ってるかも知れない…。
むずかしかったよ
- 前回は全体的に難しいと書きましたが、今回理解できていない部分をピックアップするなら return result.replace(/{userName}/g, userName); というコードですが。 /\/この辺りが良く分からない。 何だコレ状態です。
次回やる予定のこと
- 次回はいよいよツイート昨日の完成の授業です!
- 投稿日:2020-10-20T04:26:47+09:00
swiperでエラーが出ないのに、意図しない動き
- 投稿日:2020-10-20T00:31:36+09:00
オブジェクトのある特定のキーを取得する方法
オブジェクトのある特定のキーを取得する方法が日本語であまり見当たらなかったのでまとめてみました。
ユーザーの情報が格納されている
usersオブジェクト
があるとする。const users = { "Jonh": { sex: 'male', age: 30, height: 175, }, "Bob": { sex: 'male', age: 25, height: 175, }, "Hanna": { sex: 'female', age: 23, height: 160, }, };全てのキーの取得方法
オブジェクトの中にあるキーを全て取得したい場合には、
Object.keys(users); // return Jonh, Bob, Hanna
Object.keys()
メソッドの引数に、オブジェクト変数を入れてあげることで取得できる。ある特定のキーの取得方法
Object.keys(users)[0]; // return Jonh
[]
の中に取得したいオブジェクトのキー番号を指定することで取得ができる。
一番初めのキーを取得したい場合には、0を指定。これでok
参考サイト
- 投稿日:2020-10-20T00:28:11+09:00
multiple属性のinput type='file'にドラッグ&ドロップで複数のファイルを一個ずつ追加していく方法
はじめに
ファイルを選択するフォームがある際、
ドラッグ&ドロップができるとスマートで便利ですよね。mulitiple属性がつくと複数のファイルを一つのフォームで送信が可能です。
そうした上でドラッグ&ドロップで選択する仕様にするのはさほど難しくはありませんでした。
しかし、複数同時にドロップするのではなく、
その複数のファイルを一個ずつドロップできるようにしたかったのですが...ちょっとツマってしまったので、その解決方法をメモ。
実装
sample.html<form action="~~~" accept-charset="~~~" method="post"> <input multiple="multiple" type="file" name="task_images" id="task_images"> <div id="dropZone" style="width:~~~px;height:~~~px;"> ここにドラッグ&ドロップ </div> <input type="submit"> </form>
"~~~~"
やname="task_images"
は例です。
ここでは扱うファイルを画像ファイルを想定してます。
idの"task_images"
"dropZone"
でそれぞれの要素を取得していきます。sample.jsvar fileField = document.getElementById('task_images'); var dropZone = document.getElementById('dropZone'); var dt = new DataTransfer(); dropZone.addEventListener('drop', (e)=>{ e.preventDefault(); dt.items.add(e.dataTransfer.files[0]); fileField.files = dt.files; });
dt
に代入されたDataTransferオブジェクト
はドラッグ&ドロップされたデータを扱う、保持するために使われます。
なお、safariとIEはnew
に対応していないので注意!
e.preventDefault();
がないとドロップできないので大事です。
dt.items
はDataTransferItemListオブジェクト
dt.files
とe.dataTransfer.files
はFileListオブジェクト
です。
e.dataTransfer.files[0]
はFileオブジェクト
です。
非常にややこしい!
e.dataTransfer.files
にはドロップされたファイル(Fileオブジェクト
)がFileListオブジェクト
の中に同時にドロップされた数だけ格納されています。
今回は一つずつドロップし選択するのでe.dataTransfer.files[0]
をadd
メソッドでdt.items
に追加します。
そうするとdt.files
にFileListオブジェクト
として変更が反映されています。最後にinputエレメントである
fileField
のfiles
をdt.files
に差し替えます。
これでドロップによってinputのファイル選択を追加していくことができます。最後に
DataTransferにはclearData()など応用の利くメソッドがあるので、
使いようによってはファイル数の制限も実装できます。
画像プレビューにも使うこともできるので、ぜひ調べて見てください。参考
DataTransfer
Document: drop イベント
FileListは作れる!~file inputの中身も自由自在~