20211124のJavaScriptに関する記事は29件です。

Day.js を使って誕生日から今何歳かを計算したい

やりたいこと Day.js を使って誕生日から年齢を計算したい。 正攻法 const birthday = { year: 1995, month: 12, date: 6, }; const today = dayjs(); const { year, month, date } = birthday; const dayjsBirthday = dayjs(`${year}-${month}-${date}`); console.log(today.diff(dayjsBirthday, 'year')); 他の方法はないか Day.js の RelativeTime プラグインを使えないか RelativeTime プラグインとは import relativeTime from "dayjs/plugin/relativeTime" dayjs.extend(relativeTime) dayjs('1999-01-01').fromNow() // 20 years ago 主な用途 デフォルトのしきい値だと2年未満が曖昧 Range Key Sample Output 0 to 44 seconds s a few seconds ago 45 to 89 seconds m a minute ago 90 seconds to 44 minutes mm 2 minutes ago ... 44 minutes ago 45 to 89 minutes h an hour ago 90 minutes to 21 hours hh 2 hours ago ... 21 hours ago 22 to 35 hours d a day ago 36 hours to 25 days dd 2 days ago ... 25 days ago 26 to 45 days M a month ago 46 days to 10 months MM 2 months ago ... 10 months ago 11 months to 17months y a year ago 18 months+ yy 2 years ago ... 20 years ago しきい値はカスマイズできる extend 時にしきい値を thresholds という名前で渡してあげる。 ( updateLocale を使っても渡せるらしい。今回は未検証。) import relativeTime from "dayjs/plugin/relativeTime" // strict thresholds const thresholds = [ { l: 's', r: 1 }, { l: 'm', r: 1 }, { l: 'mm', r: 59, d: 'minute' }, { l: 'h', r: 1 }, { l: 'hh', r: 23, d: 'hour' }, { l: 'd', r: 1 }, { l: 'dd', r: 29, d: 'day' }, { l: 'M', r: 1 }, { l: 'MM', r: 11, d: 'month' }, { l: 'y' }, { l: 'yy', d: 'year' }, ]; dayjs.extend(relativeTime, { thresholds, }); thresholds の書き方がドキュメントに載っていない問題 コードから読み解く この辺を読む l: key label (しきい値のキーとなるラベル、新しいキーを追加したりできる) r: range (しきい値、最後の要素では定義不要) d: unit of diff (diff メソッドで計算する際の単位、単位が変わるときだけ定義) // default threasholds [ { l: 's', r: 44, d: 'second' }, { l: 'm', r: 89 }, { l: 'mm', r: 44, d: 'minute' }, { l: 'h', r: 89 }, { l: 'hh', r: 21, d: 'hour' }, { l: 'd', r: 35 }, { l: 'dd', r: 25, d: 'day' }, { l: 'M', r: 45 }, { l: 'MM', r: 10, d: 'month' }, // 46 days to 10 months { l: 'y', r: 17 }, // 11 months to 17 months { l: 'yy', d: 'year' } // 18 months+ ] 誕生日を計算してみる import relativeTime from "dayjs/plugin/relativeTime" const thresholds = [ { l: 's', r: 44, d: 'second' }, { l: 'm', r: 89 }, { l: 'mm', r: 44, d: 'minute' }, { l: 'h', r: 89 }, { l: 'hh', r: 21, d: 'hour' }, { l: 'd', r: 35 }, { l: 'dd', r: 25, d: 'day' }, { l: 'M', r: 45 }, { l: 'MM', r: 11, d: 'month' }, // 46 days to 11 months { l: 'y', r: 23 }, // 12 months to 23 months { l: 'yy', d: 'year' } // 24 months+ ] const rounding = Math.floor; // default is Math.round dayjs.extend(relativeTime, { thresholds, rounding, }); // input data const birthday = { year: 1995, month: 12, date: 6, }; const { year, month, date } = birthday; const dayjsBirthday = dayjs(`${year}-${month}-${date}`); console.log(dayjsBirthday.fromNow()); CodePen: https://codepen.io/ezawa800/pen/PoKrmGa 結論: この用途では実用性はない Day.js の RelativeTime プラグインを理解するのには良さそうなサンプルだが、実際に正確に年齢計算するなら diff メソッドでよい。 参考 Day.jsで相対日時を厳密に表示する(thresholds) - Zenn https://zenn.dev/catnose99/articles/ba540f5c233847 Thank you
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

逆引き!プロレス技図鑑!! #I♡プロレス

観戦中に、技名が分からない!!! 皆さんが好きなプロレス技は何ですか?? 私は断然、ラウンディング・ボディ・プレス。 リングコーナーに立ってこれから飛ぶぞと存分に客席にアピールしながら、誰よりも高い位置から高く飛び相手に一撃を食らわせる姿はとにかくかっこいい。受ける選手も相手の見せ場を作るように計算している。最高にエンターテイメントを感じる瞬間である。 初心者観戦あるあるだが、会場に実況のアナウンスは入らないため技が決まって会場が盛り上がっても「今のなんて技だったんだろう……?」ということが発生する。 あまり知られていないことだが、プロレス観戦は動画撮影はNGだが、写真撮影はOKなことがほとんどである。ならば、観戦中に撮影して技名を画像認識できればいいのではないか。 プロレス観戦初心者も安心!?『プロレス技図鑑』 <使い方> 1.  アプリを起動し、プロレス観戦をする。 2.  技が決まりそうなタイミングで、アプリをかざし、技名を確認! 3.  画像と技名を画面ショットで保存すれば、あなただけのプロレス技図鑑が!? <学習済の技> シャイニング・ウィザード ラウンディング・ボディ・プレス 足4の字固め ブレーンバスター ボディースラム 使ったもの Teachable Machine コード See the Pen プロレス技図鑑 by mshita (@morishitanana) on CodePen. Teachable Machine(機械学習) 私の贔屓団体が WRESTLE-1(武藤敬司が代表を務め、残念ながら現在は無期限の活動休止中)なので、武藤が得意にしている技を中心にTeachable Machineで機械学習をした。 画像はネットで検索したものをひたすら集めて、アップロードしてTeachable Machineに登録。 シャイニング・ウィザードについては、一瞬で決まる技のため撮影が難しいこと、技を使う選手への身体的負荷が大きくあまり使う選手がいないことからそれほど画像を集めることができなかった。逆にアプリを使用していても観戦中の登場頻度は少ないかもしれない。 関節技の足4の字固めは比較的撮影がしやすいのか、画像が多く集めることができた。 ラウンディング・ボディ・プレスはムーンサルトプレスも含んだものとする。(基本的に同じ技だが、使う選手によって技名が変わる) ちなみにムーンサルトプレスはシャイニング・ウィザードと並んで武藤の代名詞となっているが、足の手術のため2018年3月の試合でラスト・ムーンサルトとして披露されて以降、封印されている。 担いだ相手を真下に投げるボディースラムと、担いだ相手を後方に投げるブレーンバスターは画像だと区別が付きづらい。これぞ、という画像があまりなく枚数があまり集まらなかったが、一応判定はちゃんとできていた。 (上記画像は、それぞれ投げる瞬間の画像) さいごに 今回は比較的よく使われる技を登録したけれど、団体単位で所属する選手が使う技を登録するのも面白いかもしれない。 静止画での判定は比較的ちゃんとできていたが、実際に生観戦で判定ができるのかも試してみたい。 プロレス会場の客席は遠くからでも見えるように客席に傾斜があるので、座席によって使用感の差は出るかもしれない。 圧倒的に登録枚数が少ないので、現地で自分で撮影した画像を取り込んで学習させるのも面白い。 今回の制作にあたりプロレス動画を見ていたらやっぱりプロレス面白いなー!と再確認したので、久しぶりに会場に足を運んで思い切り観戦したい。 おまけ 以下、個人的なプロレス観戦歴。 かつて月に一度のペースで水道橋に観戦に行っていたくらい、プロレスが好き。 ルチャリブレの軽快な動きが大好きで、覆面選手に目が行きがち。 リング横で観戦していて、多いときは3回自分の席がなくなった。(席が乱闘に巻き込まれたため) 乱闘が落ち着いた頃にいそいそとパイプ椅子を戻すのも、それはそれで楽しい。 もしこれから行こうと思っている人がいれば、ひな壇席が値段的にも、安全面的にもおすすめ。 女子一人のお客さんも結構いるので、怖がらないで是非観に行って欲しい。最高のエンタメ。 どの席でも乱闘が始まったら逃げる覚悟をしておくこと(荷物は事前にまとめておく)。乱闘前にどいてどいて!とバタバタ誘導されるので、試合が始まる前にどっち側に逃げる等の逃げ道を確認しておくとよい。 良いプロレスライフを!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

逆引き!プロレス技図鑑!! #プロレスのチカラ

観戦中に、技名が分からない!!! 皆さんが好きなプロレス技は何ですか?? 私は断然、ラウンディング・ボディ・プレス。 コーナーポストに立ってこれから飛ぶぞと存分に客席にアピールしながら、誰よりも高い位置からさらに高く飛び相手に一撃を食らわせる姿はとにかくかっこいい。受ける選手も相手の見せ場を作るように計算している。最高にエンターテイメントを感じる瞬間である。 初心者観戦あるあるだが、会場に実況のアナウンスは入らないため技が決まって会場が盛り上がっても「今のなんて技だったんだろう……?」ということが発生する。 現地に観戦しに行かない方はあまり知らないだろうが、プロレス観戦は動画撮影はNGだが、写真撮影はOKなことがほとんどである。ならば、観戦中に撮影して技名を画像認識できればいいのではないか。 プロレス観戦初心者も安心!?『プロレス技図鑑』 ※(11/25差替え)Androidで起動時、インカメになっていたので修正。ios未対応。 <使い方> 1.  アプリを起動し、プロレス観戦をする。 2.  技が決まりそうなタイミングで、アプリをかざし、技名を確認! 3.  画像と技名を画面ショットで保存すれば、あなただけのプロレス技図鑑が!?(難易度高) <学習済の技> シャイニング・ウィザード ラウンディング・ボディ・プレス 足4の字固め ブレーンバスター ボディースラム 静止画ではそこそこの精度だが、動画だとどこで技が決まったか判断が付かない。 これは今後、要改善…… 使ったもの Teachable Machine コード See the Pen プロレス技図鑑 by mshita (@morishitanana) on CodePen. Webカメラの、インカメ外カメ設定は以下の記事を参考にさせていただいた。 javaScriptでWEBカメラの映像をブラウザで表示する(PC/iPhone) Teachable Machine(機械学習) 私の贔屓団体が WRESTLE-1(武藤敬司が代表を務め、残念ながら現在は無期限の活動休止中の団体)なので、武藤が得意にしている技を中心にTeachable Machineで機械学習をした。 画像はネットで検索したものをひたすら集めて、アップロードしてTeachable Machineに登録。 以下、実際に起動し静止画を認識させてみた。 シャイニング・ウィザードについては、一瞬で決まる技のため撮影が難しいこと、技を使う選手への身体的負荷が大きくあまり使う選手がいないことからそれほど画像を集めることができなかった。逆にアプリを使用していても観戦中の登場頻度は少ないかもしれない。 関節技の足4の字固めは撮影がしやすいのか、画像が多く集めることができたので比較的認識の精度が高い。横たわっている人間がいる、ということでラウンディング・ボディ・プレスと誤判定されることもある。 複数の関節技を登録して、横たわっているバリエーションが増えると精度が変化しそうである。 ラウンディング・ボディ・プレスはムーンサルトプレスも含んだものとする。(基本的に同じ技だが、使う選手によって技名が変わるため) ちなみにムーンサルトプレスはシャイニング・ウィザードと並んで武藤の代名詞となっているが、足の手術のため2018年3月の試合でラスト・ムーンサルトとして披露されて以降、封印されていた。2021年6月に3年ぶりに解禁された。 担いだ相手を真下に投げるボディースラムと、担いだ相手を後方に投げるブレーンバスターは画像だと区別が付きづらい。これぞ、という画像があまりなく枚数があまり集まらなかったが、一応判定はちゃんとできていた。 (上記画像は、それぞれ投げる瞬直前の画像) 実際に現地で、連続写真撮影して動きが分かる画像で機械学習させた方が精度が上がる気がする。 さいごに 今回は比較的よく使われる技を登録したけれど、団体単位で所属する選手が使う技を登録するのも面白いかもしれない。 静止画での判定は比較的ちゃんとできていたが、動画の認識はやはり課題が多い。 どの時点で技と判断するか設定の問題や、会場の座席位置によって画像の画角が大きく変わるのも難点。今の仕様だとリング横席、もしくはひな壇前方席でしか使えないかもしれない……。 今回のアプリ作成にあたりプロレス動画を見ていたらやっぱりプロレス面白いなー!と再確認したので、久しぶりに会場に足を運んで思い切り観戦したい。 おまけ 以下、個人的なプロレス語り。 かつて月に一度のペースで水道橋に観戦に行っていたくらいには、プロレスが好き。 ルチャリブレの軽快な動きが大好きで、覆面選手に目が行きがち。 リング横で観戦していて、多いときは1回の興行で3回自分の席がなくなった。(席が乱闘に巻き込まれたため) 乱闘が落ち着いた頃にいそいそとパイプ椅子を戻すのも、それはそれで楽しい。 興味があってもしこれから行こうと思っている人がいれば、ひな壇席が値段的にも、安全面的にもおすすめ。 女子一人のお客さんや、子供連れのお客さんも結構いるので、怖がらないで是非観に行って欲しい。最高のエンタメ。 どの席でも乱闘が始まったら逃げる覚悟をしておくこと(荷物は事前にまとめておく)。乱闘前にどいてどいて!とバタバタ誘導されるので、試合が始まる前に逃げ道を確認しておくとよい。 良いプロレスライフを!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue3はどうやってリアクティブシステムを実現しているのか:その2 値の更新とeffect

はじめに その1から順に読んでください ※ だいたいここの内容まんまです(構成は違います) ※ vue3のソースはここです ※ packages/reactivity/src内にあるファイルの内容を簡略化して説明しています。 目次 effectとは trackとtrigger 参考文献 effectとは 前編でも触れましたが、リアクティブの実現には3つの要素が必要です。 * targetに対する操作の監視 * targetが使用されている関数を(effectとして)記憶する (track) * targetを変更した際に使用箇所も同時に変更する (trigger) このうち、監視に関してはProxyで実現できました。 まずは一旦effectについて考えましょう。 リアクティブ変数を使用している関数はeffectと呼ばれ、trackで記録され、triggerで実行されます。 (実際のソースコードでは関数ではなく、その関数を内包したクラスで表現されますが、ここでは簡単のため関数とします) 具体例でeffectのイメージを掴みましょう // 重さと個数 const obj = {weight: 100, num: 4}; // 全体の重さ let totalWeight = 0; // 重さを更新するエフェクト const effect1 = () => { totalWeight = obj.weight * obj.num; } // 初期化 effect1(); console.log(totalWeight); // 400 // 値を変更して初期化 obj.num = 8; effect1(); console.log(totalWeight); // 800 この例ではオブジェクトの値を変更してからeffect1を再度実行することにより、totalWeightを再計算しています。 ただ、これではいちいちeffect1を再実行しなければならないので、リアクティブとは言えませんね。 Vueではこれをアプリ開発者が意識しなくても行えるような仕組みが用意されています。 trackとtrigger ※ 以下の内容はbaseHandler.tsおよびeffect.tsに対応します Vue3においてtrackとは、リアクティブ変数が使用されているeffectを記録し追跡できるようにしておくことです。 そして、ここでtrackされたeffectはtriggerで実行されるという流れです。 最終的にはこのような形になります function reactive(target) { return new Proxy(target, { get: (...) => { const result = Reflect.get(...) track(); return result; }, set: (...) => { trigger(); Reflect.set(...); } }) } get内でtrackを呼び、set内でtriggerを呼びます。(triggerはともかくget内でtrackを呼ぶというのが腑に落ちないかもしれませんが、もう少し読み進めてみてください) track内部から現在実行中のeffectを参照するには少し工夫が必要です。それが以下の処理です(effect.ts: effect関数参照) let activeEffect; function effect(fn) { activeEffect = fn; fn(); activeEffect = null; } const effect1 = () => { // 何らかの処理 } effect(effect1); effect関数にeffect1を渡し、activeEffectにeffect1を一時的に格納、間接的にeffect1を呼び出し、最後にactiveEffectからの参照を外すという流れです。 これにより、effect1実行中はactiveEffectからeffect1が参照できる状態になります。 先ほど説明した通り、trackはeffectを記録する処理でしたね。少々ごちゃごちゃしてるように見えるかもしれませんが、以下がtrackのコードです。 const targetMap = new WeakMap(); function track(target, key) { if(activeEffect) { let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (!dep) { depsMap.set(key, (dep = new Set())) } dep.add(activeEffect); } } 単にWeakMap,Map,Setの3つのコレクションがネストされているだけです(未登録の場合追加する) コレクションクラスが三重になっている理由は、どのリアクティブオブジェクトの、どのプロパティかを区別する必要があり、そのプロパティに依存するエフェクトが複数あるためです。 ここでWeakMapが使われていますが、ここでは特にMapと区別する必要が無いので、詳しく知りたい方は公式サイトをご覧ください。 参考 WeakMap 簡単に言うと、オブジェクトのみをキーに指定できるMapのようなものです。そして、そのオブジェクトが他所で参照されなくなったら自動的に削除されます(この性質をWeakと言っているのだろう)。制限が多い分パフォーマンス上のメリットと、開放し忘れによるメモリリークを防ぐことができるので、使い方によっては有用なコレクションクラスです。(実はWeakSetもありますが触れません。) イメージ的にはこんな感じで格納されます。(Map,WeakMapを{...}、Setを[...]で表現しています) const targetMap = { // どのリアクティブオブジェクトか dataset1: { // どのプロパティか time: [effect1], // 関連するエフェクトをSetに格納 speed: [effect1, effect3], }, dataset2: { weight: [effect2], num: [effect2], } } trackの雰囲気は掴めたでしょうか。 次はtriggerを考えましょう。ざっくりこんな感じです。 function trigger(target, key) { const depsMap = targetMap.get(target); if (!depsMap) { return; } const dep = depsMap.get(key); if (dep) { dep.forEach(effect => { effect(); }) } } 先ほどtrackで保存したeffectをtarget,keyにより取得し、ループで呼び出しています。 これで先ほど述べた3つの要素 * targetに対する操作の監視 * targetが使用されている関数を(effectとして)記憶する (track) * targetを変更した際に使用箇所も同時に変更する (trigger) が揃いました。 最終的なコードは function reactive(target) { return new Proxy(target, { get: (target, key, receiver) => { track(target, key); // targetMapにeffectを記録 return Reflect.get(target, key, receiver); }, set: (target, key, value, receiver) => { trigger(target, key); // trackで記録したeffectを実行 Reflect.set(target, key, value, receiver); } }) } const targetMap = new WeakMap(); function track(target, key) { if(activeEffect) { let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (!dep) { depsMap.set(key, (dep = new Set())) } dep.add(activeEffect); } } function trigger(target, key) { const depsMap = targetMap.get(target); if (!depsMap) { return; } const dep = depsMap.get(key); if (dep) { dep.forEach(effect => { effect(); }) } } let activeEffect; function effect(fn) { activeEffect = fn; fn(); activeEffect = null; } const dataset = reactive({time: 10, speed: 4}); let dist = 0; const effect1 = { dist = dataset.time * dataset.speed; } effect(effect1); // ここまで初期化処理 console.log(dist); // 40 dataset.time = 30; console.log(dist); // 120 dataset.speed = 2; console.log(dist); // 60 流れを整理しましょう。 1. reactive()でリアクティブオブジェクトを作成 2. effect()により間接的にeffect1を実行, effect1をactiveEffectに一時保存 3. effect1内部でdataset.time(dataset.speed)を取得、getハンドラー実行 5. track実行、targetMap.dataset.timeにeffect1を追加 6. distにdataset.time * dataset.speed (= 40)を代入, activeEffectをnullに // ここまで初期化処理 7. dataset.timeに30を代入、setハンドラー実行 8. dataset.timeの値を更新 9. 5で追加したeffect1を再度実行, distを再計算(= 120) これで、一度初期化したら、リアクティブオブジェクトのプロパティを変更時、自動的にeffectが再実行されるようになりました。 最後に リアクティブの概要の説明はこれで終わりですが、だいたいイメージは掴めたでしょうか? まだrefやcomputedの説明をしていませんが、それらは次回にしようと思います。 参考文献 https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy https://philipdevblog.hashnode.dev/vue-3-reactivity-reading-source-code-with-evan-you https://github.com/vuejs/vue-next
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vueはどうやってリアクティブシステムを実現しているのか:その1 オブジェクトの監視

はじめに ※ 主にVue3の解説になります ※ リアクティブシステムとは簡単に言うと、ある値を変更すると、関連する値を自動で更新してくれるやつです。 ※ 基本的にここを参考にしています。 ※ vue3のソースはここですが、クローンしなくても大丈夫です ※ 実際のソースコードそのままだと複雑なので、簡略化して大まかな流れを理解することを目指します ※ 長めの記事書くのは初めてなのと、周囲に詳しい人も居ないので、間違い等多いと思いますが、皆さまの指摘を受けてより良い記事にできたら良いなと思います。 Vueによるフロントエンド開発の案件に入ってちょうど1年ぐらい経ちましたが、ここ半年ぐらいでVueの宣言的UI構築の素晴らしさを体感できたので、いかにしてそれを実現しているのか興味が湧いた。 というのも、業務で(Vueアプリの一部で)ほぼスクラッチでツールを作っていたのですが、DOMの操作を命令的に操作するのが非常にしんどかったのです。モデルの値を更新するたびDOMのプロパティを操作して...みたいなことをやると自分ですら手に負えないコードが出来上がります。 幸い何とか形になりつつありますが、本音を言うと今からでもVueに乗っかった実装に変更したいです。そんな余裕はありませんが。 (開発を始めて暫くして知りましたが、VueでもSVGがHTMLと同様に扱えるので、スクラッチで作る必要は無かったんですよね) 目次 リアクティブとは何か 前提 オブジェクトの監視 補足 参考文献 リアクティブとは何か 直訳すると"反応性" ある値を更新すると、それを使用している別の式も自動的に更新される仕組みのことです。 例えば、 sample.js const dataset = {time: 10, speed: 3}; const dist = obj.time * dataset.speed; console.log(dist); // 30 dataset.time = 20; console.log(dist); // 30 このコードでは途中でdataset.timeの値を変更していますが、変更前に定義したdistの値が更新されることはありません。 手続き的な処理ならこの挙動で問題ありませんが、UIを作る際にはこれが自動的に更新された方が都合が良いんですね。 そこで登場するのがVueのリアクティブシステムです。例えばVueだと <template> <div>{{ count1 + count2 }}</div> <div>{{ 'count2は' + count2 + 'です。' }}</div> </template> と書いてcount1,count2の値を変更すれば(それらがリアクティブな値であれば)、それぞれの変数を使用している箇所全てが更新されますよね。 Vueでは、ここにはどういう値が入れば良いかを一度宣言すれば、あとは自動で更新してくれるようになるのです。 直感的にはわかりやすい挙動ですが、javascriptのプログラムとしては不自然な感じです。 本来は表示する内容を更新したい場合、更新するための処理を明示的に実行しないといけませんが、そうすると実際に画面に表示される内容とコードがかけ離れたものになってしまいます。 よくわからなかったら公式を見てください(丸投げ) 前提 ※ ソースコードが手元にある方は、packages/reactivity/src/以下のファイルを見てください。 まず、リアクティブを実現するために必要なことは(対象の値をtargetとすると) * targetに対する操作の監視 * targetが使用されている箇所を(effectとして)記憶する (track) * targetを変更した際に使用箇所も同時に変更する (trigger) (track, trigger, effectはソースコード上での呼び方です) この3つです。 そして、これらを実現するためにVue3ではreactive関数を使用します。具体的には const dataset = {time: 10, speed: 3}; const reactiveDataset = reactive(dataset); これにより、datasetオブジェクトがリアクティブ化されます。 そして、このオブジェクトの変更は使用箇所全てに適用されます。 一見不思議な挙動ですが、中でどういった処理が行われているか詳しく見ていきましょう オブジェクトの監視 ※ 以下はreactive.tsの内容を簡略化して説明しています reactive関数を通すとリアクティブな値を取得できますが、実は返却される値は、元のオブジェクトは別のオブジェクトです。ただのコピーでもありません。 console.logとかで見るとわかると思いますが、実はこれはProxyという組み込みクラスのインスタンスで元のオブジェクト、objectへの操作を傍受するためのものです。 このProxyにトラップ(イベントハンドラー)をセットすると、何か操作をするたび処理を呼び出すことができるわけです。 参考 アプリケーション開発ではほぼ使われないので、知っている人は少ないのではないかと思います。(僕も知らなかった) Proxyは代理人という意味です。第一引数は対象のオブジェクト、第二引数は各操作に対するトラップ(ハンドラー)です。 簡単な使用例を見てみましょう。proxyを介した値の取得時に2倍にして返却するだけです。 const dataset = {time: 10, speed: 3}; const datasetProxy = new Proxy(dataset, { // getハンドラーは値の読み取り時に発火 get: (target, propertyKey, receiver) => { // 元の値を2倍にして返却 return Reflect.get(target, propertyKey, receiver) * 2; } }) console.log(dataset.time); // 10 console.log(datasetProxy.time); // 20 また変なやつが出てきましたね。 Reflectというクラスは、簡単にいうとオブジェクトに対する操作をする関数をstaticメソッドとしてまとめたものです。 Reflect.getによって元の値を取得し、それを*2して返しています。 第三引数のreceiverとは、datasetProxy自身のことです。 これが無い場合、datasetProxyではなくdatasetの値を参照してしまいます。 具体的にどう困るかというと const dataset = {time: 10, speed: 3}; const datasetProxy = new Proxy(dataset, { get: (target, propertyKey, receiver) => { if (propertyKey === 'dist') { return Reflect.get(target, 'time') * Reflect.get(target, 'speed'); } return Reflect.get(target, propertyKey, receiver) * 2; } }) console.log(datasetProxy.time); // 30 datasetProxyでは各プロパティが2倍になるので、distは(10 * 2) * (3 * 2) = 120になることを期待しますよね。でも、receiverを渡さないと元の値を参照してしまうので、10 * 3 = 30となってしまいます。これがreceiverが必要な理由です。 少々脱線しましたが、今度は値の取得時ではなく変更時に処理を行いたいとします。するとこうなります。 const dataset = {time: 10, speed: 3}; const datasetProxy = new Proxy(dataset, { // setハンドラーは値の更新時に発火 set: (target, propertyKey, value, receiver) => { // 入力値を3倍にしてプロパティにセット Reflect.set(target, propertyKey, value * 3, receiver); } }) datasetProxy.time = 4; console.log(datasetProxy.time); // 12 これも特に意味のない処理ですが、timeプロパティにセットした値が実際には3倍になって格納されています。 以上のように、オブジェクトへの操作を監視するのはProxyを利用して実現ができます。 Proxyの機能は他にも色々ありますが、 Vueのコードを読むにはget、setのみ理解すれば大丈夫です。 ここでは値を操作してみましたが、実際にはここで値の更新をするロジックを組み込みます。 具体的にはget時にその実行された処理(effect)を保存、set時に保存したeffectを実行します。 とりあえず今回はここまでです。何かありましたらコメントお願いします。 補足:Vue2との比較 ここまでVue3の説明をしましたが、Vue2ではまた別の方法で変更の監視をしていました。 そもそもProxyはモダンブラウザでは問題なく使えますが、IEでは対応していないらしいです。 この辺の互換性については本題から逸れますので、あまり触れません。 ではどうやっているのかというと、Object.definePropertyによってsetter、getterを定義しているみたいです。 const obj = {a: 10, b; 11}; Object.defineProperty(obj, 'a', { get: () => { // なんか }, set: (val) => { // なんか } }) この方法のメリットは互換性ですが、デメリットは初期化処理の遅さと、後から追加したプロパティを監視できない点です。 次回 次回は、具体的にどういった処理がgetおよびsetに必要なのかを必要なのかを見ていきます。 参考文献 https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy https://philipdevblog.hashnode.dev/vue-3-reactivity-reading-source-code-with-evan-you https://github.com/vuejs/vue-next
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue3はどうやってリアクティブシステムを実現しているのか:その1 オブジェクトの監視

その2 はじめに ※ 主にVue3の解説になります ※ リアクティブシステムとは簡単に言うと、ある値を変更すると、関連する値を自動で更新してくれるやつです。 ※ 基本的にここを参考にしています。 ※ vue3のソースはここですが、クローンしなくても大丈夫です ※ 実際のソースコードそのままだと複雑なので、簡略化して大まかな流れを理解することを目指します ※ 長めの記事書くのは初めてなのと、周囲に詳しい人も居ないので、間違い等多いと思いますが、皆さまの指摘を受けてより良い記事にできたら良いなと思います。 Vueによるフロントエンド開発の案件に入ってちょうど1年ぐらい経ちましたが、ここ半年ぐらいでVueの宣言的UI構築の素晴らしさを体感できたので、いかにしてそれを実現しているのか興味が湧いた。 というのも、業務で(Vueアプリの一部で)ほぼスクラッチでツールを作っていたのですが、DOMの操作を命令的に操作するのが非常にしんどかったのです。モデルの値を更新するたびDOMのプロパティを操作して...みたいなことをやると自分ですら手に負えないコードが出来上がります。 幸い何とか形になりつつありますが、本音を言うと今からでもVueに乗っかった実装に変更したいです。そんな余裕はありませんが。 (開発を始めて暫くして知りましたが、VueでもSVGがHTMLと同様に扱えるので、スクラッチで作る必要は無かったんですよね) 目次 リアクティブとは何か 前提 オブジェクトの監視 補足 参考文献 リアクティブとは何か 直訳すると"反応性" ある値を更新すると、それを使用している別の式も自動的に更新される仕組みのことです。 例えば、 sample.js const dataset = {time: 10, speed: 3}; const dist = obj.time * dataset.speed; console.log(dist); // 30 dataset.time = 20; console.log(dist); // 30 このコードでは途中でdataset.timeの値を変更していますが、変更前に定義したdistの値が更新されることはありません。 手続き的な処理ならこの挙動で問題ありませんが、UIを作る際にはこれが自動的に更新された方が都合が良いんですね。 そこで登場するのがVueのリアクティブシステムです。例えばVueだと <template> <div>{{ count1 + count2 }}</div> <div>{{ 'count2は' + count2 + 'です。' }}</div> </template> と書いてcount1,count2の値を変更すれば(それらがリアクティブな値であれば)、それぞれの変数を使用している箇所全てが更新されますよね。 Vueでは、ここにはどういう値が入れば良いかを一度宣言すれば、あとは自動で更新してくれるようになるのです。 直感的にはわかりやすい挙動ですが、javascriptのプログラムとしては不自然な感じです。 本来は表示する内容を更新したい場合、更新するための処理を明示的に実行しないといけませんが、そうすると実際に画面に表示される内容とコードがかけ離れたものになってしまいます。 よくわからなかったら公式を見てください(丸投げ) 前提 ※ ソースコードが手元にある方は、packages/reactivity/src/以下のファイルを見てください。 まず、リアクティブを実現するために必要なことは(対象の値をtargetとすると) * targetに対する操作の監視 * targetが使用されている箇所を(effectとして)記憶する (track) * targetを変更した際に使用箇所も同時に変更する (trigger) (track, trigger, effectはソースコード上での呼び方です) この3つです。 そして、これらを実現するためにVue3ではreactive関数を使用します。具体的には const dataset = {time: 10, speed: 3}; const reactiveDataset = reactive(dataset); これにより、datasetオブジェクトがリアクティブ化されます。 そして、このオブジェクトの変更は使用箇所全てに適用されます。 一見不思議な挙動ですが、中でどういった処理が行われているか詳しく見ていきましょう オブジェクトの監視 ※ 以下はreactive.tsの内容を簡略化して説明しています reactive関数を通すとリアクティブな値を取得できますが、実は返却される値は、元のオブジェクトは別のオブジェクトです。ただのコピーでもありません。 console.logとかで見るとわかると思いますが、実はこれはProxyという組み込みクラスのインスタンスで元のオブジェクト、objectへの操作を傍受するためのものです。 このProxyにトラップ(イベントハンドラー)をセットすると、何か操作をするたび処理を呼び出すことができるわけです。 参考 アプリケーション開発ではほぼ使われないので、知っている人は少ないのではないかと思います。(僕も知らなかった) Proxyは代理人という意味です。第一引数は対象のオブジェクト、第二引数は各操作に対するトラップ(ハンドラー)です。 簡単な使用例を見てみましょう。proxyを介した値の取得時に2倍にして返却するだけです。 const dataset = {time: 10, speed: 3}; const datasetProxy = new Proxy(dataset, { // getハンドラーは値の読み取り時に発火 get: (target, propertyKey, receiver) => { // 元の値を2倍にして返却 return Reflect.get(target, propertyKey, receiver) * 2; } }) console.log(dataset.time); // 10 console.log(datasetProxy.time); // 20 また変なやつが出てきましたね。 Reflectというクラスは、簡単にいうとオブジェクトに対する操作をする関数をstaticメソッドとしてまとめたものです。 Reflect.getによって元の値を取得し、それを*2して返しています。 第三引数のreceiverとは、datasetProxy自身のことです。 これが無い場合、datasetProxyではなくdatasetの値を参照してしまいます。 具体的にどう困るかというと const dataset = {time: 10, speed: 3}; const datasetProxy = new Proxy(dataset, { get: (target, propertyKey, receiver) => { if (propertyKey === 'dist') { return Reflect.get(target, 'time') * Reflect.get(target, 'speed'); } return Reflect.get(target, propertyKey, receiver) * 2; } }) console.log(datasetProxy.time); // 30 datasetProxyでは各プロパティが2倍になるので、distは(10 * 2) * (3 * 2) = 120になることを期待しますよね。でも、receiverを渡さないと元の値を参照してしまうので、10 * 3 = 30となってしまいます。これがreceiverが必要な理由です。 少々脱線しましたが、今度は値の取得時ではなく変更時に処理を行いたいとします。するとこうなります。 const dataset = {time: 10, speed: 3}; const datasetProxy = new Proxy(dataset, { // setハンドラーは値の更新時に発火 set: (target, propertyKey, value, receiver) => { // 入力値を3倍にしてプロパティにセット Reflect.set(target, propertyKey, value * 3, receiver); } }) datasetProxy.time = 4; console.log(datasetProxy.time); // 12 これも特に意味のない処理ですが、timeプロパティにセットした値が実際には3倍になって格納されています。 以上のように、オブジェクトへの操作を監視するのはProxyを利用して実現ができます。 Proxyの機能は他にも色々ありますが、 Vueのコードを読むにはget、setのみ理解すれば大丈夫です。 ここでは値を操作してみましたが、実際にはここで値の更新をするロジックを組み込みます。 具体的にはget時にその実行された処理(effect)を保存、set時に保存したeffectを実行します。 とりあえず今回はここまでです。何かありましたらコメントお願いします。 補足:Vue2との比較 ここまでVue3の説明をしましたが、Vue2ではまた別の方法で変更の監視をしていました。 そもそもProxyはモダンブラウザでは問題なく使えますが、IEでは対応していないらしいです。 この辺の互換性については本題から逸れますので、あまり触れません。 ではどうやっているのかというと、Object.definePropertyによってsetter、getterを定義しているみたいです。 const obj = {a: 10, b; 11}; Object.defineProperty(obj, 'a', { get: () => { // なんか }, set: (val) => { // なんか } }) この方法のメリットは互換性ですが、デメリットは初期化処理の遅さと、後から追加したプロパティを監視できない点です。 次回 次回は、具体的にどういった処理がgetおよびsetに必要なのかを必要なのかを見ていきます。 その2 参考文献 https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy https://philipdevblog.hashnode.dev/vue-3-reactivity-reading-source-code-with-evan-you https://github.com/vuejs/vue-next
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptDOM編

DOMとは? DOMとは、プログラムからHTMLやXMLを操作するための仕組み。 ツリー構造を持っていて、各要素をそれぞれをノードと呼ぶ。 空白や改行もノードになる。 要素を操作しよう IDの要素を取得する document.getElementById('ID名') 文章から特定の要素を取得する document.querySelector('要素名') 中身を帰る場合は以下。 js document.querySelector('要素名').textContent 何秒後に呼び出す setTimeout(update,1000); 複数の要素を変更 document.querySelectorAll('要素名')[n].textContent nは前から何番目を表す。 ※DOMツリーの階層から指定することもできる。 addEventListener(); やりたい内容を()に入れる。 タイトル要素の変更 .title = '文字'; CSSの変更  .style.変更したいプロパティ ※プロパティ名にハイフンがある場合は、二文字目の冒頭を大文字にする。 classNameを設定する アクションがあるときに、クラスごと変更する。 html <style> .my-color{ color:red } </style> .className = 'my-color'; この記載方法だと、既存のCSSが上書きされてしまうため、 .classList.add('my-color')にすると加えられる。 また、条件分岐を使ってもできる js if(~classList.contain('my-color') === true){ ~.classList.remove('my-color'); } カスタム要素を使う HTMLでは、自分で作った要素を使うことができ、これをカスタム要素と呼ぶ。 「data-」から始まる。 JSでは 〜.dataset.[data-〇〇の〇〇部分を記入] と記入。 要素を作ってDOMに追加 1、要素を作る const item = document.createElement('li'); 2、中身を作る item.textContent = 'item2'; 3、DOMに追加 ・親要素をauerySelectorで取得 ・追加はappendChild(); 複製した要素を追加 1,コピー対象の要素を取得 2,要素をコピー  →cloneNode(true); 3,親要素とコピー対象を置く直後の要素を取得 →insetBefore(コピーした要素,直後の要素); 要素の削除 1,取得 2,removeメソッド もしくは、親Node.removeChild(削除するNode); input要素の活用 1,リストに追加する要素作成(li) 2.inputした要素を定義(text) 3.リストに追加する内容をinputにする li.textContent = text.value; 入力された値はvalue属性で取得できる。 4.appendChild(li)で追加 5.text.value =""; text.focus(); selectボックスで行う ボタンを押したら下に記載される 1.liを作成 →createElement('li') 2.セレクト要素を取得 →querySelector('select'); 3.li.textContent = color.valueにする ラジオボタン ポイントは2つ。 ①セレクトボックスと違って一つにまとまっていないので、まずはquertySelectorAll()を使ってすべてのinput要素を取得 ②要素にチェックが入っているかはcheckedプロパティで調べられる。 他のイベント ダブルクリック=dbclick マウスを動かしたら=mousemouse
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CORSエラー回避法ではなくCORSそのものについて理解したいの(初学者向け)

この記事は Ateam Lifestyle Inc. Advent Calendar 2021 1日目の記事です。 概要 API開発しているとCORSエラーに遭遇した経験はありませんか? CORSとはそもそもなんなのか?今回はエラー回避法ではなく、その成り立ちなども含めて、「CORSとはそもそもなんなのか?」を書いていきます。 なぜ成り立ちを知る必要があるのか? 仕事だと現象のエラー回避だけして、それが生まれた背景や成り立ちまで知る余裕がありません。そのままにしていては、自分の中でも応用が効かないと思ったのでこのテーマにしました。 また「CORS」についてググると、エラーの回避策についての記事が多い印象でした。 仕事としてはその方が助かりますが、せっかくのアドカレの機会なので、セキュリティの話から始めて、そもそもCORSとはなんのためにあるのか?ついて書こうと思います。 そもそもCORSってなんて読むの? あらゆるYoutubeを見て発音を聞きました。読み方はコルスまたはコースでいいと思います。 セキュリティについて JavaScriptはWebブラウザ上で手軽に実行できます。それ自体は素晴らしいとお思う一方で、危険なこともあります。Webブラウザ上でJavaScriptが実行できるということは、誰が作ったかもわからない、どう動くかもわからないプログラムが実行され得ます。 Webブラウザ上には、自分が登録しているネット銀行やクレカのWebサービスやSNSなど、重要な情報が保存されている方も少なくないでしょう。よく考えると結構危なっかしい代物ですよね。にもかかわらず、JavaScriptが原因で致命的な情報漏洩が起きた例は0ではないにしろ頻繁に聞くなんてことはないですよね? その理由は、いくつかのセキュリティ対策が働いているからでした。今回はそれに関して調べた内容を書いていきます。 サンドボックス JavaScriptが安心安全に利用できる理由は、サンドボックス(sandbox)で動作するかららしいです。 サンドボックス上で動作していると、アクセスできるファイルや実行できるプログラムが制限されてしまいます。 その代わりに、その制限下では自由な利用が許可されています。 JavaScriptのサンドボックスによる代表的な制約の一つに同一オリジンポリシー (same-origin policy) があります。 同一オリジンポリシー (same-origin policy)について まずオリジンとは、オリジン == スキーム + ホスト + ドメイン + ポート番号のことです。 同一オリジンポリシーは、今見ているWebサイトからオリジンの異なるリソースにアクセスする際に適応されるポリシーです。これがあることで、JavaScriptは、基本的にはオリジンの異なる内容を読み取ることができません。 画像引用? CORSについて 同一オリジンポリシーは、Webをユーザーとして利用する分には素晴らしいのですが、Web開発者としては困ることもあります。 例えば、JavaScriptで関数fetch()を実行しようとしても、同一オリジンからしか呼び出せなかったら焦りますよね。 とはいえ実際の開発の現場では、異なるオリジン間でAPI通信を行うことができます。 ここでやっとCORSの登場です。 CORS(Cross-origin resource sharing)という技術を利用すると、異なるオリジン間でのリソース取得が可能になります。 CORSを用いてデータの読み込みが許可されたことを確認する CORSはHTTPヘッダを使用して、サーバからブラウザへ異なるオリジン間(Cross-origin)でリクエストを可能にする仕組みです。 ここでは仮にhttps://xyz.jpからhttps://xxx.comに対してfetch("https://xxx.com")して、以下のエラーが出たとします。 Access to fetch at 'https://xxx.com' from origin 'https://xyz.jp' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. エラー文を読むと、「リクエストされたリソースに 'Access-Control-Allow-Origin'ヘッダーがありません」とあります。 さて、コレを解決したい場合、どこでどんな設定をすればいいでしょうか? CORSの判定するのは、サーバー側でもJavaScriptでもなく、常にブラウザです。そして、CORSを成功させるには、ブラウザでもJavaScriptでもなく、サーバを修正する必要があります。 CORSはサーバのレスポンスヘッダーによりブラウザーが通信を制御する仕様なので、ちゃんと検証したい場合は、簡易的でもサーバを立てるほうがいいでしょう。 そこらへんの解説は世の中にたくさんある記事に任せるとして、ここではCORSの単純な設定である以下を試してみます。 curlコマンドで、リクエストのオリジンヘッダにオリジンを設定して送信し、サーバはそのオリジンを確認して、もし通信を許可するならレスポンスのaccess-control-allow-originヘッダに設定したレスポンスを返す方法で確認してみます(実際のケース、コレで解決できるとは限りません)。 JSONPlaceholderに対してcurlコマンドで接続してみましょう。 JSONPlaceholderとはRESTで実装されたダミーデータを返してくれるAPIサーバーです。テストなどに使える便利なサービスです。 curl -v telnet://jsonplaceholder.typicode.com:80 * Trying 2606:4700:3035::ac43:83aa:80... * Connected to jsonplaceholder.typicode.com (2606:4700:3035::ac43:83aa) port 80 (#0) 以下を入力 リクエストのオリジンヘッダにオリジンを設定(http://localhost:8080) GET /posts HTTP/1.1 Host: jsonplaceholder.typicode.com Origin: http://localhost:8080 サーバからのレスポンス内容 HTTP/1.1 200 OK Date: Tue, 23 Nov 2021 08:04:37 GMT Content-Type: application/json; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive x-powered-by: Express x-ratelimit-limit: 1000 x-ratelimit-remaining: 999 x-ratelimit-reset: 1636702086 access-control-allow-origin: http://localhost:8080 vary: Origin, Accept-Encoding access-control-allow-credentials: true cache-control: max-age=43200 // 続く レスポンスには access-control-allow-origin: http://localhost:8080 が含まれています。ブラウザがこのレスポンスを受け取ると、このサーバとは一定期間クロスオリジンの通信が許可されることを意味します。 まとめ 1. CORSとは、ブラウザのポリシーで異なるオリジンへのアクセスを許可するもの 2. CORSエラーの回避方法は、アクセスを受ける(サーバ)側に対してレスポンスヘッダにAccess-Control-Allow-Originヘッダを付与する さいごに 本記事では、CORSエラーの回避方法を網羅できていません。 以下の記事を見ると、さまざまなセキュリティ対策を重ねてきた歴史があり、まだまだ知るべきことはありそうですが本記事ではここまでとします。 CORSの判定に関しては、リクエストした条件や、サーバのレスポンスによって通信の可否が分かれます。その大まかなフローに関しては、下記の書籍(p407)が役立ちました。 今回勉強した内容を基礎にして、今後の勉強に役立てます。 その他参考 アウトプット100本ノック実施中 お時間がある時にでもどうぞ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Day.jsで誕生日から年齢を計算する

環境 Day.js 1.8.16 コード いろいろとやり方はあるが、diff を使うのが一番シンプル。 // input data const birthday = { year: 1995, month: 12, date: 6, }; const today = dayjs(); const { year, month, date } = birthday; const dayjsBirthday = dayjs(`${year}-${month}-${date}`); console.log(today.diff(dayjsBirthday, 'year')); CodePen: https://codepen.io/ezawa800/pen/VwzJmmM
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript : 関数型プログラミング

関数型プログラミング ・すべての関数は値を返す ・関数には副作用がない(参照透過性) ・関数を値として扱う Code Sample // すべての関数は値を返す function helloWorld() { return "Hello Functional JavaScript !" } var xValue = 8 var yValue = 2 function calculation(calc) { return calc(xValue, yValue) } // 関数には副作用がない function add(x, y) { return x + y } // 関数を値として扱う console.log(calculation(add)) // 10 GitHub
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【javascript】for...in と列挙可能性

for...in 列挙可能プロパティに対して順不同で反復処理を実行する。 ※順不同:基本的には順番通りに出力されるが、それが担保されているわけではない。 プロトタイプチェーン内も列挙対象となる。 プロトタイプチェーンのみ列挙したい場合はObject.hasOwnProperty()を使う。 Symbolはfor..inでは列挙対象にならない。 case1 オブジェクトをinで回す場合はkeyが戻り値になる。 valueを取得する場合はobjに対して[key]を渡す形なる。 const obj = { prop1: 'value1', prop2: 'value2', prop3: 'value3' } for(let key in obj){ console.log(key, obj[key]); } case2 prototypeにメソッドを追加していた場合にメソッドを列挙対象外にしたい場合。 definePropertyのディスクリプターのenumerableをfalseにすることで列挙対象外となる。 そもそもビルトインメソッドが列挙対象外になっているのは、enumerable: falseという設定になっているから。 const obj = { prop1: 'value1', prop2: 'value2', prop3: 'value3' } Object.prototype.method = function(){} Object.defineProperty(Object.prototype, 'method',{ enumerable: false }) //ディスクリプターの設定確認 const d = Object.getOwnPropertyDescriptor(Object.prototype, 'method') console.log(d) // {writable: true, enumerable: false, configurable: true, value: ƒ} //ビルドインメソッドの enumerable: falseになっているかどうかの確認。 const c = Object.getOwnPropertyDescriptor(Object.prototype, 'hasOwnProperty') console.log(c) //{writable: true, enumerable: false, configurable: true, value: ƒ} for(let key in obj){ console.log(key, obj[key]); } case3 case2の応用として、objオブジェクトの一部だけdefinePropertyで列挙外にすることができる。 const obj = { prop1: 'value1', prop2: 'value2', prop3: 'value3' } Object.defineProperty(obj, 'prop1',{ enumerable: false }) for(let key in obj){ console.log(key, obj[key]); } case4 for...inのなかでprototypeを列挙から外し合い場合はhasOwnProtertyを使うと良い。 hasOwnProperty() メソッドは、オブジェクト自身が(継承されていない)指定されたプロパティを持っているかどうかを示す真偽値を返します。 const obj = { prop1: 'value1', prop2: 'value2', prop3: 'value3' } for(let key in obj){ //objが自身のプロパティかどうかを真偽値で評価している。 //prototypeに格納されているものは全て継承されたものなのでfalseになり、列挙対象外となる;。 if(obj.hasOwnProperty(key)){ console.log(key, obj[key]); } } case5 Symbolが列挙外になっているかの確認 変数をオブジェクトに格納する場合はkeyに[変数]:valueとなる。 const s = Symbol(); const obj = { prop1: 'value1', prop2: 'value2', prop3: 'value3', [s]: 'value4' } const e = Object.getOwnPropertyDescriptor(obj, s) console.log(s) >>> Symbol() //Symbolとして評価されている。 for(let key in obj){ console.log(key, obj[key]); } >>> prop1 value1 >>> prop2 value2 >>> prop3 value3 //Symbolは表示されなかった。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeScriptメモ 〜関数編〜

関数を作ってみる function hello(name:string) { // 何らかの処理 } 関数内の変数スコープ 宣言文 スコープ var 関数外で宣言した場合はそのソースコード全体で利用できる。関数内で宣言したものは関数内だけで使える let 宣言した{}内で使える function total(max:number) { let num = 0 for(let i = 1, i < max; i++) { num += 1 } console.log(num + i) // エラー! iが見つかりません! } 戻り値 戻り値の型を指定できる。何も返さない時はvoid function total(max:number):number { // 処理 } 複数の値を返す function calcTax(price:number):[price:number, tax:number] { // 処理 } 引数に条件型を使用 function foo(foo:number|string){} オプション引数 省略可能な引数を利用できる。 function foo(foo?:string, bar?:string) {} 引数の初期値 function foo(foo = 1) {} 可変長引数 いくつ引数が必要かわからない場合に function foo(...data:number[]){} foo(1,2,3,4) 無名関数 const f = function(foo:foo):void {} アロー関数 const f = (foo:foo):void => {} functionとアロー関数の違い foo() // Error! const foo = (foo:foo):void => {} foo() // ok function foo(foo:foo):void {} 総称型(ジェネリクス) 「数値を引数で渡したら数値を返し、テキストを渡したらテキストを返す」といった関数を定義する時に使用 // Tが指定されたものはすべて同じ型になる function foo <T> (foo:T):T {} ジェネレータ ジェネレータでは「yield」を使って値を返す。returnと違って、値を返したあとも処理を続ける。つまり「待ち状態」になる。 ジェネレータは通常の関数と使い方が違う。関数を呼び出すと戻り値としてジェネレータが返される。そのジェネレータに「next()」を呼び出すことで使える。 function* fibo(n:number) { let n1 = 0 let n2 = 1 for(let i = 0; i <= n; i++) { yield n1 let n3 = n1 + n2 n1 = n2 n2 = n3 } } const n = 10 let fb = fibo(n) for(let i = 0; i <= n; i++) { let ob = fb.next() console.log(ob.value) }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

メッセージを送信後、画面最下部まで自動でスクロールする(React)

何をやるのか LINEのように、チャット画面で新規メッセージを送信後、画面最下部まで自動でスクロールする機能の実装方法について解説します。 Reactで作ったSPAが前提の方法です。 1. チャット画面を実装 まずはサンプルとなるチャット画面を実装。 ChatScreen.jsx import React, { useState } from 'react'; import './ChatScreen.css'; // ダミーデータを生成 const dummyMessages = Array(30) .fill({}) .map((_, index) => ({ id: index, content: `ダミーメッセージ #${index + 1}`, })); export const ChatScreen = () => { const [messages, setMessages] = useState(dummyMessages); const [inputValue, setInputValue] = useState(''); const onSendMessage = () => { setMessages(messages.concat({ id: messages.length + 1, content: inputValue })); setInputValue(''); }; return ( <div className='container'> <ul className='messages-container'> {messages.map((message, index) => ( <li key={message.id} className='message'> <p>{message.content}</p> </li> ))} </ul> <div className='input-container'> <input type='text' className='input' value={inputValue} onChange={({ target }) => setInputValue(target.value)} /> <button className='button' onClick={onSendMessage}> 送信 </button> </div> </div> ); }; ChatScreen.css .container { overflow-y: overlay; display: inline-block; } .messages-container { display: flex; flex-direction: column; padding: 50px; margin-bottom: 100px; } .message { padding: 5px 20px; background-color: #dcf8c6; display: block; border-radius: 10px; margin-top: 10px; margin-bottom: 10px; clear: both; } .input-container { display: flex; flex-direction: column; justify-content: space-around; height: 100px; padding: 5px; width: 100%; position: fixed; bottom: 30px; } .input { width: 60%; margin: auto; border: 1px silver solid; font-size: 15px; line-height: 40px; } .button { width: 100px; margin: auto; } 仕上がりはこんな感じです。 2. Reactのrefを使って末尾のメッセージを管理 (1) useRefでrefを定義 ChatScreen.tsx const footRef = useRef(null) React HookのuseRefでfootRefオブジェクトを定義。 デフォルト値はnull。 (2) 末尾のメッセージへrefを付与 ChatScreen.jsx {messages.map((message, index) => ( <li key={message.id} className='message'> <p>{message.content}</p> {/* 以下の一行を追加 */} {index === messages.length - 1 && <div ref={footRef}></div>} </li> ))} messages配列の末尾の要素、つまり最下部に表示されるメッセージへfootRefを付与。 3. useLayoutEffectで自動スクロール ChatScreen.jsx useLayoutEffect(() => { footRef.current.scrollIntoView(); }, [messages]); useLayoutEffect関数を記述し、第二引数の配列に渡された値(messages)の変更があるたびに, "footRef.current.scrollIntoView()"で最下部の要素を画面上に表示。 最終的なコードは以下。 ChatScreen.jsx import React, { useLayoutEffect, useRef, useState } from 'react'; import './ChatScreen.css'; // ダミーデータ const dummyMessages = Array(30) .fill({}) .map((_, index) => ({ id: index, content: `ダミーメッセージ #${index + 1}`, })); export const ChatScreen = () => { const [messages, setMessages] = useState(messagesData); const [inputValue, setInputValue] = useState(''); const footRef = useRef(null); const onSendMessage = () => { setMessages(messages.concat({ id: messages.length + 1, content: inputValue })); setInputValue(''); }; useLayoutEffect(() => { footRef.current.scrollIntoView(); }, [messages]); return ( <div className='container'> <ul className='messages-container'> {messages.map((message, index) => ( <li key={message.id} className='message'> <p>{message.content}</p> {/* 以下の一行を追加 */} {index === messages.length - 1 && <div ref={footRef}></div>} </li> ))} </ul> <div className='input-container'> <input type='text' className='input' value={inputValue} onChange={({ target }) => setInputValue(target.value)} /> <button className='button' onClick={onSendMessage}> 送信 </button> </div> </div> ); }; こんな感じで自動スクロールが実現できました。 今回はチャット画面を題材として扱いましたが、こちらのテクニックは割といろんな場面で応用できるのではないかなと思います。 こちらあくまで一方法となりますのでご参考までに。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

配列とループ分

case lengthは要素数を評価 arry2のように途中で"0"= falsyな値があると途中でループが止まる。 const arry = [1, 2, 3, 4, 5]; const arry2 = [1, 2, 3, 0, 5]; for(let i = 0; i < arry.length; i++){ console.log(i) } >>> 1 >>> 2 >>> 3 >>> 4 >>> 5 case2 [i++]後方インクリメントは初期値を代入してから+1されるので最初のループはvに0が渡されている。 arry配列5以上場合はundifindが戻り値になるのでfalseになりループが止まる let v, i = 0 //pythonはたしかこのような変数宣言は推奨されていなかったきがする。 while(v = arry[i++]){ console.log(v); }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

IR情報の収集をiPaaSとLINE Botで効率化する

今回の記事も業務効率化です。 ホームページの更新を通知してもらうと共に、詳細を簡単に調べられるようにしたツールとなります。 月1回のルーティンだからこそ効率化する  何度か私は経営戦略部(一般的な企業だと経営企画と言われることが多いと思います。)に所属していることをお話しているかと思います。経営企画の業務の中でもIR:Investor Relations(投資家向け広報)は私の担当範囲です。月1回の月次営業情報の更新など、高頻度に実施しているわけではありませんが、株主さまや投資家の方に自社の取り組みを周知する上で、必要不可欠と言えます。  また、競争企業のIR情報をまとめておくことは、競争の戦略や業界全体のトレンドを把握した上で、自社の立ち位置を知ることができるため、これも重要な取り組みです。しかしながら、 月に1回しか実施しないため、更新を失念する 更新されていると思い、他社ホームページを見に行くと、まだ更新されていないことがある 数ある企業のホームページを回り、数値をまとめることが手間 といった問題点があり、十分な実施状況とは言えず、改善が必要と考えました。 今回のプロダクト 全体概要は図の通り integromatを用いて以下の作業を自動化 RSSより目的企業のIR更新を取得 更新内容をGoogleスプレッドシートに記載 更新をLINE Notifyで通知 Google Apps Script (GAS) を用いて以下の作業を自動化するLINE Botを構築 通知された企業名をLINE Botに入力 Google Apps ScriptでGoogleスプレッドシートに記載されたURLを取得 URLをリプライメッセージで通知 各ツールの選定理由 integromat  IFTTT,Zapierと比較して拡張性が高く、細かいカスタマイズができます。今回はプロトタイプとして1企業分を実装しましたが、今後複数企業を実装することを視野に入れ選択しました。 LINE Messaging API  今回の実装内容であれば、単純に更新URLをLINE Notifyで通知するだけでも可能ですが、今後、スプレッドシートに更新された経営数値を自動で記録するところまで実装することを考えています。同様にカスタマイズ性の高いリプライメッセージを送れるLINE Messaging APIをシステムの中に実装しました。 GAS  常時更新を通知するために、サーバーのデプロイが必要と考えました。スプレッドシートとの連携がとりやすいことを考え、GASを用いて実装しました。 実際の仕様・動作 integromat部分 Watch RSS feed items とGoogle Sheetsを連携 実際の出力(retrive RSSを使用して疑似的に作成) 1行目に左から、更新内容、分類、URL、日時を入力しヘッダーとして使用 通知については後述 この後、LINE BotではGoogleシート上の情報を参照するため、シートが更新されたことを通知 LINE Bot部分 LINE Botに企業名を入力(今回は都合上、具体的な企業名を入力しておりません。実際はシート名を企業名とし、シート毎に管理する想定です。)。更新されたURLがリプライメッセージとして返ってきます。 URLから企業ページにアクセスして、数値をまとめます。 通知した更新情報はE列に"TRUE"が入力されます。ログとして活用しつつ、重複通知を防ぎます。 サンプルコード 詳細はこちら展開ください const channelAccessToken = 'LINE Messaging APIより取得'; const ss = SpreadsheetApp.getActiveSpreadsheet(); console.log (ss.getName()); //LINE Messaging APIからPOST送信を受けたときに起動する // e はJSON文字列 let doPost = (e) => { if (typeof e === "undefined"){ //動作を終了する return; } else { //JSON文字列をパース(解析)し、変数jsonに格納する var json = JSON.parse(e.postData.getDataAsString()); //変数jsonを関数replyに渡し実行 reply(json) } } let reply = (data) => { const replyUrl = "https://api.line.me/v2/bot/message/reply"; const reply_token = data.events[0].replyToken; //reply token const text = data.events[0].message.text; //ユーザーが送信した語句 //返信語句を格納するための空配列を宣言する let replyTextList = []; const sheet = ss.getSheetByName(text); console.log (sheet.getName()); const lastRaw = sheet.getLastRow(); console.log(lastRaw); for(let i = 2 ; i < lastRaw + 1 ; i++){ if(!sheet.getRange(i, 5).getValue()){ let range = sheet.getRange(i, 3); console.log(range.getValue()); replyTextList.push(range.getValue()); //通知していないURLをreplyTextにpushする sheet.getRange(i, 5).setValue(true); //今回通知したURLのシートにtrueを書き込む } } //全て通知済みの場合、関数を終了する if(replyTextList.length < 1) { return; //replyTextListのLengthが5より大きい場合、messageLengthを5にする※一度に最大5つの吹き出ししか返信できないため } else if(replyTextList.length > 5) { var messageLength = 5; } else { var messageLength = replyTextList.length; } //"messages"に渡す配列を格納するための空配列を宣言する //[{"type": "text", "text": "返信語句その1"},{"type": "text", "text": "返信語句その2"}....] const messageArray = []; //replyTextListに格納されている返信語句を最大5つ、messageArrayにpushする for(let j = 0; j < messageLength; j++) { messageArray.push({"type": "text", "text": replyTextList[j]}); } const headers = { "Content-Type": "application/json; charset=UTF-8", "Authorization": "Bearer " + channelAccessToken, }; const postData = { "replyToken": reply_token, "messages": messageArray }; const options = { "method" : "post", "headers" : headers, "payload" : JSON.stringify(postData) }; //LINE Messaging APIにデータを送信する UrlFetchApp.fetch(replyUrl, options); } GASの実装過程 以下の記事を見ながら作成ください。 職場で実際に公表して 率直に驚かれました。 一生懸命作った甲斐があったと思います。 拡張性が高いことも伝わったので、継続的に発展させてほしいというコメントもいただけました。 しかし、これでもっと競争の分析を深められそうというコメントもいただくこととなり、自動化したはずなのに仕事は増えるんだよなあというジレンマも感じました。 結びに ここ最近リテールテックにお熱です。 実はこんなアドベントカレンダー企画も推進しております。 小売業以外の方からの、実はこんな技術が使えるのでは?という提案 技術的な実装はできていないが、こんな企画を考えてみたという案 等々お待ちしています。 特に、企画案はカレンダーの前半に記載していただければ、私が後半に実装してみた記事を上げるかもしれません笑
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【javascript】クロージャー

クロージャー レキシカルスコープの変数を関数が使用している状態 プライベート変数 関数の外部からアクセスさせないようにする case 数字をカウントアップさせていく関数があったとする。 1が3回出力され、カウントアップされない。 実行するたびに変数numが初期化されてしまうから。 increment(); increment(); increment(); function increment(){ let num = 0; //初期化 num = num + 1; console.log(num) } >>> 1 >>> 1 >>> 1 increment関数の外側でnumを初期化すればよい。 let num = 0 //初期化 increment(); increment(); increment(); function increment(){ num = num + 1; console.log(num) } >>> 1 >>> 2 >>> 3 しかし、変数の上書きも容易にできるので、予期せぬエラーが発生することがある。 let num = 0 //初期化 increment(); increment(); increment(); function increment(){ num = num + 1; console.log(num) } num = 0 increment(); >>> 1 >>> 2 >>> 3 >>> 1 結論 クロージャを使用して解決する。 クロージャーにあたるincrementFactoryを作成しそこで変数を定義 incrementFactoryの下層にincrement関数を定義してあげることで外部から変数へのアクセスを制御することができる。 function incrementFactory(){ let num = 0 //初期化 function increment(){ num = num + 1; console.log(num) } return increment; } const increment = incrementFactory(); increment(); increment(); increment(); >>> 1 >>> 2 >>> 3 動的な関数の生成 addNumberFactoryというクロージャに引数を渡す addNumberFactoryを初期化する時に引数をわたし、内部で実行するaddNumberに異なる値を渡すことによって動的な関数を生成することができる。 function addNumberFactory(num){ function addNumber(value){ return num + value } return addNumber; } const add5 = addNumberFactory(10) const add2 = addNumberFactory(10) console.log(add5(5)) console.log(add2(2))
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【javascript】レキシカルスコープ

レキシカルスコープ コードを書く場所によって参照できる変数が変わるスコープ 「静的スコープ」ともいう コードを記述した時点で決定するため。 case fn1()を実行するとfn2() が実行される。 let a = 2 //グローバルスコープ function fn1(){ let b = 1; function fn2(){ let c = 3; console.log(b) } fn2(); } fn1(); >>> 1 fn1のスコープから外れたfn2()を実行するとエラーがかえる。 すなわちfn2から変数bは見えていない。 つまり、関数を実行する場所によって使用できる変数が変わる。 let a = 2 function fn1(){ let b = 1; } function fn2(){ let c = 3; console.log(b) } fn2(); >>> error まとめ スコープは内側から外側へのスコープは取れるが、外側から内側へのスコープは通らない。 イメージはこのような感じ // windowオブジェクトは最低レイヤーなので全ての関数から参照できる。 let a = 2 function fn1(){ let b = 1; console.log(a) //参照できる。(内〜外:○) console.log(c) //fn2の3章はできない(外〜内:×) function fn2(){ let c = 3; console.log(a)//参照できる。(内〜外:○) console.log(b)//参照できる。(内〜外:○) } fn2(); } fn1();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JavaScript]イベントリスナー登録

イベントリスナーとは webページにおける「ボタンのクリック」や「キーの入力」などのユーザーの操作やページの読み込みなどを「イベント」という。イベントリスナーとは、そのイベントの発生を監視し、発生したイベントに対応して実行される処理(関数)のこと。 また、イベントが発生することを「イベントが発火する」ともいう。 イベントリスナー登録 対象要素(オブジェクト)にイベントリスナーをaddEventListener()を使って登録する。addEventListener()の書き方は以下のようになる。 <対象要素(オブジェクト)>.addEventListener(<イベントの種類>,<関数>,<イベント伝搬形式>) 【各引数について】 ■ イベントの種類: 登録するイベント('click'、'mousedown'など)を指定する。 イベントの種類については、MDNのイベント一覧を参照。(MDNのイベント一覧) ■ 関数: イベントが発生したときに実行される処理を指定する。 ■ イベントの伝搬形式: イベントの伝搬形式をtrue/falseで指定できる。通常は、falseで省略することも可能。 また、対象要素(オブジェクト)を取得するためには、getElementById()を使用する。これは、引数に指定されたid属性の値に対応した要素のElementオブジェクトを返す。 document.getElementById(<id>) 実装 上記のことを使って、ボタンが押されたときにメッセージを表示するように実装すると以下となる。 HTML <button id = "message"> メッセージ </button> JavaScript let button = document.getElementById("message"); button.addEventListener('click'()=>{ alert("ボタンが押されました!"); });
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

日本における転職活動についてのユーザーインタビューに外国人ITエンジニア大募集中!

[日本語] ※English Below Jellyfishについて 東京に拠点を置くJellyfishは、技術系の人材紹介会社です。私たちのモットーは、"Expand Your Horizon "であり、海外のIT人材が日本で夢のある仕事を見つけられるようサポートしています。 会社概要 Jellyfishは、オムニチャネル・マーケティング手法を採用戦略に応用し、柔軟性をビジネスの中核に据えています。これまでの求職者の声を聞くと、転職活動の方法やプラットフォームが多様化しており、適切なタイミングで適切な仕事を持つエージェントにアプローチすることが難しくなっています。 これは海外のITエンジニアだけでなく、日本のエンジニアにも当てはまります。タッチポイントを明確に理解し、ITエンジニアがタイムリーに良い仕事を見つけられるようにするために、皆さんの洞察力、経験、提案をもっと知りたいと思っています。 インタビュー内容 ・インタビューは、日本語、英語、韓国語、中国語、ベトナム語のいずれかの言語で行われます。 ・面接時間は45分~1時間、時間帯は9:00~19:00(平日のみ)です。 ・日本在住の外国人ITエンジニアで、日本での勤務経験が2年以上ある方を対象としています。 面接の流れ 企画書の提出を受けて、こちらからご連絡いたします。なお、ボリュームの関係上、お寄せいただいたすべてのご提案にお応えできない場合がありますので、あらかじめご了承ください。 報酬額 Amazon Card 3,000円 ご興味のある方は、以下の情報を添えてh-thu@jellyfish-g.co.jpまでにご応募ください。 ・氏名 ・年齢 ・現在の居住地(日本または海外) ・最終学歴 ・日本語レベル ・職務経験 ・日本での実務経験 ・転職経験の有無 ・現在のポジション ・開発言語 その他、ご不明な点がございましたら、お気軽にお問い合わせください。チャットを楽しみにしています。 [ENGLISH] About us Located in Tokyo, Japan, Jellyfish is a tech recruiting agency. Our motto is "Expand Your Horizon", supporting foreign IT talents to find their dream jobs in Japan. Overview Applying the omnichannel marketing method to our recruiting strategy, Jellyfish embeds flexibility at the core of its business. As we have heard the voice from our previous candidates about how diverse the methods/platforms they are using to find new jobs, it’s difficult to reach out to agencies with the right job at the right time. This does not apply only to foreign IT engineers, but also to Japanese engineers. In order to clearly understand the touchpoint and help IT engineers find a good job timely, we would be appreciated to know more about your insights, experiences, and suggestions. Interview details ・The interview is available in one of the following languages Japanese, English, Korean, Chinese, Vietnamese ・The interview should last 45 minutes to 1 hour within the timeframe of 9:00 ~ 19:00 (JST) (weekdays only) ・We are looking for foreign IT engineers living in Japan, having worked in the country for at least 2 years. Interview flow We will contact you once we have received your proposal submission. Please understand that due to volume, we might not be able to respond to all proposals submitted. Compensation rate Amazon Gift Card 3,000 yen Should you be interested, please submit your proposal to h-thu@jellyfish-g.co.jp with the following information ・Name ・Age ・Current location (Japan or Overseas) ・Final education ・Japanese level ・Work Experience ・Work experience in Japan ・Number of times you have changed jobs ・Current Position ・Development language Should you have any other questions, please feel free to contact us. Looking forward to our chat!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeScriptメモ 〜変数編〜

型アノテーション 変数には型がある。変数には決められた型の変数しか代入できない。 型アノテーションは明示的に型を示す方法。 let x = 1 // number型が自動的に決まる let y: number // number型を明示的に指定 y = 1 y = '1' // エラー any型 以下のスクリプトは問題なく実行できる。xにはany型が指定されるため。 let x x = 123 x = 'ok' 型変換 テキストを数値に変換する時は変数に+をつける let x = '1' +x または関数を利用する let x = '1' x = Number(x) 配列 型アノテーション const a: number[] = [1,2,3] 配列の中身を変更できなくする const a: readonly number[] = [1,2,3] タプル型 配列に複数の型の値を入れられる const a:[string, number] enum型 多数の中から一つを選ぶ場合 enum janken { goo, choki, paa } let goo = janken.goo enum型は何も代入していない場合数値が入っているので注意 enum Gender { male = 'male', female = 'female', other = 'other' } console.log(Gender.female) // 'female' enum Season { spring, summer, autumn, winter } console.log(Season.autumn) // 2 型のエイリアス 型名にエイリアスを設けることでわかりやすくなる。 type name = string type age = number let me:[name, age] typeで型を定義する タプル型そのものにエイリアスを作成できる。 type name = string type age = number type email = string type person = [name, age, email] const taro: person = ['taro', 20, 'foo@bar.com'] リテラル型 リテラルを方にできる type ok = 'ok' let ok:ok ok = 'ok' ok = 'foo' // error! 条件型 type msg = 'hello' | 'bye' type id = number | string 型チェック typeofを使用 type id = number | string let x:id = 1 let y:id = 'taro' console.log(typeof(x)) // 'number' console.log(typeof(y)) // 'string' ユーティリティ型 変数に様々な性質を付加する特殊な型。 type data = [string, number] // 値の変更不可なdata型を定義 type ReqData = Readonly<data> const x:data = ['foo', 20] const y:ReqData = ['bar', 40] シンボル 一意な値であることが保証された型。 const a:unique symbol = Symbol('ok') const b:unique symbol = Symbol('ok') console.log(a === b) // false console.log(a == b) // false nullかもしれない値 type data = [name:string, age?:number] 絶対にnullではない値 type data = [name!:string, age:number]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

貴方の頸椎を守り隊!!~カメラで様子をチラ見中。居眠りも起こしてくれるよ。~ #画像認識 #猫背予防

姿勢が悪すぎる 在宅勤務が中心になってから、オフィス用の机や椅子で仕事をする事がなくなり、仕事中の姿勢がかなり悪くなり、腰痛やら肩こり首のバキバキ感がレベルアップしています。 「姿勢が悪いよ!」と指摘してくれる人もいなく、ストレートネックへ一直線状態なので、少しでもいい姿勢に戻れるようなツールを作成しました! その名も 貴方の頸椎を守り隊 頸椎を守るついでに、居眠りが始まったら起こしてくれるオプションもセットとなっています。 守り隊紹介 仕事中に使用する想定のため、非アクティブ画面となるのでデザインはとてもシンプルです。 ★こちらで試せます★ カメラが認識後、10秒ごとに判定しています。 ①猫背状態(首が前に出ている)  →ポコンと音が鳴ります。 ②居眠り(下向き)  →爆発します ③居眠り(上向き)  →爆発します 仕様 ・JavaScript ・HTML ・Teachable Machine ・ml5.js ・Bootstrap Teachable Machineでの設定 カメラ映像を判定する方法として「Teachable Machine」を使用しました。 Webカメラでサンプル画像を撮るだけで、自動でカメラに写っている映像を判定してくれる優れモノです。 ①サンプル画像の作成 ②トレーニング トレーニングボタンを押すだけで、自動で学習してくれます。 ③結果確認 トレーニング結果はすぐに確認できます。 プログラム See the Pen 姿勢チェック‗公開 by IshikawaIkumi (@ishikawaikumi) on CodePen. 音源の設定 こちらの音源を使用しました。 音源はダウンロードして使用する必要があるため、ダウンロード後に個人のGitHubにファイルをアップロードし、使用しました。 <参考資料> ①GitHubへファイルをアップロード ②アップロードしたファイルの使用方法 守り隊の状況 現在の居眠り判定は高い精度になっていますが、猫背判定が服や髪型等で判定がぶれる事があるため、調整が難しい部分となっています。 判定がブレてきたな、と思ったら、その時の服や髪型でパターンを追加したいと思います。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自由に移動できる3D掲示板を作った

はじめに 3D掲示板という3Dな掲示板を作りました。 3D空間にコメントを投稿できる掲示板です。 無料で使えます。 3D掲示板 - NARUPORT なんで開発したの? Three.jsというJavaScriptの3Dライブラリを使って何か作ってみたかったのですが、横になってボーっとアイデアを考えていたら突如、この3D掲示板が浮かびました。 3D空間にコメントを投稿できるというのは、たぶんVR Chatとかだと普通なんでしょうがWebの世界ではあまり見かけないなぁと思いました。 「3D掲示板」でググってもそれらしいのはヒットしなかったので、たぶん日本初だと思います。 掲示板の使い方 キーボード操作です。↓が対応です。 エンター ... コメント投稿フォームの表示 W ... 前進 S ... 後退 D ... 右に移動 A ... 左に移動 F ... 上に移動 C ... 下に移動 上のキーを使うと3D空間上を自由に移動できます。 視点はコメントが見えづらくなるので固定にしてます。 エンターキーを押すとコメント投稿フォームが現れます。 コメントを書いて「投稿する」ボタンを押すとコメントが投稿されます。 書き込める文字は↓の通りです。 ひらがな カタカナ 大小英数字 ほか記号すこし 3D空間にしてみて面白かったところ 3D空間上にコメントが配置されるので、話題ごとにクラウドができて視覚的に面白いなぁと思いました。 3D空間内にオブジェクトなどを配置すればさらに面白くなるかもしれません。 たとえば掲示板をスレッド式にして、スレッドごとに背景を設定できるようにするとか面白いと思います。 いまのところ3D掲示板はスレッド式ではなくて単一の掲示板になっていますので、そういう仕様にする場合はカスタムが必要になりそうです。 技術的なところ 今回使った技術は↓の通りです。 Django Django REST Framework Vue Three.js なぜかSPAになっているので、Vueを使ってます。 問題としては生成されるjsファイルが重いというところです。 特にダイエットなどはしていないので1MBぐらいあります。 技術的な詳細は↓の記事をご覧ください。 Three.jsで「3D掲示板」を作った話【Django】 - narupoのブログ おわりに これからインターネット回線がもっと太くなれば3D掲示板などは一般的になるのでしょうか? 個人的にはVRにも興味があるのですが、なかなか手を出せずにいます。 3D掲示板、もしよかったら使ってみてください。 3D掲示板 - NARUPORT
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自由に移動できる3D掲示板を作った【個人開発】

はじめに 3D掲示板という3Dな掲示板を作りました。 3D空間にコメントを投稿できる掲示板です。 無料で使えます。 3D掲示板 - NARUPORT なんで開発したの? Three.jsというJavaScriptの3Dライブラリを使って何か作ってみたかったのですが、横になってボーっとアイデアを考えていたら突如、この3D掲示板が浮かびました。 3D空間にコメントを投稿できるというのは、たぶんVR Chatとかだと普通なんでしょうがWebの世界ではあまり見かけないなぁと思いました。 「3D掲示板」でググってもそれらしいのはヒットしなかったので、たぶん日本初だと思います。 掲示板の使い方 キーボード操作です。↓が対応です。 エンター ... コメント投稿フォームの表示 W ... 前進 S ... 後退 D ... 右に移動 A ... 左に移動 F ... 上に移動 C ... 下に移動 上のキーを使うと3D空間上を自由に移動できます。 視点はコメントが見えづらくなるので固定にしてます。 エンターキーを押すとコメント投稿フォームが現れます。 コメントを書いて「投稿する」ボタンを押すとコメントが投稿されます。 書き込める文字は↓の通りです。 ひらがな カタカナ 大小英数字 ほか記号すこし 3D空間にしてみて面白かったところ 3D空間上にコメントが配置されるので、話題ごとにクラウドができて視覚的に面白いなぁと思いました。 3D空間内にオブジェクトなどを配置すればさらに面白くなるかもしれません。 たとえば掲示板をスレッド式にして、スレッドごとに背景を設定できるようにするとか面白いと思います。 いまのところ3D掲示板はスレッド式ではなくて単一の掲示板になっていますので、そういう仕様にする場合はカスタムが必要になりそうです。 技術的なところ 今回使った技術は↓の通りです。 Django Django REST Framework Vue Three.js なぜかSPAになっているので、Vueを使ってます。 問題としては生成されるjsファイルが重いというところです。 特にダイエットなどはしていないので1MBぐらいあります。 技術的な詳細は↓の記事をご覧ください。 Three.jsで「3D掲示板」を作った話【Django】 - narupoのブログ おわりに これからインターネット回線がもっと太くなれば3D掲示板などは一般的になるのでしょうか? 個人的にはVRにも興味があるのですが、なかなか手を出せずにいます。 3D掲示板、もしよかったら使ってみてください。 3D掲示板 - NARUPORT
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

IntersectionObserverについて

覚えたこと この辺は定型分 const io = new IntersectionObserver(function(entries) { entries.forEach(entry => { }) }) document.querySelectorAll('.item').forEach(item => { io.observe(item); }) オプションつけたり、画面に入ったらクラスつけたりするならこんな感じで const options = { root: null, rootMargin: "-100px 0px 0px 0px", threshold:0 } const io = new IntersectionObserver(function(entries) { entries.forEach(entry => { if(entry.isIntersecting){ entry.target.classList.add('inview'); }else { entry.target.classListremove('inview') } }) }, options); ちょっと関係ないけど スクロールに合わせてナビにクラス付けるならこんな感じ entries.forEach(entry => { if(entry.isIntersecting){ const id = entry.target.getAttribute('id') document.querySelector(`nav li a[href="#${id}"]`).parentElemnt.classList.add('act'); } }) ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Harmoware-VISの紹介

Harmoware-VIS とは Harmoware-VIS は JST OPERA ( 産学共創プラットフォーム共同研究推進プログラム ) の支援を受けて、人間機械協奏技術コンソーシアム ( HMHS: Human Machine Harmonization System ) (http://hmhs.jp) において、主に名古屋大学河口研究室が中心になって開発したものです。 UBER が開発した deck.GL ( https://github.com/visgl/deck.gl ) を利用し、その上に移動体とその付加情報を可視化する機能を 追加しています。 Harmoware-VIS の仕組み UBER の deck.gl は、マップ上で位置を緯度経度で指定し、様々な3Dや2Dオブジェクトを配置することが可能です。 しかし、配置したオブジェクトをマップ上で移動させる機能はありません。 そこで Harmoware-VIS ではオブジェクトの位置を変化させたデータを、次々に deck.gl で表示させることで、オブジェクトが移動するアニメーションを実現しています。 移動データの構造 例として下図のような2つの移動体を想定します。 ひとつの移動体データは「始点」「終点」「移動経路」で構成されます。 移動体データは複数定義すること可能です。 2点間の位置は、最短距離での移動を想定し、始点からの経過時間を元に計算します。 移動データファイル 下に2つの移動体の定義例を示します。 移動データ(json形式)の定義例. [ {"operation":[ // 1つ目の移動体のデータ {"position":[136.945255, 35.190691, 0], // 経度、緯度、高度を指定(高度省略時はゼロ) "elapsedtime":1551575400}, // 上記地点の通過時間をUNIX時間で指定 {"position":[136.936864, 35.191591, 0],"elapsedtime":1551575460}, {"position":[136.945255, 35.190691, 0],"elapsedtime":1551575520}]}, {"operation":[ // 2つ目の移動体のデータ {"position":[136.900947, 35.143257, 0],"elapsedtime":1551577140}, {"position":[136.901769, 35.134622, 0],"elapsedtime":1551577260}, {"position":[136.906622, 35.127823, 0],"elapsedtime":1551577380}]} ] 移動データはjson形式で定義します。 operation に始点から順に終点までの移動経路を位置と通過時間で定義します。 Harmoware-VIS を使ったアプリ作成について Harmoware-VIS は以下で公開しています。 https://github.com/Harmoware/Harmoware-VIS 上記のgithubレジストリ内のexamplesアプリも参考にしてください。 https://github.com/Harmoware/Harmoware-VIS/tree/develop/1.7x/examples/visualize-sample その他、Harmoware-VIS を使った、簡単なサンプルを公開しています。 https://github.com/Harmoware/Harmoware-VIS-Demo https://github.com/Harmoware/Harmoware-VIS-xband https://github.com/Harmoware/Harmoware-VIS-SUMO-FCD https://github.com/Harmoware/Harmoware-VIS-Tile3DLayer https://github.com/Harmoware/Harmoware-VIS-Geojson Harmoware-VIS は react、redux 及び react-redux を使用しています。 画面更新のトリガーは基本的にルートからの props に含まれる action に定義済みの関数で行います。 アプリのカスタマイズで action に関数を追加することも可能です。 以下に Harmoware-VIS のリファレンスドキュメントを公開しています。 https://harmoware-vis.gitbook.io/harmoware-vis-documents/ その他の用例などは、Qiita内に記事を随時追加していきます。 よろしくお願いします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RTAイベント向け簡易背景システムをつくったよ

今回のレギュレーション この記事で得られる知識 そもそもRTAとは? 作ったものは? なぜ今回の形になったのか? 当日の運営について 完走した感想 この記事で得られる知識 サーバがない中で構築する動的背景の作成 NodeCGでやろう!と思える心 レガシーできたないソースは読みづらいという見本。 そもそもRTAとは? RTAとはReal Time Attackの略なのですが、 簡単に言うとゲームの早解きのことです。 どんな手を使ってもいいから何人かの人たちが自分の得意なゲームをいかに早くクリアするかを突き詰めるといったもので 世界ではGames Done Quickというアメリカで行われるイベントが一番有名だったりします。 日本でもRTA in Japanというイベントが年に2回開かれており、 今年も12月26日から6日間、24時間開催される予定となっています。 https://nlab.itmedia.co.jp/nl/articles/2012/28/news054.html こちらの記事を見ていただけると多分その様子がよく分かるかと思います。 わからなくなるかもしれませんが、でも実際にこういった物理的な手を使ってでもクリアしたら正義です。 これらのイベントは主にTwitchで行われ、イベント中に得たチアー(投げ銭)やサブスクは 全て国境なき医師団のような慈善事業団体へ寄付されます。(RTA in Japanでは2000万円ほどあつめ、それを募金しています) 皆さん何卒よろしくお願いいたします(私は運営ボランティアでの参加予定) さて、今回このシステムを利用したのは個人が運営する同様のRTAイベントで、 1ゲーム辺り数時間かかる長めのゲームのRTAイベントです。 作ったものは? ゲームを実況する際に表示するこのようなものを作成しました。 走者(プレイをする人)の情報や、解説者の情報、次の走者と走るタイトルの情報、 あと、実際に現在走っているゲームのタイトル、カテゴリ、EST(クリアまでどのぐらいかかるか?の目安)を表示しているのですが、 こちらのデザインとプログラムを担当し、作成いたしました。 今回は2回目の開催となったのですが、1回目がこちら(私はこの時作成には関わってないです) 固定の画像を背景として表示させていたため、突然なにかが変更になったときに対応が取りづらいため、 こちらを今回動的に変更できるようにしました。 今回のがこちら。 こちらなんですが本来はNodeCGを使えばもっと楽ができたのですが、 今回サーバがなかったためそちらを即断念。(後から別の運営担当が自前サーバを用意してくれたんですが…) 最初は静的なHTMLを走者に配り、OBSという配信ソフトでよみこんでもらうことで こちらを実現しておりました。 で、走者情報や次の走者情報などは、スプレッドシートからデータをとってきて、 運営その読み込み対象番号を変える事で切り替わっていくようにしていました。 さて、ここからが実際のソースと内容になります。 一部黒塗りさせていただきましたが、このように走者の一覧がスプレッドシートに書かれているので、 こちらをAppsScriptで読み込むように変更しました function loading() { var ss = SpreadsheetApp.openById("XXXXXXXXXXXXXXXXXXXXXXXXX"); var listss = ss.getSheetByName("スケジュール"); var nowList = listss.getRange("K2").getValue(); let i = 0; while(listss.getRange("A4").offset(i , 0).getValues() != ""){ var nowRow = listss.getRange("K4").offset(i , 0).getValue(); if(nowRow == nowList){ return listss.getRange(4 + i ,3 , 1 ,19 ).getValues(); } i++; } return "Final"; } function doGet(e) { return ContentService.createTextOutput(loading()).setMimeType(ContentService.MimeType.TEXT); } もう少しスマートな書き方があったかとはおもうのですが、 とりあえず自分しかみないソースだし適当で良いか というのが上記のような惨状です。 現在の走者番号がK2のセルに入っているのでそれを取得し、 K2に設定されている番号と同じものを探して見つかったらその行を取得して、行の内容全てを返してあげるというものです。 このGASを直接叩くとこんな感じの内容が帰ってきます。 塩化コボルト,天外魔境ZERO,Any%(with Turbo/Auto Fire),コマンド選択制RPG,Sat Dec 30 1899 14:26:01 GMT+0900 (日本標準時),5:40:00,Sat Dec 30 1899 09:13:59 GMT+0900 (日本標準時),Sat Dec 30 1899 14:41:00 GMT+0900 (日本標準時),2,塩化コボルト,XXXXX,twitch,,,,Ever Oasis 精霊とタネビトの蜃気楼,Any%,ショーダイ, あとはこれを本体のHTMLの方で受け取って、分解して入れ込んでいく作業。…といった感じです。 main.js $(function(){ $.ajax({ type: 'GET', url:'[GASのURL]', dataType:"text", success: function(data){ // HTML上の必要な箇所に値を設定します。 const obj = ["title","cate","est","runName","runID","runIcon","comName","comID","comIcon","nextTitle","nextCategory","nextRunner","nextCommentator"]; const colNum = [1,2,5,9,10,11,12,13,14,15,16,17,18]; const iconImage = { "youtube":"yt.png", "twitch":"tw.png", "nico":"nc.png", "twitter":"tt.png" }; var textLength = data.split(","); $("#cate").text(textLength[0]); var colArr = ["comName","comID"]; for(let i=0;i < obj.length;i++){ var clname = "#"+ obj[i]; if(obj[i] == "comName" && textLength[colNum[i]] == "") { $("#commentator").css("display","none"); $("#commentator2").css("display","none"); $(".freeSpace").css("height","300px"); }else if(obj[i] == "comName" && textLength[colNum[i]].indexOf("/") < 1){ $(".freeSpace").css("height","210px"); $("#commentator2").css("display","none"); }else if(obj[i] == "comName" && textLength[colNum[i]].indexOf("/") >= 1){ $(".freeSpace").css("height","130px"); } if(obj[i] == "comIcon" && textLength[colNum[i]].indexOf("/") >= 1){ var listIcon = textLength[colNum[i]].split("/"); for (let index = 0; index < listIcon.length; index++) { const element = listIcon[index]; $(clname + (index + 1)).addClass(element); } }else if(obj[i] == "comIcon" && textLength[colNum[i]] != "" ){ $(clname + 1).addClass( textLength[colNum[i]]); }else if(obj[i].match(/Icon/) && textLength[colNum[i]] != ""){ $(clname).addClass(textLength[colNum[i]]); }else if(obj[i] == "title" && textLength[colNum[i]] != ""){ var titleLen = textLength[colNum[i]].replace(/\r?\n/g , ""); if (titleLen.length > 40 ){ $(clname).css("font-size","100%"); } $(clname).text(titleLen); }else if(obj[i] == "cate"){ if (textLength[colNum[i]].length > 30 ){ $(clname).css("font-size","16px"); } $(clname).text(textLength[colNum[i]]); }else if(obj[i] == "est"){ $(clname).text("EST:" + textLength[colNum[i]]); }else if(obj[i] == "nextTitle" && textLength[colNum[i]] != ""){ if (titleLen.length > 40 ){ $(clname).css("font-size","16px"); } $(clname).text(textLength[colNum[i]].replace(/\r?\n/g , "")); }else if(obj[i] == "nextRunner" && textLength[colNum[i]] != ""){ $(clname).text("Runner:" + textLength[colNum[i]]); }else if(obj[i] == "nextCommentator" && textLength[colNum[i]] != ""){ $(clname).text("Commentator:" + textLength[colNum[i]]); }else if(obj[i] == "nextCategory" && textLength[colNum[i]] != ""){ if(textLength[colNum[i]].length > 30){ $(clname).css("height","60px") } $(clname).text("Category:" + textLength[colNum[i]]); }else if(obj[i] == "nextCommentator" && textLength[colNum[i]] == ""){ $(clname).remove(); }else if(colArr.includes(obj[i]) && textLength[colNum[i]] != "" ){ var listCom = textLength[colNum[i]].split("/"); for (let index = 0; index < listCom.length; index++) { const element = listCom[index]; $(clname + (index + 1)).text(listCom[index]); } }else{ $(clname).text(textLength[colNum[i]]); } } } }); }); リファクタリングしてないがゆえにここまで汚くなってしまったわけですが、 こうなったのにも理由があり、あとから色々変わっていったのと時間がなく… 自分のレベルの低さに恐れおののいてしまいますね。 もっとクラスを有効に使うべきでしたし、 とりあえず動けばいいやろで作ってしまったのが問題でした。 あと、HTMLの方は割愛しますが、地味に歯車が右へ左へと切り替わりつつ回っているので動画にてご注目いただきたいなというところです。 なぜ今回の形になったのか? 先項でも述べた通り、サーバがない状態で走者にHTMLの塊だけ渡して運営しようとしていたためです。 サーバがない状態でも突然の対応に走者が追われないために作成しました。 当日の運営について 当日はあくまでスプレッドシートをいじりさえすればOKの状態にしていました。 突然なにか変更があった場合もスプレッドシートの情報さえ変更すれば、 走者に影響なく運営はできたので、概ね思った通りの動きができていたと思います。 あと、別途裏でオープニングを作成してほしいと数時間前に言われて 慌てて作成したり… それと、エンディングも必要と言われるだろうと想定し、 エンドロールとエンディング用画面を作成しました https://www.twitch.tv/videos/1159875157?t=00h09m15s 前もっていってほしかった… 完走した感想 二度とこんな形で作りたくない。 サーバがあるならNodeCGを使おう。 まじで。 ライブ配信レイアウトを作るNode.jsのフレームワーク https://qiita.com/Hoishin/items/36dcea6818b0aa9bf1cd 1から学ぶNodeCG#1:NodeCG導入編 https://qiita.com/cma2819/items/e775bd8aa2a2fa911d4c NodeCGで配信を華やかに https://qiita.com/secchanu/items/f422f1e101cc85caf40a RTA in JapanのNodeCGレイアウトを動かしてみる https://qiita.com/pasta04/items/d676d9c2fb716176f665
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】非同期処理⑧ fetch

はじめに Udemyの【JS】ガチで学びたい人のためのJavaScriptメカニズムの講座の振り返りです。 前回の記事 目的 非同期処理についての理解を深める 本題 1.fetch サーバー上から情報を取得したりできる関数 前提 main.jsファイルと同じ階層にusers.jsonファイルを準備する (VSコードの拡張機能:Live Serverがインストールされていないとエラーになります。) 基本的にはJSと同じ書き方 しかしシングルクオーテーション使用不可 必ずダブルクオーテーション 一番最後の要素にカンマを使うとエラーになる users.json [ { "name": "Bob", "age": 23 }, { "name": "Tim", "age": 30 }, { "name": "Sun", "age": 25 } ] 例1 fetchの基本的な使い方 main.js // 第一引数にリクエスト先のURLを入力(今回はuser.jsonファイル) // fetch("users.json") // どのような戻り値が帰ってくるのかコンソールで確認 // console.log(fetch(`users.json`)) // このようにするとPromiseが返ってきていることがわかるのでthenメソッドが使える fetch(`users.json`).then(function(response){ // サーバーから返ってきたデータを格納している console.log(response); }) 出力結果は以下の通り console. body: (...) bodyUsed: false headers: Headers {} ok: true   #サーバーからデータが取得できたことを表している redirected: false status: 200 #Httpステータスが200ということは通信に問題がない状態 statusText: "OK" type: "basic" url: "http://127.0.0.1:5500/080_%E9%9D%9E%E5%90%8C%E6%9C%9F%E5%87%A6%E7%90%86/110_fetch/start/users.json" [[Prototype]]: Response この結果にあるjsonメソッドを使う(jsonファイル取得のため) main.js fetch(`users.json`).then(function(response){ // コンソールからjsonというメソッドが使えることがわかる return response.json(); }).then(function(Json){ // この引数Jsonにデータが入っているか確認する console.log(Json); }) このようにすると以下のように出力される console. (3) [{…}, {…}, {…}] 0: {name: 'Bob', age: 23} 1: {name: 'Tim', age: 30} 2: {name: 'Sun', age: 25} length: 3 [[Prototype]]: Array(0) 例2 データの取得がうまくいっていたので、応用して出力する main.js fetch(`users.json`).then(function(response){ return response.json(); }).then(function(Json){ console.log(Json); // for文を使用してループ for(const user of Json){ // テンプレートリテラルを使って出力 // nameとageがusers.jsonファイルのものを使用 console.log(`おれは ${user.name}, 今年で${user.age}になる。`) } }) // 以下のように出力されている // おれは Bob, 今年で23になる。 // おれは Tim, 今年で30になる。 // おれは Sun, 今年で25になる。 重要なのは2点 Promiseのオブジェクトを返す jsonのオブジェクトを使用するにはjsonメソッドを使用して取得する必要がある 例3 awaitとasyncで出力してみる async function fetchUsers(){ const response = await fetch(`users.json`); const Json = await response.json(); for(const user of Json){ console.log(`おれは ${user.name}, 今年で${user.age}になる。`) } } // 実行すると同じ出力結果となる fetchUsers(); 今日はここまで! 参考にさせて頂いた記事 【JS】ガチで学びたい人のためのJavaScriptメカニズム Let'sプログラミング JavaScript入門
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascript カレンダー作成

はじめに 本稿では下記動画にあるソースコードを解説します。 動画にあるカレンダーは、他のライブラリに依存しない実装になっています。 簡単なカレンダーが表示できれば十分、かつ、依存ライブラリを増やしたくない場合などで使えると思います。 ソースコード https://github.com/lashaNoz/Calendar カレンダー作成に必要な変数 // 今日の日付 EX) 2021/11/24 const date = new Date(); // 月末 EX) 2021/11/30 const lastDay = new Date(date.getFullYear(),date.getMonth() + 1,0).getDate(); // 先月末 EX) 2021/10/31 const prevLastDay = new Date(date.getFullYear(),date.getMonth(),0).getDate(); // 月初の曜日。0 ~ 6 = 日曜 ~ 土曜 EX) 1 (2021/11/1は月曜日) const firstDayIndex = date.getDay(); // 月末の曜日。 EX) 2 (2021/11/30は火曜) const lastDayIndex = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDay(); 日付表示 calndar.js // 1.前の月 // firstDayIndexは月初の曜日。EX) 1(月曜) // prevLastDayは先月の末日。EX) 31 (10/31) for(let i = firstDayIndex; i>0; --i){ days += `<div class="prev-date">${prevLastDay - i + 1}</div>`; } // 2.今月 const today = new Date().getDate(); for(let i = 1; i<=lastDay; ++i){ if(i === today){ days += `<div class="today">${i}</div>`; } else { days += `<div>${i}</div>`; } } // 3.次の月 const sunday = 6; for(let i = 1; i<=sunday-lastDayIndex; ++i){ days += `<div class="prev-date">${i}</div>`; } 前月、次月表示ボタン押下時処理 document.querySelector(".prev").addEventListener("click", () => { // 前月のカレンダーを表示 date.setMonth(date.getMonth() - 1); renderCalendar(); }); document.querySelector(".next").addEventListener("click", () => { // 次月のカレンダーを表示 date.setMonth(date.getMonth() + 1); renderCalendar(); }); DOM読み込み完了後にJavascript実行(おまけ) 動画の実装だと、javascriptを読み込むタイミングによってはカレンダーが表示されません。 window.onload内に処理を定義することで、DOM読み込みが完了した後に処理が実行されます。 DOMContentLoaded イベントに処理を定義すると、DOM読み読み込み完了後に処理が実行されます。 window.addEventListener('DOMContentLoaded', () => { // DOM読み込み完了のタイミングで実行したい処理 const date = new Date(); const renderCalendar = function(){ date.setDate(1); ~~~ });
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascriptの関数とメソッド

関数とは 関数はデータを受け取ったりして一連の処理をまとめたものです。 引数とは関数実行時に渡す値のことです。 英語でfunctionと呼びます。 関数の使い方 1番上の行のcount_plus100が関数名、numberが引数名です。 //関数を定義 function count_plus100(number) { const result = number + 100; #処理の記述 return result; } //関数を実行 const number = 900; const result = count_plus100(number); console.log(result);  //1000と出力 numberという引数900に100が足されて1000です。 関数の戻り値 ポイントはreturnをしないとundefined(未定義)と出てきてしまうことです。 returnして呼び出し元に戻します。 function count_plus100(number) { return number + 100; //returnがないと未定義と出てきてしまう } const number = 900; const result = count_plus100(number); console.log(result); //1000と出力 関数に引数がない場合は? Javascriptで引数がない時はどうなるのでしょうか。 check_messasgeという引数がない状態です。 その場合ポイントは()が必要ということです。 check_messasge()のように()をつけます。 functionの後にcheck_messasge()で関数を定義して その後にcheck_messasge();で関数を実行します。 function check_messasge() { const check = 'チェック'; alert(check); } check_messasge(); check_messasge();で関数を実行後、devtoolsでチェックと出てくるはずです。 ⚫︎参考資料 https://www.javadrive.jp/javascript/function/index2.html https://wp-p.info/tpl_rep.php?cat=js-biginner&fl=r14
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む