20210725のJavaScriptに関する記事は13件です。

Cloud Functions(Node.js)でberglasのsm://形式の秘密情報を展開する方法

本文 Cloud Runでは sm://<project_id>/<secret_name> の形式で環境変数をセットして起動すれば自動的にSecretManagerから秘密情報を取得して展開してくれます。(言語不問) 同様の機能がCloud Functionsにもあったらいいなと思ったんですが、Go言語版のみしかサポートされていなかったので、Node.js版を作成しました。 const { SecretManagerServiceClient } = require('@google-cloud/secret-manager'); async function resolveSecret(element) { const client = new SecretManagerServiceClient(); const [, , projectId, secretName] = element.split('/'); const name = `projects/${projectId}/secrets/${secretName}/versions/latest`; const [version] = await client.accessSecretVersion({ name: name, }); return version.payload.data.toString(); } async function substituteSecrets() { const resolved = await Promise.all( Object.entries(process.env) .filter(([_, element]) => element.startsWith('sm://')) .map(async ([key, element]) => [key, await resolveSecret(element)]) ); for (const [key, element] of resolved) { process.env[key] = element; } } 参考 https://github.com/GoogleCloudPlatform/berglas https://www.npmjs.com/package/@google-cloud/secret-manager
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsでJaveScriptが動かないときの原因と対処法

はじめに Railsでアプリケーション製作中、jsを思うように動かせず苦労したため、躓いたポイントをまとめました。 初学者のため基礎的な内容かと思いますが、どなたかの参考になると嬉しいです! 前提条件 Rails 5.x.x jQuery 3.x.x app/assets/javascripts/ 以下のファイルにjsを記述していることを前提とします。 原因1 そもそもJavaScript、jQueryが読み込まれていない まずは基本的なところを確認します。 ビューにスクリプトを読み込むための記述があるか application.html.erb <head> 〜省略〜 <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> </head> gemが追加されているか Gemfile gem 'jquery-rails' → bundle installを実行 application.jsファイルにJQueryを読み込むための記述があるか application.js //= require jquery3 //= require jquery_ujs //= require_tree . //= require jquery と //= require jquery_ujs は //= require_tree . より上に書く必要があります。 //= require_tree .は javascriptsディレクトリ配下のjsファイルを出力に含めるという記述なので、順番を逆にしてしまうとjQueryを読み込む前にjsファイルに記述した内容が読み込まれてしまいます。 原因2 turbolinksの影響 turbolinksとは、高速化を目的としたライブラリで、rails4以上ではデフォルトで入っています。 ページ遷移をAjaxに置き換え、画面の一部だけを更新することで高速化しようとするため、ページ読み込みを起点としたjsのイベントが発火しなくなってしまいます。 リンクを経ずに直接ページにアクセスしたり、ページをリロードした場合にはturbolinksの影響を受けないため、正常に動作します。 リンクを踏んでページ遷移した際にもjsを動作させるには下記の方法で記述します。 turbolinks:loadとすることで初回読み込み、リロード、ページ切り替えの全てで処理を実行できるようになります。 xxxx.js $(document).on('turbolinks:load', function() { 実行したい処理 }); また、リンクに下記記述をすることで特定のリンクのみturbolinksを無効化することができますが、ユーザビリティを考えるとあまり多用しない方が良いのかな...? <%= link_to "turbolinksの無効化", root_path, 'data-turbolinks': false %> 原因3 エラーが出て処理が止まっている 記述は上から順に読み込まれ、エラーが出た時点で止まってしまうため、該当部分の記述が正しくても、それより上に記述した部分でエラーが出ていると処理が実行されません。 エラー部分を修正することで正常に動作するようになります。 原因4 指定した要素を見つけられていない getElementById('hoge')などで要素を取得し、その要素に対しての処理を記述していた場合、#hogeを取得できないと当然その後の処理は実行できません。 考えられる原因としては HTMLより先にJSが読み込まれている そもそもそのページに指定した要素がない などがあるかと思います。 前者の場合、処理を下記関数内に入れてあげるとページを読み込んでから処理を実行できるようになります。 xxxx.js window.onload = function () { 実行したい処理; }; 後者の場合は、スペルミスの他、意図しないページでイベントが発火していることも考えられます。 //= require_tree . の記述をしているため、処理を実行させたいページ以外でもjsの記述が読み込まれています。 ページ読み込みをトリガーとしている場合などはイベントが発火してしまい、指定した要素を探しに行ってしまいます。 対処法は色々あると思いますが、一つの例として「要素がない場合は処理を終了する」という条件文を追加することで、続く処理でのエラーを防ぐことができます。 xxxx.js function(){ if (!document.getElementById("hoge")) return //この文を追加 '#hoge'に対する処理; }; まとめ RailsでJaveScriptが動かないときの原因と対処法 そもそもJavaScript、jQueryが読み込まれていない turbolinksの影響 エラーが出て処理が止まっている 指定した要素を見つけられていない Railsでjsを動作させるにあたって躓いたところ、調べたことをアウトプットさせていただきました。間違っている箇所がありましたらご指摘いただけると嬉しいです?‍♀️ 参考 Rails5でjqueryを動かす方法
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Denoに今後導入される新機能(2021夏ver)

Denoでは、新機能の導入がgithubのissueやdiscussionで議論されています。 最終的にどんな機能が導入されるかについてはコアチームの決定次第ですが、導入されそうなものを選んでまとめてみました。 3か月以内に予定されている変更 https://github.com/denoland/deno/issues/11168 に掲載されています。 ネイティブHTTPサーバーの導入 従来JS側で実装されていたサーバーがRustで書き直されます。 正確には、Rust製HTTPサーバーであるhyperを使うようになります。 この変更により、HTTPサーバーの処理速度がNode.jsを上回ります。 ベンチマークを見てみると、確かにNode.jsのHTTPサーバーのスループットがDenoネイティブHTTPサーバーのスループットを上回ってますね。 もともとNode.jsはRyan DahlのC10K問題(HTTPサーバーの処理能力の問題)に対する答えとして作られました。その性能を同じ作者によって作られたDenoが超えていくのは、10年越しの伏線回収みたいで激アツですね。 8月10日公開のv1.13.0で正式リリースされる予定です。それまでの間は--unstableフラグを付けて試すことが出来ます。 また、標準ライブラリのHTTPサーバーや、Deno向けサーバーサイドフレームワークの対応も進められています。 関連PR: #9935 FFIの導入 FFIが導入されます。C言語などの他言語のコードを、Denoから呼び出すことが可能になります。 似たような機能としてネイティブプラグインがありましたが、これは削除されます。 FFIを使用するには--allow-ffiフラグを指定する必要があります。これはFFIに対してファイルアクセスなどの細かいパーミッション制御ができないからです。 関連 #11152 標準ライブラリに「collection」モジュールが登場 標準ライブラリに、新しく「collection」モジュールが登場します。 主に配列・オブジェクト操作系の関数が収録されています。 既に利用可能になっています。 https://deno.land/std/collections 新しいテストAPI Denoにはテスターが搭載されています。Node.jsで言うjestやjasmineのようなものです。 今まではDeno.testというAPIを使用してテストを定義していましたが、このテストAPIを拡張して、並列テスト、テストの階層化が可能になります。 現在検討されているAPIは以下のようなものです。Deno.testに与えるコールバック関数の引数を使って、サブテストを実行することが可能になります。 Deno.test("group with subgroup", async (t) => { await t.run("subgroup", async (t) => { await t.run("case 1", () => {}); await t.run("case 2", () => {}); }); }); また、テストを階層化することで「全てのテストの前に○○を実行する」「全てのテストの後に○○を実行する」のような事が可能になります。 関連discussion: #10771 新しいサブプロセスAPI 従来のサブプロセスAPIはDeno.runでした。Deno.runAPIはゾンビプロセスを生成する可能性があることや、stdinやstdoutのハンドリングを行う必要があることから、直感的な新しいAPIの導入が検討されていました。 現在、単純で使いやすいAPIと、低レベルで複雑な設定が可能なAPIの導入が検討されています。 ユースケースとしては、 「コマンドを実行して出力を受け取るだけ」単純なAPI 「通信しながら長時間実行するサブプロセス」→複雑なAPI が想定されているようです。 関連issue: #11016 deno docのリファクタリング 現在、deno docコマンドはターミナル上への出力とjson出力のみサポートされており、HTML出力を利用するには https://doc.deno.land/ を使うしかありませんでした。 deno docのリファクタリングによって、ローカルでのHTML生成を可能にすると共に、wasm化してランタイム上で動かせるようになる見込みです。 関連issue: deno_doc#111 プロセス間のBroadcastChannel BroadcastChannel APIはブラウザのタブやウィンドウ、Worker間で通信するためのAPIです。DenoではWorker間での通信に利用されています。 このBroadcastChannel APIで、Denoプロセス間の通信が可能になるようです。 現状、プロセス間通信を既存のAPIを使って実装する場合、web socketサーバーや標準入出力などの低レベルなAPIを使う必要があると思います。 これがnew BroadcastChannel()だけで可能になるのは、結構すごい事なのではないでしょうか。 関連issue: #10750 deno実行ファイルへの署名 これはタイトル通りですね。deno.exe本体に対してデジタル署名を行うようです。 関連issue: #575 SSL証明書エラーを無視する機能 現在、fetch()を使ってSSL証明書エラーがあるサイトと通信することはできません。 SSL証明書エラーがあるサイトと通信することはセキュリティ上の観点から望ましくないという事で、新しい実行時フラグ(--allow-xxxのようなもの)を導入し、そのフラグが有効な時だけ通信可能になるようです。 この機能の導入については2018年から議論されていて、やっと導入されるようです。 関連issue: #1371 --promptフラグをデフォルトで有効化する Denoでは、ファイルの読み書きやインターネット接続を行いたい場合に--allow-xxxのようなフラグを付与する必要があります。 フラグを付与しなかった場合は、エラーが出て実行が終了します。 ここで--promptフラグを渡して実行すると、エラーを出す代わりに、アクセス許可を求めるプロンプトが表示され、プログラムの実行を継続することができます。 現在検討されているのは、--promptフラグの挙動をデフォルトにしようという案です。 ユーザー入力が求められない環境向けに、--no-promptというフラグも導入される予定です。 関連issue: #10183 デフォルトでTypescriptの型チェックをスキップ これはかなり賛否のある機能です。 DenoはTypeScriptを実行できるのですが、そのまま実行しているわけではなく、内部で自動的にJavaScriptへ変換した上で実行しています。変換には、tsc(型チェックを行う低速なトランスパイラ)か、swc(型チェックを行わない高速なrust製トランスパイラ)が利用されています。 変換時に型チェックを行うかどうかは、--no-checkフラグの有無で選択できます。 現在、デフォルトでは型チェックが行われていますが、これを変更して、デフォルトで型チェックをスキップするようになります。 型チェックありで実行したい時には--checkフラグを渡すことになります。 賛成派の意見 初心者にとって型チェックは煩わしいだけ ほとんどの人はIDEを使用しているので、型エラーがある場合はエディタ上に表示される 型チェックにはパフォーマンスの問題がある 否定派の意見 (JSではなく)TypeScriptを書いている以上、型チェックされることを期待している 正しいコードを書いていれば型エラーは発生しない 型チェックをする前提でCIが組まれている場合、期待通りに動かなくなる swcにはまだバグがある 当初v1.12.0で導入される予定でしたが、反対意見が多く、v1.13.0に延期されました。破壊的な変更であり、v2.0まで導入を待つべきだという意見もあります。 個人的には否定派です。(// @ts-nocheckや// @ts-checkの意味が無くなるので) 関連discussion: #8549 関連issue: #11340 メタデータファイル 実行時に毎回--allow-readとか書くのは面倒だということで、設定ファイルの導入が検討されています。 velociraptorというサードパーティ製ツールに近いのかなと思います。 package.jsonとは違い、プログラムのエントリポイントでのみ使われるという雰囲気です。 書き方的に、まだ「検討を進める」という段階にあるっぽいですね。 関連issue: #3179 deno deploy関連 deno deployは、Cloudflare WorkersのようなエッジCDNワーカーです。 多数の箇所に分散配置されたサーバーでJavaScriptとTypeScriptを実行することが可能で、webサイトのホスティングなどを行うことが出来ます。 年内の正式公開までの間に、いくつか新機能の導入が予告されています。 Denoランタイムとの互換性向上 これまでdeno deployはdenoランタイムと互換性が無く、ローカルで実行するにはdeployctlというツールが必要でした。 今後その制限がなくなり、deno向けのコードがdeno deployでそのまま動くようになります! これにより、Deno向けサーバーフレームワークがdeno deployでも利用可能になることが考えられます。 また、deno deploy上でDeno.readFile()がgithubリポジトリのファイルを読み込み可能とすることで、denoランタイムとの互換性を向上させるといった事も考えられているようです。 複数エッジ間の通信 BroadcastChannel APIはブラウザのタブやウィンドウ、Worker間で通信するためのAPIです。 deno deployでは、分散配置されたサーバー(=エッジ)間の通信を、このAPIを使って行うことができます。 用途としては、リアルタイムなチャットツールなどが思い浮かびます。 今後予定されている変更 Denoリポジトリのdiscussionの中に、「デザインミーティング」という名前でコアチームの議事録が掲載されています。 https://github.com/denoland/deno/discussions/11363 https://github.com/denoland/deno/discussions/11364 https://github.com/denoland/deno/discussions/11365 https://github.com/denoland/deno/discussions/11420 https://github.com/denoland/deno/discussions/11497 コアチーム内で今後のAPI設計についての方針が話し合われているようです。その中から、導入されそうな新機能を紹介したいと思います。 JSONインポート TypeScriptの対応待ちです。10月のTypeScript4.5で対応される見込みであり、Denoに導入されるのはその後です。 Deno.emit()の強化 Deno.emit()はTypeScriptコードをJavaScriptコードに変換する関数です。 Denoでは拡張子(.ts, .js)付きのimport文が使われますが、Deno.emit()は変換の際にimport文の拡張子を変更しません。 変換後のコードをそのまま動かせるように、impot文の拡張子を.tsから.jsに自動で変えるというオプションが検討されています。 関連issue: #4538 deno info / deno doc サブコマンドをランタイムAPIから使用可能にする Deno.info()やDeno.doc()のような関数を導入して、deno info/deno docコマンドをランタイムから利用できるようにすることが検討されています。 現状でもサブプロセスを使ってDeno.run({cmd: ['deno', 'info']})とすれば実行可能ですが、これは--allow-runフラグを必要とします。 --allow-runフラグを指定すると、Deno.run()を使用してDenoに任意のパーミッションを付与することが可能であり、必要以上に大きな権限を与えることになってしまいます。 --allow-runフラグ無しでもサブコマンドの実行結果を得られるようにするため、ランタイムAPIへの導入が検討されています。 関連PR: #10758 FileSystemAccess API Chromeなどに導入されているFileSystemAccess Web APIを、Denoにも導入しようとする動きがあります。 Denoの方針としては、 Web APIで対応できるものは極力Web APIで対応 Web APIで対応できないものはDeno.xxxの形でAPIを提供する という事になっています。 現在、ファイルアクセス系の関数はDeno.xxxの下にありますが、FileSystemAccess APIのサポートに切り替えていくことが検討されています。 関連issue: #11018 JSXファクトリ関数のデフォルト値を変更 JSXはトランスパイル時に関数に書きかえられます。どの関数に書き換えられるのかはtsconfig.jsonで設定できます。 現在、tsconfigに何も指定しなかった場合はReact.createElementという関数にトランスパイルされています。これはReactが使われているNode.jsでは便利ですが、Denoにとってはあまり意味がありません。 ということで、このデフォルト値をhに変更することが検討されています。 関連issue: #11186 Web USB API Web USB APIが導入予定です。 Web USB APIはJavaScriptからUSB機器の操作を可能にするAPIです。PCに繋いだICカードリーダやマイクロコントローラーなどの機器を操作することができます。 実装は完了していて、CI環境の整備を待っているようです。 関連PR: #10912 indexed DB 既にlocalStorageとsessionStorageが導入済ですが、これに加えてindexed DBもサポートされる見込みです。 ランタイムにデータベースが組み込まれていてすぐに利用可能というのは、他のランタイムに対してかなりのアドバンテージだと思います。導入が待ち遠しいです。 関連issue: #1699 v2.0で導入される破壊的変更 Denoではマイナーバージョンアップでの破壊的変更を避けており、2.0へのメジャーバージョンアップ時に破壊的変更が入ります。 2.0へのバージョンアップの時期ですが、「破壊的変更の数が溜まったら」という事になっています。 2.0で予定されている破壊的変更の一覧は、こちらで見ることが出来ます。 長くなったのでこの記事では取り上げませんが、いつか一つずつ詳しく紹介出来たらなと思います。 感想 Web USBやFileSystemAccessなどのAPIは、一部では「危険なJavaScript」と称され忌避されています。 しかし、実態としては、Denoやブラウザにおいて手動でパーミッションを付与した時にのみ有効化されるものです。 もし、Electronのようなネイティブアプリに全ての「特権」を付与するか、ブラウザのサンドボックス内で何の権限も付与されないかの2通りしか無かったとします。これでは、USBのような特定の権限だけが欲しい時にもファイルシステムへのアクセス権を付与するしかないという事であり、セキュリティの観点から安全とは言えません。 Denoやブラウザのような、サンドボックスを基本として必要な部分のみ特権を与えるセキュリティモデルが広まると嬉しいな、と思いました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【便利】コードネーム(和音)を判定するアプリを作った

構成音をポチポチ選択するだけで、コードネームが判定されるウェブアプリを作りました! ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ https://o-to.khufrudamonotes.com/o-to-modal-interchange もう似たようなアプリあるでしょ? …ぶっちゃけ、音の組み合わせからコードネームを検索するウェブアプリは他にもあります。 しかし、今回僕の作ったやつはひと味違います。 自ら「推しポイント」を語らせてください。     コードネームだけではなく… ・コードの読み方 ・詳細情報 ・コードの構成音を含む主なスケール まで同時に表示してくれます!!     ―――え?「だからどうした?」って? コードの読み方が分かるメリット 慣れないうちは、複雑なコードネームはまるで呪文です。 しかし、当たり前ですけど読み方が併記されていれば読めます。 コードに関する記事やアプリは、おそらく”コードネームが読める人”が 書いたり作ったりしていると思うので、意外と忘れがちな機能じゃないかと思います。 詳細情報が分かるメリット こういうコードネーム検索アプリを使ってくださる方って、 楽器を弾いていて… 「お!いい感じの響きのコードを見つけた!」 →「コードネームなんだろう?」 って流れで使われるんじゃないか…と思います。 でも、コードネームだけを見て 「へぇ〜」で終わってしまうのは「もったいない」と感じるんですよね。 せっかくなら、コードの仕組みや成り立ちまで理解して 応用できるところまでいければ良いのにな…と。 その点、コードの詳細情報を確認できると ”せっかく発見した素敵な響き”を応用する大きな助けになるはずです。 ↑"ドミナント機能を持つコード(不安定なコード)"の場合は、 "解決先のコード"の候補と、"手前にいそうなコード"の候補まで表示されます。 (※トライトーンの有無から判定しています。) コードの構成音を含む主なスケールが表示されるメリット 60種類を超える主なスケールの中から、 ”指定したコードの構成音を持つスケール”を自動で絞り込みます。 こちらもスケールの名前だけでなく、構成音、調号、親スケールの名前まで表示されます。 コードは前後の流れや、使われているキーに対しての立ち位置によって役割が変わります。 しかし、この機能を使えばそれらが一望できます。 また、複雑なコードを見たときの鉄板リアクションは、 「こんなコードいつ使うねん?」だと思います。 それに"答えられる"機能になっているはずです。 弱点 という感じで自画自賛の嵐でしたけど… 制作者が思う至らない部分についてもまとめておきます。 異名同音を厳密には判定していない ある程度の異名同音を区別する処理はしてあります。 しかし、異名同音を厳密に処理しようとすると色々面倒くさくなるのに加えて、 ダブルシャープやダブルフラットが出てきてビギナーには読みにくいかも… と思って躊躇しました。 UST(アッパー・ストラクチャー・トライアド)やハイブリッド・コードの解釈には対応していない。 操作の煩雑さを抑えつつ、同じUIで分かりやすく伝える方法が思い浮かばなかったので実装を見送りました。 ただ、有名なUSTやハイブリッド・コードと同じ構成音を持つコードに関しては詳細情報に表示されるようにしています。 使ってね というわけで、是非使ってみてください! できれば感想などもらえると嬉しいです!! ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ajaxでフォームから自動計算をするプログラム

はじめに この記事では、 Ajaxでフォームから自動計算をするプログラム について解説していきます。 実用例 私が作っているレシピサイトで使用している技術です。 実装 環境構築 headタグにjQueryのCDNを書きます。 <head> ...省略 <script src="https://code.jquery.com/jquery-3.3.1.js"></script> </head> form formを書きます。 結果はreadonlyで編集できないようにします。 <body> <form> <input type="text" id="input01">×2 + <input type="text" id="input02">×3 + <input type="text" id="input03">×4 = <input type="text" id="result" readonly="readonly" > </form> 計算するプログラムをscriptタグに書きます。 if (isNaN...で空欄だった場合に0で計算するようにしています。 最後に全ての値をresultに代入して結果を反映させます。 <script> $('#input01, #input02, #input03').change(function() { var input01 = $('#input01').val(); var val01 = parseInt(input01) * 2 if (isNaN(val01)) { var val01 = 0 } var input02 = $('#input02').val(); var val02 = parseInt(input02) * 3 if (isNaN(val02)) { var val02 = 0 } var input03 = $('#input03').val(); var val03 = parseInt(input03) * 4 if (isNaN(val03)) { var val03 = 0 } var result = val01 + val02 + val03 $('#result').val(result) }); </script> </body> 全体像 <body> <form> <input type="text" id="input01">×2 + <input type="text" id="input02">×3 + <input type="text" id="input03">×4 = <input type="text" readonly id="result" readonly="readonly"> </form> <script> $('#input01, #input02, #input03').change(function() { var input01 = $('#input01').val(); var val01 = parseInt(input01) * 2 if (isNaN(val01)) { var val01 = 0 } var input02 = $('#input02').val(); var val02 = parseInt(input02) * 3 if (isNaN(val02)) { var val02 = 0 } var input03 = $('#input03').val(); var val03 = parseInt(input03) * 4 if (isNaN(val03)) { var val03 = 0 } var result = val01 + val02 + val03 $('#result').val(result) }); </script> </body> さいごに Ajaxでフォームから自動計算をするプログラム について解説しました。 参考になったら、LGTMしていただけると幸いです! 最後まで読んでいただきありがとうございました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フォームから自動計算をするプログラム

はじめに この記事では、 フォームから自動計算をするプログラム について解説していきます。 実用例 私が作っているレシピサイトで使用している技術です。 実装 環境構築 headタグにjQueryのCDNを書きます。 <head> ...省略 <script src="https://code.jquery.com/jquery-3.3.1.js"></script> </head> form formを書きます。 結果はreadonlyで編集できないようにします。 <body> <form> <input type="text" id="input01">×2 + <input type="text" id="input02">×3 + <input type="text" id="input03">×4 = <input type="text" id="result" readonly="readonly" > </form> 計算するプログラムをscriptタグに書きます。 if (isNaN...で空欄だった場合に0で計算するようにしています。 最後に全ての値をresultに代入して結果を反映させます。 <script> $('#input01, #input02, #input03').change(function() { var input01 = $('#input01').val(); var val01 = parseInt(input01) * 2 if (isNaN(val01)) { var val01 = 0 } var input02 = $('#input02').val(); var val02 = parseInt(input02) * 3 if (isNaN(val02)) { var val02 = 0 } var input03 = $('#input03').val(); var val03 = parseInt(input03) * 4 if (isNaN(val03)) { var val03 = 0 } var result = val01 + val02 + val03 $('#result').val(result) }); </script> </body> 全体像 <body> <form> <input type="text" id="input01">×2 + <input type="text" id="input02">×3 + <input type="text" id="input03">×4 = <input type="text" readonly id="result" readonly="readonly"> </form> <script> $('#input01, #input02, #input03').change(function() { var input01 = $('#input01').val(); var val01 = parseInt(input01) * 2 if (isNaN(val01)) { var val01 = 0 } var input02 = $('#input02').val(); var val02 = parseInt(input02) * 3 if (isNaN(val02)) { var val02 = 0 } var input03 = $('#input03').val(); var val03 = parseInt(input03) * 4 if (isNaN(val03)) { var val03 = 0 } var result = val01 + val02 + val03 $('#result').val(result) }); </script> </body> さいごに フォームから自動計算をするプログラム について解説しました。 参考になったら、LGTMしていただけると幸いです! 最後まで読んでいただきありがとうございました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SEOフレンドリーなリンクを可視化するブックマークレット

SEOのためのリンクチェックを楽にできるブックマークレットを作りました。 このツールを使うことでGoogle botがクロール対象のURLとして認識できる可能性が高いリンクをワンクリックでハイライトできます。 [画像] ブックマークレットの使用によりリンクがハイライトされた様子 クロールとは、新しいページや更新されたページを Google インデックスに登録するため、Googlebot がそれらのページにアクセスするプロセスです。 https://developers.google.com/search/docs/advanced/guidelines/how-search-works?hl=ja 検索エンジンにとってリンクは「新しいページの発見」「各ページの価値や関係性の判断」などを行うための重要な要素ですが、以下の形式で実装されたリンクはクロールされない可能性があります。 JavaScriptのみに依存するリンク 「href属性が指定された<a>タグ」以外により実装されたリンク ボタンクリックやスクロール後にDOM内に追加されるリンク rel=nofollowなどによりGoogleにクロールされることを避けているリンク 本ツールはこれらを発見しやすくするため、リンクを色分けしてハイライトします。 緑 : Google botがクロール可能である可能性が高いリンク(href属性値がhttp,/,.,#!の何れかから始まる<a>タグ) 赤 : Google botがクロールできない可能性があるリンク(href属性値が#,javascript:の何れかから始まる、あるいはhref属性値が存在しない<a>タグ。但しhref属性値が#!から始まる場合を除く) 紫 : rel属性値の指定によりクロールボットに特殊な指示を出しているリンク(rel属性値がsponsored, ugc, nofollowの何れかのaタグ) また、以下のような形式で実装されたリンクは本ツールによりハイライトできません。Googleからも検知されない可能性があるためGoogleにクロールして欲しいリンクである場合は確認が必要です。 JavaScriptによりリンクのような動作をする要素 ブックマークレットが動作した後に追加されたリンク要素 その他あくまで本ツールが対応していないリンク要素 動作 ブックマークレットとして登録して実行するとリンクを色分けしてハイライト、もう一度実行するとハイライトを解除します。 コード ブックマークレット登録用 ブックマークレットの登録方法 → https://qiita.com/aqril_1132/items/b5f9040ccb8cbc705d04 javascript:!function(){if(window.linkChekToolStatus=window.linkChekToolStatus||!1,"undefined"==typeof linkCheckToolStyle){const e=document.createElement("style");e.type="text/css",e.innerText=".seo-friendly-link {background-color:#00800040 !important; border: dashed #00ff7ba8 !important; opacity:0.8 !important} .seo-unfriendly-link {background-color:yellow !important; border: dashed #ff0000a8 !important; opacity:0.8 !important} .seo-blocked-link {background-color:#ff00d936 !important; border: dashed #ff00d936 !important; opacity:0.8 !important}",document.getElementsByTagName("HEAD").item(0).appendChild(e)}linkChekToolStatus?(linkChekToolStatus=!1,[].forEach.call(document.querySelectorAll(".seo-friendly-link,.seo-unfriendly-link,.seo-blocked-link"),function(e){e.classList.remove("seo-friendly-link"),e.classList.remove("seo-unfriendly-link"),e.classList.remove("seo-blocked-link")})):(linkChekToolStatus=!0,[].forEach.call(document.querySelectorAll('a[href^="http"],a[href^="/"],a[href^="."],a[href^="#!"]'),function(e){let o=e.getAttribute("rel"),l=null!==o&&o.match(/sponsored|ugc|nofollow/g)?"seo-blocked-link":"seo-friendly-link";e.classList.add(l),[].forEach.call(e.querySelectorAll("*"),function(e){e.classList.add(l)})}),[].forEach.call(document.querySelectorAll('a:not([href]),[href^="#"]:not([href^="#!"],[href^="javascript:"],[href=""]'),function(e){e.classList.add("seo-unfriendly-link")}))}(); 圧縮前のコード 以下コードをコピペしてブラウザ開発者ツールのConsoleからお試し頂けます。 Chrome DevToolsのConsole機能の超簡単な使い方 → https://qiita.com/aqril_1132/items/a0f7e81a772006847ec3 seo-friendly_checker (function(){ window.linkChekToolStatus = window.linkChekToolStatus || false; if(typeof linkCheckToolStyle === 'undefined'){ const linkCheckToolStyle = document.createElement('style'); linkCheckToolStyle.type = 'text/css'; linkCheckToolStyle.innerText = '.seo-friendly-link {background-color:#00800040 !important; border: dashed #00ff7ba8 !important; opacity:0.8 !important} .seo-unfriendly-link {background-color:yellow !important; border: dashed #ff0000a8 !important; opacity:0.8 !important} .seo-blocked-link {background-color:#ff00d936 !important; border: dashed #ff00d936 !important; opacity:0.8 !important}'; document.getElementsByTagName('HEAD').item(0).appendChild(linkCheckToolStyle); } if(linkChekToolStatus){ linkChekToolStatus = false; [].forEach.call(document.querySelectorAll('.seo-friendly-link,.seo-unfriendly-link,.seo-blocked-link'),function(e){ e.classList.remove('seo-friendly-link'); e.classList.remove('seo-unfriendly-link'); e.classList.remove('seo-blocked-link'); }); }else{ linkChekToolStatus = true; [].forEach.call(document.querySelectorAll('a[href^="http"],a[href^="/"],a[href^="."],a[href^="#!"]'),function(e){ let rel = e.getAttribute('rel'); let selectedClassName = (rel !== null && rel.match(/sponsored|ugc|nofollow/g))?'seo-blocked-link':'seo-friendly-link'; e.classList.add(selectedClassName); [].forEach.call(e.querySelectorAll('*'),function(se){se.classList.add(selectedClassName);}); }); [].forEach.call(document.querySelectorAll('a:not([href]),[href^="#"]:not([href^="#!"]),[href^="javascript:"],[href=""]'),function(e){ e.classList.add('seo-unfriendly-link'); }); } })(); 参考になる情報 Google リンクをクロールできるようにする | Google 検索セントラル  |  Google Developers Google のクローラがたどれるリンクは、href 属性が指定された <a> タグのみです。その他の形式のリンクはたどれません。 https://developers.google.com/search/docs/advanced/guidelines/links-crawlable?hl=ja ユーザー補助に対応した AJAX サイトの設計  |  検索セントラル  |  Google Developers Googlebot は、HTML リンクの構造を理解することには長けていますが、ナビゲーションに JavaScript を使用しているサイトの検出は苦手です。Google では JavaScript の読解能力の強化に取り組んでいますが、Google や他の検索エンジンによるクロールが可能な、コンテンツへの HTML リンクを記載したサイトを作成することをおすすめします。 https://developers.google.com/search/docs/advanced/guidelines/ajax?hl=ja SEO 用に外部リンクの関係性を伝える | Google 検索セントラル  |  Google Developers サイト上の特定のリンクについて、リンクされているページとの関係を Google に通知できます。これを行うためには、<a> タグの rel 属性で次のいずれかの値を使用します。 (中略) - rel="sponsored" - rel="ugc" - rel="nofollow" https://developers.google.com/search/docs/advanced/guidelines/qualify-outbound-links JavaScript SEO の基本を理解する | Google 検索セントラル  |  Google Developers Googlebot がページ内のリンクを探すときには、HTML リンクの href 属性内の URL だけが対象になります。 クライアント側ルーティングを使用するシングルページ アプリケーションでは、History API を使用して、ウェブアプリのビュー間にルーティングを実装します。Googlebot がリンクを確実に見つけられるように、フラグメントを使用して別のページ コンテンツを読み込むことは避けてください。 https://developers.google.com/search/docs/advanced/javascript/javascript-seo-basics Google can crawl AJAX just fine “You no longer need to do anything special for hash-bang URLs,” Google’s John Mueller said on the September 17 edition of #AskGoogleWebmasters, “we’ll just try to render them directly.” https://searchengineland.com/google-can-crawl-ajax-just-fine-322254 Bing Webmaster Guidelines - Bing Webmaster Tools Bing recommends that all pages on a site are linked to at least one other discoverable and crawlable page. - Crawlable links are <a> tags with an href attribute. (中略) - Make a reasonable effort to ensure that any paid or advertisement links on your site use rel="nofollow" or rel="sponsored" or rel="ugc" attribute to prevent the links from being followed by a crawler and from potentially impacting search rankings. https://www.bing.com/webmasters/help/webmaster-guidelines-30fba23a 以上、ご活用頂けますと幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

htmlファイルのみでPDFを生成できるようなのでドラゴン桜計算プリントを作ってみた

はじめに  子供が算数のテストで単純な計算ミスをよくしているようなので、 計算力をつけたいと思い、簡単な四則演算のプリントを印刷するために、 Googleスプレッドシートを使って問題を生成して毎日5分で5人家族全員で解いていました。  これでも結構使えましたが、知り合いの子供もやりたいという声があり、共有を考えましたが、 閲覧権限だと問題のランダム生成ができないし、編集権限をつけると間違って触って式が壊れるかもしれないので、 シンプルにPDFを出力したほうがいいかと思い実現方法を調べてみました。 するとjavascriptのみでPDFを生成してフォントも埋め込めるライブラリがあったので、 それを使って作ってみた手順をまとめました。 必要なもの パソコン:今回は Windows10 の PC を使いましたが、Mac などでも同じようにできます。 ブラウザ(Chrome最新版, IE11 は pdf-lib が対応です。):動作確認用 テキストエディタ:html 編集用 埋め込み用フォント:Rounded Mgen+ (ラウンデッド ムゲンプラス) から rounded-mgenplus-1m-regular.ttf をダウンロードしました。 Webサーバー:ローカルで html を開くとフォントを読み込めないので、 OS 問わず使いやすい Chrome拡張の Web Server for Chrome を使います。 最小限のサンプル javascript で PDF を生成するライブラリはいくつかあるようですが、 今回は比較的新しそうな PDF-LIB を使います。 テキストエディタで以下の html ファイルを作成します。 html を保存した同じ場所(/dev/pdf)に rounded-mplus-1m-regular.ttf も配置します。 /dev/pdf/index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8" /> <script src="https://unpkg.com/pdf-lib"></script> <script src="https://unpkg.com/@pdf-lib/fontkit/dist/fontkit.umd.js"></script> </head> <body> 準備中... <script> (async function() {  //PDF生成 const pdf = await PDFLib.PDFDocument.create(); //埋込フォント使用 pdf.registerFontkit(fontkit); //日本語フォント読込 const font = await pdf.embedFont(await (await fetch('rounded-mgenplus-1m-regular.ttf')).arrayBuffer()); //ページ追加(単位はポイント、A4サイズにしています) const page = pdf.addPage([842, 595]); //文字描画(こちらも size, x, y の単位はポイント、原点は左下) page.drawText('JavaScript で PDF を生成したよ', { font: font, size: 14, x: 100, y: 300 }); //PDF表示 location.href = URL.createObjectURL(new Blob([await pdf.save()], { type: 'application/pdf' })); })(); </script> </body> </html> pdfフォルダは以下の内容になります。 Webサーバーの設定で、CHOOSE FOLER を押して、/dev/pdf フォルダをしています。 表示されている Web Server URL(s) をクリックしすれば以下のように PDF が表示されます。 pdf-lib ではフォントを指定しない場合、日本語が使えないようなので、これが最小のサンプルになると思います。 あとは描画する文字のサイズを計算する font.widthOfTextAtSize, font.heightAtSize, 矩形を描画する page.drawRectangle を使えば任意の位置に文字と枠線を表示できるので、四則計算100問のプリントPDFを作るプログラムを書きました。 完成版 完成版 <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8" /> <script src="https://unpkg.com/pdf-lib"></script> <script src="https://unpkg.com/@pdf-lib/fontkit/dist/fontkit.umd.js"></script> </head> <body> 準備中... <script> (async function() { /* ランダムで計算100問のPDFを出力する pdf-libの単位はpt: 1pt = 0.353mm, 1mm = 2.835pt */ const PAGE_WIDTH = 842; //A4 210mm×297mm = 84pt×595pt const PAGE_HEIGHT = 595; const FONT_SIZE = 10.5; const MARGIN = FONT_SIZE * 2; //上下左右余白 const ROWS = 27; //1ページ行数 const COLUMNS = 4; //1ページ列数 const COLUMN_WIDTH = (PAGE_WIDTH - MARGIN * 2) / COLUMNS; const THICKNESS = 0.001; //線の太さ const PADDING = 0.5; //セルの余白(1で1文字のサイズ) const FONT_URL = 'rounded-mgenplus-1m-regular.ttf'; //http://jikasei.me/font/rounded-mgenplus/ const Y = y => PAGE_HEIGHT - y; //pdf-libのy座標は下から上方向なので上から下方向に変換 const range = (start/*:Number*/, count/*:Number*/) => Array.from(Array(count)).map((_, i) => i + start); const rand_int = max/*:Number*/ => Math.floor(Math.random() * max); const isNumber = x => typeof(x) == 'number'; const expand_text = text => isNumber(text) ? Array.from(Array(Number(text))).reduce((a, i) => a + ' ', '') : text; //問題、解答作成 const pairs = range(1, 10).flatMap(x => range(1, 10).map(y => [x, y])); const questions = range(1, 100).map(n => { const prefix = '[' + n + '] '; switch(rand_int(4)) { case 0://加算: 0~50 + 0~50 { const left = rand_int(51); const right = rand_int(51); return [prefix + left + '+' + right + '=', left + right]; } case 1://減算: 0~99 -0~99(答えは0以上) { const pair = [rand_int(100), rand_int(100)]; const left = pair[0] > pair[1] ? pair[0] : pair[1]; const right = pair[0] > pair[1] ? pair[1] : pair[0]; return [prefix + left + '-' + right + '=', left - right]; } case 2://乗算: 1~10 × 1~10 { const pair = pairs.splice(rand_int(pairs.length), 1)[0]; const left = pair[0]; const right = pair[1]; return [prefix + left + '×' + right + '=', left * right]; } case 3://除算: 1~100 ÷ 1~10(余りなし) { const pair = pairs.splice(rand_int(pairs.length), 1)[0]; const left = pair[0] * pair[1]; const right = pair[1]; return [prefix + left + '÷' + right + '=', left / right]; } } }); //PDF作成 const pdf = await PDFLib.PDFDocument.create(); const title = '計算' + questions.length + '問' pdf.setTitle(title); pdf.setAuthor('nakazawaken1'); //日本語フォント読込 pdf.registerFontkit(fontkit); const font = await pdf.embedFont(await (await fetch(FONT_URL)).arrayBuffer()); const unitFontWidth = font.widthOfTextAtSize('響', FONT_SIZE) const fontHeight = font.heightAtSize(FONT_SIZE); const lineHeight = (PAGE_HEIGHT - MARGIN * 2) / ROWS; const drawCell = (page, texts/*:String|Number|[String|Number]*/, x/*:Number|'left'|'right'|'center'*/, y/*:Number*/) => { if(!(texts instanceof Array)) texts = [texts]; const widths = texts.map(t => font.widthOfTextAtSize(expand_text(t), FONT_SIZE) + unitFontWidth * PADDING * 2); const totalWidth = widths.reduce((total, width) => total + width); if(x == 'left') x = MARGIN; else if(x == 'right') x = PAGE_WIDTH - MARGIN - totalWidth; else if(x == 'center') x = (PAGE_WIDTH - MARGIN * 2 - totalWidth) / 2; texts.forEach((text, i) => { text = expand_text(text); page.drawText(text, { font: font, size: FONT_SIZE, x: x, y: y - fontHeight / 3 }); page.drawRectangle({ x: x - unitFontWidth * PADDING, y: y - fontHeight * 1.2 / 3 - fontHeight * PADDING, width: font.widthOfTextAtSize(text, FONT_SIZE) + unitFontWidth * PADDING * 2, height: fontHeight * (2 / 3 + PADDING * 2), borderColor: PDFLib.rgb(0, 0, 0), borderWidth: THICKNESS }); x += widths[i]; }); }; //ページ作成 pages = Array.from(Array(Number(location.search.slice(1) || 1))).map(i => false); pages.push(true); pages.forEach(answer => { const page = pdf.addPage([PAGE_WIDTH, PAGE_HEIGHT]); let y = MARGIN + (lineHeight - fontHeight) / 2; //ヘッダ描画 drawCell(page, title + (answer ? '解答' : ''), 'left', Y(y)); drawCell(page, ['氏名', 6, '日時', 6, '試験時間', 6, '点数', 6], 'right', Y(y)); y += lineHeight * 2; //計算式描画 range(0, COLUMNS).forEach(column => { const rows = Math.ceil(questions.length / COLUMNS); range(0, rows).forEach(row => { const question = questions[column * rows + row]; page.drawText(answer ? question[0] + question[1] : question[0], { font: font, size: FONT_SIZE, x: MARGIN + column * COLUMN_WIDTH, y: Y(y + row * lineHeight + fontHeight / 3) }); }); }); }); //表示 location.href = URL.createObjectURL(new Blob([await pdf.save()], { type: 'application/pdf' })); })(); </script> </body> </html> 以下のように作成しました。 記入用ページと解答ページを出力します。記入用ページは ?5 のようにパラメータで数を指定するとページが増えるようにしています。これで例えば5人分印刷する際に印刷設定をかえなくてもそのまま印刷できます。 各問題はランダムで加算、減算、乗算、除算が出力されます。 加算は左辺 0~50、右辺 0~50 のランダムで式が生成されます。 減算は左辺 0~99、右辺 0~99 のランダムで答えは0以上になる式が生成されます。 乗算は左辺 1~10、右辺 1~10 のランダムで同じ式が出ないよう生成されます。 除算は左辺 1~100、右辺 1~10 のランダムで余りなしで同じ式が出ないよう生成されます。 さいごに デモページ 中1, 小5, 小2と父、母で最初は5分間でやって100点が取れれば1分ずつ短縮していく感じでやっています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】道路標識を簡単に、見つけることができるアプリを、作りました。

私は、家の近所を散歩をすることが趣味です。 そこで、とある疑問が出てきました。 道路の標識って、たくさんあるけど、 パッと見でなんとなくの、 感覚でしか意味わかってないよなと。 ということで、簡単に道路標識の意味を、 サッと理解できる、アプリを作りました。 アプリはこちら https://orb.main.jp/app/roadsign/ GitHubはこちら https://github.com/fujioka8700/roadsign このアプリを作って、解決したかったこと。 私が、このアプリで解決したかったことは、この2つです。 1.できるだけ、目当ての標識を、早く見つけたい。 2.アプリには、標識を大きく表示したい。 なぜ、この2つのことを、解決したかったのか。 1.できるだけ、目当ての標識を、早く見つけたい。 みなさんもご存じの通り、 標識には似たものが、たくさんあります。 似たものがたくさんあるので、 「あれ?この意味であってたっけ?」となります。 かつ、誤った理解をしていると、非常に危険です。 2.アプリには、標識を大きく表示したい。 インターネットで検索すれば、 標識一覧はたくさん、出てくるのですが、 どれも、画像一枚に、標識がびっしり。 ただでさえ、似たものが多い標識なのに、 びっしりある標識の中から、 目当ての標識を見つけるのは、大変だなと思いました。 標識の見た目の特徴で、まとめました。 目当ての標識を見つけるためには、 標識の見た目の特徴で、まとめた方が、 探しやすいよな、と思いました。 なので、赤の丸い標識や、青い四角と矢印の標識など、 見た目が、似ているものでまとめて、 できるだけ、探しやすくしました。 標識の表示が大きいから、すぐに見つけることができる。 先ほども言いましたが、一枚の画像にびっしり 書かれた標識から、目当ての標識を探すのは大変です。 なのでこのアプリでは、標識ひとつひとつを、 大きく表示し、標識を見やすくしました。 そして、見つけやすくしました。 まとめ 実際に散歩中に、安全な場所に移動してから、 このアプリを使ってみました。 今までは、標識の特徴を文字で入力して、 検索して、探し回っていましたが、 このアプリを使うと、すぐに目当ての標識を 見つけることができました。 解決したいと思っていた2つの事は、 無事に解決することができました。 これからも、世の中の不満や、疑問など 少しでも解決できるようなアプリを、開発していきます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

plunkerでvue その55

概要 plunkerでvueやってみた。 phinaでブロック崩しやってみた。 サンプルコード new Vue({ el: '#app',  data: { }, beforeMount: function() { this.init() }, methods: { init() { phina.globalize(); var SCREEN_WIDTH = 640; var SCREEN_HEIGHT = 960; var MAX_PER_LINE = 8; var BLOCK_NUM = MAX_PER_LINE * 5; var BLOCK_SIZE = 64; var BOARD_PADDING = 50; var PADDLE_WIDTH = 150; var PADDLE_HEIGHT = 32; var BALL_RADIUS = 16; var BALL_SPEED = 16; var BOARD_SIZE = SCREEN_WIDTH - BOARD_PADDING * 2; var BOARD_OFFSET_X = BOARD_PADDING + BLOCK_SIZE / 2; var BOARD_OFFSET_Y = 150; phina.define("MainScene", { superClass: 'CanvasScene', init: function(options) { this.superInit(options); this.scoreLabel = Label('0').addChildTo(this); this.scoreLabel.x = this.gridX.center(); this.scoreLabel.y = this.gridY.span(1); this.scoreLabel.fill = 'white'; this.group = CanvasElement().addChildTo(this); var gridX = Grid(BOARD_SIZE, MAX_PER_LINE); var gridY = Grid(BOARD_SIZE, MAX_PER_LINE); var self = this; (BLOCK_NUM).times(function(i) { var xIndex = i % MAX_PER_LINE; var yIndex = Math.floor(i / MAX_PER_LINE); var angle = (360) / BLOCK_NUM * i; var block = Block(angle).addChildTo(this.group).setPosition(100, 100); block.x = gridX.span(xIndex) + BOARD_OFFSET_X; block.y = gridY.span(yIndex) + BOARD_OFFSET_Y; }, this); this.ball = Ball().addChildTo(this); this.paddle = Paddle().addChildTo(this); this.paddle.setPosition(this.gridX.center(), this.gridY.span(15)); this.paddle.hold(this.ball); this.ballSpeed = 0; this.one('pointend', function() { this.paddle.release(); this.ballSpeed = BALL_SPEED; }); this.score = 0; this.time = 0; this.combo = 0; }, update: function(app) { this.time += app.deltaTime; this.paddle.x = app.pointer.x; if (this.paddle.left < 0) { this.paddle.left = 0; } if (this.paddle.right > this.gridX.width) { this.paddle.right = this.gridX.width; } (this.ballSpeed).times(function() { this.ball.move(); this.checkHit(); }, this); if (this.group.children.length <= 0) { this.gameclear(); } }, checkHit: function() { var ball = this.ball; if (ball.left < 0) { ball.left = 0; ball.reflectX(); } if (ball.right > this.gridX.width) { ball.right = this.gridX.width ball.reflectX(); } if (ball.top < 0) { ball.top = 0; ball.reflectY(); } if (ball.bottom > this.gridY.width) { ball.bottom = this.gridY.width ball.reflectY(); this.gameover(); } if (ball.hitTestElement(this.paddle)) { ball.bottom = this.paddle.top; var dx = ball.x - this.paddle.x; ball.direction.x = dx; ball.direction.y = -80; ball.direction.normalize(); this.ballSpeed += 1; this.combo = 0; } this.group.children.some(function(block) { if (ball.hitTestElement(block)) { var dq = Vector2.sub(ball, block); if (Math.abs(dq.x) < Math.abs(dq.y)) { ball.reflectY(); if (dq.y >= 0) { ball.top = block.bottom; } else { ball.bottom = block.top; } } else { ball.reflectX(); if (dq.x >= 0) { ball.left = block.right; } else { ball.right = block.left; } } block.remove(); this.combo += 1; this.score += this.combo * 100; var c = ComboLabel(this.combo).addChildTo(this); c.x = this.gridX.span(12) + Math.randint(-50, 50); c.y = this.gridY.span(12) + Math.randint(-50, 50); return true; } }, this); }, gameclear: function() { var bonus = 2000; this.score += bonus; var seconds = (this.time / 1000).floor(); var bonusTime = Math.max(60 * 10 - seconds, 0); this.score += (bonusTime * 10); this.gameover(); }, gameover: function() { this.exit({ score: this.score, }); }, _accessor: { score: { get: function() { return this._score; }, set: function(v) { this._score = v; this.scoreLabel.text = v; }, }, } }); phina.define('Block', { superClass: 'RectangleShape', init: function(angle) { this.superInit({ width: BLOCK_SIZE, height: BLOCK_SIZE, fill: 'hsl({0}, 80%, 60%)'.format(angle || 0), stroke: null, cornerRadius: 8, }); }, }); phina.define('Ball', { superClass: 'CircleShape', init: function() { this.superInit({ radius: BALL_RADIUS, fill: '#eee', stroke: null, cornerRadius: 8, }); this.speed = 0; this.direction = Vector2(1, -1).normalize(); }, move: function() { this.x += this.direction.x; this.y += this.direction.y; }, reflectX: function() { this.direction.x *= -1; }, reflectY: function() { this.direction.y *= -1; }, }); phina.define('Paddle', { superClass: 'RectangleShape', init: function() { this.superInit({ width: PADDLE_WIDTH, height: PADDLE_HEIGHT, fill: '#eee', stroke: null, cornerRadius: 8, }); }, hold: function(ball) { this.ball = ball; }, release: function() { this.ball = null; }, update: function() { if (this.ball) { this.ball.x = this.x; this.ball.y = this.top - this.ball.radius; } } }); phina.define('ComboLabel', { superClass: 'Label', init: function(num) { this.superInit(num + ' combo!'); this.stroke = 'white'; this.strokeWidth = 8; if (num < 5) { this.fill = 'hsl(40, 60%, 60%)'; this.fontSize = 16; } else if (num < 10) { this.fill = 'hsl(120, 60%, 60%)'; this.fontSize = 32; } else { this.fill = 'hsl(220, 60%, 60%)'; this.fontSize = 48; } this.tweener.by({ alpha: -1, y: -50, }).call(function() { this.remove(); }, this); }, }); phina.main(function() { var app = GameApp({ title: 'Breakout', startLabel: 'splash', width: SCREEN_WIDTH, height: SCREEN_HEIGHT, backgroundColor: '#999', }); app.run(); }); }, } }); 成果物 以上。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

plunkerでvue その54

概要 plunkerでvueやってみた。 phinaでbox2dやってみた。 サンプルコード new Vue({ el: '#app',  data: { }, beforeMount: function() { this.init() }, methods: { init() { phina.globalize(); var SCREEN_WIDTH = 640; var SCREEN_HEIGHT = 960; phina.define("MainScene", { superClass: 'DisplayScene', init: function() { this.superInit(); this.backgroundColor = 'aqua'; var layer = Box2dLayer({ width: SCREEN_WIDTH, height: SCREEN_HEIGHT, }).addChildTo(this); var ball0 = CircleShape().addChildTo(this); ball0.setPosition(this.gridX.span(3), this.gridY.span(3)); ball0.alpha = 0.5; layer.createBody({ type: 'dynamic', shape: 'circle', }).attachTo(ball0); this.ball1 = CircleShape().addChildTo(this); this.ball1.setPosition(this.gridX.span(4), this.gridY.span(3)); this.ball1.alpha = 0.5; this.ball = layer.createBody({ type: 'dynamic', shape: 'circle', }).attachTo(this.ball1); var ball2 = CircleShape().addChildTo(this); ball2.setPosition(this.gridX.span(5), this.gridY.span(3)); ball2.alpha = 0.5; layer.createBody({ type: 'dynamic', shape: 'circle', }).attachTo(ball2); var floor0 = RectangleShape({ width: 500, height: 32, }).addChildTo(this); floor0.setPosition(this.gridX.span(4), this.gridY.span(9)); floor0.alpha = 0.5; layer.createBody({ type: 'static', shape: 'box', }).attachTo(floor0).body.SetAngle(Math.degToRad(10)); var floor1 = RectangleShape({ width: 500, height: 32, }).addChildTo(this); floor1.setPosition(this.gridX.span(13), this.gridY.span(13)); floor1.alpha = 0.5; layer.createBody({ type: 'static', shape: 'box', }).attachTo(floor1).body.SetAngle(Math.degToRad(-15)); }, update: function() { if (this.ball1.y > 900) { //alert(this.ball.body) //this.ball1.body.setPosition(this.gridX.span(4), this.gridY.span(3)); var p = new b2.Vec2(0, 0); this.ball.body.SetPosition(p); } }, }); phina.main(function() { var app = GameApp({ startLabel: 'splash', title: 'box2d', }); app.run(); }); }, } }); 成果物 以上。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

悪徳ひきこもり支援業者一覧アプリケーション

悪徳ひきこもり支援業者一覧アプリケーションを開発しました 一覧表示部分 <!-- 悪徳ひきこもり業者一覧作成システム --> <!-- 新規作成 2021/07/22--> <!-- 作成者 乃木坂好きのITエンジニア--> <!DOCTYPE html> <html lang="ja"> <!-- ヘッダー部分--> <head> <meta charset="utf-8"> <link rel="stylesheet" href="ichiran.css"> <title>悪徳ひきこもり業者一覧作成</title> </head> <!-- ボディー部分--> <body> <h1>悪徳ひきこもり業者一覧</h1> <h2>結果リスト</h2> <?php $pdo = new PDO("mysql:host=127.0.0.1;dbname=jissyu;charset=utf8", "root", ""); // データベースからデータを取得する try{ $sql = "SELECT id,company,address,hiyou FROM hikikomori;"; $stmt = $pdo->prepare($sql); $stmt -> execute(); } catch(PDOException $e){ echo "例外処理が発生しました"; echo $e->getMessage(); } ?> <table> <tr> <th>id</th> <th>会社名</th> <th>住所</th> <th>月額費用</th> </tr> <?php // 取得したデータを表示する while ($row = $stmt -> fetch(PDO::FETCH_ASSOC)) { ?> <tr> <td><?= $row["id"] ?></td> <td><?= $row["company"] ?></td> <td><?= $row["address"] ?></td> <td><?= $row["hiyou"] ?></td> </tr> <?php } ?> </table> <a href="hikikomori.php" id="return">入力画面に戻る</a> </body> <footer> <p class="hattori"> by 乃木坂好きのITエンジニア</p> </footer> </html> 入力フォーム <!-- 引きこもり悪徳業者一覧管理システム --> <!-- 新規作成 2021/07/22--> <!-- 作成者 乃木坂好きのITエンジニア--> <!DOCTYPE html> <html lang="ja"> <!-- ヘッダー部分--> <head> <meta charset="utf-8"> <link rel="stylesheet" href="hikikomori.css"--> <title>ひきこもり支援悪徳業者一覧管理システム</title> <script type="text/javascript"> function check(){ //変数の定義 const company = document.getElementById('company'); const submit = document.getElementById('submit'); if(company.value.replace(/\s+/, '').length == 0 ){ alert('運営会社名が入力されていません。'); } else { if(window.confirm('送信してよろしいですか?')){ // 確認ダイアログを表示 return true; // 「OK」時は送信を実行 } else{ // 「キャンセル」時の処理 window.alert('キャンセルされました'); // 警告ダイアログを表示 return false; // 送信を中止 } } } // --> </script> </head> <!-- ボディー部分--> <body> <?php // データベースに接続する $pdo = new PDO("mysql:host=127.0.0.1;dbname=jissyu;charset=utf8", "root", ""); // print_r($_POST); // 受け取ったデータのレコードを削除する if (isset($_POST["delete_id"])) { $delete_id = $_POST["delete_id"]; $sql = "DELETE FROM hikikomori WHERE id = :delete_id;"; $stmt = $pdo->prepare($sql); $stmt -> bindValue(":delete_id", $delete_id, PDO::PARAM_INT); $stmt->execute(); } // 受け取ったデータを書き込む if ((isset($_POST["company"])) && (isset($_POST["address"])) && (isset($_POST["hiyou"]))) { try{ $company = $_POST["company"]; $address = $_POST["address"]; $hiyou = $_POST["hiyou"]; $regist = $pdo->prepare("INSERT INTO hikikomori(company,address,hiyou) VALUES(:company,:address,:hiyou)"); $regist ->bindValue(":company", $company); $regist ->bindValue(":address", $address); $regist ->bindValue(":hiyou", $hiyou); $regist->execute(); } catch(PDOException $e) { echo "例外処理が発生しました"; echo $e->getMessage(); } } ?> <h1>引きこもり悪徳業者一覧アプリケーション</h1> <h2>引きこもり悪徳業者一覧入力フォーム</h2> <form id = "entry" action="hikikomori.php" method="post" role="form" onSubmit= "return check()"> <div class="form-group"> <dd>ひきこもり悪徳業者名を入力してください <dt> <span class="must"> * </span></dt> <dd> <label>ひきこもり悪徳業者名</label></dd> <dd> <input type="text" name="company" id="company"></dd> </div> <div class="form-group"> <dd>住所を入力してください <dd> <label>住所</label></dd> <dd> <input type="text" name="address" id="address"></dd> </div> <div class="form-group"> <dd>月額の費用を入力してください <dd> <label>月額の費用</label></dd> <dd> <input type="text" name="hiyou"></dd> </div> <p></p> <button type="submit" id="submit" onMouseOver="changeColor()" onMouseOut="revertColor()">データ登録</button> <script> function changeColor(){ document.getElementById('submit').style.backgroundColor = 'yellow'; } function revertColor(){ document.getElementById('submit').style.backgroundColor = null; } </script> </form> <a href="hikikomori_ichiran.php" class="ichi">ひきこもり支援悪徳業者一覧</a> <h2>一覧リスト</h2> <?php // データベースからデータを取得する $sql = "SELECT id,company,address,hiyou FROM hikikomori ORDER BY id DESC;"; $stmt = $pdo->prepare($sql); $stmt -> execute(); ?> <table> <tr> <th>id</th> <th>ひきこもり支援悪徳業者名</th> <th>住所</th> <th>月額費用</th> </tr> <?php // 取得したデータを表示する while ($row = $stmt -> fetch(PDO::FETCH_ASSOC)) { ?> <tr> <td><?= $row["id"] ?></td> <td><?= $row["company"] ?></td> <td><?= $row["address"] ?></td> <td><?= $row["hiyou"] ?></td> <td> <form action="hikikomori.php" method="post"> <input type="hidden" name="delete_id" value=<?= $row["id"] ?>> <button class="delete" type="submit" onclick="deleted()">削除</button> </form> </td> </tr> <?php } ?> <script language="javascript" type="text/javascript"> function deleted(){ if(window.confirm('データを削除してよいですか?')){ // 確認ダイアログを表示 return true; // 「OK」時は送信を実行 } else{ // 「キャンセル」時の処理 window.alert('キャンセルされました'); // 警告ダイアログを表示 return false; // 送信を中止 } } </script> </table> <a href="../index.html" class="menu">メニュー画面に戻る</a> </body> <footer> <p class="hattori"> by 乃木坂好きのITエンジニア</p> </footer> </html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初めてのnpm パッケージをTypeScript で作って公開してみた

TypeScriptの勉強のために小さなnpmパッケージを作成・公開しました。本記事は、パッケージを作成・公開するときに学んだこと・つまづいたことをまとめた内容になります。 本記事で言いたいことは、TypeScriptの勉強法としてnpmパッケージ作成は有効であり、公開するだけであればその敷居は思ったより低くないということです。 作ったもの commitlintという、コミットメッセージがコミット規約に従っているのかどうかをチェックするnpmツールがあります。 ESlintのように、プラグイン・ルールを組み合わせて拡張することで、プロジェクトに合ったコミット規約をカスタマイズできます。 今回は、コミットメッセージがIssue駆動開発のコミット命名規則に則っているか?をチェックするという、とても小さなプラグインを作りました。コミットメッセージ本文に#issue番号があるかどうかをチェックするだけのルールで、ほぼ自分用に作ったものではありますが、もしよければ使ってくださると幸いです。 作った動機 自分は普段Goでバックエンド開発をしているのですが、直近でTypeScriptが知識が必要になりました。 そこで、プログラミングTypeScript等の技術書を読んで基本的な読み書きができるようになったのですが、勉強を進めていくうちに何か一つ作ってみるかとなり、React×TypeScriptでのアプリ開発を考えました。しかし、筆者はReactに精通しているわけでもないため、ハマった時に何が原因なのかが混乱してしまい、学習効果が薄くなるだろうと思いました。 というわけで、自分は TypeScript単体の勉強としてコードを書きたかった node_moduleの勉強もしたかった ので、簡単なnpmパッケージを作ることにしました。 どのようにして作っていったか 以下の順番で進めていきました。 npmパッケージの作成・公開フローを知る commitlintのプラグインの開発ルールを知る npmパッケージ公開用のTypeScriptのビルド設定を知る 他のcommitlintプラグインのコードを真似ながら、開発していく また、開発を進める際は以下の資料を参考にしました。 npmパッケージの作り方 TypeScriptのビルド設定周り の[TypeScriptのビルドと実行]の章 あとは、@commitlintの型を使うため@commitlint/typesのソースコードを読んだり、npmで他のcommitlintのプラグインのソースコードを探して読んだりしました。 学べたこと 実際にTypeScriptで書いたコードは、ちょっとしたスクリプト程度の量だったので、TypeScript完全に理解した!というより、JavaScript・TypeScriptのエコシステムについて学んだことが多かったです。 TypeScriptのビルド周りの設定 TypeScriptはバイナリではなく、JavaScriptのソースコードをビルドします。そのため、どのプラットフォームで動くJavaScriptを生成するか?他の開発者のためにソースマップや型定義を生成するか?等を決める必要があり、それらの設定はtsconfig.jsonで細かく指定ができます。 業務では、他の開発者の方が設定してくれているので、今回位置から設定することでTypeScriptのビルド設定を知る良い機会になりました。 また、「export ? module.exports?どっちがどっちで、何がちがうのか?」といったCommonJsとNode.jsの記述の違いについて理解しておかないといけないため、開発前のタイミングで他の方が書かれたJavaScriptの変遷について説明した記事などで知識を整理できたのも良かったです。 TypeScriptの型定義 今回作成したcommitlintプラグインのソースコードでは、@commitlintの型を使用しています。その際に@commitlintのソースコードを読んだのですが、ジェネリクスの使い方などは非常に参考になりました。 保守性・拡張性の高いOSSのコードを読むことは、TypeScript初心者にとってとても良い勉強方法だったのではないかと思います。 npmパッケージの公開の仕方 npmパッケージはインストールして使ったことは何回もありますが、公開したことはありませんでした。開発前は設定が色々必要なのかな?と思っていたのですが、npmパッケージ開発の方法について解説している記事が多く、そもそもnpmパッケージの公開が簡単だったこともあるため敷居は低かったです。 つまづいたポイント 開発に必要なツールの設定 eslintやprettier、jestの設定につまづきました。TypeScriptの開発をするために、どのパッケージをインストールしたり、設定を追加すればよいのかを把握するのに時間をかけてしまいました。よくわからないまま他のプロジェクトを真似て突っ込むのではなく、ドキュメントを確認しながら一つづつ入れていけば良かったです。 個人の感想なのですが、Goと比較すると、あちらは言語に標準でフォーマッタやテストツールが付属しており、Go用のVSCode拡張を入れれば動くため、こちらは諸々の設定が面倒だなと感じました(単に自分に知識が足りないことや慣れの問題もありますが)。 また、設定ファイルは.jsでもymlでもjsonでもなんでもござれという点には面食らいました。 動作確認方法 yarnやnpmには、linkというローカルの開発中のnpmパッケージに対してシンボリックリンクを貼り、手元で動作確認ができる仕組みがあります。 誠に恥ずかしながら、開発中の自分はこの仕組みを知りませんでした。「テストに通っているからこれでええやろ!」と一通り単体テストを行いつつ、パッケージとして組み込んだ際の動作はスクリプトをそのままcommitlintに組み込んだりして確認していました。 そのため、実際の動作確認はnpmに公開してから行っていました。今振り返るととんでもないことです。普通に考えると何かしら動作確認ができる仕組みはあるはずなのですが、自分は気づきませんでした。npmほどのプラットフォームであれば、必ず開発ツールは充実しているはずなので、焦らずうまく検索ワードを変えて根気よく調べられれば良かったです。 おわりに npmパッケージをTypeScriptで書くことによって、コードの書き方だけでなく巨大なJavaScriptのエコシステムについてもある程度学べる機会となりました。 ある程度入門的な内容(リテラルの書き方など)を勉強した後に、何かサラッと書いてみる分には、npmパッケージ開発はうってつけだと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む