- 投稿日:2020-11-24T23:29:26+09:00
三点リーダーを❤に置換するJavaScript
TLに流れてきたので。
replace.jsfunction rplc(){ h=[...'❤'][0]; document.body.innerHTML = document.body.innerHTML.replace(/…/gi, h); } rplc();Unicode絵文字だからすんなり行けるかと思ったけど、「❤」を出力するのは一手間いる感じだった。
おまけ。
ブックマークレット中身javascript:{function r(){h=[...'❤'][0];document.body.innerHTML=document.body.innerHTML.replace(/…/gi, h);};r();}
- 投稿日:2020-11-24T23:13:18+09:00
JavaScriptでジャイロセンサー(方向センサー)つかって遊んでみた
ジャイロセンサ使ってみた。色々遊べそう。
(※センサを搭載していないデバイスで閲覧している場合、何も面白くないものが表示されていると思います。)センサを搭載してても、Qiita記事への埋め込みだとセンサが取れないっぽい。
codepenのサイトから直接閲覧する必要あり。See the Pen 3D Sensor test by kob58im (@kob58im) on CodePen.
three.jsを使用しているのは手軽に可視化したかったからであって、ジャイロセンサの取得は素のJavaScriptで書けます。(参考サイト参照)
なお、記事作成時点(2020.11.24)で、参考サイトによると
これは実験的な機能です。本番で使用する前にブラウザー実装状況をチェックしてください。
および
警告: Chrome と Firefox では角度の扱い方が異なり、一部の軸の向きが逆になっています。
だそうです。
参考サイト
- 投稿日:2020-11-24T22:50:53+09:00
JavaScript勉強日記#1
①確認ダイアログボックスの表示
・目的:下記のダイアログボックスを表示する
https://gyazo.com/3da5a0c50544d85af25c1c7cd4adce3b
https://gyazo.com/f9808943633140799071c34fea626e74index.html<script> if(window.confirm('ゲームスタート!準備はいいかい?')) { console.log('ゲームを開始します!'); } else { console.log('ゲームを終了します。'); } </script>
window.confirm('message')
で表示できた。
alertメソッドとの違いはT/Fでリターンを返してくる事。②テキストを持ったダイアログを表示
・目的:下記のダイアログの表示
https://gyazo.com/c24d08dedc4a761b0f2faea00317ae3e
https://gyazo.com/67b10557bbb964c2fbcf729268dc9fb0
window.confirm('message')
で表示できた。
これは変数を用いる事で保存が可能になる。
例えばlet answer(変数名) = window.confirm('message')
と書けばこれ以降answerと書く事でmessageを取得できる。③入力内容で動作を変更する
・目的:動作を条件分岐させたいindex.htmlconst answer = window.prompt('ヘルプを見ますか?'); if(answer === 'yes') { window.alert('タップでジャンプ、障害物をよけます。'); }constで生成された変数は後から上書きできない。
- 投稿日:2020-11-24T22:48:08+09:00
初プルリクで心がボロボロにならないためのセルフチェック観点をまとめてみた
この記事はモチベーションクラウドシリーズアドベントカレンダー2020の3日目の記事です。
自己紹介
初めまして!こんにちは!19年新卒の小宮と申します!
エンジニア歴は約1年程度で、バックエンドやQAも関わりつつ、
現在メインはフロントエンドエンジニアとして日々開発業務に勤しんでおります!この記事を書こうと思った背景
この話は本当にあった怖い話です...
上のキャプチャは自分が初めて出した大きめのPRです。ご覧いただけますでしょうか?
Conversation 73
という恐ろしい文字列が・・・自分がこの73コメント分(一部CodeCovなどの自動コメントも含まれますが)、
先輩エンジニアの時間を奪っているんだと勝手に責任感を感じて、
いそいそといただいたコメントに対応していたのが1年前だと思うと、時の流れを早く感じます。上記のような現象、「自分も同じ経験をしたことがあるなあ・・・」という方も
一定数いらっしゃるのではないでしょうか?
この現象が起きる理由として、パッと思いつくだけでも
- その開発現場のコードのお作法を知らない
- 動けばいいや!という精神でコーディングしてしまう
- ここまで考慮できてますよ!というアピールで不必要なコードを書いてしまう
などと、他にもいっぱいあるんじゃないかなあと思っております。
今回、アドベントカレンダーで執筆の機会をいただけたので、一年前の自分のように
先輩の時間を奪って申し訳ねえ!というやるせない気持ちになる駆け出しエンジニアが
少しでも減る一助になれれば良いなあと思い、本記事を執筆しました!具体的なセルフチェックチェック観点
言語共通
命名
コードというのは「書くこと」より「読む/読まれること」の方が圧倒的に多いのです!
命名に関してチェックするべき観点のほんの一例を紹介します!naming.js// 略語について number // Good! なるべく略語は使わない! no // More! 「いいえ」のNoと勘違いする可能性も0じゃないですね // 真偽値について isParent // isで始めたり、 hasChild // 動詞で始めたり、 alreadyAppeared // 副詞+動詞の形だと真偽値を返してくれることが察しやすいですね! // メソッドや変数名について(例えばECサイトでカートへ追加するメソッド名) const add = function() { return ** } // 何をどこに加えるのかが分かりにくい。 const addToCart = function() { return ** } // ToCartを入れるだけでも何やっているか分かりやすくなったですね!本当はまだまだあると思いますが、いったんこれくらいで!
ちなみに僕のエンジニアの師匠のさらにその師匠が
「エンジニアは、名前をつけるのがお仕事です。」と言っていたようです。あと、命名の際はDeepL(Google翻訳よりも精度高い気がします)を使って、
日本語で何をしたいメソッドなのか言語化してから翻訳かけたりしています!まとめられる箇所をまとめられていない
コピペを何度も繰り返しているところはメソッドに切り出してまとめましょう!
よくDRY(Don't Repeat Yourself)
って言われたりしますね!dry.jsconsole.log('hi! taro') console.log('hi! hanako') console.log('hi! hiroshi') // こんな風にいちいち呼び出さずに const nameCall = function(name) { console.log(`hi! ${name}`) } // メソッドでくくっちゃいましょう! nameCall('taro') nameCall('hanako') nameCall('hiroshi')DRYを徹底することのメリットとして、
- コード量が減る
- 上記の例だと実感しづらいかもしれませんが、これは本当!笑
- 後から修正が入った時に修正が必要な箇所が少なくなる
- 上記の例だと「hi!」と挨拶していたのが、「hello!」になった時、nameCallだけ編集すればOK!
- コードが読みやすくなる
- 上記の例だと「あ、これは名前を呼んでいるんだな!」とメソッド名から推察しやすくなりますね!
不必要なコードも書いてしまう
よほどのことがない限り、今後これを使うことになるだろうから、今やっちゃおう!と
今必要ないコードを加えることはお節介になる可能性が高いです!
よくYAGNI(You ain't gonna need it)
って言われたりしますね!yagni.js// 例えば多言語対応することになり、先に英語版からリリースして、来年に中国版をリリースしよう!となったとき、 // 先んじて中国語の定数も定義しようというのはお節介になる可能性が高いです!! export const LANGUAGE_TYPES = { JA: { KEY: 'ja', TEXT: '日本語' }, EN: { KEY: 'en', TEXT: '英語' }, // ZH: { いらない!! // KEY: 'zh', // TEXT: '中国語' // }, }YAGNIを徹底することのメリットとして、
- 負債になる可能性のあるコードを残さない
- 上記の例だと中国語版のリリースが白紙になった場合、完全に負債になりますね!
- 読み手の「?」を減らす
- 上記の例だと中国語版のリリースが白紙になってから1年ぐらい経ち、あなたがそのプロジェクトを外れていた場合、「え、これって必要なコードなんですか?」と後から読む人の混乱を招く可能性がありますね!
- 後から実装する人がバグを出してしまう可能性を減らす
- 上記の例だと違う人が中国語を担当したときに、「あ、中国語も一緒にやってくれたんだ!」と安心してしまい、対応するべき箇所全てに対応するのを失念してしまう可能性がありますね!
スコープを意識しましょう
スコープとは、あなたが定義した変数や関数が参照される範囲のことです!
無駄に広いスコープで変数を定義すると、読み手に不親切な上に、不具合の温床になったりするので要注意です!scope.jsconst scope = "Wide Scope" function getNarrowScope() { const scope = "Narrow Scope" return scope } function getWideScope() { return scope } console.log(getNarrowScope()) // Narrow Scope console.log(getWideScope()) // Wide Scopeスコープを意識することのメリットとしては、
- コードを疎結合に保てる
- 上記の例だと"Wide Scope"の値を持つ方の変数scopeとgetWideScopeが相互に依存関係にありますね!コードは疎結合に保つことが正義です!
- 読みやすくなる
- 上記の例だとまだ読めますが、これが数百行のコードになった時、"Wide Scope"の値を持つ方の変数scopeを100行下で呼び出していたりしたら、読み手にかける負担が大きくなりますね!
- 書きやすくなる
- 実際にコードを書くときも、意識するべきことが減るので、実装する際においても考えることが減ると思われます!
マジックナンバー
マジックナンバーとは、意味を持つ数字を、直接書いて、読み手が
「なんだこれ?」となってしまうような数字のことです。magicNumber.js// 例えば100円ショップでお買い物をする時 const price = 100 * 1.10 // 1.10が何のことだか分からない! const tax = 1.10 const price = 100 * taxマジックナンバーを撲滅することのメリットとしては、DRYの説明で書いたのと同じで、
- コード量が減る
- 上記の例でも実感しづらいかもしれませんが、これは本当!笑
- 後から修正が入った時に修正が必要な箇所が少なくなる
- 上記の例だと消費税が20%になった時(やだなぁ...)、taxだけ編集すればOK!
- コードが読みやすくなる
- 上記の例だと「あ、これは消費税のことだな!」と定数名から推察しやすくなりますね!
インデントやtypo
これらは基本的なことでかつ、すぐ見つけられることなので、もう一度見直しましょう!
インデントを見やすくしたり、typoらしきものを色付けしてくれる拡張機能も多くのエディターで備えてくれているので、
それらも是非活用しましょう!(以下例はVSCodeのCode Spell Checker
)
JavaScript
console.logの消し忘れ
開発中に書いていたconsole.logを消し忘れていないかは要チェックですね!
これをやったときはだいぶ死にたくなりました!変数の定義
こちらはES6で新しくできるようになったことですが、変数の宣言に
var
以外が使えるようになりました
→参考 : var/let/constの使い分けのメモ
(良書でも古いものだと未だにvarが使われたりしますが、今やvarは使わない!と言っても過言ではないです)
他にもES6は便利なので、極力活用しましょう!(ここで書くとボリューミーなので、今回は省略)variables.jsvar name = "taro" // varは使わない!!! const name = "taro" // 真っ先に使うべきはconst // 名前が「田中ジェイソン太郎」というミドルネーム持ちの人がいて、海外ではジェイソンと呼ばれていて、 // どうしても再代入が必要な場合だけletを使いましょう! let name if (country === "japan") { name = "taro" } else { name = "Jason" }変数定義にこだわることで、以下のメリットがあります!
- ファイル内で変数にスコープを持たせられる
- 読み手にとって親切
- 例えばletで変数が定義されていた場合、読み手は「あ、これはどこかで再代入されるのか!」と予測しながら読み進められる
CSS
カラーパレット
文字の色や、背景色を定義するとき、
カラーパレットを使える場合はそちらを活用しましょう!!background-color: #bf2926; // 直接カラーコードを書かない! $PASSIONATE_RED: #bf2926; // 共通で使えるカラーパレットがどこかで定義されているかどうか確認して background-color: $PASSIONATE_RED; // ある場合はそのカラーパレットに割り当てられた定数を使いましょう!カラーパレットを一貫して使うことのメリットとしては以下が挙げられると思います
- プロダクトに使われているそれぞれの色に役割を持たせやすくなる
- レッドはエラー、グリーンは成功、オレンジは注意などと、命名と共に定義すると色に役割を割り当てやすくなりますね!
- どんな色だかイメージしやすくなる
- 例えば
#bf2926;
が何の色だかぱっと見で判るほど記憶力の良い人はいないと思いますが、$PASSIONATE_RED
なら「情熱的な赤い色なんだろうな〜」とイメージできやすくなりますね!8の倍数ルール
marginやheight、font-sizeなどの大きさを8や4の倍数で統一している可能性があります!
メリット等は以下の記事などを読むとわかりやすいので、是非ご一読を!
→参考 : 8の倍数ルールでデザインする理由とメリット・デメリット最後に
夢中で思いつく限りのことを書いてみました。
(本当はvue.js特有のことなども書こうと思ったんですが、力尽きましたw)コードレビューに出す内容って相手への敬意の現れだなと思っていて、
正直、上記の内容を一回指摘されるぐらいなら全然OKだと思う(新人だし仕方ないよ!ってなると思う)んですが、
一度指摘されたことは次のPRでは絶対指摘されないぞ!という心意気が一番大事かなと思います!!ではみなさん良い12月を!!
僕は月末までに彼女を作ります!!!おまけ(参考資料)
リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)
これは自分がエンジニアになったときに職場の先輩から真っ先に読め!と言われた本です。
当時は「はいはい、なるほどね〜」くらいだったんですが、今改めて読み返すと、
「当時の自分全然わかってなかったんやな...」と奥の深さを知れるエンジニアの登竜門的な本プリンシプル オブ プログラミング 3年目までに身につけたい 一生役立つ101の原理原則
一番最初にあげたリーダブルコードより新しいもので、今回自分が挙げたYAGNIとかDRYとか、
そのほかにもエンジニアの道標?心得?的なのがたくさん載ってます。
リーダブルコードと違って、具体的なコードが一切載っていなかったり、
日本人が書いたものなので、初心者にも読みやすいのかなと思います。改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで
そもそものJavaScriptへの入門として、こちらの教材は少し難解な部分もあるのですが、
一通りしっかりやると、かなりの実力がつくのではないかなあと思います。
JavaScriptの古い記法から、新しいES6の記法までをさらっているため、
よりES6のメリットが分かったり、現場によっては割と古い記法で書かれているところもあるので、
そういった方にもお勧めできますね!やっぱりプログラミング(今回はvue)の学習といえばアプリ作成!ということでカレンダー!
学習のために、最初の方は色々と教材を買ってやってみたのですが、
やっぱり自分でアプリを作るのが一番学習になります。
ということで私はこちらの記事を参考に、カレンダー的なものを作りました!
追加機能として例えばモーダルを自作してみたりしてもいいかもしれませんね!
- 投稿日:2020-11-24T22:46:03+09:00
【JavaScript ~カウントダウンタイマー応用~】勉強メモ14
JavaScriptちゃんと学習中。
今回はカウントダウンタイマーの応用です。
残り時間が刻一刻と変化する実装をします。
ほぼ自分の勉強メモです。
過度な期待はしないでください。前回の記事を参照の元作成してます。
必要が有れば参照下さい。
【JavaScript ~カウントダウンタイマーの作成~】勉強メモ13完成図
実装手順
- 残り時間を再計算をする関数を作成
- その関数を1秒ごとに繰り返す為の関数を作成
前回迄のコード
JavaScript'use strict'; function countdown(set) { const now = new Date(); const rest = set.getTime() - now.getTime(); const sec = Math.floor(rest / 1000) % 60; const min = Math.floor(rest / 1000 / 60) % 60; const hours = Math.floor(rest / 1000 / 60 / 60) % 24; const days = Math.floor(rest / 1000 / 60 / 60 / 24); const count = [days, hours, min, sec]; return count; } let goal = new Date(); goal.setHours(23); goal.setMinutes(59); goal.setSeconds(59); const counter = countdown(goal); const time = `${counter[1]}時間${counter[2]}分${counter[3]}秒`; document.getElementById('timer').textContent = time;HTML<p>後、<span id="timer"></span>で1日が終わります。</p>
再計算する関数作成
- functionを使って新たな関数作成
下記の3行を関数にして、その関数を呼び出す記載をする
const counter = countdown(goal); const time = `${counter[1]}時間${counter[2]}分${counter[3]}秒`; document.getElementById('timer').textContent = time;
関数名は(なんでも良いのだが)、recalcという名で作成。
関数の定義は、「function( )」と書き、その後ろの中括弧「{ }」の中にまとめたい処理を
書くことで関数を用意することができ、「関数名( )」とする事で関数を呼び出す。JavaScriptfunction recalc() { const counter = countdown(goal); const time = `${counter[1]}時間${counter[2]}分${counter[3]}秒`; document.getElementById('timer').textContent = time; } recalc();
ここで、プログラムの処理の流れですが、JavaScript'use strict'; // ④ function countdown(set) { const now = new Date(); const rest = set.getTime() - now.getTime(); const sec = Math.floor(rest / 1000) % 60; const min = Math.floor(rest / 1000 / 60) % 60; const hours = Math.floor(rest / 1000 / 60 / 60) % 24; const days = Math.floor(rest / 1000 / 60 / 60 / 24); const count = [days, hours, min, sec]; return count; } // ① let goal = new Date(); goal.setHours(23); goal.setMinutes(59); goal.setSeconds(59); // ③ function recalc() { const counter = countdown(goal); const time = `${counter[1]}時間${counter[2]}分${counter[3]}秒`; document.getElementById('timer').textContent = time; } // ② recalc();先ず、変数goalが設定された後に、関数recalcが実行される。
その後、関数recalcは、関数countdownを呼び出し残り時間を計算するという流れ。そして、関数recalcの中に、関数recalcを1秒ごとに繰り返す為の関数を作成する。
繰り返し処理の関数を作成
関数recalcの中に関数recalcを1秒ごとに繰り返す為の関数を呼び出す記載をし、
また、1秒ごとに繰り返す為の関数を作成。これ実装する為にsetTimeoutメソッドを使用する。
書き方:
setTimeout(処理内容,実行タイミング)
setTimeoutメソッドは、第二引数に与えられた実行タイミング(ミリ秒)で、第一引数に定義された
処理内容を1度実行する。
つまりは、ある処理を一定時間後に実行するように命令することができる、すなわち繰り返して実行してくれるメソッド。繰り返して実行してくれる関数の名をrepeatという名で作成。
そして、setTimeoutメソッドの第一引数には、再計算する関数recalcを、
第二引数には、1秒ごとに繰り返すので、1秒はミリ秒単位で1000なので、1000を指定する。
ちなみに、引数に関数を指定する際には、関数の後ろの( )は付けない。JavaScriptfunction recalc() { const counter = countdown(goal); const time = `${counter[1]}時間${counter[2]}分${counter[3]}秒`; document.getElementById('timer').textContent = time; // 関数repeatの呼び出す repeat(); } // 1秒ごとに繰り返す為の関数repeatを定義 function repeat() { setTimeout(recalc, 1000); } recalc();
これで、残り時間が刻一刻と変化するカウントタイマーの完成。
下記が完成図JavaScript'use strict'; function countdown(set) { const now = new Date(); const rest = set.getTime() - now.getTime(); const sec = Math.floor(rest / 1000) % 60; const min = Math.floor(rest / 1000 / 60) % 60; const hours = Math.floor(rest / 1000 / 60 / 60) % 24; const days = Math.floor(rest / 1000 / 60 / 60 / 24); const count = [days, hours, min, sec]; return count; } let goal = new Date(); goal.setHours(23); goal.setMinutes(59); goal.setSeconds(59); function recalc() { const counter = countdown(goal); const time = `${counter[1]}時間${counter[2]}分${counter[3]}秒`; document.getElementById('timer').textContent = time; repeat(); } function repeat() { setTimeout(recalc, 1000); } recalc();
補足
さらにちょっとだけ手を加えて、
下記のようなもの作成(ちょっと悪ふざけが過ぎる気がするが。。。)
これを実装するにあたって、
前回のコードと異なる部分は、-Dateオブジェクトのよる日時設定方法
-padStartメソッドを使用
-Stringオブジェクトを使用
- Dateオブジェクトのよる日時設定方法
今までは、setHoursメソッド等を使用して日時の設定をおこなってきたが、
今回は、日時を設定した状態でDateオブジェクトを初期化する。new Date( )の( )内にパラメータを含めておくと、予め日時を設定した状態で初期化出来る。
ただし、パラメータのうち、「年」、「月」は必須なので、ここの2つの設定しない場合はこのやり方は出来ない。
また、注意する点として、月の設定は、「実際の月−1」
とする必要があるので、
今回で有れば7月で設定する場合は6を指定しなければならない点に注意。書き方は、
new Date(年、 月、 日、 時、 分、 秒、 ミリ秒 )実際に書くコードはこちら
JavaScriptconst goal = new Date(2999, 6, 31);
今度は数字の桁数を揃える為に下記の事をする。
一番の上の完成図を見て貰えれば分かるが、秒数が一桁の時と二桁の時では、桁数が異なる為
ずれてしまい、ちょっとカッコが悪いので、それを修正する。
- padStartメソッド
padStartメソッドとは、指定した長さに合わすため、前方に文字列を加えるメソッド。
文字列を加えるので、第二引数にはクォテーションが付いている。
言葉だけだと理解しにくいので、例を交えて説明。先ずは、書き方
文字列.padStart(揃える文字数, 埋め合わせ用の文字)次に、padStartメソッドで、頭にゼロをつけるサンプル
JavaScriptconst str = "90" console.log(str.padStart(3, "0")); // 出力結果 → 090 console.log(st1.padStart(5, "0")); // 出力結果 → 00090 console.log(st1.padStart(2, "0")); // 出力結果 → 90
- Stringオブジェクト
Stringオブジェクトは、文字列を扱うオブジェクト。
なので、String( )は、( )の内のパラメータとして渡されたデータを文字列に変化する。
今回、padStartメソッドを使用して揃えたい文字列の対象は、counter[2]の分とcounter[3]の秒
という数値のデータです。数値のデータのままだとpadStartメソッドが使用出来ない為、Stringオブジェクトを
使用して文字列に変換する。書き方はこんな感じになる
JavaScriptString(counter[2]).padStart(2, '0'); String(counter[3]).padStart(2, '0');
これで異なる実装は完了、
後は完成コードを下記に記載JavaScript'use strict'; function countdown(set) { const now = new Date(); const rest = set.getTime() - now.getTime(); const sec = Math.floor(rest / 1000) % 60; const min = Math.floor(rest / 1000 / 60) % 60; const hours = Math.floor(rest / 1000 / 60 / 60) % 24; const days = Math.floor(rest / 1000 / 60 / 60 / 24); const count = [days, hours, min, sec]; return count; } const goal = new Date(2999, 6, 31); function recalc() { const counter = countdown(goal); document.getElementById('day').textContent = counter[0]; document.getElementById('hour').textContent = counter[1]; document.getElementById('min').textContent = String(counter[2]).padStart(2, '0'); document.getElementById('sec').textContent = String(counter[3]).padStart(2, '0'); repeat(); } function repeat() { setTimeout(recalc, 1000); } recalc();HTML<h2>大予言です<br> 2999年7月世界が滅亡します</h2> <p>あと<span id="day"></span>日<span id="hour"></span>時間<span id="min"></span>分<span id="sec"></span>秒</p>
過去投稿記事
【JavaScript ~変数・定数、if文・switch文~】勉強メモ
【JavaScript ~for文、配列、オブジェクトについて~】勉強メモ②
【JavaScript ~関数について~】勉強メモ③
【JavaScript ~クラスやインスタンス、メソッドについて~】勉強メモ④
【JavaScript ~ファイルの分割について~】勉強メモ⑤
【JavaScript 読み込み】勉強メモ⑥
【JavaScript ~配列のメソッド~】勉強メモ⑦
【JavaScript ~コールバック関数~】勉強メモ⑧
【JavaScript ~HTMLを置き換え、ダイアログボックス~】勉強メモ⑨
【JavaScript ~イベント~】勉強メモ⑩
【JavaScript ~イベント(入力内容を取得)とDateオブジェクト~】勉強メモ11
【JavaScript ~Mathオブジェクト~】勉強メモ12
【JavaScript ~カウントダウンタイマーの作成~】勉強メモ13
- 投稿日:2020-11-24T22:40:39+09:00
ドラクエ5の結婚イベントを考える
結婚する相手毎にメッセージを変更する。
index.html.rb<script> 'use strict'; if(window.confirm('ビアンカと結婚しますか?')) { window.alert('偽善者が!!!'); } else { if(window.confirm('フローラと結婚しますか?')) { window.alert('金の亡者が!!!!'); } else { if(window.confirm('デボラと結婚しますか?')) { window.alert('性欲猿が!!!'); } else { window.alert('一生独身決定!!!'); }}} </script>コードの内容は
window.confirm()とwindow.alert()、条件式if(){}
だけなのでとてもシンプルです。
ドラ○エらしくYes/Noで作りたかったのでこの形を取りました。
windows.prompt()
を使えばもっと短く表現できるのであとで追記しようかと思っています。ちなみに私はフローラを選びました(笑)
- 投稿日:2020-11-24T22:13:27+09:00
Udemy フルスタック・Webエンジニア講座 セクション5.jQuery
jQueryとは
jQueryとは、JavaScriptで作成されたライブラリで、JavaScriptの書き方などを簡単にすることが出来る。実際、私がJavascriptを勉強した後にjQueryを学習してみると、Javascriptよりコードを書く量が少なく、簡単に書くことが出来た。clickの使い方
clickのメソッドは、要素をクリックすることでイベントを呼び出すことが出来る。
test.js$("#circle").click(function(){ alert("円がクリックされました!"); });上のようなコードを書くと、円("#circle")をクリックすることで『円がクリックされました!』というアラート(alert)を呼び出すことが出来る。
test.js$("#circle").click(function(){ $("#circle").fadeOut("slow"); });上のようなコードを書くと、円が徐々に消えていくイベントをつくることが出来る。"slow"の他に"nomal"や"fast"を使うことで文字の消えていく速さを変えることが出来る。
jQuery UI
jQuery UIでは、ユーザーインターフェイスの相互作用、効果、ウィジェット、およびテーマのセット参考にできる。起こしたいイベントをjQuery UIから探して参考にすれば、効率的にコードを書くことが出来るだろう。
jQuery UIのページ他にも、jQuery日本語リファレンスというページもjQueryの構文が書いてあるので参考にしてみよう。
jQuery日本語リファレンスのページよく出たエラー
要素の中身を()や{}で囲い忘れてしまい、エラーがよく出た。しかし、前回の反省でインテンドをそろえていたのですぐに修正することが可能であった。やはりコードを書く際は綺麗さを重視することが大事なのだと再認識した。
最後に
プログラミングの勉強をしていく中で、これから自分は将来どうしていくかどうかのキャリアを考えてみた。私は現在大学三年生であり、周りの同年代の友人はインターンシップなどの就職活動を本格的に始めている。とりあえず私は現在のインターン先で提示されている課題を取り組み、プログラミング技術の向上を図りたい。そして、大学四年生でWeb系企業に内定をもらえるよう努力する。そして就職した後、そこの企業で実務を経験し、より自らのスキルを強化する。これを実現。
また、どのプログラミング言語を中心にやっていくかどうかは、まだ、プログラミングを深く学んでいないので、現時点でははっきりとは言えない。なのでこれから勉強を進めていく中で自分がなにがしたいのか決定できるようにしたい。最後に
現在ここでインターンしています。
まだまだ駆け出しですが頑張ります!やる気は人一倍です!
https://senren.work/
- 投稿日:2020-11-24T21:35:14+09:00
Vue歴5日でポートフォリオサイトを作成する 1
JavaScriptはRailsでアプリを作成する際に数行しか書いたことがありませんでしたが、Vueを触り始めて5日ほどで簡単なポートフォリオサイトなら作成することが出来ました。その過程について書いていきます。
事前準備
手始めにVueで開発する為に必要なものをインストールしていきます。
コマンドラインインタフェースをインストールします。
$ npm install -g @vue/cli次にプロジェクトを作成します。今回はリファレンスの多さからVue2で書いています。
$ vue create my_portfolioプロジェクトの作成が完了した後、一旦立ち上げてみます。
$ cd my-portfolio $ npm run serve以下のメッセージが表示されましたら、localhost:8080にアクセスします。
Your application is running here: http://localhost:8080導入成功です。
次にVue.js Devtoolsを導入していきます。これはChromeブラウザの拡張機能で、Vueで書かれたコード内のデータを手っ取り早く確認することが出来る便利な機能です。導入した後Devtoolを開くとVueと書かれたタブが表示されているはずです。Vueファイルが読まれている時はアイコンがアクティブになります。
これにて事前準備は完了です。
Vueと仲良くなる
publicディレクトリのindex.htmlを見てみます。
public/index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body> <noscript> <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <!-- built files will be auto injected --> </body> </html>注目するべきはbodyタグ内の
<div id="app">
です。このappは一体何者でしょうか?Devtoolで確認してみると...何やらdivタグの中でさらに展開されています。
この中身についてはApp.vueを見てみましょう。App.vue<template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js App"/> </div> </template> <script> import HelloWorld from './components/HelloWorld.vue' export default { name: 'App', components: { HelloWorld } } </script>index.htmlのappはこれを呼び出しているみたいです。
さらにApp.vueの中でHelloWorldというcomponentを読むことで部品としてHelloWorldをタグに使用していますね。
Vueではindex.htmlをそのまま表示するのではなくてbuildを挟むことで./dist/index.htmlへと変換します。これをブラウザで表示している訳です。次にmain.jsを確認します。
main.jsimport Vue from 'vue' import App from './App.vue' Vue.config.productionTip = false new Vue({ render: h => h(App), }).$mount('#app')まずVueを動かすためのインスタンスをvueから作り出し、先ほどの
<div id="app">
にマウントしていることがわかります。だからindex.html内でApp要素が表示されたんですね。Vueの基本
データバインディング
まずはデータバインディングをやってみます。
データバインディングをするには要素をマスタッシュ(二重の中括弧)で囲みます。
scriptタグにはdata()で要素をreturnしてあげます。App.vue<template> <div id="app"> {{ msg }} </div> </template> <script> export default { name: 'App', data () { return { msg: "Data-binding!" } } } </script>Vueはホットリローディングしているので、scriptタグ内のmsgを変更するとそれにしたがってテキストが自動変更されます。上のコードの場合ページ上にData-binding!の文字だけ表示されるはずです。
コンポーネント
コンポーネントはVueファイルをそれぞれ部品のように扱うことで、分離して開発することが可能な機能です。使うには呼び出したいVueをscriptタグ内でimportしてからcomponentsに登録します。templateタグ内でタグとして使用できます。
App.vue<template> <div id="app"> {{ msg }} <HelloWorld></HelloWorld> </div> </template> <script> import HelloWorld from "./components/HelloWorld.vue" export default { name: 'App', components: { HelloWorld }, data () { return { msg: "Data-binding!" } } } </script>HelloWorld.vueに書かれていた内容が表示されたかと思います。
フォームとの同期
v-modelを使うことで入力や選択をDOMに反映させることができます。
App.vue<template> <div id="app"> {{ msg }} <input type="text" v-model="msg"> </div> </template> <script> export default { name: 'App', data () { return { msg: "Data-binding!" } } } </script>フォームに入力したテキストが即座に反映されていることが確認できるかと思います。
Vueにはまだまだ機能がありますが、列挙するとキリがないので、次回からポートフォリオ作成編に入っていきます。次回もよろしくお願いします。
- 投稿日:2020-11-24T21:35:14+09:00
JavaScriptをまともに触ったことない初心者がVueを利用したポートフォリオサイトを作成する 1
JavaScriptはRailsでアプリを作成する際に数行しか書いたことがありませんでしたが、Vueを触り始めて5日ほどで簡単なポートフォリオサイトなら作成することが出来ました。その過程について書いていきます。
事前準備
手始めにVueで開発する為に必要なものをインストールしていきます。
コマンドラインインタフェースをインストールします。
$ npm install -g @vue/cli次にプロジェクトを作成します。今回はリファレンスの多さからVue2で書いています。
$ vue create my_portfolioプロジェクトの作成が完了した後、一旦立ち上げてみます。
$ cd my-portfolio $ npm run serve以下のメッセージが表示されましたら、localhost:8080にアクセスします。
Your application is running here: http://localhost:8080導入成功です。
次にVue.js Devtoolsを導入していきます。これはChromeブラウザの拡張機能で、Vueで書かれたコード内のデータを手っ取り早く確認することが出来る便利な機能です。導入した後Devtoolを開くとVueと書かれたタブが表示されているはずです。Vueファイルが読まれている時はアイコンがアクティブになります。
これにて事前準備は完了です。
Vueと仲良くなる
publicディレクトリのindex.htmlを見てみます。
public/index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body> <noscript> <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <!-- built files will be auto injected --> </body> </html>注目するべきはbodyタグ内の
<div id="app">
です。このappは一体何者でしょうか?Devtoolで確認してみると...何やらdivタグの中でさらに展開されています。
この中身についてはApp.vueを見てみましょう。App.vue<template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js App"/> </div> </template> <script> import HelloWorld from './components/HelloWorld.vue' export default { name: 'App', components: { HelloWorld } } </script>index.htmlのappはこれを呼び出しているみたいです。
さらにApp.vueの中でHelloWorldというcomponentを読むことで部品としてHelloWorldをタグに使用していますね。
Vueではindex.htmlをそのまま表示するのではなくてbuildを挟むことで./dist/index.htmlへと変換します。これをブラウザで表示している訳です。次にmain.jsを確認します。
main.jsimport Vue from 'vue' import App from './App.vue' Vue.config.productionTip = false new Vue({ render: h => h(App), }).$mount('#app')まずVueを動かすためのインスタンスをvueから作り出し、先ほどの
<div id="app">
にマウントしていることがわかります。だからindex.html内でApp要素が表示されたんですね。Vueの基本
データバインディング
まずはデータバインディングをやってみます。
データバインディングをするには要素をマスタッシュ(二重の中括弧)で囲みます。
scriptタグにはdata()で要素をreturnしてあげます。App.vue<template> <div id="app"> {{ msg }} </div> </template> <script> export default { name: 'App', data () { return { msg: "Data-binding!" } } } </script>Vueはホットリローディングしているので、scriptタグ内のmsgを変更するとそれにしたがってテキストが自動変更されます。上のコードの場合ページ上にData-binding!の文字だけ表示されるはずです。
コンポーネント
コンポーネントはVueファイルをそれぞれ部品のように扱うことで、分離して開発することが可能な機能です。使うには呼び出したいVueをscriptタグ内でimportしてからcomponentsに登録します。templateタグ内でタグとして使用できます。
App.vue<template> <div id="app"> {{ msg }} <HelloWorld></HelloWorld> </div> </template> <script> import HelloWorld from "./components/HelloWorld.vue" export default { name: 'App', components: { HelloWorld }, data () { return { msg: "Data-binding!" } } } </script>HelloWorld.vueに書かれていた内容が表示されたかと思います。
フォームとの同期
v-modelを使うことで入力や選択をDOMに反映させることができます。
App.vue<template> <div id="app"> {{ msg }} <input type="text" v-model="msg"> </div> </template> <script> export default { name: 'App', data () { return { msg: "Data-binding!" } } } </script>フォームに入力したテキストが即座に反映されていることが確認できるかと思います。
Vueにはまだまだ機能がありますが、列挙するとキリがないので、次回からポートフォリオ作成編に入っていきます。次回もよろしくお願いします。
- 投稿日:2020-11-24T21:22:13+09:00
配列内の文字列を置換する
配列内の文字列を置換したい
userNameをたけしくんにする覚えがきです
index.jsconst array = ["userNameくんかっこいいよね","userNameくんどこに住んでるの" ,"userNameくん乳首透けてるよ"]index.js//配列を展開 array.forEach((text) => { //指定した文字列が入っているか判断する(userName) const stringInUserName = text.includes("userName"); if (stringInUserName) { //tureならuserNameをたけしくんにする text = text.replace("userName", "たけし"); } return text; });index.jsたけしんくんかっこいいよね たけしくんどこにすんでるの たけしくん乳首透けてるよ以上
- 投稿日:2020-11-24T21:07:03+09:00
getBoundingClientRectとツールチップ
この記事は
JavaScriptの
getBoundingClientRect
を用いたツールチップについての解説です。環境
Vue2
TypeScript(decorator使用)背景
?
アイコンにカーソルを当てた時だけに表示させる汎用的なツールチップを作りたい。課題
?
アイコンの直下に表示させたい。
画面をスクロールしてもそれに追従させたい。解決
ツールチップコンポーネントtemplate
ソース
<template> <span @mouseover="activate" @mouseleave="deactivate" > <slot name="activator"></slot> <div :class="{'tool-tip__content--active': isActive}" :style="contentStyleObj" ref="content" > <slot></slot> </div> </span> </template>解説
mouseoverとmouseleaveでツールチップの表示有無処理を発火します。
<span @mouseover="activate" @mouseleave="deactivate" >
?
アイコンは親コンポーネントからslotしてきます。<slot name="activator"></slot>表示フラグが立っている時にツールチップ本体を表示します。
:class="{'tool-tip__content--active': isActive}"script部で算出した座標をスタイルに反映します。
:style="contentStyleObj"ツールチップコンポーネントscript
TypeScriptを用いています。
<script lang="ts"> import { Component, Prop } from 'vue-property-decorator'; import BaseView from '@/views/Base'; @Component export default class ToolTipComponent extends BaseView { // ツールチップ表示フラグ isActive = false; // 表示位置 contentPosition = { top: 0, left: 0 } // ツールチップを表示する基準位置 defaultPosition = { top: 40, left: 100 } // ?アイコンにマウスオーバーした時の表示処理 activate() { const target = event!.target as any; const rect = target!.getBoundingClientRect(); this.contentPosition.top = rect.top + window.pageYOffset + this.defaultPosition.top; this.contentPosition.left = rect.left - this.defaultPosition.left; this.isActive = true; } // マウスオーバーの解除で非表示 deactivate() { this.isActive = false; } // 座標をスタイルに指定 get contentStyleObj() { return { top: `${this.contentPosition.top}px`, left: `${this.contentPosition.left}px`, } } // 一番上の親要素の直下にツールチップを配置する mounted() { const app = document.getElementById('app'); app!.appendChild(this.$refs.content as any); } // コンポーネント破壊時にツールチップを削除する beforeDestroy() { const app = document.getElementById('app'); app!.removeChild(this.$refs.content as any); } } </script>解説
表示処理では、
getBoundingClientRect()
を用いてビューポートからの座標を取得します。
ツールチップの表示位置をデフォルトポジションとして調節し、表示座標を算出します。
その後、表示スタイルを有効にするためのフラグを立てます。activate() { const target = event!.target as any; const rect = target!.getBoundingClientRect(); this.contentPosition.top = rect.top + window.pageYOffset + this.defaultPosition.top; this.contentPosition.left = rect.left - this.defaultPosition.left; this.isActive = true; }そして算出された座標をスタイルに渡すゲッターを定義します。
get contentStyleObj() { return { top: `${this.contentPosition.top}px`, left: `${this.contentPosition.left}px`, } }ツールチップは画面描画時には非表示ですが、DOM内には配置しておきます。
一番外側の要素の子要素として配置します。
画面遷移時等のコンポーネント破壊時には配置した要素も忘れず削除するようにしましょう。// 一番上の親要素の直下にツールチップを配置する mounted() { const app = document.getElementById('app'); app!.appendChild(this.$refs.content as any); } // コンポーネント破壊時にツールチップを削除する beforeDestroy() { const app = document.getElementById('app'); app!.removeChild(this.$refs.content as any); }利用コンポーネント
親コンポーネントでは
?
アイコンをactivator
スロットに配置。
またツールチップ内の文章はデフォルトスロットに配置します。<ToolTip class="tool-tip" v-show="toolTipMessage"> <template slot="activator"> <img src="@/assets/img/icon/icon-help.png" alt="help"> </template> <div class="tool-tip__help-text"> {{ toolTipMessage }} </div> </ToolTip>まとめ
少々トリッキーなコンポーネントとなってしまいましたが、
getBoundingClientRect
はこういう使い方もできる点は参考になりますね。
応用もできると思うので、座標を取って何かするような動作のコンポーネントに使うと良いかもしれません。
間違っている点、改善点あればご教授頂けますと幸いです。
- 投稿日:2020-11-24T20:47:56+09:00
Nuxt.js、Firebase、axiosでパパッと掲示板!
この記事の概要
超簡単な掲示板アプリをパパっと作成します。
細かいことはいいからとりあえずNuxt.jsで何かアプリを作ってみたいという方にオススメです。以前書いた記事のNuxtバージョンです。
目標物
開発環境
・macOS Catalina 10.15.7
・@nuxt/cli v2.14.7
・npm 6.9.0
・node v10.16.0前提
・nuxtのプロジェクトが作成済み。
・firebaseのプロジェクトを作成済み。
・Cloud FirestoreのDBを作成済み。axiosの導入
プロジェクト直下で
npm add @nuxtjs/axios
を実行ターミナルプロジェクト名$ npm add @nuxtjs/axiosnuxt.config.jsに以下の様に記述
nuxt.config.jsexport default { //・・・省略 modules: [ '@nuxtjs/axios' ], //・・・省略View作成
pages/index.vue<template> <div> <h1>掲示板!</h1> <br> 名前 <div> <input type="text" v-model="name"> </div>コメント <div> <textarea v-model="comment"></textarea> </div> <br> <button @click="submitPosts">投稿する</button> <br><br> <h2>投稿一覧</h2> </div> </template> <script> export default { deta() { return { name: '', comment: '' } }, methods: { submitPosts() { console.log('submit'); } } } </script>
とりあえず【投稿】ボタンを押したら【submit】と出力させておきましょう。
データを送る
データを送る為に
this.$axios.$post()
を使用します。
第一引数:サーバーのURL
第二引数:データの内容
第三引数:オプション(任意)pages/index.vue<script> export default { deta() { return { name: '', comment: '' } }, methods: { submitPosts() { //----↓ここから-------------- this.$axios.$post( "https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/posts", { fields: { name: { stringValue: this.name }, comment: { stringValue: this.comment } } } ).then(() => { this.name = ''; this.comment = ''; }); //---↑ここまで-------------- } } } </script>URLは
https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/cities/LA
↑を入れます。
こちらに乗ってあるのを持って来ただけです。※※※
しかしこれでは不十分で、URL内のYOUR_PROJECT_ID
の部分を自分のプロジェクトIDに置き換える必要があります。
以下、適宜
YOUR_PROJECT_ID
を自分のプロジェクトIDに置き換える必要があることに注意してください。
YOUR_PROJECT_ID
のままだと以下のエラーが出ます。
次に、URL末尾の
cities/LA
を任意のコレクション名(データを格納する場所の名前)にします。
今回はposts
とします。最終的に↓になります。
https://firestore.googleapis.com/v1/projects/自分のプロジェクトID/databases/(default)/documents/posts,
データが入っています!
データの取得
では今度はサーバーからデータを取ってきましょう。
データの取得はthis.$axios.$get()
を使用します。
第一引数:サーバーのURL
第二引数:オプション(任意)サーバーのURLは
this.$axios.$post()
で使用したものと全く同じです。取得するタイミングはロード時とデータ送信時に行いたいので、
getPosts
メソッドを作り各所で呼び出しましょう。pages/index.vue<script> export default { data() { return { name: '', comment: '' }; }, //----↓ここから-------------- created() { this.getPosts(); }, //----↑ここまで-------------- methods: { submitPosts() { this.$axios.$post( "https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/posts", { fields: { name: { stringValue: this.name }, comment: { stringValue: this.comment } } } ) .then(() => { this.name = ''; this.comment = ''; //----↓ここ-------------- this.getPosts(); }); }, //----↓ここから-------------- getPosts() { this.$axios.$get( "https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/posts" ) .then(res => { console.log(res.documents); }); } //----↑ここまで-------------- } }; </script>
.then(res => {
console.log(res.documents);
});
このres
の中に取得したデータが入っているので確認してみます。
バッチリ入っています。
あとはこの配列をv-for
で順番に表示させていきます。データの表示
・
date
に空配列posts
を準備。
・getPosts
が呼ばれたタイミングでres.data.documents
を配列posts
に格納。
・配列posts
をリストレンダリングしています。pages/index.vue<template> <div> <h1>掲示板!</h1> <br> 名前 <div> <input type="text" v-model="name"> </div>コメント <div> <textarea v-model="comment"></textarea> </div> <br> <button @click="submitPosts">投稿する</button> <br><br> <h2>投稿一覧</h2> <br> <!-----↓ここから--------------------------------------------------------> <div v-for="post in posts" :key="post.name"> <hr> <br> <p>名前:{{post.fields.name.stringValue}}</p> <br> <p>コメント:{{post.fields.comment.stringValue}}</p> <br> </div> <!-----↑ここまで--------------------------------------------------------> </div> </template> <script> export default { data() { return { name: '', comment: '', //----↓ここ-------------------------- posts: '' }; }, created() { this.getPosts(); }, methods: { submitPosts() { this.$axios.$post( "https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/posts", { fields: { name: { stringValue: this.name }, comment: { stringValue: this.comment } } } ).then(() => { this.name = ''; this.comment = ''; this.getPosts(); }); }, getPosts() { this.$axios.$get( "https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/posts" ) .then(res => { //----↓ここ-------------------------- this.posts = res.documents; }); } } }; </script>完成!
ここまで見て頂きありがとうございました!
とりあえず作って動かすを目的にしているので細かい解説はしていません(←できません)
コピペで動かす際はURLの
YOUR_PROJECT_ID
を適宜自身のプロジェクトIDに置き換えることを注意してください。
- 投稿日:2020-11-24T19:55:25+09:00
Javascript再び
前回の復習も兼ねてまた始めてみようと思う。
憎きjavascriptに復讐。出力とコメント
出力って。いちいち言葉が難しい。私は「何らかのデータ(結果)を表示すること」と解釈した。
出力の種類
- window.alert()
- document.write()
- innerHTMLプロパティ
- console.log
console.logから馴染んでいこうと思う
コメントアウトのやり方
- 1行のコメントは「//」
//1行コメントをここに書く
- 複数行のコメントアウトの場合ははじめに
/*開始~終わり*/
を記載します。(cssと同じだね!(コメントアウトって、チーム内でのコードの役割や使い方のメモの他に、自分用でも内容整理のために使っていくと良いと思った。
デバッグについて
console.logを使用したデバッグを使う方法で行っていく(Googleが提供しているChrome Developer Toolsを利用)
演習
//文字列"りんご"を出力 console.log("りんご"); //割り算の結果 console.log(10 / 5); // 34を4で割った余り console.log(34 % 4); //変数の掛け算 let fu = 10; let ba = 4; console.log(fu * ba);var / let / const
JavaScriptで書く「var,let,const」の違いと使い分け方法
var、let、constとは、JavaScriptで変数を宣言する際に使うキーワード。
その中で、letとconstは、ECMAScript2015から採用された、新しい宣言方法のキーワード。var
- varでは再宣言、再代入が可能
varで宣言された変数は関数スコープ(function scope)を持つため、その変数には宣言された関数のどこからでもアクセスできる。let
- letでは再宣言が禁止。
- letで宣言された変数はブロックスコープ(block scope)を持つ。つまり、それらの変数にアクセスできるのは、それらの変数が宣言されたブロック(またはサブブロック)の内側だけ。const
- constでは、再宣言、再代入が禁止const > let > var
varはもうほとんど使わないらしい?▼参考にしたページ
https://codezine.jp/article/detail/11353
https://techacademy.jp/magazine/14872言葉難しすぎるんよ・・・・・。このへんもう少し理解不足だなぁ;
- 投稿日:2020-11-24T19:07:33+09:00
TypeScriptの基本的な型
参考
超TypeScript入門 完全パック(2020)
→非常に分かりやすいです。ありがとうございます。Typescript 型についてのメモ
udemyの講座より学んだTypeScriptの型に関するメモになります。
初学者故に意味を履き違えている箇所がある可能性があります。
お気づきになられた方はお手数ですがご指摘いただけると幸いです。個人的にJavaやC#をかじった経験があるので、TypeScriptの書き方は
非常に馴染みやすく、スッと頭に入りました。
Java、C#のご経験のある方は一度触れてみてはいかがでしょうか!boolean
let hasValue: boolean = true;(因みに)
変数名をマウスホバーすることで、どの型で設定されたかがわかる。
number
numberで整数、負数、不動小数すべてを表現
let count: number = 10; let pi: number = 3.14; let nega: number = -50;
string
let single: string = 'hoge'; let double: string = "hoge"; let back: string = `hoge`;
型注釈と型推論
型注釈
以下の部分
let single: string = 'hoge'型推論
型注釈を書かなかった場合、tsが推測で型を認識する。
let hasValue = true;
→ コレは多分booleanだ!結論
- 基本的には型推論、それ以外の初期化しない場合などに型注釈を用いる
- anyについては型注釈を用いる
オブジェクトに型をつける
型注釈
const person: { name: string; //★ age: number; //★ コロンであることに注意!! } = { name: 'jack', age: 25 }or 型推論で直接オブジェクトリテラルを記述するのがよい。
const person = { name: 'jack', age: 25 }
配列
型注釈あり(特定の型)
const fruits: string[] = ['Apple','Banana','Lemon']型注釈あり(any型)
const fruits: any[] = ['Apple',3,'Banana','Lemon',5]型注釈なし
// Union型になる const fruits = ['Apple',3,'Banana','Lemon',5]
Tuple
要素が決まったオブジェクトのような配列を定義したい場合
※明示的に型注釈をつける必要がある
※初期値にのみ制限をかける。pushする際は制限が無いconst book: [string, number, boolean] = ['business'. 1500, true]
Enum
enumのリテラルに文字列を与えない場合、値は数値の連番でinitializeされる
// enumの命名規則は以下のようにする enum CoffeeSize { SHORT = 'SHORT', TALL = 'TALL', GRANDE = 'GRANDE', VENTI = 'VENTI' } const coffe = { hot: true, size: CoffeeSize.TALL }(enumのコンパイル後)
// オブジェクトになる var CoffeSize; (function (CoffeSize) { CoffeSize["SHORT"] = "SHORT"; CoffeSize["TALL"] = "TALL"; CoffeSize["GRANDE"] = "GRANDE"; CoffeSize["VENTI"] = "VENTI"; })(CoffeSize || (CoffeSize = {}));
any
何でも入れることができる型
素のjavascriptと同じ動きになる
※なるべく使わないようにする
unknown
anyと似ているが、anyより微妙に厳しくなっている
let unknownInput: unknown = 20; let anyInput: input = "hello"; let text: string; text = anyInput; // OK text = unknownInput; // NG // unknownを使用する際 if(typeof(unknownInput) === 'string') { text = unknownInput; }
union
複数の型を受け入れる
// 変数 let unionType :number | string = 10; unionType.toUpperCase(); // エラー unionType = 'hello'; unionType.toUpperCase(); // エラーにならない // 配列 let unionTypes: (number | string)[] = [21, 'hello'];
リテラル
const apple: 'apple' = 'hello' // エラー const num: 20 = 30 // エラー // ■ unionと同時に使う // union + リテラル ≒ Enumのように扱える let clothSize: 'small' | 'medium' | 'large' = 'large'; const cloth: { color: string; size: 'small' | 'medium' | 'large'; } = { color: 'white', size: 'medium' //or small or large }
TypeAlias
型に別名をつけて扱う
type ClothSize = 'small' | 'medium' | 'large'; let clothSize: ClothSize = 'large'; // = let clothSize: 'small' | 'medium' | 'large' = 'large';
関数(宣言時)に型をつける
引数と戻り値につける
// function add(num1[: 引数の型], num2[: 引数の型])[: 戻り値の型] function add(num1: number, num2: number): number { return num1 + num2 }引数の型推論 → anyになる、やらない方がいい
戻り値の型推論 → 冗長になる場合以外は書く戻り値を返さない場合
function sayHello(): void { console.log('Hello!'); } // しかし、上記の場合undefinedは返却される // しかし以下のように型注釈でundefinedを記述するのはtypescriptで許されていない function sayHello(): undefined { }戻り値は無いが明示的にreturnするパターン
function sayHello(): void { console.log('Hello!'); return; } // この場合であればsayHello()自体の型注釈はundefinedでもvoidでも許可される
関数(変数代入時)に型をつける
const anotherAdd: (n1: number, n2: number) => number // ←これは戻り値 = function (num1: number, num2: number): number { return num1 + num2 }; // 左辺か右辺どちらかに型情報があればOK const anotherAdd: (n1: number, n2: number) => number // ←これは戻り値 = function (num1, num2) { return num1 + num2 };アロー関数
const doubleNumber: (num: number) => num = num * 2;関数式の戻り値には :number ではなく => number で表現する
コールバック関数に型をつける
// cb: (num: number): void // → 引数に1つのnumberをとりnumberの戻り値を返す関数 function doubleAndHandle(num: number, cb: (num: number) => number) { const doubleNum = cb(num * 2); console.log(doubleNum); } doubleAndHandle(21, doubleNum => { return doubleNum; }); // コールバック関数の戻り値宣言にvoid,nullを設定すると // その定義した関数は機能しなくなるnever型
戻り値が無い関数につける型
ver3〜登場している。
neverを明示的に付与しない場合の型推論は「: void」// never function error(message: string): never { throw new Error(message); } console.log(error('this is an error'));
- 投稿日:2020-11-24T18:10:41+09:00
続・「シューティングゲームの当たり判定をQRコード読み取りでやってみた」やつをスマホで使えるようにした。
スマホでシューティングゲーム
スマホで使えるようになった!!#protoout pic.twitter.com/XojNglWjiI
— sawa (@sawakoshi_yy) November 24, 2020まずは前回の記事を参照してください。
シューティングゲームの当たり判定をQRコード読み取りでやってみた
構成(前回との変更点)
射撃:Webアプリ内のボタン押下 → obnizのボタン押下
カメラ:フロントカメラ → リアカメラ(背面カメラ)動かしてみたいかたは↓のURLをクリック
https://nervous-ardinghelli-c10e69.netlify.app
※リアカメラがない端末では動きません
※スマホの場合は横向きにしてください
※ご自身のobnizをご用意ください■obnizについては↓を参照
https://obniz.com/ja/products/obnizboardコード
See the Pen QRコードで当たり判定。トリガーはobniz by sawa (@sawakoshi_yy) on CodePen.
課題
・スマホでフルスクリーンにできない
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">上のタグを入れてもブラウザのヘッダー・フッターが消えない
→Webアプリに接続したら、フルスクリーンでカメラを表示させたい・射撃時に音がでない。
var audio = new Audio('https://soundeffect-lab.info/sound/battle/mp3/beamgun1.mp3'); audio.play();上を追加しても音がでない。
・射撃時にヒットしたら、LINEnotifyで通知をしたいがCORSのエラーを解消できない
↓記事参照
CORSのエラーが出たけど解消できなかったときのメモ
- 投稿日:2020-11-24T18:00:02+09:00
prettierの使い方とコーディングルールの登録
prettier
コードの整形ができるツール。ESLintと併用でき、ESLintよりも整形力にすぐれている。
install
npm i prettier // ESLintと併用 + Googleのcoding rule npm i prettier-eslint prettier-eslint-cli eslint-config-googlesetting
登録したいルールを下記のように記述する。
// .prettierrc.js ( new file ) module.exports = { singleQuote: true, semi: false, }packageも書き加える。
// package.json "scripts": { "format": "prettier --write 'src/**/*.{js,json,md,html,ts}'" // + }また、ESLintの設定もできる。
// eslintrc.json ( new file ) { "env": { "browser": true, "es6": true }, "extends": ["eslint:recommended", "plugin:prettier/recommended"], "rules": { "prettier/prettier": [ "error", { "singleQuote": true, "trailingComma": "es5" } ] } }run
npm run format
- 投稿日:2020-11-24T17:52:35+09:00
JavaScriptの変数宣言 let,ver,const
何度調べても覚えられないのでざっくりまとめ
結論
const
定数
一度入力した値は変更されないlet
ローカル変数
一度入力した値を変更できるvar
グローバル変数
いつでもどこでも変更可能
なるべく使わない使い方
上に書いたやつ優先で使う
constが最優先で、次点がlet使用例
雰囲気で感じろ
Sample.jsif(true){ const hogeConst = "hoge1"; let hogeLet = "hoge2"; var hogeVar = "hoge3"; console.log(hogeConst); // hoge1 console.log(hogeLet); // hoge2 console.log(hogeVar); // hoge3 hogeConst = "hoge4" // エラー hogeLet = "hoge5" // hoge5 hogeVar = "hoge6" // hoge6 console.log(hogeLet); // hoge5 console.log(hogeVar); // hoge6 } console.log(hogeConst); // エラー console.log(hogeLet); // エラー console.log(hogeVar); // hoge6まとめ
値の変更 スコープ外からの参照 const 不可 不可 let 可 不可 var 可 可 スコープのところは、上の例で言うと、ifの中で書いたらifの中でしか使えないよってこと
参考文献
ここ見たら全部わかるよ
"巻き上げ"のところは上に書いてないから読んでおいた方がいいかも
https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Grammar_and_Types
- 投稿日:2020-11-24T14:17:51+09:00
javascript 基礎まとめ②
①の基礎まとめの続き
1.関数
Rubyでいう「メソッド」はJavaScriptでいう「関数」
Ruby だとdef メソッド名 処理内容 endこれがJavaScriptだと
function 関数名 (引数) { 処理内容 }例
function hello (){ console.log("こんにちは!") } hello()になる。
ちなみに、↑の書き方は「関数宣言」、↓が「関数式」
hello =function (){ console.log("こんにちは!") } hello() /// こんにちは!と表示される///この2つは読み込む順番が違う。関数式は
hello() hello =function (){ console.log("こんにちは!") } ///エラーになる///としても先にhello = function()、、、の方を読み込まれるので
この書き方はエラーになる。ちなみにこの
function (){は無名関数と言われている。
さらに
function (){}は
() =>{}と書換が可能
書いていくうちに頭は整理されるけど、見返すと
説明わかりづらすぎる。。。
※間違い等あればいただきたいです!
- 投稿日:2020-11-24T13:27:28+09:00
Vue.jsとReact.js
私について
・文系
・プログラミング経験1年
・サマーハッカソン優勝
・大手IT企業を目指して就活中なぜ記事を書くのか?
今まで楽しくてコードはたくさん書いてきたが、ほとんどが動いたら良いやと仕組みやシステムについてあまり考えてこなかった。一流のエンジニアを目指す上で、なんとなくではなく、根拠をもって技術選定ができたりエラーを解決をできるように記事を書いていく。
技術選定の仕方
・自分のやりたい事が実現しやすい
・情報量が多いプログラミングはあくまでも手段でしかなく、プロダクトの価値を最大化させることこそが目的である。またプログラミング言語は、基礎文法は少し変わるぐらいで大した差はない。言語に縛られるよりも、自分が言語に合わせて勉強していくほうが、プロダクトの価値も開発効率も上がる。
Vue.jsとReact.jsどっちが簡単?
結論から言うと、vue.jsのほうが圧倒的に簡単です。
Vue.jsのメリット
①SFC(Single File Component)
HTML/JavaScript/CSSが区別して管理でき、Web制作上がりの僕のような人にはとっつきやすい。②Vue CLIが便利
TypeSclipt、ESLint などの plugin の使用などをコマンドラインの指示に従って簡単に導入できる。③HMR(Hot Module Replacement)
ブラウザ画面の再描画(リロード)すること無しにコードの変更をブラウザに適用してくれるので、手間が減って開発が楽になる。上記3つの点が、僕のような初学者でも簡単に感覚的にコードを書くことができ、かつ多くのことが容易に実現できる。
では、React.jsのメリットって?
①主にクラスベースで実装するため、大規模開発に適している。
②typescriptとの組み合わせが良い。
最後に
手軽さという面でVue.jsを選定してきましたが、これからtypescriptがもっと流行すること、会社での大規模開発に携わるであろうことから、Reactの開発もしていこうと考える。
最後まで読んでいただき、ありがとうございました。
- 投稿日:2020-11-24T11:43:42+09:00
join()メソッドの区切り文字について
join()メソッドを理解する
仕事でjoin()メソッドを使った際に、「配列を文字列に変換できるんだなー」という浅い理解にとどまっていたことを痛感しました。
特に区切り文字について理解不足と感じたので、再度理解を深めるために記載しました。公式を確認
公式を確認します。
join() メソッド 公式きちんと読むととてもわかりやすく記載頂いてます。いつもさらっと必要な箇所だけ流し読みしてしまうので、きちんと読む癖をつけたいなと思います。
説明の抜粋
join() メソッドは、配列 (または配列風オブジェクト) の全要素を順に連結した文字列を新たに作成して返します。区切り文字はカンマ、または指定された文字列です。配列にアイテムが一つしかない場合は、区切り文字を使用せずにアイテムが返されます。
const elements = ['Fire', 'Air', 'Water']; console.log(elements.join()); // expected output: "Fire,Air,Water" console.log(elements.join('')); // expected output: "FireAirWater" console.log(elements.join('-')); // expected output: "Fire-Air-Water"再確認
ここで
配列.join();
と配列.join('');
では挙動が違うことがわかります。
配列.join();
のように、区切り文字を省略した場合
配列の要素はカンマ (",") で区切られます。
配列.join('');
のように、区切り文字を空文字にした場合、
すべての要素の間が区切り文字なしで繋がります。このように配列を何で区切るか設定するとき、設定なし・空文字では挙動が違います。
まとめ
配列を文字列にする、そのくらいの浅い理解でしたが、区切り文字について理解不足でした。他にも理解不足な箇所もあると思うので、引き続き勉強し続けたいと思います。
- 投稿日:2020-11-24T11:11:54+09:00
npmを特定のポート番号で動かしたい
ご無沙汰してます、おおのんです。
一時的に指定したポート番号でnuxtアプリを立ち上げる方法。ターミナルでnuxtアプリを5000番で動かす。npm run dev -- --port 5000はい。
- 投稿日:2020-11-24T10:17:18+09:00
お問い合わせフォーム、jsでバリデーション
contact.jswindow.addEventListener('DOMContentLoaded', () => { // 「送信」ボタンの要素を取得 const submit = document.querySelector('#contact-submit'); // エラーメッセージと赤枠の削除 function reset(input_infomation, error_message){ const input_info = document.querySelector(input_infomation); const err_message = document.querySelector(error_message); err_message.textContent =''; input_info.classList.remove('input-invalid'); }; // 「お名前」入力欄の空欄チェック関数 function invalitName(input_target, error_target, error_message){ const name = document.querySelector(input_target); const errMsgName = document.querySelector(error_target); if(!name.value){ errMsgName.classList.add('form-invalid'); errMsgName.textContent = error_message; name.focus(); name.classList.add('input-invalid'); // 動作を止める return false; }; // 動作を進める return true; }; // 「ふりがな」入力欄の空欄チェック関数 function invalitHurigana(input_target, error_target, error_message){ const hurigana = document.querySelector(input_target); const errMsgHurigana = document.querySelector(error_target); if(!hurigana.value){ errMsgHurigana.classList.add('form-invalid'); errMsgHurigana.textContent = error_message; hurigana.focus(); hurigana.classList.add('input-invalid'); // 動作を止める return false; }; // 動作を進める return true; }; // 「郵便番号」入力欄の空欄チェック関数 function invalitPostal(input_target, error_target, error_message){ const postal = document.querySelector(input_target); const errMsgPostal = document.querySelector(error_target); if(!postal.value){ errMsgPostal.classList.add('form-invalid'); errMsgPostal.textContent = error_message; postal.focus(); postal.classList.add('input-invalid'); // 動作を止める return false; }; // 動作を進める return true; }; // 「住所」入力欄の空欄チェック関数 function invalitAddress(input_target, error_target, error_message){ const address = document.querySelector(input_target); const errMsgAddress = document.querySelector(error_target); if(!address.value){ errMsgAddress.classList.add('form-invalid'); errMsgAddress.textContent = error_message; address.focus(); address.classList.add('input-invalid'); // 動作を止める return false; }; // 動作を進める return true; }; // 「電話番号」入力欄の空欄チェック関数 function invalitTel(input_target, error_target, error_message){ const tel = document.querySelector(input_target); const errMsgTel = document.querySelector(error_target); if(!tel.value){ errMsgTel.classList.add('form-invalid'); errMsgTel.textContent = error_message; tel.focus(); tel.classList.add('input-invalid'); // 動作を止める return false; }; // 動作を進める return true; }; // 「メールアドレス」入力欄の空欄チェック関数 function invalitEmail(input_target, error_target, error_message){ const email = document.querySelector(input_target); const errMsgEmail = document.querySelector(error_target); if(!email.value){ errMsgEmail.classList.add('form-invalid'); errMsgEmail.textContent = error_message; email.focus(); email.classList.add('input-invalid'); // 動作を止める return false; }; // 動作を進める return true; }; // 「会社名」入力欄の空欄チェック関数 function invalitCompany(input_target, error_target, error_message){ const company = document.querySelector(input_target); const errMsgCompany = document.querySelector(error_target); if(!company.value){ errMsgCompany.classList.add('form-invalid'); errMsgCompany.textContent = error_message; company.focus(); company.classList.add('input-invalid'); // 動作を止める return false; }; // 動作を進める return true; }; // 「お問い合わせ内容」入力欄の空欄チェック関数 function invalitContent(input_target, error_target, error_message){ const content = document.querySelector(input_target); const errMsgContent = document.querySelector(error_target); if(!content.value){ errMsgContent.classList.add('form-invalid'); errMsgContent.textContent = error_message; content.focus(); content.classList.add('input-invalid'); // 動作を止める return false; }; // 動作を進める return true; }; // 「送信」ボタンの要素にクリックイベントを設定する submit.addEventListener('click', (e) => { // デフォルトアクションをキャンセル e.preventDefault(); reset('#name-js', '#err-msg-name'); reset('#hurigana-js', '#err-msg-hurigana'); reset('#postal-js', '#err-msg-postal'); reset('#address-js', '#err-msg-address'); reset('#tel-js', '#err-msg-tel'); reset('#email-js', '#err-msg-email'); reset('#company-js', '#err-msg-company'); reset('#content-js', '#err-msg-content'); const focus = () => document.querySelector('#name-js').focus(); // 「お名前」入力欄の空欄チェック if(invalitName('#name-js', '#err-msg-name', 'お名前が入力されていません')===false){ return; }; // 「ふりがな」入力欄の空欄チェック if(invalitHurigana('#hurigana-js', '#err-msg-hurigana', '入力必須です')===false){ return; }; // ひらがなチェック const hurigana = document.querySelector("#hurigana-js"); const errMsgHurigana = document.querySelector("#err-msg-hurigana"); const huriganaCheck = /[^ぁ-んー ]/u; if(hurigana.value.match(huriganaCheck)){ errMsgHurigana.classList.add('form-invalid'); errMsgHurigana.textContent = 'ひらがなで入力してください'; hurigana.focus(); hurigana.classList.add('input-invalid'); return; }else{ errMsgHurigana.textContent =''; hurigana.classList.remove('input-invalid'); hurigana.blur(); }; // 「郵便番号」入力欄の空欄チェック if(invalitPostal('#postal-js', '#err-msg-postal', '入力必須です')===false){ return; }; // 郵便番号形式チェック const postal = document.querySelector("#postal-js"); const errMsgPostal = document.querySelector("#err-msg-postal"); const postalCheck = /([0-9]{7})$/; // const postalCheck = /^\d{7}$/; if(postal.value.match(postalCheck)){ errMsgPostal.textContent =''; postal.classList.remove('input-invalid'); postal.blur(); }else{ errMsgPostal.classList.add('form-invalid'); errMsgPostal.textContent = '郵便番号の形式が違います'; postal.focus(); postal.classList.add('input-invalid'); return; }; // 「住所」入力欄の空欄チェック if(invalitAddress('#address-js', '#err-msg-address', '入力必須です')===false){ return; }; // 「電話番号」入力欄の空欄チェック if(invalitTel('#tel-js', '#err-msg-tel', '入力必須です')===false){ return; }; //電話番号形式チェック const tel = document.querySelector("#tel-js"); const errMsgTel = document.querySelector("#err-msg-tel"); const telCheck = /0\d{1,4}\d{1,4}\d{4}/; if(tel.value.match(telCheck)){ errMsgTel.textContent =''; tel.classList.remove('input-invalid'); tel.blur(); }else{ errMsgTel.classList.add('form-invalid'); errMsgTel.textContent = '電話番号の形式が違います'; tel.focus(); tel.classList.add('input-invalid'); return; }; // 「メールアドレス」入力欄の空欄チェック if(invalitEmail('#email-js', '#err-msg-email', '入力必須です')===false){ return; }; // Email形式チェック const email = document.querySelector("#email-js"); const errMsgEmail = document.querySelector("#err-msg-email"); const emailCheck = /^[-a-z0-9~#&'*/?`\|!$%^&*_=+}{\'?]+(\.[-a-z0-9~#&'*/?`\|!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)|(docomo\ezweb\softbank)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i; if(email.value.match(emailCheck)){ errMsgEmail.textContent =''; email.classList.remove('input-invalid'); email.blur(); }else{ errMsgEmail.classList.add('form-invalid'); errMsgEmail.textContent = 'Emailの形式で入力してください'; email.focus(); email.classList.add('input-invalid'); return; }; // 「会社名」入力欄の空欄チェック if(invalitCompany('#company-js', '#err-msg-company', '入力必須です')===false){ return; }; // 「お問い合わせ内容」入力欄の空欄チェック if(invalitContent('#content-js', '#err-msg-content', '入力必須です')===false){ return; }; document.customerinfo.submit(); }, false); }, false);contact.php<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title></title> <link href="https://fonts.googleapis.com/css?family=Amatic+SC" rel="stylesheet"> <link type="text/css" rel="stylesheet" href="./contact.css"> <script src="https://yubinbango.github.io/yubinbango/yubinbango.js" charset="UTF-8"></script> </head> <body> <?php // require "header.php"; ?> <main> <section class="container container-ornament" id="contact"> <h2 class="container-title"><span>お問い合わせ</span></h2> <div class="container-body"> <div class="container-required"> <p class="Required-title"><span class="Required">※</span>は入力必須項目になります。</p> </div> <form action="contact_db_connect.php" class="form form-m h-adr" method="post" name="customerinfo"> <!-- <form action="" class="form form-m h-adr" method="post" name="customerinfo"> --> <table> <tr> <th class="th"><span class="Required">※</span>お名前</th> <td class="td"> <span class="err_msg" id="err-msg-name"><?php if(!empty($err_msg['name'])) echo $err_msg['name']; ?></span> <input class="input input-l" id="name-js" name="name" type="text" placeholder="例)神戸 太郎" value="<?php if(!empty($_POST['name'])) echo $_POST['name']; ?>" > </td> </tr> <tr> <th class="th"><span class="Required">※</span>ふりがな</th> <td class="td"> <span class="err_msg" id="err-msg-hurigana"><?php if(!empty($err_msg['kana'])) echo $err_msg['kana']; ?></span> <input class="input input-l" id="hurigana-js" name="kana" type="text" placeholder="例)こうべ たろう" value="<?php if(!empty($_POST['kana'])) echo $_POST['kana']; ?>" > </td> </tr> <span class="p-country-name" style="display:none;">Japan</span> <tr> <th class="th"><span class="Required">※</span>郵便番号</th> <td class="td"> <span class="err_msg" id="err-msg-postal"><?php if(!empty($err_msg['zip'])) echo $err_msg['zip']; ?></span> <input type="text" class="input input-l p-postal-code" id="postal-js" name="zip" size="8" maxlength="8" placeholder="ハイフン無し" value="<?php if(!empty($_POST['zip'])) echo $_POST['zip']; ?>" > </td> </tr> <tr> <th class="th"><span class="Required">※</span>住所</th> <td class="td"> <span class="err_msg" id="err-msg-address"><?php if(!empty($err_msg['addr'])) echo $err_msg['addr']; ?></span> <input type="text" class="input input-l p-region p-locality p-street-address p-extended-address" id="address-js" name="addr" placeholder="住所" value="<?php if(!empty($_POST['addr'])) echo $_POST['addr']; ?>" > </td> </tr> <tr> <th class="th"><span class="Required">※</span>電話番号</th> <td class="td"> <span class="err_msg" id="err-msg-tel"><?php if(!empty($err_msg['tel'])) echo $err_msg['tel']; ?></span> <input class="input input-l" id="tel-js" name="tel" type="tel" placeholder="例)09012345678 半角 ハイフンなし" maxlength="13" value="<?php if(!empty($_POST['tel'])) echo $_POST['tel']; ?>" > </td> </tr> <tr> <th class="th sp-br"><span class="Required">※</span>メール<br>アドレス</th> <td class="td"> <span class="err_msg" id="err-msg-email"><?php if(!empty($err_msg['email'])) echo $err_msg['email']; ?></span> <input class="input input-l" id="email-js" name="email" type="email" placeholder="例)example@.com" value="<?php if(!empty($_POST['email'])) echo $_POST['email']; ?>" > </td> </tr> <tr> <th class="th"><span class="Required">※</span>会社名</th> <td class="td"> <span class="err_msg" id="err-msg-company"><?php if(!empty($err_msg['company'])) echo $err_msg['company']; ?></span> <input type="text" class="input input-l" id="company-js" name="company" placeholder="例)〇〇〇〇株式会社" value="<?php if(!empty($_POST['company'])) echo $_POST['company']; ?>" > </td> </tr> <tr> <th class="th">部署名</th> <td class="td"> <input type="text" class="input input-l" name="department" placeholder="" value="<?php if(!empty($_POST['company'])) echo $_POST['company']; ?>" > </td> </tr> <tr> <th class="th"><span class="Required">※</span>お問い合わせ内容</th> <td class="td"> <span class="err_msg" id="err-msg-content"><?php if(!empty($err_msg['text'])) echo $err_msg['text']; ?></span> <textarea class="input input-l input-textarea mb-xxl" id="content-js" name="text" placeholder="お問い合わせ内容" value="<?php if(!empty($_POST['text'])) echo $_POST['name']; ?>" ></textarea> </td> </tr> </table> <button class="btn btn-corp btn-l" id="contact-submit">送信</button> </form> </div> </section> </main> <footer class="footer"> </footer> <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script> <script src="./app.js"></script> <script src="./contact.js"></script> </body> </html>
- 投稿日:2020-11-24T10:00:16+09:00
Auth0のJavaScriptチュートリアルをシンプルな構成で試してみた
Auth0はドキュメントやチュートリアルが充実していますが、それを参考にしつつシンプルな構成でお試ししてみました。
Auth0について
下記に概要をまとめていますので、ご参考ください。
Auth0の機能を調べてみた - Qiita
https://qiita.com/kai_kou/items/b3dd64e4f8f53325020eAuth0(オースゼロ)とはAuth0, Inc.が提供するiDaaS(アイダース)で、Identity as a Serviceの略となりクラウドでID管理・認証機能などを提供してくれるサービスです。
iDaaSが提供する機能としては認証・シングルサインオン、ID管理・連携、認可、ログなどによる監査などがあり、Auth0もそれらを提供しています。アカウントを作成する
Auth0には無償プランがありますので、それを利用します。
Pricing - Auth0
https://auth0.com/pricing/Auth0のサイトの右上にある「サインアップ」からサインアップ画面へ移動します。
Auth0: Secure access for everyone. But not just anyone.
https://auth0.com/jp/メールアドレス、GitHub、Google、Microsoftアカウントを利用してサインアップできます。
サインアップできるとAuth0の設定に進みます。専用のドメインとリージョンが指定できます。サブドメイン部分は任意で変更可能です。
次に法人・個人利用か選択してサインアップ後の設定は完了となります。
アプリケーション設定
アカウントが作成できたら次にAuth0で認証ができるようにAuth0のApplicationを設定します。
Auth0のダッシュボードが表示されますので、左メニューから「Applications」を選択してApplications画面へ移動します。
Default App
というApplicationがすでに用意されていますので、それを利用します。
Default App
を選択するとApplicationの設定が確認できます。
今回試用するのに必要となる設定は以下となります。
- Application Properties
- Application Type:
Single Page Application
- Application URIs
- Allowed Callback URLs:
http://localhost:3000
- Allowed Logout URLs:
http://localhost:3000
- Allowed Web Origins:
http://localhost:3000
設定できたら画面下部にある「SAVE CHANGES」ボタンで設定を保存します。
他の設定をみてみると最初からGoogleアカウントを利用したログイン設定がすでにされていました。
クライアント実装
クライアントの実装をするのに、Applicationの「Quick Start」からサンプルを確認することができます。今回はJavaScriptのサンプルを利用します。
チュートリアルがとても丁寧でわかりやすいので本来はチュートリアルどおりに進めるのがよいと思います。実際に動作するコードはダウンロードまたはGitHubから取得することもできます。
構成
今回はサンプルを参考にして下記のような実装をしました。CSSは少なかったので
index.html
にまとめています。
Auth0のSDKはhttps://cdn.auth0.com/
で提供されているのでHTMLのscript
タグで読み込めば利用できます。
- index.html
- js
- app.js
- auth_config.json
index.html<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>SPA SDK Sample</title> <style> .hidden { display: none; } label { margin-bottom: 10px; display: block; } </style> </head> <body> <h2>SPA Authentication Sample</h2> <p>Welcome to our page!</p> <button id="btn-login" disabled="true" onclick="login()">Log in</button> <button id="btn-logout" disabled="true" onclick="logout()">Log out</button> <div class="hidden" id="gated-content"> <p> You're seeing this content because you're currently <strong>logged in</strong>. </p> <label> Access token: <pre id="ipt-access-token"></pre> </label> <label> User profile: <pre id="ipt-user-profile"></pre> </label> </div> <script src="https://cdn.auth0.com/js/auth0-spa-js/1.9/auth0-spa-js.production.js"></script> <script src="js/app.js"></script> </body> </html>Auth0の機能はSDKに含まれる
createAuth0Client
にauth_config.json
のパラメータを渡して実行することで利用できるようになります。今回利用しているメソッドは以下となります。
- createAuth0Client: 初期化や再認証など
- auth0.isAuthenticated: 認証済みか
- auth0.getTokenSilently: トークン取得
- auth0.getUser: ログインしているユーザーの情報取得
- auth0.handleRedirectCallback: Auth0からの認証結果確認、トークン保存、セッション設定
- auth0.loginWithRedirect: Auth0のログイン画面へ移動
- auth0.logout: ログアウト実行
js/app.jslet auth0 = null; const fetchAuthConfig = () => fetch("/auth_config.json"); const configureClient = async () => { const response = await fetchAuthConfig(); const config = await response.json(); auth0 = await createAuth0Client({ domain: config.domain, client_id: config.clientId }); }; const updateUI = async () => { const isAuthenticated = await auth0.isAuthenticated(); document.getElementById("btn-logout").disabled = !isAuthenticated; document.getElementById("btn-login").disabled = isAuthenticated; if (isAuthenticated) { document.getElementById("gated-content").classList.remove("hidden"); document.getElementById( "ipt-access-token" ).innerHTML = await auth0.getTokenSilently(); document.getElementById("ipt-user-profile").textContent = JSON.stringify( await auth0.getUser() ); } else { document.getElementById("gated-content").classList.add("hidden"); } }; window.onload = async () => { await configureClient(); updateUI(); const isAuthenticated = await auth0.isAuthenticated(); if (isAuthenticated) { return; } const query = window.location.search; console.log(query); if (query.includes("code=") && query.includes("state=")) { await auth0.handleRedirectCallback(); updateUI(); window.history.replaceState({}, document.title, "/"); } }; const login = async () => { await auth0.loginWithRedirect({ redirect_uri: window.location.origin }); }; const logout = () => { auth0.logout({ returnTo: window.location.origin }); };こちらはAuth0のドメインやClient IDを保持するファイルになりますので、それぞれで内容を変更する必要があります。
auth_config.json{ "domain": "xxxxx.us.auth0.com", "clientId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }サーバ起動
Auth0のサンプルだとサーバはnode.jsを利用していましたがもっとシンプルにPythonでWebサーバを起動します。Pythonのバージョンは3前提です。
参考) Pythonの標準ライブラリでさくっとAPIサーバとWebサーバを立ち上げる - Qiita
https://qiita.com/kai_kou/items/6cf5930330b85fa583b0> cd index.htmlがあるディレクトリ > python -m http.server 3000 Serving HTTP on :: port 3000 (http://[::]:3000/) ...Webサーバが立ち上がったら
http://localhost:3000
へアクセスします。
動作確認
ブラウザに表示されたページでログイン、ログアウトを試してみます。
「Log in」ボタンをクリックすると、auth_config.json
で指定したAuth0のドメインへ遷移します。
「Sign in with Google」でGoogleアカウントを利用したサインアップをします。
Googleアカウントから戻ってくるとAuth0側でGoogleから提供されるプロファイルの利用可否を確認されますので、「チェック」ボタンをクリックします。
ログインが完了すると、
http://localhost:3000
へコールバックで戻ってきてトークンやアカウント情報が取得できたのが確認できます。「Log out」ボタンをクリックするとログアウトされます。
Auth0のユーザ設定をみるとユーザー登録されたことが確認できます。
Auth0の設定を最低限にしていますので、このままだと、ログイン後、ブラウザのリロードをしてもログイン状態が保持されなかったりしますが、Auth0側で設定変更すれば対応可能となります。
まとめ
実装はシンプルですが、Auth0を利用した認証ができるようになりました。
この実装のまま外部向けのサービスやサイトを公開するのはセキュリティ上あまりよろしくありませんが、Auth0の調査目的として利用するのであれば、Auth0側の設定を変更しつつ挙動を確認してAuth0の仕組みを把握するのには十分利用できそうです。参考
Auth0の機能を調べてみた - Qiita
https://qiita.com/kai_kou/items/b3dd64e4f8f53325020ePricing - Auth0
https://auth0.com/pricing/Auth0: Secure access for everyone. But not just anyone.
https://auth0.com/jp/Pythonの標準ライブラリでさくっとAPIサーバとWebサーバを立ち上げる - Qiita
https://qiita.com/kai_kou/items/6cf5930330b85fa583b0
- 投稿日:2020-11-24T07:54:10+09:00
React hooksを基礎から理解する (useRef編)
React hooksとは
React 16.8 で追加された新機能です。
クラスを書かなくても、state
などのReactの機能を、関数コンポーネントでシンプルに扱えるようになりました。
- React hooksを基礎から理解する (useState編)
- React hooksを基礎から理解する (useEffect編)
- React hooksを基礎から理解する (useContext編)
- React hooksを基礎から理解する (useReducer編)
- React hooksを基礎から理解する (useCallback編)
- React hooksを基礎から理解する (useMemo編)
- React hooksを基礎から理解する (useRef編) 今ここ
useRefとは
関数コンポーネントでは、Classコンポーネント時のref属性の代わりに、
useRef
を使って要素への参照を行います。
またuseRef
では、useState
のようにコンポーネント内での値を保持することが出来ます。構文
const refObject = useRef(initialValue) //例 const number = useRef(100); console.log(number.current); // 100
useRef
は、.current
プロパティが渡された引数(初期値はinitialValue)をrefObject
へ返します。
この引数の値が書き換え可能な.current
プロパティーの値であり、.current
プロパティ内に保持することができます。DOMを参照したい場合
const inputElement = useRef(null) //例 <input ref={inputElement} type="text" /> console.log(inputElement); // current: nullDOMの参照例
useRef
でrefオブジェクトを作成したものをref属性(HTML要素)に指定してDOMを参照しています。const App = () => { const inputEl = useRef(null); const handleClick = () => { inputEl.current.focus(); console.log("inputEl.current:", inputEl.current); //inputEl.current: <input type="text"> }; return ( <> <input ref={inputEl} type="text" /> <button onClick={handleClick}>入力エリアをフォーカスする</button> </> ); };ボタンクリックで
<input type="text">
がfocusされました。
参照:React公式サイトuseRefとuseStateをくらべてみる
useRefを使ってDOMを参照
useRefを利用すると
text
のstate更新時にのみコンポーネントの再描画が発生します。const App = () => { const inputEl = useRef(null); const [text, setText] = useState(""); const handleClick = () => { setText(inputEl.current.value); }; console.log("描画!!"); return ( <> <input ref={inputEl} type="text" /> <button onClick={handleClick}>set text</button> <p>テキスト : {text}</p> </> ); };useStateを使ってDOMを参照
入力中の文字列をステート
inputElement
に格納、ボタンが押された時にsetText(inputElement)
でtext
stateへ代入することでuseRef
を使った時と同じ挙動にしています。この場合、text
とinputElement
のstate更新時の両方でコンポーネントの再描画が発生しています。const App = () => { const [inputElement, setInputElement] = useState(""); const [text, setText] = useState(""); const handleClick = () => { setText(inputElement); }; console.log("描画!!"); return ( <> <input value={inputElement} onChange={(e) => setInputElement(e.target.value)} type="text" /> <button onClick={handleClick}>setText</button> <p>テキスト : {text}</p> </> ); };
useState
を利用している場合はstateの変更される度にコンポーネントの再描画が発生しますが、useRef
は値が変更になっても、コンポーネントの再描画が発生しません。useRef
を利用するかどうかの判断基準のひとつは、コンポーネントの再描画が発生するかどうかで良さそうです。
- 投稿日:2020-11-24T06:21:04+09:00
スクロールして画面に表示されたらイベントが発生する
やりたいこと
スクロールして要素が画面に表示されると同時にイベントを発生させたい。
stylesheet.scss$(window).scroll(function () { $(".slide").each(function (index, el) { let scroll = $(window).scrollTop(); let offset = $(el).offset().top; let winH = $(window).height(); if (scroll > offset - winH / 2) { $(el).addClass("in"); } }); });表示させたい要素のクラス名が「slide」で、複数の要素に当てたいので、「each」を使って繰り返し処理をかけています。
とりあえず画面中央まで来たらイベントが発生するように記述していますが、winH/2の2の部分を、小さい数字に変えていくと、早くイベントを発生させることができますし、大きい数字に変えると遅くイベントを発生させることができます。
結論
elが何者かよくわかっていないのでこれからも勉強します。。
thisのようなものだとは聞いたがイマイチ…
- 投稿日:2020-11-24T05:49:15+09:00
【JavaScript】クリックが要素の内側か外側か判断するプログラムを作った【addEventListener】
はじめに
addEventListener
のコールバック関数へ引数を渡す方法はいくつかありますが、
引数を渡せるようにしたコールバック関数をとるイベントリスナを、
removeEventListener
で解除するのは、結構苦労するものです。この記事は
-addEventListener
のコールバック関数に引数を渡し
- なおかつ任意のタイミングでremoveEventListener
を可能とし、
- ひとつのプログラム上でそれらをどう実現するか
を実践してみた結果と道のりを記した記事となります
(私の以前の記事の実践編となります)※ES6以前の
JavaScript
を使用しています目指したもの
今回目指すものはこちらです
※赤いサークルが現れているときにクリックしています動画のとおり、
クリックしたら入力フォームが現れて、
入力フォームの内側をクリックしている限りはフォームはそのままですが
入力フォーム以外の何でもクリックしたらフォームが閉じる
そんなプログラムを制作します。出来上がったもの
以下、制作過程になります
ひな形
今回作成するもののひな形のHTMLとCSSです。
See the Pen
JjKgLgY by Kabooley (@kabooley)
on CodePen.
クリックイベントに一度だけ発火するリスナ
ここの"+ Add new card..."
のボックスをクリックしたら、
フォームが登場するようにし、
フォームが登場している間は、
アプリケーション全体にclick
イベントを検知できるようにしていきます。1. フォームにclickイベントリスナを設置する
まずフォームをクリックされたら検知できるようにします。
このクリック検知は一回限りにしたいので、
その場で解除します。/**************************************************** * @param: * formElement はフォーム要素、div.form-partのこと * * div.form-partがクリックされたらコンソールに'click!'と出力し * 以降は発火しません。 * * */ var addEventListenerToForm = function(formElement) { formElement.addEventListener('click', (function(){ return function callback(event) { console.log('click!'); // 例) // 引数、Eventオブジェクト両方とることができる someFunctionRequiresArgument(event, target); formElement.removeEventListener('click', callback, false); } })(), false); }即時関数を
addEventListener
のコールバック関数として渡し、
即時関数に内部関数をこさえてクロージャとします。クロージャ内部で呼び出す関数は
addEventListener
の
コールバック関数としての責務を負わないので
その引数を自由にセットできます。ここでは
formElement.addEventListener()
のコールバック関数は、
callback
になるので、
その解除にはcallback
が必須になります。内部関数内で
removeEventListener
を実行することで、
一度きりの発火を実現できます。2. HTML全体でclickイベント検知できるようにする
次に、フォームが登場している限り、
HTML全体でclick
イベントを検知できるようにします。
先ほどと同様に即時関数とクロージャの組み合わせを利用します。/********************************************* * @param: * target is 'div.form-part' element. * * */ var addEventListenerToDocument = function(target) { document.addEventListener('click', (function(){ return function callback(event) { console.log('click-event has detected!'); console.log(event); document.removeEventListener('click', callback, false); } })(), false); } // div.form-partにイベントリスナをセットする関数 var addEventListenerToForm = function(formElement) { console.log('f set addEventListener to forms'); formElement.addEventListener('click', (function(){ return function callback(event) { // // addEventListenerをセットする関数を呼び出す // addEventListenerToDocument(formElement); formElement.removeEventListener('click', callback, false); } })(), false); }これでどこをクリックしてもこのイベントリスナが発火します。
click-event has detected! MouseEvent{}
フォームが現れている間は、
フォーム内部をクリックしている限りはフォームはそのままで、
フォーム以外のどれをクリックしてもフォームを閉じるようにしたいので
フォームのどこをクリックされたのか把握できるようにします。これには
MouseEvent
オブジェクト内部にpath
というプロパティがあり、
そこにはclick
された要素からhtml
の間に存在する
直系の親要素たちが含まれます
(すごく雑な説明)となるとフォーム要素である
div.form-part
が含まれている限り、
そのクリックは必ずフォーム内部をクリックしたと判定できます。var addEventListenerToDocument = function(target) { document.addEventListener('click', (function(){ return function callback(event) { // 内か外か判定する if(event.path.includes(target)) { console.log('inside'); return; } else { console.log('outside'); document.removeEventListener('click', callback, false); } } })(), false); }出力結果
これで内側か外側か判断できるようになりました。
あとは入力フォームの処理を追加していくだけです。
(この記事では省きます)スコープ外でイベントリスナを解除する
スコープ外の何が問題なのか
さて、クリックされたら現れるフォームへ、実際に入力して
Today's Taskに新たなカードを追加できるようになったとします。
たとえばこのフォームだと、
外側をクリックするイベントと閉じるボタンをクリックするイベントが発火したら、
どちらも「フォームを閉じる」という共通の処理を行います。共通の処理は次の通りとなります
- フォームを閉じる
- documentのclickイベントリスナを解除する
- もう一度フォームにclickイベントリスナをセットする外側をクリックされたときに実行される関数と、
閉じるボタンをクリックされたときに実行される関数は
お互いスコープ外です
(内部で定義されたものに関して、という意味です)なので、
document.removeEventListener
で解除しようにも、
解除に必須なcallback
にアクセスできないため解除できない問題が生じます。/******************************************** * @param: * parent is 'div.form-part' element. * * * */ var closeBtnClickHandler = function(parent) { var close = document.querySelector('button.close'); close.addEventListener('click', function(event){ // フォームを閉じるための共通の処理を実行 // - フォームを閉じる // - documentのclickイベントリスナを解除する // - もう一度フォームにclickイベントリスナをセットする // // 問題:callbackがスコープ外だから解除できない! document.removeEventListener('click', callback, false); // callbackが不明なので無視される }, false); } var addEventListenerToDocument = function(target) { document.addEventListener('click', (function(){ return function callback(event) { if(event.path.includes(target)) { console.log('inside'); return; } else { console.log('outside'); // // 閉じるボタン処理関数の呼出し // closeBtnClickHandler(target); // フォームを閉じるための共通の処理を実行 // - フォームを閉じる // - documentのclickイベントリスナを解除する // - もう一度フォームにclickイベントリスナをセットする } } })(), false); }引数としてコールバック関数を外部へ渡す
解決方法はとっても簡単で、
callback
をcloseBtnClickHandler
へ引数として渡せばいいだけでした/******************************************** * @param: * parent is 'div.form-part' element. * * * */ var closeBtnClickHandler = function(parent, callback) { var close = document.querySelector('button.close'); close.addEventListener('click', function(event){ // フォームを閉じるための共通の処理を実行可能 // 解除できる! document.removeEventListener('click', callback, false); }, false); } var addEventListenerToDocument = function(target) { document.addEventListener('click', (function(){ return function callback(event) { if(event.path.includes(target)) { console.log('inside'); return; } else { console.log('outside'); // // 閉じるボタン処理関数の呼出し // closeBtnClickHandler(target, callback); // フォームを閉じるための共通の処理を実行可能 } } })(), false); }今回は短い簡単なプログラムなのでこれでも問題ないですが、
ネストが深いところの関数にcallback
を送らなくてはならない場合、
ただそれを実現するために間の関数でバケツリレーをしなくてはならなくなり、
非常に無駄が多いです。なのでこれを回避するために工夫を施します。
バケツリレー回避案
ということで筆者の少ない頭を絞って出てきたのは
両関数の共通のスコープに
document.removeEventListener
が実現可能なクロージャを用意する案を
思いつきました。var canceler; /************************************************ * @param: * documentELCallback is callback function for document.addEventListener('click') * * closingFormListElement is div.form-part * */ var closeFormHandler = function(documentELCallback) { return function closeForm (closingFormListElement) { // フォームを閉じる関数 toggleForm(false, closingFormListElement); // documentクリックイベント検知の解除 document.removeEventListener('click', documentELCallback, false); // フォームのクリックイベントリスナの再設置 addEventListenerToForm(closingFormListElement); } } var closeBtnClickHandler = function(parent) { var close = document.querySelector('button.close'); close.addEventListener('click', function(event){ // フォームを閉じる処理実行 canceler(parent); }, false); } var addEventListenerToDocument = function(target) { document.addEventListener('click', (function(){ return function callback(event) { // クロージャのインスタンスをここで作成 canceler = closeFormHandler(callback); if(event.path.includes(target)) { console.log('inside'); return; } else { console.log('outside'); closeBtnClickHandler(target, callback); canceler(target); } } })(), false); }完成品
See the Pen JjKgZPO by Kabooley (@kabooley) on CodePen.
さいごに
以上で、引数を渡すことが可能・あとから解除可能・どこでも解除可能を(なんとか)実現したプログラムができました。
目からうろこな解決策は思いつきませんでしたが、
個人的には引数でコールバック関数を渡せばいいという
(至極当たり前な)解決策を身に着けることができてよかったとは思っています。こちらの記事がご覧になってくださった方へ、少しでも何かの役に立っていただけたら幸いです。
というのは定型句ですが、
そこ間違っているよ、もっといい方法があるよなどの
先達の皆様からのご指摘をどしどしいただきたいので、
ぜひコメントなり何なりでご指摘くださいましたら本当に助かります。
よろしくお願いします。ご覧いただきありがとうございました。
補足
- こちらの記事はJavaScript初心者がその学習過程をアウトプットした記事となります。
- innerHTML使っているけどここでは正規表現などを用いた入力内容の検査はしてません。
- 関数名のセンスのなさ
- 投稿日:2020-11-24T05:46:13+09:00
ドラッグで移動できるdialogタグをざっくりと作る
<dialog>
タグとは
<dialog>
: ダイアログ要素 - MDN web docs
https://developer.mozilla.org/ja/docs/Web/HTML/Element/dialogまともに動くのはChromeとChromeベースのEdgeだけですね。
使いどころが難しいですが、社内システムとかで使う分には良いかなーって感じですね。まずはダイアログを表示するコード
<dialog id="dialog"> <h1>hello</h1> </dialog> <button id="show">show</button> <script> const showBtn = document.querySelector('#show'); const dlg = document.querySelector('#dialog'); showBtn.addEventListener('click', () => { dlg.showModal() }); </script>
dialog要素
はデフォルト非表示になります。
画面にはshow
ボタンしか表示されてませんね。
dialog要素
はjsと共に使用され、dialog要素.showModal()
で表示できます。モーダルの枠線とかはデフォルトです。
モーダルの背景が若干灰色になってますね。
次はスタイルをいじってみましょう。スタイルをいじる
<style> /* モーダル */ dialog { border: none; border-radius: 10px; } /* モーダルの背景 */ dialog::backdrop { background-color: rgba(0, 0, 0, 0.5); } </style>枠線を消して、角丸を入れてみました。
dialog::backdrop
はモーダルの背景の疑似要素です。半透明の黒を設定してみました。ドラッグできるようにする
<style> /* モーダル */ dialog { border: none; border-radius: 10px; } /* モーダルの背景 */ dialog::backdrop { background-color: rgba(0, 0, 0, 0.5); } /* モーダル内のテキストとかを選択不可にする */ dialog * { user-select: none; } </style> <dialog id="dialog" draggable="true"> <h1>hello</h1> </dialog> <button id="show">show</button> <script> const showBtn = document.querySelector('#show'); const dlg = document.querySelector('#dialog'); showBtn.addEventListener('click', () => { dlg.showModal() }); /** * モーダルのどこをつかんで移動を開始したか保存する用 */ let mouse = { x: 0, y: 0, }; /** * モーダルのどこをつかんで移動を開始したか保存する * ドラッグ時についてくる半透明の要素を空のdivにして消す */ dlg.addEventListener('dragstart', evt => { mouse.y = dlg.offsetTop-evt.pageY; mouse.x = dlg.offsetLeft-evt.pageX; evt.dataTransfer.setDragImage(document.createElement('div'), 0, 0); }); /** * ドラッグ時の座標を */ dlg.addEventListener('drag', evt => { if (evt.x === 0 && evt.y === 0) return; const top = evt.pageY + mouse.y; const left = evt.pageX + mouse.x; const right = window.outerWidth - evt.pageX; dlg.style.top = top + 'px'; dlg.style.left = left + 'px'; dlg.style.right = right + 'px'; }); /** * モーダルのどこをつかんで移動を開始したかをリセットする */ dlg.addEventListener('dragend', evt => { mouse = { x: 0, y: 0, }; }); </script>こんな感じで移動できます。
ドラッグ開始したマウスの位置を設定しないと、マウスの位置がモーダルの左上となります。また、
dialog要素
はデフォルトでdialog { left: 0; right: 0; position: absolute; ...他 }が入ってるのでleftとrightの両方を設定します。
evt.dataTransfer.setDragImage(document.createElement('div'), 0, 0);
は無くても良いですが、半透明の要素がくっついてきます。おしまい
ライブラリも何もなしにモーダル表示できるのは楽ですね。
ちなみにdialog要素
はform要素
を内包して使うことが多いです。<dialog> <form method="dialog"> <button value="ok">OK</button> </form> </dialog> <script> document.querySelector('dialog').addEventListener('close', () => { console.log( document.querySelector('dialog').returnValue ); }); </script>
method="dialog"
を設定してsubmitするとモーダルが非表示になります。
OKのボタンが押されるとok
という文字列をjsで受け取ることもできます。皆さんも使ってみてはいかがでしょうか?
- 投稿日:2020-11-24T03:55:55+09:00
UserScriptでの@includeで詰まったので
atcoder関連のUserScriptを書いていて、
https:// atcoder.jp/users のみでスクリプトを実行したいが
https:// atcoder.jp/users/kemkemG0?graph=rank や、
https:// atcoder.jp/users/kemkemG0/history 等では実行してほしくない ということがあった。シンプルに
// @include *://atcoder.jp/users* // @exclude *://atcoder.jp/users/*?graph=rank // @exclude *://atcoder.jp/users/*/history*でよかった。