20210916のJavaScriptに関する記事は18件です。

Vueのタグが見えないようにローディング画面を付ける

Vueの規模が大きくなってくると、Vueのロードが完了するまでの間、{{ xxxx }}のようなタグが目立ってくるようになります。 Vueのコンポーネントを使えばうまく回避できるのかもしれませんが、Vueインスタンスと双方向データバインディングだけで満足している私にとっては、少々悩ましいです。 そこで、VueやJavascriptに頼らず、CSSだけでVueのロード前の画面をかぶせるローディング中画面を作ろうかなあと思います。 実際のローディング画面を見たい場合は、以下のWebページを開くと、一瞬だけローディング画面が見れます。 Webページ  https://poruruba.github.io/utilities/ (参考)GitHub  https://github.com/poruruba/utilities やってること Flexパネルを画面全体で覆って、CSSアニメーションを表示させ、Vueのロードが完了したら、Flexパネルをフェードアウトさせる、です。 画面全体を覆うFlexパネルの定義 ロード完了したらフェードアウトさせる処理 フェードアウトを開始する処理 ロード中のアニメーションのひな型を選ぶ HTMLにロード中アニメーションのエレメントを挿入する Javascriptにフェードアウト開始の呼び出しを追加する 画面全体を覆うFlexパネルの定義 CSSで以下のように表現します。 public/css/start.css #loader-background { background: #fff; height: 100%; width: 100%; position: fixed; display: flex; top: 0px; left: 0px; z-index: 9999; --sk-color: #00ced1; } heightとwidthを100%にしてます。 ※--sk-colorは後で説明します。 ロード完了したらフェードアウトさせる処理 CSSで以下のように表現します。 public/css/start.css .loader-loaded { animation: fadeout-anim 1s linear forwards; } @keyframes fadeout-anim { 100% { opacity: 0; visibility: hidden; } } 上記にて、CSSアニメーションを使って1秒かけて透明およびhiddenにしてます。 フェードアウトを開始する処理 前のCSSで、loader-loadedのクラスがついていれば、フェードアウトのアニメーションが開始されるようにしました。 なので、Vueのロードが完了したタイミングで、Javascriptから以下を呼び出せばよいです。 public/js/vue_utils.js function loader_loaded(){ const element = document.getElementById("loader-background"); element.classList.add('loader-loaded'); } ロード中のアニメーションのひな型を選ぶ こちらのCSSが単純そうだったので採用しました。 tobiasahlin/SpinKit デモページもありますので、どんなアニメーションか確認できます。 CDNにも登録されているのですぐに使えます。 public/index.html <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/spinkit/2.0.1/spinkit.min.css" /> 例えば、以下のようなHTMLを追加すればよいようです。 <div class="sk-chase"> <div class="sk-chase-dot"></div> <div class="sk-chase-dot"></div> <div class="sk-chase-dot"></div> <div class="sk-chase-dot"></div> <div class="sk-chase-dot"></div> <div class="sk-chase-dot"></div> </div> HTMLにロード中アニメーションのエレメントを挿入する <html> の <body> の先頭あたりに以下を追加します。 上記のアニメーションを使う場合 public/index.html <div id="loader-background"> <div class="sk-chase sk-center"> <div class="sk-chase-dot"></div> <div class="sk-chase-dot"></div> <div class="sk-chase-dot"></div> <div class="sk-chase-dot"></div> <div class="sk-chase-dot"></div> <div class="sk-chase-dot"></div> </div> </div> 要するに、idが「loader-background」をもったdivエレメントの中にさきほどのアニメーションの定義を記載するということです。 そのとき、classに「sk-center」を付けてあげます。そうすると、アニメーションが画面中央で動いてくれるようになります。 また、FlexパネルのCSS指定のところで「--sk-color: #00ced1;」というCSS変数を追加していました。これは、SpinKitのアニメーションの色の指定です。お好みで色を変えてください。 Javascriptにフェードアウト開始の呼び出しを追加する Vueでのロード完了として、mountedに以下を追加しましょう。 public/js/start.js mounted: function(){ // ・・・ loader_loaded(); // ★これ } おわり 私は、上記をすでに埋め込み済みの以下のテンプレートをいつも使ってます。 poruruba/express_template 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ぼくがかんがえたさいきょうの"定員付きフォーム"

今回は、GASで定員付きのGoogleフォームを作りました。 背景 定員付きのGoogleフォームを作成する記事は他にもありますが、どれも回答数が一定のラインに達したらフォームを閉じるだけのもので、「残りの枠数の表示」などの実務で使う上では必要な機能は搭載されていません。 また、そのようなサイトで紹介されているコードは、どんなに小さな変更(定員を変える等)でもコードをいじらなければならないことがほとんどです。これでは保守管理ができる人が限られてしまいます。この点も実務で使う上では不便だと感じました。 そこで、つくった 実務で差し支えなく使用できるよう、以下のような機能を搭載しました。 フォームの説明欄に残り人数を表示できる 枠が残り少なくなったときにだけ、フォームの説明欄に残り人数を表示するようにできる ITに詳しくなくても、GUIで保守・管理ができる この記事は、開発する上で心がけたことの話が中心となります。導入方法や使い方などの実際に使う上での情報が欲しいという方は、下記のブログをご覧ください。 使用言語 Google Apps Script 普段からJavaScriptに慣れているので、すんなりと馴染めました。ES6の記法にも対応していて便利です。 ソースコード 汎用性や保守のしやすさを重視した結果、140行近くに上りました(笑) ここに全体を示します。 `use strict` const propaty_display_limit = PropertiesService.getScriptProperties().getProperty("DISPLAY_LIMIT"); const propaty_max = Math.floor(PropertiesService.getScriptProperties().getProperty("MAX")); const propaty_old = PropertiesService.getScriptProperties().getProperty("old"); let LIMIT_COUNT = propaty_max || propaty_max === 0 ? propaty_max : 5;//定員 let old_dis = propaty_old ? propaty_old : "";//古い定員の残り人数通知 let display_limit = Math.floor(propaty_display_limit || propaty_display_limit === 0 ? formatLimit(propaty_display_limit) : -1);//残りの枠を表示するしきい値 const kaigyo = "\n\n---------------------------------\n\n";//残り枠数のあとに追加される文字列 function endFormCheck(changed) { const form = FormApp.getActiveForm();//アクティブなフォーム const remaining = LIMIT_COUNT - form.getResponses().length;//残り人数 const displayRemaining = remaining < 0 ? 0 : remaining;//残り人数が負の場合は0にする if ((display_limit === -1 || remaining <= display_limit) && Number(display_limit !== 0)) { const description = `定員${LIMIT_COUNT}名のところ、これまでに${form.getResponses().length}名が申し込みました。\n残りは${displayRemaining}枠です。` + (remaining < 0 ? `(超過${form.getResponses().length - LIMIT_COUNT})` : "");//定員があと何人か if (old_dis && form.getDescription().indexOf(old_dis) !== -1) { form.setDescription(form.getDescription().replace(old_dis, description));//概要文を書き換え } else { form.setDescription(description + kaigyo + form.getDescription());//新規 } PropertiesService.getScriptProperties().setProperty("old", description); } else if (old_dis && form.getDescription().indexOf(old_dis) !== -1) { form.setDescription(form.getDescription().replace(old_dis + kaigyo, ""));//概要文を書き換え } if (form.getResponses().length >= LIMIT_COUNT) { if (changed === "changed" && form.isAcceptingResponses()) { FormApp.getUi().alert(`回答を締め切りました。\n現在の定員は${LIMIT_COUNT}名です`);//定員の変更によってフォームが閉鎖された場合に、ポップアップで通知 } form.setAcceptingResponses(false); } else if (changed === "changed") { if (!form.isAcceptingResponses()) { /* 定員に余裕がある場合には、回答の収集を再開することを提案 */ const ui = FormApp.getUi(); const alert = ui.alert("回答の収集を再開", `回答の収集を再開しますか?\n現在の定員は${LIMIT_COUNT}人、残りは${remaining}枠です。`, ui.ButtonSet.YES_NO); if (alert === ui.Button.YES) { form.setAcceptingResponses(true); } } else if ((display_limit !== -1 && remaining > display_limit) || display_limit === 0) { /*フォームの説明欄に残りの人数が記述されない場合は、ポップアップでお知らせ*/ FormApp.getUi().alert(`定員${LIMIT_COUNT}名\n残り${LIMIT_COUNT - form.getResponses().length}枠`); } } } function onOpen() { const ui = FormApp.getUi(); // Uiクラスを取得する const menu = ui.createMenu('定員制御'); // Uiクラスからメニューを作成する menu.addItem('定員を変更', 'setMax'); // メニューにアイテムを追加する menu.addItem('残りの枠数を表示するしきい値を変更', 'setDisplayLimit'); menu.addItem('最新の状態に更新', 'refresh'); // メニューにアイテムを追加する menu.addToUi();// メニューをUiクラスに追加する } function refresh() { endFormCheck("changed");//更新用 } function setMax() { /* 定員を変更 */ const form = FormApp.getActiveForm();//アクティブなフォーム const ui = FormApp.getUi(); const title = '定員の設定'; const body = `半角数字で定員を入力してください。\n現在の定員は${LIMIT_COUNT}人で、残りは${LIMIT_COUNT - form.getResponses().length}枠です。` const prompt = ui.prompt(title, body, ui.ButtonSet.OK_CANCEL);//プロンプトを表示 const text = prompt.getResponseText(); if (prompt.getSelectedButton() === ui.Button.OK) { if (!isNaN(text) && Number(text) >= 0) { /*数字であることが確認された場合の処理*/ if (!text) return;//空欄だった場合 LIMIT_COUNT = Math.floor(Number(text)); PropertiesService.getScriptProperties().setProperty("MAX", Math.floor(Number(text))); display_limit = Math.floor(propaty_display_limit || propaty_display_limit === 0 ? formatLimit(propaty_display_limit) : 0); refresh(); return; } else { /* 数字じゃなかったら */ const ui2 = FormApp.getUi(); const title2 = 'むむ?'; const body2 = Number(text) < 0 ? '0以上の数を半角で入力してください。' : '半角数字で入力してください。' const alert2 = ui2.alert(title2, body2, ui2.ButtonSet.OK); if (alert2 === ui2.Button.OK) setMax(); } } } function setDisplayLimit() { /* しきい値を変更 */ const form = FormApp.getActiveForm();//アクティブなフォーム const ui = FormApp.getUi(); const title = '残りの枠数を表示するしきい値を設定'; const body = `残りの枠数がいくつ以下になったら、回答者数・残りの枠数をフォームの概要文に表示するかを半角数字で入力してください。\n例)定員が100人で、残り5枠以下になったら残りの枠数をフォームの概要文に表示したい場合→「5」と入力する\n\nまた、残りの枠数が定員の○%以下になったら、というように設定することもできます。\n例)残りの枠数が定員の20%以下になったら残りの枠数をフォームの概要文に表示したい場合→「20%」と入力する\n\n概要文に常に残りの枠数を表示したい場合は、「-1」と入力してください。\n概要文に残りの枠数を表示させない場合は、「0」と入力してください。\n\n現在の設定は「${/%/.test(propaty_display_limit) ? propaty_display_limit : Math.floor(Number(propaty_display_limit))}」です。\n現在の定員は${LIMIT_COUNT}人、残りは${LIMIT_COUNT - form.getResponses().length}枠です。\n\n`; const prompt = ui.prompt(title, body, ui.ButtonSet.OK_CANCEL);//プロンプトを表示 const text = prompt.getResponseText(); if (prompt.getSelectedButton() === ui.Button.OK) { if (/%/.test(text)) { const onlyNum = text.replace("%", ""); if (!isNaN(onlyNum) && Number(onlyNum) >= 0 && Number(onlyNum <= 100)) { /*パーセントで入力された場合*/ if (!onlyNum) return; display_limit = formatLimit(text);//しきい値を変更 PropertiesService.getScriptProperties().setProperty("DISPLAY_LIMIT", text);//しきい値を保存 refresh(); return } } else if (!isNaN(text) && Number(text) >= -1) { if (!text) return;//空欄だった場合 display_limit = formatLimit(Number(text)); PropertiesService.getScriptProperties().setProperty("DISPLAY_LIMIT", Math.floor(Number(text))); endFormCheck(); return; } else { /* 数字じゃなかったら */ const ui2 = FormApp.getUi(); const title2 = 'むむ?'; const body2 = "数字または割合(%)を半角で正確に入力してください。" const alert2 = ui2.alert(title2, body2, ui2.ButtonSet.OK); if (alert2 === ui2.Button.OK) setDisplayLimit(); } } } function formatLimit(int) { if (/%/.test(int)) { /*%を実数に*/ return Math.floor(LIMIT_COUNT * (Number(int.replace("%", "")) / 100)); } else { /*%でなければ、小数点以下を切り捨てて戻す*/ return Math.floor(int); } } 工夫したところ アラートやプロンプトなど、GUI要素をふんだんに取り入れた 誰でも保守管理ができるよう、設定はすべてGUIで完結するようにしました。 最初にこのコードをコピペして必要なトリガーをセットすれば、あとはすべてGUIで操作できます。 とにかく高機能 残りの人数を概要文に表示する機能を搭載しました。これ、ないと不便ですよね。 また、あまりにも申込者数が少ないと申し込みをためらってしまう人が多いのではと考え、申込者数が増えてきてから残りの人数を表示できる機能も追加しました。 汎用性を高くした 既存のフォームにもすぐに取り入れられるような設計になっています。もちろん新規のフォームでも問題ありません。前述したとおり、残りの人数を概要文に表示する機能を搭載したのですが、それがすでに概要欄に記述してある文章と統合されるようにしました。コードの量は増えますが、処理の中身は簡単です。 概要欄に表示されている「残り○人」のカウントダウンの文言(これをAとします)をプロパティに保存し、次にフォームが送信された際に、概要文中のAを、新しい残りの人数を含むカウントダウンの文言に置き換える、そしてその新しい文言もプロパティに保存する、.....(以下、無限ループ)という処理を行っています。 まとめ プログラミングを使って、Googleのサービスに様々な付加価値を加えていくのはとても楽しいですし、達成感も大きいです。 今後もちょこちょこGASを使っていこうと思います。 最後まで読んでいただきありがとうございました! 関連サイト 今回ご紹介したGASの導入方法・使い方について ソースコード
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

新技術は怖くない、jQuery時代からReact時代へ勇気を出して踏み出した

自分にとって未知の技術、なんだか怖い。 いずれ身に付けなければならないとは思いつつも、とても難しいものに思えてなかなか踏み出せなかったです。しかし考え方を変えてみたらあっさりとその壁を乗り越えて次の段階へと自分をステップアップさせることができました。 考え方の面から誰かの助けになればと思い、初投稿します。 この記事について JavaScriptについてjQuery全盛期に学習した後、技術的に停滞していた自分がついにReactに触れて個人アプリを作るようにまでなったので、その体験を簡潔に書きます。同じような境遇の人にとって新しい一歩を踏み出せる助けになると嬉しいです。 想定している読者は「ある技術を過去に習得したがレガシーになりつつある人」や「新しい技術を学んだ人が羨ましいが尻込みしてしまっている人」など、新しいことに焦燥感を抱えている人です。 jQuery時代に取り残された焦燥感 jQueryが悪いという意図はありません 私は社会人となる前の「JavaScriptのライブラリといえばjQuery」という時代にJavaScriptを学びました。それから社会人となりSES系の企業に就職しましたが、幸か不幸かそこは既に習得している技術だけでほとんどの業務をこなせるような技術水準の環境でした。 時折目にする統計では既にAngularやReactといった次世代のライブラリが主流になりつつありましたが、JavaScriptを触る機会があっても採用されているライブラリはjQuery1.8などで、周りを見渡しても同じような状態で誰も次世代ライブラリを触っていません。 QiitaなどでJavaScriptに関する記事のトレンドを見かけることがあってもjQueryが登場することはなく、「新入社員がReactで何かを作ってみた」というような記事が目に飛び込んできては眩しい思いがしていました。 いずれ今の自分の技術が通用しなくなることが薄々理解していながらも、ちょろっと見るだけでは全く理解できそうになく見えた次世代ライブラリを避けるように現状に甘んじていました。 Angular/React/Vueの中でAngularの人気が落ちてきた、という記事を見ては「Angularの時代を乗り切ったのだからjQueryで繋ぎつつ次の時代から飛び級してやるぜ」という謎のスマートフォンの機種変更のような心境でいました。 それでも本当にこれでいいのか、将来の自分のキャリアに満足できているだろうかという不安を抱えていました。余談ですがこういった焦燥感を抱くことを「クオーターライフ・クライシス」と呼ぶそうです。 新しい考え方がひらめき一気に変わった いささか乱暴です そんなモヤモヤを抱えながらシャワーをしていたある日(といっても最近です)、自分の中に新しい考え方が水と一緒に降ってきて、一気に新しい技術(React)への恐怖心がなくなりサクサクと技術習得が進んでいくようになりました。 経験のなかった新人にできて自分に出来ないわけがない 言葉は悪いですがこの考えに至りました、私は根拠もなく自信が持てる人間ですので。 よくよく考えてみればプログラミングの世界において、人気で主流となる新技術というのは原則として従来のものよりも便利で簡単に扱えるからです。従来のモノ(jQuery)よりも便利で簡単な新しいモノ(React)が登場したから時代が変わったのです。 であればjQueryが扱えるようになった自分が、より楽に色々できるReactを扱えないはずがありません。自分より若く経験も浅いどころか、未経験だった新社会人が次々と習得できているのですから。 実際、Reactで何個か個人アプリを作ってみましたが完全に理解しました。 ⇒もしかして:ダニング=クルーガー効果 考え方に関してはここまで、いったんまとめ 要するに、同じような境遇の人に伝えたいことは以下です。 主流となる新技術は従来よりも便利で簡単だからこそ主流となった。だから従来のものを習得できたあなたに新しいものが習得できないはずがない。 未経験だった人でも習得して仕事に出来ています。 ここまで読んでくれた方、ありがとうございました、よければ最後まで。 次は簡潔にjQuery時代からReact時代にステップアップした手順を書いていきます。 jQueryからReactに行くためにやったこと Reactのチュートリアルを愚直にやる Next.jsでウェブサイトを作る Next.jsのブログテーマを作る npmへReactコンポーネントの公開もした 以上です。勉強するライブラリは悩みましたがReactが人気のようだったのでReactを勉強することに決めました。そしてせっかく勉強したのですからReactを使った作品を作ろうと思い、ReactをベースとしたフレームワークNext.jsを使用してアプリを作り実践としました。 React公式チュートリアル Reactの公式チュートリアルは本当に優秀なチュートリアルです。 これまでの私はQiitaなどの入門記事を読んでは難しそう、で引き返していましたが大本であるReact公式のチュートリアルへアクセスし、そこに書かれている通りに簡単なゲームを作りました。手取り足取り説明が書かれているのでやることが明確にわかりました。 (実はこの時、英語表記でGoogle翻訳を有効にしながらやっていたのですが、公式に日本語表示があったようなので今からやる人は日本語表示が良いと思います) なんとなくReactはコンポーネントという部品の組み合わせで表現する仕組みで、不変な引数Propsと可変な状態を表すStateであることが分かるようになりました。Stateの相互作用についてもドキュメントが非常に理解の助けになりました。 Next.jsでウェブサイトを作った Reactで何か作りたいと思い探したところ、Next.jsというフレームワークが手軽に自分のウェブサイトをReactで作成して公開することが出来ることが分かりました。 私は趣味でアメリカ株への投資を行っているので、決算情報をグラフィカルに表示することのできるウェブサイトを作ってみることにしました。Next.jsのチュートリアルをやった後、作ってみました。このチュートリアルも非常に分かりやすかったです。 これでなんとなくコンポーネントの作り方が分かってきました。Atomic Designというデザインパターンにも出会いましたが、よく細分化の粒度が理解できず粗削りな状態ですが一旦形になったので満足しています。 Next.jsのブログテーマを作った 実は個人ブログを Infoseek ⇒ livedoor ⇒ WordPress ⇒ Hugo と渡り歩いてきた私です。いつもカスタマイズをせずにいられなかった私は身に付けたばかりのReactでオリジナルデザインなブログを作ることにしたく時を開けずにNext.jsでブログテーマを作ることにしました。 Reactコンポーネントをnpmパッケージとして公開した せっかくなのでブログテーマを作る途上で作成したブログパーツをnpmパッケージとしても公開してみました。これも初めての体験でした。 まとめ このように出来るはずだと思い立ち、チュートリアルで学んだことを何かしら自作アプリを作ってみることで、ある程度新しい技術を定着させることができたように思います。 学んでいる最中は久しく忘れていた未知のものに触れて扱えるようになっていくワクワク感がありました。(初めてHTMLにスタイルを付けて、文字色を変更できた時のことを思い出しました) まだまだReactのstateのリフトアップやHookについての理解度が足りないという事が課題と感じていますが、明らかにjQueryしか扱えなかった頃の自分よりスキルアップすることができているのでモチベーションも高くなりました。(同じロジックでTypeScriptやWebPackの学習も始めました) ぜひ同じように新しいことへの挑戦に尻込みしている人がいれば、新しい技術は怖くないよ、ということで一歩踏み出してみる助けになれたらと思います。読んでいただきありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.jsで.envが読み込んでるのに動かない

初めまして、嶋芋です。Qiitaには初の投稿になります! 背景 FirebaseでGoogle認証を作成していたら、.env.localが読み込めているのにauth/invalid-api-keyが出て躓いたので、記事を書くことにしました。 環境 Next.js : 11.0.1 問題 Next.jsでFirebaseのGoogle認証を作成していました。 .env.localにAPIキーなどを書いて、Next.jsから読み込み実行しました。 「auth/invalid-api-key」が出たので、APIキーの誤字だと思い何度も間違っていないかを確認しましたが、間違いは見つけられず、周りの人に確認してもらっても間違っていないということになりました。 console.logで出力してみたりといろいろしていると、サーバー側のコンソールではちゃんと読み込まれているのに、ブラウザ側のコンソールで {} になっていることが判明しました。 その時のコードは以下の通りです。 export const firebaseConfig = {  apiKey: process.env.API_KEY,  authDomain: process.env.AUTH_DOMAIN,  projectId: process.env.PROJECT_ID,  storageBucket: process.env.STORAGE_BUCKET,  messagingSenderId: process.env.MESSAGEING_SENDER_ID,  appId: process.env.APP_ID,  measurementId: process.env.MEASUREMENT_ID }; 解決方法 ブラウザ上で読み込まれていないことを調べていると、Next.jsのこんな仕様を見つけました。 - Use .env.local to load environment variables - Expose environment variables to the browser by prefixing with NEXT_PUBLIC_ .env.localで環境変数にNEXT_PUBLICをつけるとブラウザ側でも読み込まれるそうです。なので、以下のように変更しました。 export const firebaseConfig = {  apiKey: process.env.API_KEY,  authDomain: process.env.NEXT_PUBLIC_AUTH_DOMAIN,  projectId: process.env.NEXT_PUBLIC_PROJECT_ID,  storageBucket: process.env.NEXT_PUBLIC_STORAGE_BUCKET,  messagingSenderId: process.env.NEXT_PUBLIC_MESSAGEING_SENDER_ID,  appId: process.env.NEXT_PUBLIC_APP_ID,  measurementId: process.env.NEXT_PUBLIC_MEASUREMENT_ID }; 無事、ブラウザ側でも読み込むことができ、実装ができました。 この問題の記事にたどり着くのに時間がかかったので誰かのためになれば嬉しいです。 開発お疲れ様です! 資料
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

storybook6のcontrolsまとめ

はじめに 本記事では,UIカタログとして利用されるstorybookについて記述します. storybookにはcontrolsと呼ばれる便利機能があり,コンポーネントなどに与えるデータやプロパティをstorybook上で簡単に変更することができます.新たに作ったUIコンポーネントのテストはもちろん,すでに作られたコンポーネントの挙動を理解するためにも大いに役立ちます. storybookのcontrols機能では,前述のように,UIコンポーネントにデータやプロパティを変更できる機能のことです.ここに貼ったgif画像では,bool値であるprimaryの値をスライドボタンで切り替えたり,ボタンのラベルを変更しています.storybookではこのような値の変更方法以外にも,様々な方法が用意されています.本記事はVue.js環境におけるこれらの記述方法を備忘録的にまとめたものです. 本記事では,あくまでcontrolsの記述方法にフォーカスしていますので,storybookそのもののセットアップ方法や概要などを詳しく知りたい方は,公式ドキュメントや,他の方の記事を参照してください.他の方の記事ですが以下の記事は個人的にとてもわかりやすかったです. 参考記事:Vue と CSF によるモダンな Storybook 6 のはじめかた どんな入力方法があるのか? storybook公式ドキュメントのcontrolsの項目では,以下のようにまとめられています. 15種類のControl Typeがあることがわかります. 以降から,storybookをインストールした直後の環境で動くサンプルコードを載せていきます.作成するファイルは全てsrc/stories/配下に置いていきます. それでは一つづつ記述方法と動作を確認してみましょう. boolean boolean booleanでは,文字通りtrue,falseの値を切り替えることができるcontrolです. 動作確認用に,bool値を受け取る,flagというpropsを持つsampleコンポーネントを作成します.flagに渡される値によって表示される文字が変化します. sample.vue <template> <div> <p v-if=flag>true</p> <p v-else>false</p> </div> </template> <script> export default { name: 'Sample', props: { flag: { type: Boolean, required: true, }, }, }; </script> 次に,sample.vueを呼び出すstories.jsファイルを作成します. sample.stories.js import Sample from './sample.vue'; export default { title: 'Example/Sample', component: Sample, }; const Template = (args) => ({ components: { Sample }, setup() { return { args }; }, template: '<sample v-bind="args" />', }); export const Primary = Template.bind({}); Primary.args = { flag: true, // デフォルト値 }; 実際に動作確認してみます. flagのトグルボタンによってコンポーネントへ渡されているデータが変化していることがわかります.<sample v-bind="args" />でargsを渡すことで,flag: trueのデフォルト値を渡しているのです.しかし,実際のところ,最後のPrimary.argsは記述せずともほぼ同様に動作します.これは,storybookが非常に賢いために,呼び出されているsample.vueに定義されているpropsを解析して,その型に適した入力方法を表示してくれるのです.つまり,NumberやString,Objectなどの型を指定したpropsなどは,storiesに呼び出してargsをv-bindするだけで利用できるようになります. Number Number 前項のsample.vueを少し書き加えてみましょう sample.vue <template> <div> <p v-if=flag>true</p> <p v-else>false</p> {{ num }} <!-- 追記部分 --> </div> </template> <script> export default { name: 'Sample', props: { flag: { type: Boolean, required: false, }, num : { // 追記部分 type: Number, default: 0, } }, }; </script> propsに数字を受け取るように書き換えました.実行結果はこんな感じ. Number(range) rengeでは,Number型のインプットをスライドバー形式で操作ができるようになります.通常のNumberとは異なり,必ずstoriesファイルで追記が必要になります. sample.vue <template> <div> {{ rangeNum }} </div> </template> <script> export default { name: 'Sample', props: { rangeNum : { type: Number, default: 0, } }, }; </script> storiesファイルのargTypesプロパティを指定します.今回は,rangeNumという変数をcontrol type:'range'で指定します.デフォルト値を指定したい場合はargsで指定してあげましょう. sample.stories.js import Sample from './sample.vue'; export default { title: 'Example/Sample', component: Sample, // ここの記述がポイント argTypes: { rangeNum: { control: { type: 'range', }, }, }, }; const Template = (args) => ({ components: { Sample }, setup() { return { args }; }, template: '<sample v-bind="args" />', }); export const Primary = Template.bind({}); Primary.args = { rangeNum: 0, // デフォルト値 }; Object Object コンポーネントにObjectを受け取るpropsを用意し,storiesファイルでObject型の変数を渡してあげます.argTypesの記述は必要ありません(もちろん明示的に記述しても構いません). sample.vue <template> <div> {{ sampleObject }} </div> </template> <script> export default { name: 'Sample', props: { sampleObject: { type: Object, required: false, default: {}, }, }, }; </script> sample.stories.js import Sample from './sample.vue'; export default { title: 'Example/Sample', component: Sample, }; const Template = (args) => ({ components: { Sample }, setup() { return { args }; }, template: '<sample v-bind="args" />', }); export const Primary = Template.bind({}); Primary.args = { sampleObject: { apple: 'red', grape: 'purple', }, }; 実行結果はこんな感じ Array Array ArrayもObjectと同様に,Array型の変数を受け取るpropsを用意してあげて,storiesファイルでpropsにArray型の変数を渡してあげます. sample.vue <template> <div> {{ sampleArray }} </div> </template> <script> export default { name: 'Sample', props: { sampleArray: { type: Array, required: false, default: [], }, }, }; </script> sample.stories.js import Sample from './sample.vue'; export default { title: 'Example/Sample', component: Sample, }; const Template = (args) => ({ components: { Sample }, setup() { return { args }; }, template: '<sample v-bind="args" />', }); export const Primary = Template.bind({}); Primary.args = { sampleArray: ['a', 'b', 'c'], }; 実行結果はこんな感じ Array(file) ファイルをinputすることもできます(あまり使う機会はなさそう). Arrayのサンプルコンポーネントをそのまま流用して,storiesファイルのみ書き換えていきます.fileの場合はargTypesを記述します. sample.stories.js import Sample from './sample.vue'; export default { title: 'Example/Sample', component: Sample, // ここがポイント argTypes: { sampleArray: { control: { type: 'file', }, }, }, }; const Template = (args) => ({ components: { Sample }, setup() { return { args }; }, template: '<sample v-bind="args" />', }); export const Primary = Template.bind({}); Primary.args = { sampleArray: [], }; 実行結果はこんな感じ enum enumではいわゆる選択式のデータの渡し方を記述できます.6種類の入力方法があります.この6種類は基本的に描き方が同様です.  radio radioでは,storiesファイルのargTypesのtypeにradioを指定し,選択肢をoptionsに配列として記述します. sample.vue <template> <div> {{ fruit }} </div> </template> <script> export default { name: 'Sample', props: { fruit: { type: String, required: false, default: '', }, }, }; </script> sample.stories.js import Sample from './sample.vue'; export default { title: 'Example/Sample', component: Sample, argTypes: { fruit: { control: { type: 'radio', // ここと, options: ['apple', 'banana', 'grape', 'orange'], // ここ }, }, }, }; const Template = (args) => ({ components: { Sample }, setup() { return { args }; }, template: '<sample v-bind="args" />', }); export const Primary = Template.bind({}); Primary.args = { fruit: 'apple', }; 実行結果はこんな感じ 実際に渡したいデータにラベルを指定したい場合は,argTypesにlabelsを以下のようにオブジェクト形式で記述してあげると実現できます.vueファイルの変更はありません. sample.stories.js import Sample from './sample.vue'; export default { title: 'Example/Sample', component: Sample, argTypes: { fruit: { control: { type: 'radio', options: ['apple', 'banana', 'grape', 'orange'], labels: { // ここでラベルを指定する apple: 'りんご', banana: 'ばなな', grape: 'ぶどう', orange: 'オレンジ' }, }, }, }, }; const Template = (args) => ({ components: { Sample }, setup() { return { args }; }, template: '<sample v-bind="args" />', }); export const Primary = Template.bind({}); Primary.args = { fruit: 'apple', }; 実行結果はこんな感じ inline-radio radioの選択肢を選択肢ごとに改行せずに横並びに表示します.radioの記述方法と同様で,argTypesのtypeをinline-radioにすることで動作します. sample.stories.js import Sample from './sample.vue'; export default { title: 'Example/Sample', component: Sample, argTypes: { fruit: { control: { type: 'inline-radio', // 変えるのはここだけ options: ['apple', 'banana', 'grape', 'orange'], labels: { apple: 'りんご', banana: 'ばなな', grape: 'ぶどう', orange: 'オレンジ' }, }, }, }, }; const Template = (args) => ({ components: { Sample }, setup() { return { args }; }, template: '<sample v-bind="args" />', }); export const Primary = Template.bind({}); Primary.args = { fruit: 'apple', }; 実行結果はこんな感じ 選択肢の数が多い場合やラベルの文字列が長い場合はradio,そうでない場合はinline-radioを使うのがいいかと思います. check checkはradioと記述方法は同様ですが,複数選択が可能で,入力を配列としてコンポーネントに渡します. vueファイルはradioと同様です. sample.stories.js import Sample from './sample.vue'; export default { title: 'Example/Sample', component: Sample, argTypes: { fruit: { control: { type: 'check', // 変えるのはここだけ options: ['apple', 'banana', 'grape', 'orange'], labels: { apple: 'りんご', banana: 'ばなな', grape: 'ぶどう', orange: 'オレンジ' }, }, }, }, }; const Template = (args) => ({ components: { Sample }, setup() { return { args }; }, template: '<sample v-bind="args" />', }); export const Primary = Template.bind({}); Primary.args = { fruit: 'apple', }; 実行結果はこんな感じ inline-check checkの選択肢を選択肢ごとに改行せずに横並びに表示します.checkの記述方法と同様で,argTypesのtypeをinline-checkにすることで動作します. sample.stories.js import Sample from './sample.vue'; export default { title: 'Example/Sample', component: Sample, argTypes: { fruit: { control: { type: 'inline-check', // 変えるのはここだけ options: ['apple', 'banana', 'grape', 'orange'], labels: { apple: 'りんご', banana: 'ばなな', grape: 'ぶどう', orange: 'オレンジ' }, }, }, }, }; const Template = (args) => ({ components: { Sample }, setup() { return { args }; }, template: '<sample v-bind="args" />', }); export const Primary = Template.bind({}); Primary.args = { fruit: 'apple', }; 実行結果はこんな感じ radio同様,選択肢の数が多い場合やラベルの文字列が長い場合はcheck,そうでない場合はinline-checkを使うのがいいかと思います. select selectでは,プルダウン形式で単一の選択肢を行うことができます. 他のenumと同様の記述方法で,argTypesのtypeをselectにすることで動作します. sample.stories.js import Sample from './sample.vue'; export default { title: 'Example/Sample', component: Sample, argTypes: { fruit: { control: { type: 'select', // 変えるのはここだけ options: ['apple', 'banana', 'grape', 'orange'], labels: { apple: 'りんご', banana: 'ばなな', grape: 'ぶどう', orange: 'オレンジ' }, }, }, }, }; const Template = (args) => ({ components: { Sample }, setup() { return { args }; }, template: '<sample v-bind="args" />', }); export const Primary = Template.bind({}); Primary.args = { fruit: 'apple', }; 実行結果はこんな感じ radioとの使い分けは,より選択肢が多い場合にselect,そうでもない時にradioを使うのがいいかと思います. multi-select selectのようなUIで,複数選択できるようにしたものがmulti-selectです.ctrlキーを押しながら選択することで複数選択が出入るようになります. 選択されたデータは配列の形でコンポーネントに渡されます. 他のenumと同様の記述方法で,argTypesのtypeをmulti-selectにすることで動作します. sample.stories.js import Sample from './sample.vue'; export default { title: 'Example/Sample', component: Sample, argTypes: { fruit: { control: { type: 'multi-select', // 変えるのはここだけ options: ['apple', 'banana', 'grape', 'orange'], labels: { apple: 'りんご', banana: 'ばなな', grape: 'ぶどう', orange: 'オレンジ' }, }, }, }, }; const Template = (args) => ({ components: { Sample }, setup() { return { args }; }, template: '<sample v-bind="args" />', }); export const Primary = Template.bind({}); Primary.args = { fruit: 'apple', }; 実行結果はこんな感じ string String コンポーネントにtype: Stringのpropsを追加することで表示することができます.argTypesの指定は必要ありません(明示的に指定しても当然動作します.). sample.vue <template> <div> {{ message }} <!-- 変更部分 --> </div> </template> <script> export default { name: 'Sample', props: { message: { // 変更部分 type: String, required: false, default: '', }, }, }; </script> sample.stories.js import Sample from './sample.vue'; export default { title: 'Example/Sample', component: Sample, }; const Template = (args) => ({ components: { Sample }, setup() { return { args }; }, template: '<sample v-bind="args" />', }); export const Primary = Template.bind({}); color colorを用いることで,カラーパレットを使ってカラーコードを指定することができるようになります. argTypesでtypeにcolorを指定することで実現できます. vueファイルはstringと同様です. sample.stories.js import Sample from './sample.vue'; export default { title: 'Example/Sample', component: Sample, argTypes: { fruit: { control: { type: 'color', // ここで指定 }, }, }, }; const Template = (args) => ({ components: { Sample }, setup() { return { args }; }, template: '<sample v-bind="args" />', }); export const Primary = Template.bind({}); 実行結果はこんな感じ date dateを用いることでカレンダーから日付と時刻を選択することができるようになります. コンポーネントに渡される値はUNIX Time形式になります. argTypesでtypeにdateを指定することで実現できます. vueファイルはstringと同様です. sample.stories.js import Sample from './sample.vue'; export default { title: 'Example/Sample', component: Sample, argTypes: { fruit: { control: { type: 'date', // ここで指定 }, }, }, }; const Template = (args) => ({ components: { Sample }, setup() { return { args }; }, template: '<sample v-bind="args" />', }); export const Primary = Template.bind({}); 実行結果はこんな感じ おわりに この記事ではStoryBook6に用意されているconnrols addonを一通り動かしてみました.これらを組み合わせると非常にリッチなコンポーネントのカタログを作成することができます. storybookをつかった開発にお役立ていただけたら幸いです.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[初学者]スコープとvarの巻き上げ

(初学者)javascriptの理解してなかった所 はじめに 本記事はネット教材でサラッと勉強したけど後に深く理解してなくて、なんだこれ?となって調べたものです。 参考文献 javascript primer 目次 javascriptのスコープの性質とはなんですか? varの巻き上げってなんですか? おわり javascriptのスコープの性質とはなんですか? スコープとは変数や関数の引数をとって来れる範囲です。 そしてもう1つ重要なのが{ }で囲っている所をブロックと呼びます。ブロックの外から中は参照出来ません。 *逆にブロック内のものはブロック外を参照出来ます。 例を下に書くと const A = 10; ←グローバルスコープで定義されたグローバル変数 { const B = 20; { console.log(A);  //10 ←内から外へ参照を探しに行くスコープチェーン } } console.log(B); ←ブロック内の変数Bを参照出来なかった //Uncaught ReferenceError: B is not defined varの巻き上げってなんですか? *そもそもなんでconstとletが追加されたのか 先ず基礎知識でconstとlet、varの違いについて const 再宣言、再代入できない。 let   再宣言は出来ないが、再代入は出来る。 var   どっちも出来る。 再宣言が出来るとはなんなのか、コードで書いてみるとvarは初期20から再宣言により200になってます。 短いコードならまだ良いですが、長いと意図せずに同じ変数を宣言する可能性があるので危険です。varは非推奨 let A = 10; var B = 20; let A = 100; var B = 200; console.log(A); //エラー Uncaught SyntaxError: Identifier 'A' has already been declared console.log(B); //200 次にletとvarの他の違いについて、 let C = 30; console.log(C); //30 console.log(D); //Uncaught ReferenceError: Cannot access 'D' before initialization let D = 40; var E = 50; console.log(E); //50 console.log(F); //undefined var F = 60; コードは基本的に上から下へ行くものですが何故がconsole.log(F);にはundefinedが表示されます。 undefinedは未定義の意味なので、何かわからないものがある判定になってます。怖いですね。 引用すると 変数宣言が宣言と代入の2つの部分から構成されていると考えてみましょう。 varによる変数宣言は、 宣言部分が暗黙的にもっとも近い関数またはグローバルスコープの先頭に巻き上げられ、 代入部分はそのままの位置に残るという特殊な動作をします さっきのコードで改めて書くと 人から見たコード console.log(F); var F = 60; 実際の動き var F console.log(F); var F = 60; この人に見えない特殊な動きを巻き上げと言うそうです。 おわり 初めて書いて見ましたが3時間ぐらいかかりました。 参照を元にアウトプットしたつもりがだたの自己流の書き直し感があり、すみません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オブジェクトのデータの持ち方について

javascriptのオブジェクトについて勉強した。(jquery) 工夫したのはデータの持ち方(hobbyオブジェクト)について。 hobbyのbookやらsportsやらをたくさん外部から設定できる。 [追記]2021.09.17 以下のソースが冗長だったので、頂いたコメントを参考に jquery的に書き直しました。 objectuse.js let a = {}; a = user.getbookhobby(); $.each(a, function(key, val){ console.log(val); }); objectuse.js $(function(){ let user = { name: '山田', age: 27, address: '東京', hobby:{ 'book':{}, 'sports':{}, }, getbookhobby:function(){ return user.hobby.book; }, setbookhobby:function($key, $value){ user.hobby.book[$key] = $value; }, gethobby:function(){ return user.hobby; }, }; user.setbookhobby('0', 'AAA'); user.setbookhobby('1', 'BBB'); user.setbookhobby('2', 'CCC'); /* let a = {}; a = user.getbookhobby(); $.each(a, function(key, val){ console.log(val); }); */ $.each(user.getbookhobby(), (key, val) => { console.log(val); }); });
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

重力レンズ効果を描画してみた(JavaScript)

初めに  最近p5.jsというものを知りまして、インタラクティブにGUIを操作できるものが簡単に作れるのが楽しくて色々作ってました。それで何かしら物理現象をシミュレーションしようと思い立ちまして、重力によって光が曲がる様子を描画してみることにしました。  厳密に再現するのは能力的にも計算量的にも難しそうなので今回は位置の補正のみ行います。 重力レンズの仕組み  重力レンズは銀河などの重力によって後方にある星の光などが曲がることで実際とは異なる見え方をする現象です。  重力によって歪められた光の角度には以下の関係が成り立ちます \beta = \theta - \alpha(\theta)  今回はレンズに使う重力は質点のように一つの点から重力が発生している様子を想定しているので$\alpha$は以下の式によって表されます \alpha = \frac{C(r - r')}{|r - r'|^2} \quad (Cは適当な定数)  ここで$\theta$が十分小さければ$r-r'= $ $\vec\theta$とみなせるのでここで$\vec\beta$と$\vec\theta$について重力の中心を原点とすればこの二つは平行なので、この方向の単位ベクトル$\vec e$に対して \vec\beta = \beta \vec e\\ \vec\theta = \theta \vec e とすると \alpha(\theta) = \frac{C}{\theta} したがって$\beta$は \beta = \theta + \frac{C}{\theta} 通常観測できるのは$\theta$であり実際の位置$\beta$を出力するためこの式を使って$\beta(\theta)$の関数として使用するだけですむが、今回は$\beta\rightarrow\theta$を行いたいので、この式を$\theta$について解いて \theta = \frac{\beta±\sqrt{\beta+4C}}{2} これを用いて描画していく p5.jsでの描画  これを実装したのがこちらです。p5jsのCollectionsです。見れなかったら教えてください https://editor.p5js.org/aokyut/collections/U5-u0xfNh 使い方はキャンバス上でクリックだけです。そうすると重力が発生しその影響で線や点の位置が歪むのが確認できると思います。ぜひ遊んでみてください 最後に  今回は重力レンズによる光の屈折を描画しました。本当であれば位置だけでなく光の形が変化する様子も再現したかったのですが難しいので諦めました。pythonの方で再現するのもありだと思います。他にもp5.jsで色々作っているのですがそちらも公開しようか悩んでいます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebAssembly (Rust) によるブラウザ上の画像処理

概要 ブラウザ上で動画をリアルタイムに画像処理する。 Rustによって作成したWebAssemblyと、JavaScriptとの処理速度の比較を行う。 結果は、Firefox(JS) > Chrome(Rust) > Chrome(JS) > Firefox(Rust) [速い順] となった。 画素数1920x1080でのフレームレートは、最速のFirefox(JS)で約10~12fpsとなる。 この記事で示す処理以外のものが入ったときのフレームレートである。ただここでの処理が他のに比べて大きくボトルネックにはなっている。 WebAssemblyとは WebAssembly1とは、ブラウザ上で動くバイナリコードである。JavaScriptに比べて処理速度が速いという特徴がある。 JavaScriptからWebAssemblyの関数が呼び出されたり、WebAssemblyからJavaScriptの関数が呼び出されたりする。 WebAssemblyからHTMLの要素を操作することはできない。 C/C++, Rust, C#, Goなどの言語によって開発することができる。 UnityのWebGLビルドのスタンダードになっている2。 この記事では、Rustによって開発されたWebAssemblyについて記述する。 Rustとは Rust(ラスト)3とは、マルチパラダイム(手続き型、オブジェクト指向、関数型プログラミングなど)の実装手法をサポートしているC言語に似たプログラミング言語である。 C/C++に代わるコンピュータのハードウェアの操作、制御のために使用されるプログラミング言語を目指している4。 この記事で扱う画像処理について ここで扱う画像処理は、フルHD(1920x1080ピクセル)の画像を10x10のセルに分割し、そのそれぞれのセルで平均を取るものとする。上の図では参考用に処理結果を右の画像で表示しているが、実際には、各セルの平均値を計算するところまでの処理時間を計測するとする。 結果:処理速度 1画面のセル化、平均化処理にかかる時間を、Chrome、Firefoxブラウザにおいてそれぞれ、JavaScriptはJSと表記、WebAssemblyはRustとして図に示す。測定条件については次のようになっている。 Dell Inspiron 7501 Windows 10 Home x64 (10.0.19042 ビルド 19042) Core i7-10750H 2.60GHz, RAM 16.0GB Chrome 93.0.4577.82 (64ビット) Firefox 92.0 (64ビッド) 計測は100回実施した平均値を、それぞれのブラウザ、JavaScript/WebAssemblyの項目を変えながら2回以上求めたおおよその値 開発、実行環境の概要 WebAssemblyはRustで開発し、node.jsのwebpackによってパッケージ化する。 index.html: ブラウザで表示するファイル。 index.js: index.htmlから読み込まれ、動画の読み込み、画像データの取得、WebAssemblyの読み込み、画像処理、結果の表示を行う。 TimeChecker.js: 処理時間を計測するためのJavaScript。 lib.rs: WebAssemblyになるRustのコード。 WebAssemblyは、wasm-packにより開発環境の構築を構築した。 以下にまず、それぞれの開発したソースコードを示し、最後に開発環境構築について記述する。 HTML(index.html) HTMLは、wasm-packで作成されたプロジェクトのstatic/index.htmlを編集する。 以下は、HTMLの全文。 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>WebAssembly(Rust) vs. JavaScript</title> <!-- 速度計測のためのJavaScript、内容についてはこの記事で後述する。--> <script src="TimeChecker.js"></script> </head> <body> <video id="video"></video> <canvas id="canvas"></canvas> <script src="index.js"></script> </body> </html> JavaScriptのコード(index.js) WebAssemblyのRustコードは、後述するwebpackで作成されているものとする。 JavaScriptのコードは、wasm-packで作成されたプロジェクトのjs/index.jsに記述する。 以下は、プログラムの全文。 // WebAssembly(Rust)の読み込みをawaitを使用して同期的に行うため、処理をasync関数として実装する。 (async () => { // WebAssemblyの読み込み let wasm = await import("../pkg/index.js").catch(console.error); // WebAssemblyのmemory変数を使用するために読み込む let wasm_bg = await import("../pkg/index_bg.wasm").catch(console.error); // htmlのvideoエレメントで動画を再生する。 const video_opt = { width: { ideal: 1920, min: 640 }, height: { ideal: 1080, min: 480 }, }; const stream = await navigator.mediaDevices.getUserMedia({audio:false,video:video_opt}); const settings = stream.getTracks().filter(e => e.kind === "video")[0].getSettings(); console.log(settings); const video = document.getElementById("video"); video.srcObject = stream; video.play(); // 動画はcanvasエレメントに画像をコピーして使用する。 const canvas = document.getElementById("canvas"); canvas.width = settings.width; canvas.height = settings.height; const ctx = canvas.getContext("2d"); // Universeという名前で公開されたRustのクラスのようなものを生成する。 const universe = wasm.Universe.new(settings.width, settings.height,10,10); const memory = wasm_bg.memory; // Rust上に作成された画像データをJavascript側で共有するためのUint8Arrayを作成する。 // これは、JavaScriptからRust側へ送る画像データとして使用する。 const wasm_pixels0 = new Uint8Array(memory.buffer, universe.pixels0(), settings.width * settings.height * 4); // Rustで画像処理した結果が保存されているメモリをUint8ClampedArrayで共有する。 const wasm_pixels2 = new Uint8ClampedArray(memory.buffer, universe.pixels2(), settings.width * settings.height * 4); // Rustのpixels2のメモリを共有したUint8ClampedArrayによるImageDataを生成する。 // ImageDataはCanvasエレメントに描画される。 const img2 = new ImageData(wasm_pixels2, settings.width); // HTMLの描画タイミングで呼ばれるrenderLoopメソッドを作成する。 const renderLoop = () => { // videoエレメントの動画をキャプチャしてcanvasに描画する。 ctx.drawImage(video, 0, 0, settings.width, settings.height); // ここから処理速度の計測を始める。TimeChecker.jsについては後述する。 TimeChecker.check(0); // canvasから各画素のデータを取得する。 const img = ctx.getImageData(0, 0, canvas.width, canvas.height); // 各画素のデータを、Rustのメモリにコピーする。 wasm_pixels0.set(img.data); // Rustでの画像処理を起動する。 universe.cells_update(0.01); // Rustの処理結果はpixels2に保存され、それを共有しているimg2を使ってcanvasを上書き描画する。 ctx.putImageData(img2, 0, 0); // ここまでの処理を計測する。 TimeChecker.check(1); // 次のHTML再描画のタイミングでrenderLoopが呼ばれるように登録する。 requestAnimationFrame(renderLoop); }; // renderLoop関数をHTMLの再描画時に呼び出すように登録する。 // これ以後は、renderLoop内で再度、自分自身のrenderLoop関数を再描画時の // ハンドラとして登録し続けることによって繰り返し処理を実現する。 requestAnimationFrame(renderLoop); })(); 処理時間を計測するためのJavaScript(TimeChecker.js) 処理速度計測用のjsファイルの全文、このファイルは、wasm-packで作成されたプロジェクトの「static」フォルダにTimeChecker.jsファイルとして新規作成する。 // 処理時間を計測する。 // TimeChecker.num回数だけ実施し、その平均をコンソールに表示する。 const TimeChecker = { buffer: undefined, num: 100, check: function (st) { if (this.buffer === undefined) { this.buffer = Array(this.num).fill(0); this.buffer_index = 0; } if (this.buffer_index === this.num) { console.log(this.buffer.reduce((a, e) => a + e) / this.num); this.buffer_index = 0; } if (st === 0) { this.buffer[this.buffer_index] = Date.now(); } else { this.buffer[this.buffer_index] = Date.now() - this.buffer[this.buffer_index]; this.buffer_index += 1; } }, sample_function: ()=>{ for(let i=0;i<200;i++){ TimeChecker.check(0); [...Array(10000).keys()].forEach(e=>e*e); TimeChecker.check(1); } }, }; WebAssemblyのコード(lib.rs) Rustのコードは、wasm-packで作成されたプロジェクトのsrc/lib.rsを編集する。 以下は、プログラムの全文。 use wasm_bindgen::prelude::*; use web_sys::console; // 比較関数の使用のために定義する。 use std::cmp; // 英語で書かれたコメントは、rust-webpackテンプレートから受け継がれたままのコード // When the `wee_alloc` feature is enabled, this uses `wee_alloc` as the global // allocator. // // If you don't want to use `wee_alloc`, you can safely delete this. #[cfg(feature = "wee_alloc")] #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; // This is like the `main` function, except for JavaScript. #[wasm_bindgen(start)] pub fn main_js() -> Result<(), JsValue> { // This provides better error messages in debug mode. // It's disabled in release mode so it doesn't bloat up the file size. #[cfg(debug_assertions)] console_error_panic_hook::set_once(); // Your code goes here! // 以下のように記述するとブラウザのコンソールに文字が表示される。 // 例えば数値を出力したい場合は、from_f32を使用する。 console::log_1(&JsValue::from_str("started wasm")); Ok(()) } // 画像を分割する各セルの構造体 #[derive(Default)] pub struct Cell { x: u32, y: u32, w: u32, h: u32, size: u32, ave: [f32;3], prev_ave: [f32;3], } // JavaScript側に公開されるクラスのようなもの #[wasm_bindgen] pub struct Universe { width: u32, height: u32, pixels0: Vec<u8>, pixels2: Vec<u8>, cells: Vec<Cell>, } // クラスのようなものの実態 #[wasm_bindgen] impl Universe { // クラスを生成する。 pub fn new(w:u32,h:u32,cell_w:u32,cell_h:u32) -> Universe { let width = w; let height = h; // pixels0は、javaScriptから画像データを受け取るために使用する。 let pixels0: Vec<u8> = (0..(w*h*4)) .map(|_| {0}) .collect(); // pixels2は、処理結果を保存するために使用する。 let mut pixels2 = pixels0.clone(); for y in 0..h { for x in 0..w { let idx:usize = ((y*w+x)*4) as usize; pixels2[idx+3] = 255; } } let cells = Universe::create_cells(w,h,cell_w,cell_h); // 作成されたインスタンスを戻す。 // Rustはセミコロンは文を区切るために使用し、セミコロンが無いとCのreturnのようになる。 Universe { width, height, pixels0, pixels2, cells, } } // Rustのメモリのポインタを返す。 pub fn pixels0(&self) -> *const u8 { self.pixels0.as_ptr() } pub fn pixels2(&self) -> *const u8 { self.pixels2.as_ptr() } // セル構造体を作成する内部関数 fn create_cells(canvas_w:u32,canvas_h:u32,cell_w:u32,cell_h:u32) -> Vec<Cell>{ let p0:[u32;2] = [ (((canvas_w % cell_w) /2) - cell_w) % cell_w, (((canvas_h % cell_h) /2) - cell_h) % cell_h, ]; let mut cells = Vec::new(); for y in (p0[1]..canvas_h).step_by(cell_h as usize){ for x in (p0[0]..canvas_w).step_by(cell_w as usize){ let mut cell = Cell{ x : cmp::max(0,x), y : cmp::max(0,y), ..Default::default() }; cell.w = cmp::min(x+cell_w,canvas_w)-cell.x; cell.h = cmp::min(y+cell_h,canvas_h)-cell.y; cell.size = cell.w * cell.h; cells.push(cell); } } cells } // 画像処理を実行する関数。 // 引数r0は、差分を更新する割合(大きいほど画素の平均に最新画素の情報が大きく反映される) pub fn cells_update(&mut self,r0:f32){ // 処理効率のために、予めr0の排反事象の確率r1 = (1 - r0)を求めておく。 let r1 = 1_f32-r0; // 各セルでループする。 for i in 0..self.cells.len(){ // 前回実行時の平均値をバックアップし、最新の平均値を初期化する。 for j in 0..3 { self.cells[i].prev_ave[j] = self.cells[i].ave[j]; self.cells[i].ave[j] = 0_f32; } // セル内のすべての画素値を足し合わせる。 for x in self.cells[i].x..(self.cells[i].x+self.cells[i].w){ for y in self.cells[i].y..(self.cells[i].y+self.cells[i].h){ let idx:usize = ((y*self.width+x)*4) as usize; for j in 0..3 { self.cells[i].ave[j] += self.pixels0[idx+j] as f32; } } } // 合計になっている画素値を画素数で割って、新しい平均値を前の平均値からの指定された割合で // 混合して新しい平均値を求める。 for j in 0..3 { self.cells[i].ave[j] = r1*self.cells[i].prev_ave[j] + r0*self.cells[i].ave[j] / (self.cells[i].size as f32); } // 平均化した画素値でセル内のすべての画素を上書きする。 // ただしこの処理の部分は、JavaScriptを処理速度を比較するために、 // 処理速度の計測時は、この部分はコメントアウトして実行した。 for x in self.cells[i].x..(self.cells[i].x+self.cells[i].w){ for y in self.cells[i].y..(self.cells[i].y+self.cells[i].h){ let idx:usize = ((y*self.width+x)*4) as usize; for j in 0..3 { self.pixels2[idx+j] = self.cells[i].ave[j] as u8; } } } } } } WebAssemblyの開発環境構築 Rustのチュートリアルの「4.1.Setup」, 「4.2. Hello, World!」に従い、開発環境を構築する。ここでは、エラーが発生した場合の対処方法を記述する。 cargo install cargo-generateでlink.exeの1181エラー等 Build Tools for Visual Studio 2019をインストールする。Build Toolsは、2021.09.16現在で、公式サイトの画面下にある「Visual Studio 2019のツール」「Build Tools for Visual Studio 2019」からダウンロードできる。 スタートメニューから「Developer Command Prompt for VS 2019」を起動してコマンドを実行し直す。 npm init wasm-app wwwでerrno: -4058, code ENOENT, syscall spawn git, path git git for windowsからgitをインストールする。 「4.4. Implementing Life」まで実施すると、WebAssemblyによるLife Gameがブラウザ上で実行される。 プロジェクトのwebpack化 Rustチュートリアルで作成したWebAssemblyは、node.jsをサーバとしたnode moduleとして作成されている。ここでは、Apacheなどの他のWebサーバで実行できるようにwebpack化する方法について示す。 webpack5とは、HTML,CSS,JavaScriptなどの複数のファイルを1つにまとめてくれるもので、node.jsのパッケージ(機能)である。 この記事内では、wasm-packを使用してwebpack化されたプロジェクトを作成する。 wasm-packをインストールしていない場合、wasm-pack-init.exe ここからダウンロードでwasm-packのインストールする。 以下は、基本的にはwasm-webpackのチュートリアル5.1.2. Using your libraryに従って実行する。 npm init rust-webpack your-package-name によって、上記のチュートリアルとは別のプロジェクトを作成する。 コマンド実行したフォルダに、your-package-nameのフォルダが作成される。 2021.09.16時点でプロジェクトフォルダ内に作成されるファイルの場所が、チュートリアルと以下のように違っている。 index.html -> static/index.html crate/src/lib.rs -> src/lib.rs static/index.html, src/lib.rs, js/index.jsファイルを、Rustチュートリアルの「4.4. Implementing Life」の状態に書き換える。 js/index.jsに以下のコードを追加する。 (async ()=>{ let wasm = await import("../pkg/index.js").catch(console.error); let wasm_bg = await import("../pkg/index_bg.wasm").catch(console.error); // here is sample codes... // const ptr = wasm.published_get_pointer_function(); // const pixels = new Uint8Array(wasm_bg.memory.buffer, ptr, 1920 * 1080 * 4); })(); プロジェクトのフォルダでnpm run buildを実行する。 distフォルダが作成される。 distフォルダをWebとして公開する。 Apacheサーバでdistフォルダを公開してエラーになる場合、以下のMIMEタイプの設定を行う。 vi /etc/mime.types 最後の行に、application/wasm wasmを追加する。 sudo service httpd restart 等でapacheを再起動する。 https://webassembly.org/ ↩ https://blog.unity.com/ja/technology/webassembly-is-here ↩ https://www.rust-lang.org/ja ↩ https://www.infoq.com/news/2012/08/Interview-Rust/ ↩ https://webpack.js.org/ ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EC-CUBE4.xのオリジナルテンプレートでカスタマイズcss / jsを反映させる方法

概要 EC-CUBE4.xでオリジナルテンプレートを使用する際、管理画面から設定できる customize.css customize.js が反映されない場合の対処法メモ書きです。 開発環境 EC-CUBE4.x+Git+scss オリジナルテンプレートを使用 参考文献 公式ドキュメント:CSSの利用 Qiitaでシンタックスハイライト可能な言語一覧 Qiita Markdown 書き方 まとめ 40言語で「Hello World」をやってみよう!! 一般的な使い方 通常、管理画面から独自のcss/jsを編集→登録した際は、下記パスにファイルが更新されます。 css管理:コンテンツ管理 > CSS管理 html/user_data/assets/css/customize.css JS管理:コンテンツ管理 > Javasctipt管理 html/user_data/assets/js/customize.js この場合、指定テンプレートに関係なく反映される形になるのですが、独自のテンプレートを使っている場合はデフォルトテーマと挙動が異なるパターンももちろんあるため、テンプレート独自のCSS/JSを実装する必要が出てきます。 独自テンプレートへの適用:CSS(SCSS) customize.css の内容をコピペして下記に貼り付ける形が良さそうです。 html/template/[テンプレート名]/assets/scss/style.scss @importで下層scssを読み込んでいるので、最後部に追記することで優先反映されるようになります。 独自テンプレートへの適用:JS html/template/vvstore/assets/js/function.js にコピペします。 デフォルトでjquery構文が記載されているので、それと分離させて記述してください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Git上でEC-CUBEのカスタマイズcss / jsを反映させる方法

概要 EC-CUBE4.xでオリジナルテンプレートを使用する際、管理画面から設定できる customize.css customize.js が反映されない場合の対処法メモ書きです。 開発環境 EC-CUBE4.x+Git+scss オリジナルテンプレートを使用 参考文献 公式ドキュメント:CSSの利用 Qiitaでシンタックスハイライト可能な言語一覧 Qiita Markdown 書き方 まとめ 40言語で「Hello World」をやってみよう!! 一般的な使い方 通常、管理画面から独自のcss/jsを編集→登録した際は、下記パスにファイルが更新されます。 css管理:コンテンツ管理 > CSS管理 html/user_data/assets/css/customize.css JS管理:コンテンツ管理 > Javasctipt管理 html/user_data/assets/js/customize.js この場合、指定テンプレートに関係なく反映される形になるのですが、独自のテンプレートを使っている場合はデフォルトテーマと挙動が異なるパターンももちろんあるため、テンプレート独自のCSS/JSを実装する必要が出てきます。 独自テンプレートへの適用:CSS(SCSS) customize.css の内容をコピペして下記に貼り付ける形が良さそうです。 html/template/[テンプレート名]/assets/scss/style.scss @importで下層scssを読み込んでいるので、最後部に追記することで優先反映されるようになります。 独自テンプレートへの適用:JS html/template/vvstore/assets/js/function.js にコピペします。 デフォルトでjquery構文が記載されているので、それと分離させて記述してください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript初心者のためのエラーガイド

1. Uncaught TypeError: Cannot read property 'b' of undefined at ... 解釋 : Undefinedのbというプロパティーを読めません。 例 : a = undefined; // undefined a.b // Uncaught TypeError: Cannot read property 'b' of undefined at ... *ポイント : bではなく、aがundefinedです。 *対策1 : if (a) { a.b = ... } // aがundefinedでは無い時だけ、実行できます。 対策2 : Optional Chaining c = a?.b; 2. Uncaught TypeError: Cannot read property 'b' of null at ... 例 : a = null; // null a.b // Uncaught TypeError: Cannot read property 'b' of null at ... *ポイント : bではなく、aがnullです。 3. Uncaught TypeError: a.b is not a function at ... 例 : a = {} // {} a.b() // Uncaught TypeError: a.b is not a function at ... console.log(a) // {} // undefined *ポイント : オブゼットa内に、function bが無いこと。 *対策 : Uncaught TypeError: a.b is not a function at ...と言うエラーが出た時は、console.log()で確認しましょう。 4. Uncaught ReferenceError: a is not defined at ... 例 : let a, const a のように宣言してない時 import a from 'library'; const a = require('library'); のようにimport、requireの宣言してない時 4. Function位置に対する理解や注意点 例 : setTimeout(() => { console.log('hello'); }, 2000); // 正しい : 予想通り、2秒後に'hello'を出力 setTimeout(function { console.log('hello'); }, 3000); // 正しい : 予想通り、3秒後に'hello'を出力 setTimeout(console.log('hello'), 4000); // 正しく無い : 直ぐに'hello'を出力 window.addEventListener('click', () => { console.log('hi')); // 正しい : clickをすると'hi'を出力 window.addEventListener('click', console.log('hi')); // 正しく無い : 直ぐに'hi'を出力 *ポイント : window.addEventListener('click', console.log('hi')); console.log('hi')は、Functionでは無いです。 Functionの呼び出しです。 つまり、Return値です。 もう少し説明すると、 console.log は、Functionです。 console.log() は、呼び出しです。Return値です。 // console.log()の値は、undefinedです。 下の2行のコードは、同じ意味です。 window.addEventListener('click', console.log('hi')); window.addEventListener('click', undefined); window.addEventListener('click', () => {}); // () => {}は、Functionです。 window.addEventListener('click', () => {}()); // 呼び出しためには、()を付けます。 setTimeout(console.log('hello'), 4000); // console.log('hello')が実行されて、直ぐに'hello'を出力。 // その後、console.log('hello')はundefinedになります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Leafletでヒートマップを作る

はじめに Leafletとは、Web地図サービスで広く使われるオープンソースのJavaScriptライブラリです。 仕事でLeafletを使うことなったので、勉強してみました。 基礎部分は以下にまとめましたので、こちらも参考にしてください。 https://qiita.com/ryou83480820/items/06238ef347062f65435e 今回はヒートマップを作製します。基本的には公式のチュートリアルをやってみたという内容になります。 https://leafletjs.com/examples/geojson/ https://leafletjs.com/examples/choropleth/ 外部ファイルを読み込む 外部ファイルはGeoJson形式で読み込みます。 詳しくはこちら https://leafletjs.com/examples/geojson/ GeoJsonは以下のようなものです。 { "type": "Feature", "properties": { "name": "Alabama", "density": 94.65 }, "geometry": :{"type":"Polygon", "coordinates":[[-87.359296,35.00118],[-85.606675,34.984749],[-85.431413,34.124869],[-85.184951,32.859696]...]}}, } "coordinates"でポリゴンの隅のlat,lonを指定します。 "properties"の値はヒートマップの色分けをする際に使います。 Leafletでヒートマップを作る Leafletの基本的な部分については既知であるとして話を進めます。 よくわからない人は以下を参照してください。 https://qiita.com/ryou83480820/items/06238ef347062f65435e ヒートマップを作るコードは以下になります。 <!DOCTYPE html> <html> <head> <title>Quick Start - Leaflet</title> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin=""/> <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script> <style> /*右側の情報欄にかかわるCSS*/ .info { padding: 6px 8px; font: 14px/16px Arial, Helvetica, sans-serif; background: white; background: rgba(255,255,255,0.8); box-shadow: 0 0 15px rgba(0,0,0,0.2); border-radius: 5px; } .info h4 { margin: 0 0 5px; color: #777; } /*カラーバーに関わるCSS*/ .legend { line-height: 18px; color: #555; } .legend i { width: 18px; height: 18px; float: left; margin-right: 8px; opacity: 0.7; } </style> </head> <body> <div id="mapid" style="width: 600px; height: 400px;"></div> <script src="us-states.js"></script> <script> //地図の中心と、zoomレベルを設定 var mymap = L.map('mapid').setView([37.8, -102], 4); //タイルレイヤーの追加(この例ではMapbox Streetsタイルレイヤーを用いる) L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', { maxZoom: 18, attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, ' + 'Imagery © <a href="https://www.mapbox.com/">Mapbox</a>', id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1//APIはデフォルトで512x512のタイルを返すので(256x256ではなく)、ズームを-1でオフセット。 }).addTo(mymap); //色を返す関数 function getColor(d) { return d > 1000 ? '#800026' : d > 500 ? '#BD0026' : d > 200 ? '#E31A1C' : d > 100 ? '#FC4E2A' : d > 50 ? '#FD8D3C' : d > 20 ? '#FEB24C' : d > 10 ? '#FED976' : '#FFEDA0'; } //styleを指定する関数 function style(feature) { return { fillColor: getColor(feature.properties.density), weight: 2, opacity: 1, //境界線の色 color: 'white', //境界線の間隔 dashArray: '3', fillOpacity: 0.7 }; } L.geoJson(statesData, {style: style}).addTo(mymap); //右上にマウスオーバーされているdensityと州名を表示 var info = L.control(); info.onAdd = function (mymap) { this._div = L.DomUtil.create('div', 'info'); // create a div with a class "info" this.update(); return this._div; }; // method that we will use to update the control based on feature properties passed info.update = function (props) { this._div.innerHTML = '<h4>US Population Density</h4>' + (props ? '<b>' + props.name + '</b><br />' + props.density + ' people / mi<sup>2</sup>' : 'Hover over a state'); }; info.addTo(mymap); function highlightFeature(e) { var layer = e.target; info.update(layer.feature.properties); } function resetHighlight(e) { var layer = e.target; info.update(); } function onEachFeature(feature, layer) { layer.on({ mouseover: highlightFeature, mouseout: resetHighlight, }); } geojson = L.geoJson(statesData, { style: style, onEachFeature: onEachFeature }).addTo(mymap); //カラーバーを作成 var legend = L.control({position: 'bottomright'}); legend.onAdd = function (mymap) { var div = L.DomUtil.create('div', 'info legend'), grades = [0, 10, 20, 50, 100, 200, 500, 1000], labels = []; // loop through our density intervals and generate a label with a colored square for each interval //描写時に使用したgetColor関数の色を参照 //gradesに次の値があるか for (var i = 0; i < grades.length; i++) { div.innerHTML += '<i style="background:' + getColor(grades[i] + 1) + '"></i> ' + grades[i] + (grades[i + 1] ? '&ndash;' + grades[i + 1] + '<br>' : '+'); } return div; }; legend.addTo(mymap); </script> </body> </html> 完成形はこのような感じです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コミット前にテストを実行しよう ~husky ver7~

はじめに コミットする前にテストを実行させるための設定をします。huskyを使うことで簡単に設定ができます。huskyは、ver4から6に上がった際に仕様が大きく変わっているため、改めてインストール手順をまとました。 huskyのドキュメント 開発環境 husky : 7.0.2 node : v14.17.6 インストール jestのコマンドの設定とjestはインストール済みです。jestのインストール方法 まずはnpmを使ってhuskyをインストールします。 $ npm install --save-dev husky package.jsonのscriptsにprepareコマンドを追加します。 package.json "scripts": { "test": "jest", "build": "babel src -d dist", "start": "babel -w src -d dist", "prepare": "husky install" } prepareコマンドを実行します。実行すると、.huskyフォルダが作成されます。 $ npm run prepare コミット直前にnpm testを実行したいので、Git hooksを追加します。下記のコマンドを実行すると、.huskyフォルダにprecomitのファイルが作成されます。コマンドを修正したい場合は、precomitファイルに記載されているコマンドを修正します。 $ npx husky add .husky/pre-commit "npm test" husky - created .husky/pre-commit 実行結果 huskyの設定ができたので、実行してみます。 $ git add . $ git commit -m "husky install" .... Test Suites: 1 skipped, 4 passed, 4 of 5 total Tests: 1 skipped, 107 passed, 108 total Snapshots: 0 total Time: 21.261 s 無事コミット直前にtestが実行されていることが確認できました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Chrome Extensions をMV3に移行しなくちゃ、ね。

はじめに MV3で何が変わるの、か? Chromeオフィシャルサイトによると大まかには以下が変更点という、ことらしい。 機能の概要 サービスワーカーによるバックグラウンドページの置き換え。 ネットワークリクエストの変更は、declarativeNetRequestAPIで処理される。 リモートでホストされたコードは許可されず、パッケージに含まれているJavaScriptのみを実行可能。 多くのメソッドにPromiseのサポートを追加。 えーっと、、とりあえずわかったふりをしつつ、、。 やってみよう 今回MV3に移行するExtensionsでは、バックグラウンド処理としてはコンテキストメニューのみ。ただ、formをDOM生成してPOST送信している。だが、DOMは扱えない、んだ。 どうするのか? 解決策としては2つ。 個人情報は扱ってなく、受信側もPOST・GET両方に対応させてるので、GET送信に変更。 新しくTabページを開いてPOST送信。 まずは、manifest.jsonの更新 manifest.json { - "manifest_version": 2, + "manifest_version": 3, ... - "browser_action": { + "action": { "default_icon": { "16": "img/icon16.png", "32": "img/icon32.png", "48": "img/icon48.png", "128": "img/icon128.png" }, "default_title": "__TITLE__", "default_popup": "popup.html" }, - "content_scripts": [ - { - "matches": [ "http://*/*", "https://*/*" ], - "js": [ "js/jquery-3.6.0.min.js", ... ] - } - ], "permissions" : [ "activeTab", - "contextMenus", + "contextMenus" - "http://*/*", - "https://*/*" ], + "host_permissions": [ + "http://*/*", + "https://*/*" + ], "background": { - "scripts": [ "background.js" ], - "persistent": false + "service_worker": "background.js" } } content_scriptsの理解が出来てなく、パッケージ内で使用してるjsを全て書いていたので削除しました。 バックグラウンド処理 GETで送信 に変更する場合 background.js chrome.contextMenus.removeAll(function() { chrome.contextMenus.create({ id: '__ID__', title: '__TITLE__', contexts: ['image', 'video'], type: 'normal' }); }); chrome.contextMenus.onClicked.addListener(function(info, tab) { if (info.menuItemId == '__ID__') { - var frm = document.createElement('form'); - frm.setAttribute('method', 'POST'); - frm.setAttribute('target', '_blank'); - frm.setAttribute('action', '__URL__'); - var req = document.createElement('input'); - req.setAttribute('type', 'hidden'); - req.setAttribute('name', 'img_url'); - req.setAttribute('value', info.srcUrl); - frm.appendChild(req); - document.body.appendChild(frm); - frm.submit(); + chrome.tabs.create({ url: '__URL__?img_url=' + info.srcUrl }); } }); GETメソッドで扱えるデータに上限があるので、今回はPOSTを選択。 POSTで送信 する場合 background.js chrome.contextMenus.removeAll(function() { ... }); chrome.contextMenus.onClicked.addListener(function(info, tab) { if (info.menuItemId == '__ID__') { - ... + postForm('__URL__', {'img_url': info.srcUrl}); + + function postForm(url, data) { + chrome.tabs.create( + { url: chrome.runtime.getURL('postform.html') }, + function(tab) { + var handler = function(tabId, changeInfo) { + if(tabId == tab.id && changeInfo.status == "complete"){ + chrome.tabs.onUpdated.removeListener(handler); + chrome.tabs.sendMessage(tabId, {url: url, data: data}); + } + } + chrome.tabs.onUpdated.addListener(handler); + chrome.tabs.sendMessage(tab.id, {url: url, data: data}); + } + ); + } } }); 新しくTabページを開き、ServiceWorkerとページ間でメッセージのやり取りが可能なので、開いたページ(postform.html)にdataを渡します。 postform.html <html><body><script src="js/postform.js"></script></body></html> 開いたページではDOM操作が可能だが、インラインJavaScriptは書くことが出来ないので注意が必要です。 postform.js var onMessageHandler = function(message){ chrome.runtime.onMessage.removeListener(onMessageHandler); var frm = document.createElement('form'); frm.setAttribute('method', 'post'); frm.setAttribute('action', message.url); for(var key in message.data) { var req = document.createElement('input'); req.setAttribute('type', 'hidden'); req.setAttribute('name', key); req.setAttribute('value', message.data[key]); frm.appendChild(req); } document.body.appendChild(frm); frm.submit(); } chrome.runtime.onMessage.addListener(onMessageHandler); メッセージを受け取ったらformをDOM生成しPOST送信。 まとめ バックグラウンドでDOMが扱えなくなった、んだ。どうにかDOMを扱うことは出来ないのか?、、fake-domのようなもので代替出来ないのか?、、、、試行錯誤し、、模索し、あきらめかけましたが、、新しく開いたページではDOM操作が可能という事だったので、MV2と同じ機能のままMV3に移行することが出来ました。 今回MV3に移行したChrome Extensions
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【メモ】Visual Studio CodeでのJavaScriptのデバッグ

毎度忘れるのでメモ 事前に拡張機能のLive Serverをインストールする。 index.htmlとscript.jsのHTML/JavaScriptのニコイチを用意する。 script.jsのコードのどこかにF9でブレークポイントを設置 index.htmlをF5でデバッグする launch.jsonが起動するので、以下のように設定する。 { "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Launch Chrome Local Debug", "url": "http://localhost:5500/index.html", "webRoot": "${workspaceFolder}" } ] } 5500のポートはlive Serverのポート
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

協調性のない非同期処理(async/await)

以前に非同期処理を会社組織に例えて説明する。の記事を書きましたが 今回はpromiseを意識せずにasync/awaitについて書いてみたいと思います。 どこの社会にもいますね。 協調性がなく、自分の事だけを黙々とやる人。 事務仕事に例えると、コピー機を占有したまま、待っている人の事を考えないとか。 非同期処理とは、そういう人の事です。 (仮に社員Aとしておきましょう) では、なぜこのAが会社にとって必要なのか? 遠い場所にある荷物をとってきてくれるからなのです。 (たまに失敗もしますが) それでは、後の人が困ってしまう? でもAが荷物を取ってこない事には、その部署の仕事が回らないのである。 じゃあ、どうしよう? 「Aは荷物を取ってきたか?失敗したかどうか?必ず報告をする」 (失敗を許さない日本社会を考えたらダメです) そこで、その部署自体をAを中心に考えたのです。 これがasync(非同期)です。 そしてAの仕事にawait(待つよ!)をつけてやる事にしました。 Aは荷物を取ってこれたかどうか?それにより後の仕事のフローを回していくのです。 コードにすると async busho(){ // 部署だとする await a_task // Aの仕事 .then(() => { 成功したよ!さぁ次の仕事だ! }         .catch(() => { 失敗しました!今日は帰るわ! } } どこかで「おまじない」のようにasync/awaitを使用すると書いてありましたが もう少しだけ掘り下げて考えてみました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【TypeScript】typeとinterfaceの違い

TypeScriptのtypeとinterfaceの違いをまとめています。 結論 typeエイリアスは、複数の場所で使い回す型に名前をつける。 interfaceは、オブジェクトや関数の構造を定義する。 具体的なtypeとinterfaceの違いは以下です。 ①同名の宣言 typeは、同名のtypeを宣言するとエラー。 interfaceは、同名のinterfaceを宣言するとマージされる。 ②継承 typeは、継承できない。 interfaceは、継承できる。 ①同名の宣言 typeは同名のtypeを宣言できません。(エラーになる。) type Person = { name: string age: number } type Person = { hight: number } //エラー interfaceは同名のinterfaceが宣言されるとマージされる。 interface Person { name: string age: number } interface Person { hight: number } //Personにhightというプロパティが付け足される。 const taro: Person = { name: "taro", age: 20, hight: 170 } 上記の場合、Person型を付けたtaroオブジェクトにはname、age、hightの3つのプロパティが必須になっています。 ②継承 typeは継承できません。 interfacemは、extendsを使って継承できます。 interface Person { name: string age: number } interface Employee extends Person { occupation: string } //Personを継承したEmployee型を定義 const taro: Employee = { name: "taro", age: 20, occupation: "sales", } ちなみにtypeは継承はできませんが、交差型で新しい型を作ることはできます。 type Person = { name: string age: number } type Hobby = { hobby1: string hobby2: string } type Introduction = Person & Hobby //交差型で新しい型を宣言 const taro: Introduction = { name: "taro", age: 20, hobby1: "tennis", hobby2: "shopping" } まとめ typeもinterfaceも実際ほとんど同じように使えますが、違いは以下。 ①同名の宣言 typeは、同名のtypeを宣言するとエラー。 interfaceは、同名のinterfaceを宣言するとマージされる。 ②継承 typeは、継承できない。 interfaceは、継承できる。 interfaceの方が柔軟に型を変化させることができるので、推奨されている様子です。 ただ、「柔軟に変化」できる分、思わぬバグを生みやすいとも言えそうなのでtypeが劣るというわけではない。 結論、違いを理解した上で、状況に応じて適切に使い分けていきましょう。 今回は以上です!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む