20211128のJavaScriptに関する記事は25件です。

TypeScriptで命令型と宣言型を書き比べてみる

概要 せっかくJavaScriptがマルチパラダイムな言語なので書き比べてみます。 ずっと命令型なスタイルで開発していたので、宣言型なスタイルは慣れないんですよね。 実行するべき関数を組み立ててくみたいな考え方の切り替えに苦戦しています。 命令型 class Person { private name: string; private age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } public sameAgePersons(persons: Person[]): Person[] { return persons.filter((person: Person) => this.age === person.age); } } const p1 = new Person('一郎', 20); const p2 = new Person('二郎', 19); const p3 = new Person('三郎', 20); console.log(p1.sameAgePersons([p2, p3])); 宣言型 type Person = { name: string, age: number }; const p1: Person = {name: '一郎', age: 20}; const p2: Person = {name: '二郎', age: 19}; const p3: Person = {name: '三郎', age: 20}; const selectorSameAge = (age: number) => (person: Person) => age === person.age; const findPersonsBySelector = (persons: Person[], selector: (person: Person) => boolean) => persons.filter(selector); console.log(findPersonsBySelector([p2, p3], selectorSameAge(p1.age)));
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ティラノスクリプトのタグコード解析

はじめに ティラノスクリプトのコードからタグの詳細な処理を解析します。 メッセージ [cm] NextImg、全てのメッセージレイヤーの文字、フリーレイヤーをクリアする。 this.kag.ftag.hideNextImg(); this.kag.layer.clearMessageInnerLayerAll(); this.kag.layer.getFreeLayer().html("").hide(); [er] NextImg、カレントのメッセージレイヤーの文字をクリアする。 this.kag.ftag.hideNextImg(); this.kag.getMessageInnerLayer().html(""); [l] NextImgを表示し、クリック待ちする。 // スキップ、オートの場合はif文で次の処理に進む。 this.kag.ftag.showNextImg(); [p] クリック待ち+改ページ。 [l][cm]との違いは不明(コードは異なる)。 this.kag.stat.flag_ref_page = true; this.kag.ftag.showNextImg(); [r] 改行(< br />)を追加する。 var j_inner_message = this.kag.getMessageInnerLayer(); var txt = j_inner_message.find("p").find(".current_span").html() + "<br />"; j_inner_message.find("p").find(".current_span").html(txt);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Amazonの検索結果を「Amazonが販売・発送する商品」のみに絞り込むブックマークレット

Amazonのブラックフライデーセールで話題になっていたこのツイート。 Amazonのブラックフライデーセールが始まりましたが、Amazonでお買い物する時に損しないためにこれだけは絶対やって欲しい3つのことをまとめました pic.twitter.com/pplejPIt06— じゅじゅ (@jujulife7) November 26, 2021 この3つ目のパラメータ追加検索がすごく便利だったので、簡単ですがブックマークレットにしました。 それがこちら。 javascript:(function(){window.location.href=document.URL+"&emi=AN1VRQENFRJN5";})(); ブックマークレットの登録方法についてはここでは記載しません。検索すればすぐ出てきます。 このブックマークレットの機能はすごく単純で、開いているページのURL末尾にパラメータを追加して開き直すだけです。 そのため、使用時には検索結果ページでは商品の絞り込みが行われ、それ以外のページではただの再読み込みになります。 また、検索結果ページでも使用後のURLには残らず、hiddenパラメータとして機能するようなので、使用後のURLにパラメータが残っていなくても心配いりません。 毎回URLにコピペするよりは楽だと思うので、よければ使ってみてください。 追記 Amazon以外のサイトで間違って押してしまうとどうなるかわからないので、URLにamazonを含むページのみで動作するようにしました。 javascript:(function(){var u=document.URL;if(u.match(/amazon/)){window.location.href=document.URL+"&emi=AN1VRQENFRJN5";}})();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ブラウザ上で動作するツールを超簡単に公開したい

これは株式会社POL テックカレンダー 2021 3日目の記事です。 株式会社POLでサーバサイドエンジニアをやっております岩井です。ですが、今回サーバサイドの話は一切ありません。入社から半年の間でかなり大規模なリファクタリングを自分の発案や設計でやったと思うのですが、ノウハウとして整理できている段階ではないかなと。そういうのはちゃんと整理できた段階で書きます。 前置き 自分はよくブラウザ上で動作する自分用のツールを作って色々な作業をするのですが、たまーに他の人に使ってもらってもいい出来かなと思うことがあります(大半は汎用性が全くないので公開には適していないのですが)。 旧世紀からインターネットを使っていた関係で、「インターネットに何かを公開する」=「レンタルサーバを借りてそこにアップロードする」という方式を最近まで貫いていましたが、サーバが不自由すぎたので最近AWSに移行しました。 とはいえ、AWSへのデプロイも、CI/CDでやるから気にしていないだけで、徒手空拳的にやるのは面倒だと思います。HTMLとJavascriptを公開したいだけなのに、いちいち大袈裟な設定をするのもなんか違うなと思いませんか? 規模が大きい開発についてのノウハウと比べると、極小規模のノウハウって話題になりづらい印象です。 また、自分のプログラマーとしての今のテーマは『「絵を描けるようになること」が「絵で収入を得られるようになること」を目指すことと一致しないのと同じように、「プログラミングを覚えること」が「プログラミングで収入を得られるようになること」を目指さないのが当たり前になること』を目標としているので、すごくシンプルな物を作って簡単に公開することのハードルは低ければ低いほど良いと考えています。 なので、今回は「HTMLとJavaScriptだけならgithub.ioで簡単に公開できる」というだけの話をあえてやります。 公開 公開したいものがGitHubのリポジトリとして存在している前提です。 公開したいリポジトリの Settings→Pages でGitHub Pagesの設定を開き、Sourceで公開するブランチとパスを選ぶだけで公開されます。 更新 選択したブランチにpushすると公開している物も自動的に更新されます。 これ以上簡単な公開方法が今この世にありますか?? 短所 サーバサイドで処理することができないため、どうしても機能に大幅な制限がかかるのが欠点です。 自分の場合は普段こういうツールを作るときは「データはjson形式で表示するからそれをコピペしてメモ帳に保存」「画面にjsonの入力欄を設け、そこからデータをロードする」という方法で、サーバサイドに頼らずセーブ・ロードを実現していますが、あくまで個々のユーザで完結するものだからこれで済んでいるだけで、「他のユーザと相互にデータを閲覧したい」ようなものはどうにもならないですね。 JavaScriptでGitHubのAPIを叩いてブランチとプルリクを作成すれば、マージするひと手間はあるけど「サーバ側にデータを蓄えてロードする」ことは可能かな…?いつか試すかも というわけで、HTMLとJavaScriptだけで動作する簡単なツールを超簡単に公開する話はこれでおしまいです。 明日はタイミング100%であだ名が決まった原島さんです。 作ったもの 以下、例として公開することにした、今年作ったものの説明なので読み飛ばしてもらってOKです ツールを公開しているURL:https://waiiwai.github.io/drawTool 画面  機能説明 画面左上から  ①プレビュー(2倍サイズ、1倍サイズ) 現代のディスプレイで1ドットを1ドットで表示するとあまりにも小さいので2倍サイズのプレビューを用意しました。まだ小さいかも。  ②カラーピッカー 色相環で色相を選択し、明度と彩度を選択するわりとオーソドックスなカラーピッカーです。 RGB値やHSV値の直接入力にも対応しています。 色相環をCanvasに1度ずつ描いたせいか、よく見るとモアレっぽくなっていますが気にしないでください。  ③パレット 16色までしか使えません。 ここで選んだ色で描画した後、パレット上の色をカラーピッカーで変更すると描画色も即時変更されます。なのでどうやっても16色しか使うことはできません。  ④ツール 現在は描画、消しゴム、塗りつぶしの3つしかありません。 気が向いたら全体ずらしとか、図形描画とか作るかもしれません。  ⑤セーブ・ロード 前述の通り、jsonでデータを出力するのでコピペでメモ帳に保存してください。 と、言いたいところですが、github.ioに公開していることによってクッキーを使えるので描画内容とパレット内容をjson形式でセーブします。 テキストボックスにも同じものを出力するので。メモ帳を利用した複数セーブデータの保持にも対応しています。 ロード時は、テキストボックスが空の場合はクッキーからテキストボックスにロードしてから、テキストボックスの内容を描画・パレットにロードします。  ⑥描画領域 32*32固定ですが、気が向いたら大きさを変更できるようにすると思います。 マウスなどでドラッグすることでの描画も可能ですが、アローキーとスペースでカーソルの移動と描画を行えます。  実装説明 あまり特筆すべき実装もありませんが、気に入ってる部分だけ。  色相環の描画 const hueCanvas = document.getElementById('hueCanvas'); huecontext = hueCanvas.getContext("2d"); huecontext.lineWidth = 20; const deg90 = Math.PI/2*3; for (let i = -1; i < 360; i++) { huecontext.beginPath(); huecontext.arc(100, 100, 90, deg90 + i * Math.PI/180, deg90 + (i+1) * Math.PI/180, false); huecontext.strokeStyle = "hsl(" + i + ",100%,50%)"; huecontext.stroke(); } 円を1度ごとに色相を変えながら360度描画しています。 色相0=赤を真上にしたのですが、画像検索してみると向きも回転方向もまちまちですね。 Google画像検索:色相環 アドベントカレンダーはあくまでクリスマスのイベントなので、カラフルでそれっぽいものをお見せしております。 モアレが出るのは何が悪いのか…  色相環のクリック位置取得 function hueChange(x, y) { let deg = Math.atan2(x-100, -1*(y-100))* 180 / Math.PI; if (deg < 0) deg += 360; selectedH = Math.floor(deg); hueDispRefresh(); colorChange(); } 色相環に幅があり、また、ある程度内側にも外側にも余白があるので、XY座標から角度に変換して色相としています。  消しゴムツールでの描画(描画の消去) if (toolNo == "eracer" && field[y][x] != -1){ field[y][x] = -1; dcontext.globalCompositeOperation = "xor"; dcontext.fillRect(posX+lineWidth, posY+lineWidth, cellWidth-lineWidth*2, cellWidth-lineWidth*2); vcontext.globalCompositeOperation = "xor"; vcontext.fillRect(x*viewCellWidth, y*viewCellWidth, viewCellWidth, viewCellWidth); v2context.globalCompositeOperation = "xor"; v2context.fillRect(x, y, 1, 1); } 肝心なのはこれですねcontext.globalCompositeOperation = "xor"; 「消す」ということを素直に考えると「透明色で描画する」イメージになりますが、それをそのまま実施すると「今塗ってある色の上に透明を塗る」という感じになり、結果的には何も起こりません。 なので、xorで重ね塗りすることで重複した部分を消しています。  パレット上の色が変更された時の処理 function colorChange() { const color = "#" + colors[paletteNo]; dcontext.globalCompositeOperation = "source-over"; dcontext.fillStyle = color; vcontext.globalCompositeOperation = "source-over"; vcontext.fillStyle = color; v2context.globalCompositeOperation = "source-over"; v2context.fillStyle = color; for (let y = 0; y < cells; y++) { for (let x = 0; x < cells; x++) { if (field[y][x] == paletteNo) { dcontext.fillRect(cellWidth*x+lineWidth, cellWidth*y+lineWidth, cellWidth-lineWidth*2, cellWidth-lineWidth*2); vcontext.fillRect(x*viewCellWidth, y*viewCellWidth, viewCellWidth, viewCellWidth); v2context.fillRect(x, y, 1, 1); } } } } これはあまり良くない実装です。 「全ピクセルをチェックして、変更があったパレットの色の場合、色を更新する」という処理になっていますが、描画領域の大きさに従ってループの計算量が増えてしまうので直したほうがいいですね。 パレット番号ごとに、塗ったピクセルを記録しておけばそのパレットで塗ったピクセル数のループにしかならないのでそうしたほうが良いはず(ファミコンなんかだとパレットの切り替えで画面全体のアニメーションを実現している(例えばドラクエの足踏みや波のアニメーションはパレット切り替えで行われている)ので、もっと効率良く全体の色を置き換える方法があるような気がしますが…)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactで複数classNameを扱いづらかったので使いやすくしてみた

ReactのclassNameが使いづらい ReactでclassNameが複数あったり、条件によって付けたりつけなかったりするときに //複数 <div className={ "btn" + " " + "red" }> </div> //条件分岐 <div className={ condition ? "red" : "blue" }> </div> //組み合わせ <div className={ "btn" + " " +(condition ? "red" : "blue" ) }> </div> うん、見にくいし、" "をつけ忘れる。 一応classNamesなるライブラリがあるが、いまいち使いづらいので、自分で作ってしまいました。 結論 cn関数を実装しました cnの使い方 <div className={cn( "btn", [condition,"red"], [condition,"red","blue"], )}> </div> cn関数の実装 function cn(...classNames:(string|[boolean,string,string?])[]):string{ let ans = "" ; classNames.forEach((className)=>{ if(typeof className === "string" ){ ans += className ; ans += " " ; }else if(className instanceof Array){ const [con,t,f=""] = className ; if(con){ ans += t ; }else{ ans += f ; } ans += " " ; }else{ throw new Error("className must be string or array "+className) ; } }); return ans ; } 独り言 switch的な感じで cn( [type, 0,"type-0", 1,"type-1", 2,"type-2", ], ) って書けたらもっと幸せですね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

既存のJSに安全にPrettierを一括適用した話

この記事は ビビッドガーデン Advent Calendar 2021 の2日目です。 こんにちは、食べチョクの開発をお手伝いさせていただいている@ogawa65aです。 大きくなり始めたコードベースのリーダビリティを改善するために、コードフォーマッターを導入したいと思ったことはありませんか? しかし、既存のコードベースに一括で自動フォーマット修正を適用するのは少し不安が残りますよね? かといって変更内容をすべて目で確認するのも至難の業です。 そこで、食べチョクでは、どのように安全かつ効率的にPrettierを既存のJSに適用したかをご紹介します。 Prettierとは 設定できるオプションが非常に少ないのが特徴のコードフォーマッターです。 Prettierの設計思想は、「コーディングスタイルに関するすべての議論を止める」ことです。 もしもっとたくさんのオプションがあると、どのオプションを有効にするかの議論が始まってしまい本末転倒であるという考えが根底にあります。 たくさんのプログラミング言語がサポートされていますが、今回はJSに対象を絞ってお話ししたいと思います。 Prettier導入後の課題 食べチョクでは今年、チームのエンジニアの人数が急激に増加したことに伴って、コードのフォーマットがバラバラになる問題に直面し、Prettierを導入しました。 Prettierを導入した当初は既存のコードにはPrettierを適用せず、プルリクで新規追加または変更されたファイルに対してのみGitHub ActionsでPrettierのチェックを走らせるという運用をしていました。 しかし、既存のファイルのほんの一部を修正しただけで、他の多くの箇所でPrettierによる自動修正が適用され、プルリクの差分がPrettierの修正だらけになってしまって、コードレビューがしにくい状態が続いていました。 Prettierによる修正のコミットを分けてレビューすることで問題は多少軽減できましたが、レビューでの指摘事項の修正コミットがさらに上に積まれたりすると、Prettierのコミットが他のコミットに挟まれて、やはりレビューしづらいという問題がありました。 どうしてもPrettierの修正が邪魔になってしまう場合は、.prettierignoreに追加して一時的にチェック対象外にしてしまうということもしていましたが、これでは一向に前に進まないという状況でした。 ですから、既存のコードへのPrettier適用もコツコツ進めていかなければ…、と思いつつ、なんとかうまく効率良く解決できないかと模索していました。 まずはコードベース全体に適用してみる とりあえず何が起こるか試しにコードベース全体にPrettierを適用してみました。 これではとてもじゃないですが怖すぎてマージできませんし、レビューもできません。 というか、そもそも重すぎてFile changedのタブを開くことすらままなりません。 よくよく見てみると実際の修正内容は、 改行位置の修正 末尾カンマの追加 {}の内側のスペースの追加 がほとんどで、フォーマッターなので当然といえば当然ですが、ほぼすべて構文上等価な修正であるという点に気が付きました。 であれば、webpackでJSをビルドしてしまえば最終的には同じバンドルができあがるのでは?という見通しが立ちました。 Prettier適用前後でバンドルが一致するか確認してみる Prettierを適用する前後でそれぞれproductionビルドしてできたバンドルがもしも同じであれば、最終的に配布するのはバンドルですから、安心してPrettierを適用できるわけです。 以下のように、バンドルは複数できて、ファイル名は[name]-[contenthash].jsとなっています。 $ webpack build --node-env=production $ ls public/entries app1-578d7f8fbcd7d5a41f90.js app2-b9a19f69d7d0c0e12917.js ... Prettier適用前のpublic/をpublic_before/に退避し、Prettier適用後のpublic/をpublic_after/とリネームします。 次に、バンドルが一致するかどうかを確認するスクリプトを用意します。 今回は、Rubyでスクリプトを書きました。 ここでは、ファイル名の[contenthash]が一致すれば(つまり、ファイル名が一致すれば)、バンドルの内容も同じであることを利用しています。 before_bundles = Dir.glob("**/*.js", base: "public_before/entries").sort after_bundles = Dir.glob("**/*.js", base: "public_after/entries").sort same_bundles = before_bundles & after_bundles puts same_bundles puts same_bundles.length different_bundles = before_bundles - same_bundles puts different_bundles puts different_bundles.length このスクリプトを実行してみると、約100個あるバンドルの内、なんと95個のバンドルが一致することがわかりました。 これで、一致したバンドルのソースコードについては安心してPrettierを適用できることになります。 バンドルが変わってしまったソースコードを確認してみる ここまでくれば、一致しなかったバンドルのソースコードについてはそのまま残しておいて、後で目視でレビューしても問題ないと思いますが、せっかくなのでもう少し踏み込んでみます。 一般に複数のソースコードが1つのファイルにまとめてバンドルされます。 ですから、変わってしまったバンドルは5つですが、それに対応するソースコードは5個以上あります。 この中でもすべてのソースコードのPrettier修正がバンドルを変えてしまう原因になっているわけではないはずです。 ですから、バンドルを変えてしまっているソースコードを特定したいと思います。 実際にバンドルが具体的にどのように変わってしまったかを確認してみます。 ここで、Prettier適用前後のバンドルに対して直接diffを取ってしまうと、バンドルは最小化されて1行になってしまっているので、どこが変更されたかわかりません。 ですから、バンドルに対して一度Prettierを適用してからdiffを取ります。 $ prettier --write public_before/entries/app1-578d7f8fbcd7d5a41f90.js $ prettier --write public_after/entries/app1-28b709482ec2bd6e901a.js $ diff public_before/entries/app1-578d7f8fbcd7d5a41f90.js public_after/entries/app1-28b709482ec2bd6e901a.js そうすると、差分の要因の箇所が表示されます。 その差分がどのソースコードから来ているかは、grepして確認すれば多くの場合すぐにわかります。 ちなみに、今回は、以下のようなReactのソースコードのPrettierによる修正がバンドルの差分を産んでいるということがわかりました。 <span> - あいうえお{" "} - かきくけこ --- + あいうえお かきくけこ </span> 上記のようにテキスト中の改行で空白を明示的にJSXで挿入していた箇所が、1行にまとめたことで空白が生のテキストとして挿入される形に修正されています。 このように、具体的なソースコードの修正箇所を特定することで、そのソースコード以外はPrettierを適用しても問題ないことが確認できます。 今回は、変わってしまった5つのバンドルで、結果的に計5つのソースコードが原因で変わってしまっていることがわかりました。 最初、724のファイルに変更があった内、この5つを除く719のソースコードに一括でPrettierを適用してもバンドルが変わらないという結果になりました。 残り5つのファイルは、別のプルリクでPrettierを適用して目でレビューして、これで無事すべての既存のJSにPrettierが適用できました! 最後に 手探りで無策の状態からのスタートでしたが、非常に満足な結果になりました。 ここで説明した方法は、JS以外のコードやPrettier以外のツール(例えば、sedで一括置換するような場合等)にも適用できるかもしれません。 食べチョクのコードベースにはフロント・サーバーサイドともにまだまだ課題がたくさんありますが、試行錯誤や地道な改善を続けていければと思っています。 もし興味を持っていただけましたら、ぜひ食べチョクを覗いて行ってみてください! ついでに、プロダクトチームも覗いて見てはいかがでしょうか?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MUI (Material-UI) で簡単なクイズアプリを作った話

概要 React の学習を兼ねて,新しくなった MUI (Material-UI) を用いたクイズアプリを作成した.作成したクイズアプリは GitHub Pages で公開している.また,ソースコードは GitHub にある. 詳細 具体的な実装については,GitHub にあるソースコードを見ていただくことにし,本記事ではプログラムの構成や役割について述べる. プログラムの構成 GitHub リポジトリの src ディレクトリ内にあるプログラムのうち,クイズアプリを実現する本質的なものは以下の5つである. │ quiz.js │ └─components qbcheckboxlist.js qbradiolist.js qbtextfield.js quizboard.js 以下,この5つのプログラムの役割を説明する. プログラムの役割 quiz.js アプリに表示する問題を記述するプログラムである.以下にその一例を示す. quiz.js const quiz = [ { id: 1, statement: '10000000の100000倍は1(  )である。括弧にあてはまる漢字1字を答えよ。', type: 'text', answer: '兆', }, { id: 2, statement: '4と6の最小公倍数はいくつか。', type: 'number', answer: '12', }, ]; 上記のように,アプリに表示する問題をオブジェクトの配列 quiz として記述する.オブジェクトの各プロパティの意味は次の通りである. id : 問題番号 statement : 問題文 type : 回答欄の種類 answer : 正答 プロパティ type には text,number,radio,checkbox の4種類が用意されている.radio,checkbox を指定した場合,上記4つのプロパティに加えて choices : 選択肢 が必要である.choice には,選択肢をオブジェクトの配列で次のように指定する. choices: [ { value: 'choice1', label: '偶数と偶数の和は奇数である。', }, { value: 'choice2', label: '偶数と奇数の和は偶数である。', }, { value: 'choice3', label: '奇数と偶数の和は奇数である。', }, { value: 'choice4', label: '奇数と奇数の和は奇数である。', }, ] プロパティ value には,各選択肢を表す一意的なIDを指定する.value に指定された値は,前述のプロパティ answer で正答を指定するのに用いる.また,プロパティ label には,クイズアプリに表示される選択肢の文言を指定する. さらに,プロパティ type に checkbox を指定した場合,プロパティ answer には正答を配列で指定する.以下にその一例を示す. { id: 4, statement: '次のうち、素数であるものをすべて選べ。', type: 'checkbox', choices: [ { value: 'choice1', label: '1', }, { value: 'choice2', label: '2', }, { value: 'choice3', label: '3', }, { value: 'choice4', label: '4', }, ], answer: [ 'choice2', 'choice3', ], } quizboard.js quiz.js に記述された各オブジェクトを基に,クイズを画面に表示するコンポーネントである.quiz.js の記述に問題がある場合は,エラーを表示する. エラーが表示される例 プロパティ type に radio を指定しているが,プロパティ choice が配列で指定されていない qbtextfield.js quizboard.js の子コンポーネントであり,回答欄にテキストボックスを表示する.quiz.js において,プロパティ type に text,number が指定された場合に使用される. qbradiolist.js quizboard.js の子コンポーネントであり,回答欄にラジオボタンリストを表示する.quiz.js において,プロパティ type に radio が指定された場合に使用される. qbcheckboxlist.js quizboard.js の子コンポーネントであり,回答欄にチェックボックスリストを表示する.quiz.js において,プロパティ type に checkbox が指定された場合に使用される. 参考 MUI: The React component library you always wanted
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

今みてるwebページをクリスマス仕様にするクソアプリを作りました

はじめに これはクソアプリ Advent Calendar 2021の カレンダー3の3日目の記事です。 クソアプリアドカレは毎年盛況で今見てる時点でカレンダー4までいってます、すごい。 私もクソアプリを作りたくなったので参加することにしました。 作ったもの コード 拡張を公開するにはデベロッパー登録しないといけないため社内限定公開にしてあります。 ソースコードはgithubにアップしてありますので手元に持ってきて自身で拡張登録すればどなたでもお使いいただけます。 https://github.com/yamamoto-hiroya/xmas_kuso_app 解説 軽くソースコードを解説します。 ブラウザ上部の拡張アイコンをクリックすると background.jsが発火します。 background.js // backgroundで動くjs // ブラウザ上部のアイコンをクリックすることで発火し、content.jsの方にメッセージを飛ばす(トリガー) chrome.browserAction.onClicked.addListener(function(tab) { /** * 第一引数: タブのID * 第二引数: ポストするキーとバリュー * 第三引数: コールバック関数(レスポンスが戻ってきた時に実行される) */ chrome.tabs.sendMessage(tab.id, { trigger: "on" }, function(msg) { console.log("background側のconsole:", msg); }); }); 発火した結果content.jsに処理が移る。 xmas_kuso_app.js $(function(){ /** * @param object message backgroundからポストされた値 * オブジェクトになっていてmessage.trigger = 'on'のような形になっている * @param sender * @sendResponse * これらは2つとも宣言してないとsendResponseが最後に返せなかったので設定した * * backgroundからメッセージが送られてきた時(ブラウザボタンをクリックされた時)に発火する */ chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { // 意図したbackground.jsからの送信でない場合は弾く if(message.trigger !== 'on'){ return false; } /** ----- main ----- **/ /** * 画面上の全ての要素をループで回す(ちょっと重いかも) * 背景色を緑にしてツリーっぽくする * 文字色を各種オーナメントを模して黄色、赤、白にする */ $.each($("*"), function(index, element){ $(element).css('background-color', '#0B7B48'); if(index%3 === 0) { $(element).css('color', '#B51802'); } else if(index%3 === 1){ $(element).css('color', '#FFE33A'); } else if(index%3 === 2){ $(element).css('color', '#FFFFFF'); } }); /** * 雪を降らせる * こちらのコードを参考にさせていただきました。 * @see https://rui-log.com/css_make-it-snow/ */ $('body').before("<div class='snow'>●</div> "); sendResponse('Done'); }); }); chrome.runtime.onMessage.addListener でbackgroud.jsからのメッセージを取得 意図したメッセージなら処理するようにしている。 画面上の全要素をループで回し、background-colorをツリー色に変更。 文字色は剰余算して3パターンにし、オーナメントっぽく赤・白・黄に変更。 雪を降らせる部分のcssはこちらを参考にさせていただきました。 https://rui-log.com/css_make-it-snow/ xmas_kuso_app.css .snow { color: snow; /*雪の色*/ font-size: 18px; /*雪の大きさ*/ position: fixed; top: -5%; /*初期位置*/ z-index: 9999; text-shadow: 5vw -100px 2px, 10vw -400px 3px, 20vw -500px 4px, 30vw -580px 1px, 39vw -250px 2px, 42vw -340px 5px, 56vw -150px 2px, 63vw -180px 0, 78vw -220px 4px, 86vw -320px 9px, 94vw -170px 7px; animation: anim 5s linear infinite; } @keyframes anim { 100% { color: transparent; top: 150%; } } おわりに 連打すると雪が増えるし元に戻すにはリロードするしかないです。 開発時間は1時間程度です。 このあたりがクソアプリ感ありますね。 少しでもクリスマスを感じられると良いとおもいます(こなみかん) 以下宣伝です。 Hameeでもアドカレをやっていますので興味がある方はこちらもチェックしてみてください! Hameeのアドカレ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】モジュール③ モジュールコンテキストとモジュールスコープ

はじめに Udemyの【JS】ガチで学びたい人のためのJavaScriptメカニズムの講座の振り返りです。 前回の記事 目的 モジュールについての理解を深める 本題 1.モジュールコンテキスト モジュールコンテキストではthisが使えない moduleB.js // thisを出力してもundefinedと出力される console.log(this); // 関数でも同様 function fn(){ console.log(this); } // moduleを使わない場合にはwindowオブジェクトが取れていた fn(); module.js function fn(){ console.log(this); } fn(); // objの中に格納した場合 const obj = { fn } // fnはobjのメソッドとして使える obj.fn(); // 呼び出し元のobjのことを参照する 2.モジュールスコープ moduleA.js, moduleB.jsを用意 基本的にはそれぞれで使う変数や関数はそれぞれのファイル内だけで使用できる 外部の変数や関数を使う場合はimportやexportで明示的に宣言する必要がある 例 moduleA.js const a = 0 moduleAで上記のように宣言しても, moduleB.jsに持ってこれない(importしない限り) moudleB.js // 関数外で変数を宣言 const a = 0 function fn(){ console.log(this); // レキシカルスコープは使用可能 console.log(a); } fn(); const obj = { fn } obj.fn(); グローバルスコープも使用可能 moduleA.js // window.bに1を代入 window.b = 1 moduleB.js // moduleA.jsで宣言された変数を使用するために読み込む import "./moduleA.js" // 以下で1と出力される console.log(window.b); // fromを書かなくてもmoduleAを実行するだけなのでOK 今日はここまで! 参考にさせて頂いた記事 【JS】ガチで学びたい人のためのJavaScriptメカニズム Let'sプログラミング JavaScript入門
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで動的に関数を指定する

はじめに JavaScriptで実行する関数をif文で振り分けるのではなく変数で指定する方法を示す。 動的に関数を指定する 参考にしたのがこちら。 基本のかたち 連想配列とか辞書とか呼ばれるキーと値から構成される配列で、値として関数を指定する。 関数を直接記載するのではなく、「関数を呼び出す」という内容の無名関数を指定する。 基本 var doSomething = { "A": function(){funcA();}, // 関数を呼び出すやり方 "B": function(){console.log("Bが実行された");}, // 直接記載するやり方 "C": funcC(), // こう書くと定義された瞬間に呼び出されてしまうようだ }; function funcA(){ console.log("Aが実行された"); } function funcC(){ console.log("Cが実行された"); } // キーの変数で関数を呼び出す var funcName = "A"; doSomething[funcName](); // 変数を使わないならこう書いてもよい doSomething.B(); //以下、実行結果 Cが実行された Aが実行された Bが実行された 引数を使う 関数に引数を使うときはこうする。 hogeよりも明示的に変数であることをあらわす変数名は…hensuだ! hensuしかない! 引数あり var doSomething = { "A": function(x){funcA(x)}, }; function funcA(x){ console.log("Aが実行された:引数は" + x); } var funcName = "A"; var hensu = 123; doSomething[funcName](hensu); //以下、実行結果 Aが実行された:引数は123 関数の戻り値を利用する 連想配列の各キーの値は「関数を呼び出す」という内容の無名関数。だから呼び出された関数がreturnで戻り値を返すだけでは駄目。呼び出された関数だけでなく無名関数にもreturnが必要だ。 戻り値を活用 var calc = { "+": function(a, b){return funcAdd(a, b)}, // 正しい "-": function(a, b){funcSubtract(a, b)}, // 正しくない:undefinedを返す "*": function(a, b){return a*b}, // 正しい }; function funcAdd(a,b) { return a+b; } function funcSubtract(a, b) { return a-b; } var ans = calc["+"](5, 2); console.log(ans); //以下、実行結果 7 オチ これを活用して、DateオブジェクトからgetDate()やgetHours()といった個別のメソッドを呼び出すのではなく、引数として"d"を指定してやれば日付を返し"H"を指定してやれば時を返すような独自関数を作ることを考えた。というか見事に作り上げた。 そして記事を書こうとして、その途中でMoment.jsを知り、自分がこれまでおこなってきたことが無駄だったと知ったのだった。 私の場合、VBAやPythonなど他言語も正しい教材で学んだわけではないが、特にJavaScriptに関してはつまみ食いの傾向が強かった。桃鉄ガチャを作ったときにjQueryを知ったが、そのときに「JavaScriptには便利な外部ライブラリがありそれを取り込んで使うことができる」ことをより汎用的に学んでおけばこのような二度手間もなかったのだが。 終わりに これはこれで良しとしよう。私は経験を積んだのだ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nuxt.js2.15.8をインストールした時に生じる脆弱性とビルド時のエラーを力技で修正する

はじめに Nuxt.jsの学習を始めてみよう!と思ったところ、環境構築でエラーに遭遇したのでその対策をまとめてみました。 開発環境 Apple M1 Docker 20.10.8 Docker Compose 1.29.2 Node.js 16.13.0(LTS) 修正方法 まずは、パッケージの脆弱性がどうなっているかを npm audit で確認してみましょう。 1. glob-parent <5.1.2 「fix available ~」以降ですが、「npm audit fix --forceでこの脆弱性を修正することができますが、Nuxt.jsのバージョンが2.13.3になりますよ」と言っています。fixし、npm run devすると 2. DeprecationWarning: Use of deprecated folder mapping "./" ~ 添付の内容を要約すると、「/src(Nuxt.jsが入るディレクトリ)/node_modules/@nuxt/components/package.jsonで非推奨のフォルダマッピング『./』が使われてます」となっており、「Update this package.json to use a subpath pattern like "./"」と言われているので、/src/node_modules/@nuxt/components/package.jsonの「./」を「./」へ修正します。 というエラーも出た場合は、同じ方法で修正してあげます。その後、npm run devすると 3. Module build failed (from ./node_modules/babel-loader/lib/index.js) こちらに関しては、 https://stackoverflow.com/questions/66325582/nuxt-js-cannot-find-module-babel-preset-env-lib-utils を参考にさせていただきました。「@babel/preset-env updated, use old version 7.12.17」と書かれているため、 npm i @babel/preset-env@7.12.17 をします。その後、npm run devすると 4. These dependencies were not found こちらに関しては、https://akizora.tech/npm-run-build-corejs-error-3208 を見ていただければと思います。 サイト内にある通り、 npm add core-js@2 します。その後、npm run devをしてみてください。おそらくビルド時のエラーはないと思われます。お疲れ様でした!! さいごに 高い脆弱性をもつパッケージを修正し、エラーもなんとか解決することはできましたが、node_modules内のファイルを直接変更しているので、この方法がベストではないと思います。より良い方法をご存知でしたら、教えていただけると幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Asanaの本文をコードハイライトするChrome拡張を作りました

はじめに この記事はHamee Advent Calendar 2021の4日目の記事です。 Hamee開発部は長年タスク管理にRedmineを使ってきましたが、つい最近Asanaへ切り替えを行いました。 Asana本文でコードハイライトができないようで、Redmineと同じような使い方をしようとすると不都合があったためコードハイライトできるように拡張を作りました。 デモ 拡張アイコンをクリックするとasanaの本文がコードハイライトされます。 もう1度押すと元に戻ります。 コード 拡張を公開するにはデベロッパー登録しないといけないため社内限定公開にしてあります。 ソースコードはgithubにアップしてありますので手元に持ってきて自身で拡張登録すればどなたでもお使いいただけます。 https://github.com/yamamoto-hiroya/asana_code_highlight 解説 軽くソースコードを解説します。 ブラウザ上部の拡張アイコンをクリックすると background.jsが発火します。 background.js // backgroundで動くjs // ブラウザ上部のアイコンをクリックすることで発火し、content.jsの方にメッセージを飛ばす(トリガー) chrome.browserAction.onClicked.addListener(function(tab) { /** * 第一引数: タブのID * 第二引数: ポストするキーとバリュー * 第三引数: コールバック関数(レスポンスが戻ってきた時に実行される) */ chrome.tabs.sendMessage(tab.id, { trigger: "on" }, function(msg) { console.log("background側のconsole:", msg); }); }); 発火した結果content.jsに処理が移る。 asana_code_highlight.js /** * 基本方針 * 画面が動的変わるので適用が面倒くさい * 拡張アイコンをクリックしたタイミングで適用/解除をすることにする * 元の要素を直接置き換えてしまうとフロントの保存処理が効かなくなってしまうため * タスク本文欄はhide,showで切り替え、highlight欄はafter,removeで切り替えて * さも切り替わっているように見せることにした * * ハイライトは以下のライブラリを使用 * @see https://github.com/highlightjs/highlight.js */ $(function(){ /** * @param object message backgroundからポストされた値 * オブジェクトになっていてmessage.trigger = 'on'のような形になっている * @param sender * @sendResponse * これらは2つとも宣言してないとsendResponseが最後に返せなかったので設定した * * backgroundからメッセージが送られてきた時(ブラウザボタンをクリックされた時)に発火する */ chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { // 意図したbackground.jsからの送信でない場合は弾く if(message.trigger !== 'on'){ return false; } /** ----- main ----- **/ // APPEND: タスク本文以外(コメント欄やメッセージ欄など)にも適用できると嬉しいかも // タスク説明文が表示状態で存在していればハイライトする var task_description = $(".TaskDescription"); if(task_description.is(':visible')){ console.log('タスク本文要素を発見したのでハイライト欄に切り替えます。'); var task_description_body = task_description.find(".ProsemirrorEditor").html(); var highlight_block = "<div id='highlight'></div>"; task_description.hide(); task_description.after(highlight_block); /** * ```で囲まれている部分を置換しハイライトする */ if(task_description_body.match(/```(.*?)```/)){ task_description_body = task_description_body.replace(/```php(.*?)```/g, "<pre><code class='highlight php'>$1</code></pre>") task_description_body = task_description_body.replace(/```ruby(.*?)```/g, "<pre><code class='highlight ruby'>$1</code></pre>") task_description_body = task_description_body.replace(/```sql(.*?)```/g, "<pre><code class='highlight sql'>$1</code></pre>") task_description_body = task_description_body.replace(/```js(.*?)```/g, "<pre><code class='highlight js'>$1</code></pre>") task_description_body = task_description_body.replace(/```sh(.*?)```/g, "<pre><code class='highlight sh'>$1</code></pre>") task_description_body = task_description_body.replace(/```html(.*?)```/g, "<pre><code class='highlight html'>$1</code></pre>") task_description_body = task_description_body.replace(/```css(.*?)```/g, "<pre><code class='highlight css'>$1</code></pre>") task_description_body = task_description_body.replace(/```go(.*?)```/g, "<pre><code class='highlight go'>$1</code></pre>") task_description_body = task_description_body.replace(/```py(.*?)```/g, "<pre><code class='highlight py'>$1</code></pre>") task_description_body = task_description_body.replace(/```(.*?)```/g, "<pre><code class='highlight'>$1</code></pre>") } // textで入れると改行されないのでhtmlごと入れる $('#highlight').html(task_description_body); // ハイライト処理 $('.highlight').each(function(i, block) { hljs.highlightBlock(block); }); // ハイライト欄があれば元に戻す } else if($("#highlight").length !== 0) { console.log('ハイライト欄を発見したので元の要素に切り替えます。'); $('#highlight').remove(); task_description.show(); // 対象の要素がなければ何もしない } else { console.log("対象の要素がなかったため何もしません。"); } sendResponse('Done'); }); }); chrome.runtime.onMessage.addListener でbackgroud.jsからのメッセージを取得 意図したメッセージなら処理するようにしている。 ハイライトのメイン処理は https://github.com/highlightjs/highlight.js こちらを利用させていただきました。 hljs.highlightBlock(block); を呼ぶだけでいい感じにハイライトしてくれるからめっちゃ便利でした。 後は基本的にコメントに書いている通り、コード読めば大体理解できるかと思います。 苦労したところ asanaの画面が動的にDOM要素が変わるため、ボタンを仕込むとか画面が切り替わる度に再適用するとか、そういったことは面倒くさそうでした。 なので拡張アイコンのクリックによって切り替える方針にしました。 はじめは.text()を取得しようとしていたんですが、asanaのタスク本文が普通のinput要素ではなくpタグとcodeタグで囲まれたhtmlだったため、.html()で取得するようにしました。 また、切り替え方に関してもタスク本文を.empty()して要素を追加、戻すときは元のhtmlを入れて戻す、みたいな方針で実装していたんですが、元のcloneしておいたhtmlを入れても自動保存のjsが効かなくなってしまいました。 ハイライト処理すると保存できなくなるのは困るので試行錯誤した結果、hide,showで切り替わってるように見える実装方法に変更しました。 また、最初はタスク全文をハイライトしていましたが意図しないハイライトがかかったりして逆に見づらかったので ```のようなマークダウン記法で囲まれた部分のみを置換するように方針変更しました。 チームに共有して使ってもらったところ「拡張入れてない人からしたら```が謎に入ってかえって見づらいかも」との意見をもらって、うーん一長一短かーと思っているところです。 今後アップデートするかも、しないかも。 何か良い案があればコメントかissueかプルリクかください。 おわりに これでasana上でもいい感じにコードが見れるので見間違いなどのリスクが減らせるといいなー。 タスク本文以外の部分にも適用した方がいいか悩み中。 まぁそれは今後需要があればということで。 実装時間約4時間にしてはやりたいこともできてるし我ながら上出来かな。 それではこの後もHameeのアドカレの記事をお楽しみに!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ブラウザでの画面共有でも使われる「MediaDevices.getDisplayMedia() / Screen Capture API」の話

 以下の記事に出てくるお試しにも登場した、また、ブラウザで Google Meet や Zoom等を使った場合で画面共有をする時に使われる「MediaDevices.getDisplayMedia() / Screen Capture API」の話です。 ●【概要編】1つのHTMLファイルに「カメラ映像・スライド・コメント表示」などの複数のものを同時に表示させてみる(3パターン) - Qiita  https://qiita.com/youtoy/items/e104921ad5caa99019ad デモ まずは、デモサイトで動作確認をします。 ●getDisplayMedia demo  https://www.webrtc-experiment.com/getDisplayMedia/ とりあえず、「Test getDisplayMedia API」というボタンを押した後に、共有対象のウィンドウやタブを何か選んでみてください。 その共有操作を行った後にいったん、共有対象にしたウィンドウやタブにフォーカスがあたるかもしれません。 その共有が行われている状態で、上記のデモページに戻ってくると、共有対象として選んだウィンドウやタブの表示部分が、上記のデモページ内でも表示されます。 ブラウザの API で画面共有を実行。デモサイトで試した時の動作の様子。●getDisplayMedia demo https://t.co/pPIUD6735a pic.twitter.com/ErJ6gLw4EV— you (@youtoy) November 28, 2021 上のツイートに添付した画像は、この記事を書いていたタブを共有対象にしてみた様子で、画像の真ん中辺りに出ている枠で囲まれている部分が、キャプチャ&表示している部分です。 技術的な部分 上記のデモサイトのオプション 上記のデモサイトで、とりあえず共有を試しましたが、いくつかのオプションを選ぶこともできるようになっています。 アスペクト比・フレームレート・解像度・カーソル表示の有無などといった、いくつかの内容を指定できるようです。 この後のお試しでは、カーソル関連のオプションのみ利用してみます。 技術情報が掲載されたページを見てみる 次に、技術情報が掲載されたページをいくつか見てみようと思います。 以下は、W3C・MDN のサイトで「MediaDevices.getDisplayMedia() / Screen Capture API」が説明されているところです。 Screen Capture MediaDevices.getDisplayMedia() - Web APIs | MDN Using the Screen Capture API - Web APIs | MDN ソースコード(元にするもの) 上記 3つのリンクのうち、3つ目の中を見ていきます。 その中で「Examples ⇒ Simple screen capture」という部分があるので、それを活用して簡単な画面共有を試します。 いくつか、お試しのもとにする部分をピックアップします。 const videoElem = document.getElementById("video"); const logElem = document.getElementById("log"); const startElem = document.getElementById("start"); const stopElem = document.getElementById("stop"); // Options for getDisplayMedia() var displayMediaOptions = { video: { cursor: "always" }, audio: false }; // Set event listeners for the start and stop buttons startElem.addEventListener("click", function(evt) { startCapture(); }, false); stopElem.addEventListener("click", function(evt) { stopCapture(); }, false); async function startCapture() { logElem.innerHTML = ""; try { videoElem.srcObject = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions); dumpOptionsInfo(); } catch(err) { console.error("Error: " + err); } } function stopCapture(evt) { let tracks = videoElem.srcObject.getTracks(); tracks.forEach(track => track.stop()); videoElem.srcObject = null; } <p>This example shows you the contents of the selected part of your display. Click the Start Capture button to begin.</p> <p><button id="start">Start Capture</button>&nbsp;<button id="stop">Stop Capture</button></p> <video id="video" autoplay></video> <br> <strong>Log:</strong> <br> <pre id="log"></pre> ソースコード(お試し用) 上記のログ出力まわりは削りつつ、最小限の部分を残して作ってみます。 具体的には、以下のようになりました。 <html> <head> <meta charset="UTF-8"> <title>test</title> </head> <body> <p>This example shows you the contents of the selected part of your display. Click the Start Capture button to begin.</p> <p><button id="start">Start Capture</button>&nbsp;<button id="stop">Stop Capture</button></p> <video id="video" autoplay></video> <br> </body> <script> const videoElem = document.getElementById("video"); const startElem = document.getElementById("start"); const stopElem = document.getElementById("stop"); // Options for getDisplayMedia() var displayMediaOptions = { video: { cursor: "always" }, audio: false }; // Set event listeners for the start and stop buttons startElem.addEventListener("click", function (evt) { startCapture(); }, false); stopElem.addEventListener("click", function (evt) { stopCapture(); }, false); async function startCapture() { try { videoElem.srcObject = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions); } catch (err) { console.error("Error: " + err); } } function stopCapture(evt) { let tracks = videoElem.srcObject.getTracks(); tracks.forEach(track => track.stop()); videoElem.srcObject = null; } </script> </html> お試し版を実行してみる 上記のお試し用のプログラムを実行してみます。 以下のツイートの添付画像のとおり、他ウィンドウの内容を取得して表示することができました。 ブラウザでの画面共有でも使われる「MediaDevices.getDisplayMedia() / Screen Capture API」を使ったお試しプログラムを、自前でテスト実装して、動作確認している時の様子。別タブの Qiita の記事(このお試しについて書いている記事)を、画面内に取り込んで表示できています。 pic.twitter.com/tCHYPE25in— you (@youtoy) November 28, 2021 キャプチャ元の画面が、でかでかと表示されています。 このあたりは、うまく API のオプションや、表示側での工夫を入れることで、良い感じのサイズにする必要がありそうです。 【追記1】 良い感じの表示にする 上記で、「キャプチャしたウィンドウ・タブの表示が、表示側で大きく出すぎていたりする問題」は、 object-fit: contain; を使うことで、良い感じの表示にできました。 「object-fit: contain;」を使えば、良い感じになるのか! pic.twitter.com/vcJSg5R7Yn— you (@youtoy) November 28, 2021 上記ツイートの内容は、元のお試し用のビデオタグの部分を以下のように変更したものです。 なお HTMLタグ内に追記をしていますが、CSS で指定しても大丈夫です。 変更前 <video id="video" autoplay></video> 変更後 <video id="video" autoplay width="640" height="480" style="object-fit: contain;"></video> この対応は、「追記2」として書いた MDN のサンプルで CSS 内容をチェックしたあとに、以下のページで object-fit: contain; まわりの内容を確認しました。 ●【CSS】object-fitはCSSだけで画像をコンテナーにフィットさせてトリミングもできるとっても素晴らしいプロパティー | WEBDESIGNDAY  https://webdesignday.jp/inspiration/technique/css/7976/ 【追記2】 MDN のサンプル MDN のページ内で、iframe を使って表示されていたデモページがあったのですが、以下の URL で直接アクセスできるようでした。 ●Using the Screen Capture API - Simple_screen_capture - code sample  https://yari-demos.prod.mdn.mozit.cloud/en-US/docs/Web/API/Screen_Capture_API/Using_Screen_Capture/_sample_.Simple_screen_capture.html 【追記3】 p5.js との組み合わせ p5.js を使ったプログラムで、今回の話を活用して、パワポのプレゼン画面を p5.js のキャンバスに取り込むということをやってみました。 #p5js と「MediaDevices.getDisplayMedia() / Screen Capture API」を使って、パワポのウィンドウ(発表者モード)でプレゼンしている画面を、p5.js のキャンバスに取り込んでみた!パワポ部分は、PCの画面でも別アプリのウィンドウでもOKで、活用法を何か考えてみたいな。 pic.twitter.com/fh1XXU5ikU— you (@youtoy) November 28, 2021
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PostgreSQLのpgcrypto(encrypt関数)で暗号化したデータをBigQueryで復号化する

PostgreSQLの機能にpgcryptという暗号関数を提供するライブラリがあります。pgcryptで暗号化したデータをBigQueryにそのまま連携し複合化をしたいということがあったのでその方法を紹介します。 概要と結論 今回pgcryptではencryptという関数にてAESを利用してデータの暗号化を行いました、以下がそのデータを復号化するBigQueryのクエリとなります。 CREATE TEMPORARY FUNCTION decrypt(_text STRING) RETURNS STRING LANGUAGE js AS """ if (_text === null) { return _text} let bas64_text = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Hex.parse(_text)) let key = CryptoJS.enc.Utf8.parse('PASSWORD\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000') let iv = CryptoJS.enc.Utf8.parse('') let options = { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }; let _decrypt = CryptoJS.AES.decrypt(bas64_text, key, options); return _decrypt.toString(CryptoJS.enc.Utf8); """ OPTIONS (library="gs://xxxxxxxxx/crypto-js-4.1.1/crypto-js.js"); SELECT decrypt(substring('\\xba9a0a4189f691d761c431c89f42e319', 3)); 今回使うPostgreSQLの暗号化関数 encrypt関数 今回例として使う pgcrypt の暗号化関数として encrypt 関数を例として扱います。pgcryptには、PGP暗号化の導入もあり、暗号化のみの関数はあまり使用されませんが、今回は引っかかりポイントが多かったのと単純化のためこの関数を利用します。 暗号化方式 encrypt 関数の利用時にオプションとしてアルゴリズムやモード等を選択できます。今回はアルゴリズムをAES、その他すべてデフォルト値を利用します。また、パスフレーズはPASSWORDとします。まとめると以下のようになります。 アルゴリズム: AES(Rijndael-128) モード: CBC パディング: pkcs パスフレーズ: PASSWORD 以下がencryptを利用した例となります sselect encrypt('text', 'PASSWORD', 'aes'); 結果 BA 9A 0A 41 89 F6 91 D7 61 C4 31 C8 9F 42 E3 19 BigQueryでの復号化 UDFの利用 BigQueryでの復号化にはUDFを利用します。UDFはJavascriptで自前の関数を定義しBigQueryで利用できるようにする機能です。 crypto-js 復号化のために、UDFの中で crypto-js というライブラリを利用しました。 crypto-js はJavascriptの暗号化ライブラリです。 これを事前にGCSの特定のバケットに登録を行なっておきます。 復号化 改めて以下が、復号化するBigQueryのクエリです。 CREATE TEMPORARY FUNCTION decrypt(_text STRING) RETURNS STRING LANGUAGE js AS """ if (_text === null) { return _text} let bas64_text = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Hex.parse(_text)) let key = CryptoJS.enc.Utf8.parse('PASSWORD\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000') let iv = CryptoJS.enc.Utf8.parse('') let options = { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }; let _decrypt = CryptoJS.AES.decrypt(bas64_text, key, options); return _decrypt.toString(CryptoJS.enc.Utf8); """ OPTIONS (library="gs://xxxxxxxxx/crypto-js-4.1.1/crypto-js.js"); SELECT decrypt(substring('\\xba9a0a4189f691d761c431c89f42e319', 3)); 結果は以下のようになります。 text 無事復号化に成功しました。 解説 前提 まず前提として、PostgreSQLのデータをBigQueryに連携したときにString型に変換しています。Stringに変換すると先程の例だと \xba9a0a4189f6 91d761c431c89f42e319 といった形になりますこれは、暗号化データの16進数文字列になります。 16進数文字列の \x を削除 substring('\\xba9a0a4189f691d761c431c89f42e319', 3) まず最初に、16進数文字列に邪魔な \x が入っているので substring 関数で削除します。 16進数文字列をbase64変換 let bas64_text = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Hex.parse(_text)) 次に複合化関数に渡すデータがbase64になっている必要があるため、16進数文字列をbyte変換後にbase64を行います。 keyを16byteにする let key = CryptoJS.enc.Utf8.parse('PASSWORD\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000') 続いて、keyを定義します。このときPostgreSQL側でパスフレーズとして、 PASSWORD を使いました。ただし、AES(Rijndael-128)では、keyとして16byteが求められます。実はpgcryptoのencrypt関数では、バイト数が足りない場合パスフレーズの後続に0をパディングしたものがkeyをして利用されていました。そのため \u0000 で残りの8byteを埋めています。 ivを0にする let iv = CryptoJS.enc.Utf8.parse('') AESのCBCモードを利用する場合iv(初期化ベクトル)が必要になりますが、PostgreSQLでは指定していませんでした。この場合、PostgreSQLではドキュメントにある通り0で埋められます。 正確にブロック長でない場合、切り詰められるか、もしくはゼロで埋められます このあたりも、pgcryptoのencrypt関数を直接利用しないほうがいい要因になるかと考えています。 オプションを指定して復号化 let options = { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }; let _decrypt = CryptoJS.AES.decrypt(bas64_text, key, options); return _decrypt.toString(CryptoJS.enc.Utf8); 最後に CryptoJS.AES.decrypt 関数を利用してdecryptを行います。このとき、PostgreSQLで利用した、アルゴリズムやモードなどを指定します。最後にそれを文字列にキャストしたら完了です。 まとめ 本記事では、PostgreSQLのpgcrypto(encrypt関数)で暗号化したデータをBigQueryで復号化する方法を紹介しました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【javascript】Promise

Promis 非同期処理をより簡単に、可読性が上がるように書けるようにしたもの。 ES6から導入された。 構文 thenなど連続して扱える。非同期チェーンがしやすい。 resolve, rejectいずれかを呼ぶ new Promise(function(resolve, reject){ //同期処理 resolve('args') // thenの処理へ reject() // catchの処理へ console.log('Promise') }).then(function(args){ //非同期処理 //resolveから引数を受け取れる。 console.log('then1' + args) // >>> then1 args return args //return 引数とすると次のthenに引数を渡すことができる。 }) .then(function(args){ console.log('then2 +' + args) console.log('then2') throw new Error(); //catchの処理へ移行できる。 }) .then(function(){ console.log('then3') }) .catch(function(){ //非同期処理 console.log('catch') }) .finally(function(args){ //非同期処理, resolve,reject共に終了処理される。 //引数は受け取れない console.log('finally' + args) // >>> finallyundefined }) Promiseチェーン 引数valを受け取り、Promiseのインスタンス化したsleep関数実行 Promiseを実行 rejectがない場合は省略可能。 1秒後に引数valを受け取ったresolveを実行 引数valをインクリメントしコンソールに出力 thenのreturnにさらにsleepを実行することで連続してsleep関数を実行することができる。 function sleep(val) { return new Promise(function(resolve){ setTimeout(function(){ console.log(val++); resolve(val); }, 1000); }); } sleep(0).then((val) =>{ return sleep(val)}) .then((val) =>{ return sleep(val)}) .then((val) =>{ return sleep(val)}) .then((val) =>{ return sleep(val)}) .then((val) =>{ return sleep(val)}) >>> 0 >>> 1 >>> 2 >>> 3 >>> 4
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[React,react-hooks,closure]そのstate更新がうまくいかないのは、stale-closureのせいかも?

はじめに Reactで非同期処理を用いてStateを更新する際、処理の書き方に気をつけないとクロージャの性質によって最新の値を基にStateが更新されないことがある。 useEffectで値のSubscribeをしてるときなどは、特に注意が必要である。(筆者はこれでハマったため) 本稿は、stale-closure(古くなったクロージャ)が引き起こすstate更新での問題とは何か?とその解決策を解説する。 stale-closureによってどんな問題が起きるか 例えば、以下のようにsetIntervalを用いて1秒ごとにカウントアップを行うプログラムがあったとする。 CodeSandboxはこちら import { useEffect, useState } from "react"; export default function App() { const [count, setCount] = useState(0); const countUp = () => { return setInterval(() => { setCount(count + 1); }, 1000); }; useEffect(() => { countUp(); }, []); return <div>{count}</div>; } このプログラムは1ずつカウントアップしていくことが期待されるが実際にはそうはならず、一度1に上がった後それ以上カウントアップされない。 期待される挙動: 0から順に1ずつカウントアップされる 実際の挙動:カウントは1以上にならない (ここでは非同期処理の例としてsetIntervalを使っているが、更新用の関数の作成タイミングと実行タイミングがずれていることが重要で、頭の中で何らかの非同期処理と置き換えてもらうとご自身のユースケースと重ねやすいかもしれない.) countステートはAppコンポーネントに定義されているし、countUp関数はそのステートを間違いなく参照しているように見えるのになぜこうなるのか?? 原因 この問題を引き起こしているのはstale-closure、つまり古くなった(鮮度の落ちた)クロージャである。 クロージャとは ここで一度Javascriptのクロージャについて軽くまとめておく。 クロージャは、組み合わされた(囲まれた)関数と、その周囲の状態(レキシカル環境)への参照の組み合わせです。言い換えれば、クロージャは内側の関数から外側の関数スコープへのアクセスを提供します。JavaScript では、関数が作成されるたびにクロージャが作成されます。 出典:https://developer.mozilla.org/ja/docs/Web/JavaScript/Closures これだけだとよくわからないが、以下の説明でしっくりきた。 クロージャは関数とその関数が作られた環境という 2 つのものの組み合わせです。この環境は、クロージャが作られた時点でスコープ内部にあったあらゆる変数によって構成されています。 出典:同上 つまり、クロージャは関数を作成した時点での変数の値と関数を保持しているものなのである。 今回のコードでの問題点 const countUp = () => { return setInterval(() => { setCount(count + 1); }, 1000); }; 上の部分で、countUp関数を作成する際にsetInterval関数の中にあるcountはクロージャに捕らえられてしまい、この時点でのcountの値(=0)を保持し続けることになってしまっていたのである。そのため、setIntervalで何度setCountが実行されようと 0+1を延々と繰り返し、カウントアップできなかったのである。 詳細な流れ 1.初期描画(countUp作成) 2.useEffect実行(countUp呼び出され、setIntervalが実行される) 3.+1秒後 0(初期値) + 1 = 1 -> setState 4.再描画 ここでstale-closure発生!! dependenciesを[]にしているため、再描画されてもuseEffectは実行されない。 そのため再描画で新しく作られたcountUpではなく、初期描画時のcountUpが使われる. そしてこのcountUpはクロージャによって古い値を参照したままになっている) 5.+1秒後 0(初期値) + 1 = 1 -> setState stateの値が変わらないため、以後再描画はされない 6.+1秒後 0(初期値) + 1 = 1 -> setState ... n.+1秒後 0(初期値) + 1 = 1 -> setState useState単体使用の例 上記ではuseEffectを例として出したがuseState単体を用いても非同期処理が絡めば、stale-closureが悪さをすることはある。 CodeSandboxはこちら。 import { useState } from "react"; export default function App() { const [count, setCount] = useState(0); const dummmy = () => { setTimeout(() => { setCount(count + 1); }, 1000); }; return ( <> <div>{count}</div> <button onClick={dummmy}>Button</button> </> ); } 上記のコードで素早くボタンを複数回押すと、カウントアップは1ずつしかされないことが確認できるはずである。 素早くとしているのは、もし連打ではなくカウントアップ(state更新)を待ってからボタンを押した場合は、state更新に伴う再描画により関数も再作成されて、新しいクロージャが作成されstale-closureが発生しないためである。 対策 さて、ここまででstale-closureがstate更新でどのように悪さをするかは述べた。 では、どのようにすればこのstale-closureを回避できるのか。 ここでは2つの方法を紹介する。(他にもあるとは思うのでコメントお待ちしてます) ※useEffectにdependenciesを設定する方法もあるかとは思うが、setIntervalの例では、setIntervalを実行するたびにタイマー処理が追加されていき,countの増加が不自然になったので今回は例としてわかりづらいので割愛) useState(prevState=> {}) を利用する クロージャに捕らえられていた古い値を参照していたのが今回の問題なのでuseState関数の機能を使って常に最新のstateを参照して処理を行うようにする。 ※useStateはuseState(prevState=> {})のように記述することで最新のstateを参照して任意の処理を行うことができる CodeSandboxはこちら。 import { useEffect, useState } from "react"; export default function App() { const [count, setCount] = useState(0); const countUp = () => { return setInterval(() => { console.log(count); setCount((prevState) => prevState + 1); }, 1000); }; useEffect(() => { countUp(); }, []); return <div>{count}</div>; } useReducerを使う useReducerを使い、更新処理関数の中ではdispatchを行うだけにする。 これにより、closureが新鮮かどうかを気にせず常にreducerで最新のstateを基に処理を行うことができる CodeSandboxはこちら。 import { useEffect, useReducer, useState } from "react"; const reducer = (state, action) => { switch (action.type) { case "COUNT_UP": return state + 1; default: return state; } }; export default function App() { const [count, dispatch] = useReducer(reducer, 0); const countUp = () => { return setInterval(() => { dispatch({ type: "COUNT_UP" }); }, 1000); }; useEffect(() => { countUp(); }, []); return <div>{count}</div>; } 実践的なユースケース 随時更新 ここまでで、stale-closureに気をつけてstate更新をする手法をsetIntervalやsetTimeoutを用いて説明してきた。以下ではもう少し具体的にこの知見が活かせるケースを紹介する。 というよりも、実は筆者が以下の処理を技術書で学習しているときに「useStateだとclosureが悪さするからuseReducerにしているよ」と言った記述があり、何を言っているんだ?となったのがこの記事執筆のきっかけである。 subscription処理 以下にAmplifyでデータの追加を検知してstate更新を行うアプリケーションの一部を抜粋して掲載する. Amplifyを具体的に出しているが、Amplifyに限らず購読処理を行う場合であれば本稿の対策が同様に当てはめられるはずである。 import {OnCreatePostSubscription, Post} from "./API"; import {API, graphqlOperation, Storage} from "aws-amplify"; import {useEffect, useReducer, useState} from "react"; import {listPosts} from "./graphql/queries"; import {isDefined} from "./utils"; import {GraphQLResult} from '@aws-amplify/api-graphql' import {onCreatePost} from "./graphql/subscriptions"; interface SetPosts { type: 'SET_POSTS', posts: ClientPost[] } interface AddPost { type: 'ADD_POST', post: ClientPost } type Action = SetPosts | AddPost type ClientPost = { id: string; imageKey: string; title: string; imageUrl: string; } const reducer = (state: ClientPost[], action: Action) => { switch (action.type) { case 'SET_POSTS': return action.posts; case "ADD_POST": return [action.post, ...state] default: return state; } } const getSignedPosts = async (posts: Post[]): Promise<ClientPost[]> => { const signedPosts = await Promise.all( posts.map(async (item) => { if (!item.imageKey) return; const signedUrl = await Storage.get(item.imageKey) const clientPost: ClientPost = { id: item.id, imageKey: item.imageKey, title: item.title, imageUrl: signedUrl }; return clientPost }) ) return signedPosts.filter(isDefined); } type PostSubscriptionEvent = { value: { data: OnCreatePostSubscription } }; const Posts = () => { const [posts, dispatch] = useReducer(reducer, []) const update = async ({value: {data}}: PostSubscriptionEvent) => { const newPost = data.onCreatePost; if (!newPost || !newPost.imageKey) return; const signedUrl = await Storage.get(newPost.imageKey) const newClientPost: ClientPost = { id: newPost.id, imageKey: newPost.imageKey, title: newPost.title, imageUrl: signedUrl } dispatch({type: 'ADD_POST', post: newClientPost}) } useEffect(() => { fetchPosts(); const client = API.graphql( graphqlOperation(onCreatePost) ) if ("subscribe" in client) { const subscription = client.subscribe({ next:update }) return () => subscription.unsubscribe(); } }, []) const fetchPosts = async () => { const postData = await API.graphql(graphqlOperation(listPosts)) as GraphQLResult<{listPosts:{items:Post[]}}> const items = postData.data?.listPosts.items; if (!items) return; const signedPosts = await getSignedPosts(items) dispatch({type: 'SET_POSTS', posts: signedPosts}) updatePost(signedPosts) } return ( <div> <h2 style={heading}>Posts</h2> {posts.map(post => ( <div key={post.id} style={postContainer}> <img style={postImage} src={post.imageUrl} alt=""/> <h3 style={postTitle}>{post.title}</h3> </div> ))} </div> ) } const postContainer = { padding: '20px 0px 0px', borderBottom: '1px solid #ddd' } const heading = {margin: '20px 0px'}; const postImage = {width: 400}; const postTitle = {marginTop: 4} export default Posts; 注目して欲しいのはstate更新処理とuseEffectの部分である。 state更新処理(正確にはdispatchしてるだけ) const update = async ({value: {data}}: PostSubscriptionEvent) => { const newPost = data.onCreatePost; if (!newPost || !newPost.imageKey) return; const signedUrl = await Storage.get(newPost.imageKey) const newClientPost: ClientPost = { id: newPost.id, imageKey: newPost.imageKey, title: newPost.title, imageUrl: signedUrl } dispatch({type: 'ADD_POST', post: newClientPost}) } useEffectでSubscriptionの設定をしている部分 useEffect(() => { fetchPosts(); const client = API.graphql( graphqlOperation(onCreatePost) ) if ("subscribe" in client) { const subscription = client.subscribe({ next:update }) return () => subscription.unsubscribe(); } }, []) 上記はデータが追加された際にstateを更新するというSubscription処理だが、これは今回の解説で示してきたコードと同じくuseEffectの中でstateを更新する関数を非同期で実行するケースである。 したがって、subscribeで取得した値(state)の管理はuseStateではなく、useReducerを用いており、update関数ではdispatchするに留めて、常にreducerの中で最新のstateをもとに更新処理が行われている。 もし、これをuseStateで行った場合は、subscribeした値を初期値([])に加えるだけになり、stateは[subscribeした値]となる。 本来得たい(初期フェッチで得た値 + subscribeした値)の配列にはならない。 また、対策セクションで述べたように以下のようにuseState(prevState=>{})を利用しても更新可能である。 const newClientPost: ClientPost = { id: newPost.id, imageKey: newPost.imageKey, title: newPost.title, imageUrl: signedUrl } updatePost(prevState => [...prevState,newClientPost]) おわりに 本稿が皆さんの「なんでうまくstate更新されないんだ!!」問題の一助になれば 参考 Full Stack Serverless : Modern Application Development with React, AWS, and GraphQL Be Aware of Stale Closures when Using React Hooks 7 things you may not know about useState クロージャ (MDN)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MediaDevices.getUserMedia() で PTZ(パン・チルト・ズーム)用の API があるのを知って試してみる

MediaDevices.getUserMedia() で PTZ(パン・チルト・ズーム)ができる仕組みを見かけて、とりあえず試してみたという話です。 ●Control camera pan, tilt, and zoom  https://web.dev/camera-pan-tilt-zoom/ デモページ 以下がデモページのようです。 ●ptz.glitch.me  https://ptz.glitch.me/ MediaDevices.getUserMedia() で、画像のPTZ(パン・チルト・ズーム)を実現する API の件、まずはデモページでお試し。●https://t.co/MPsfQsPQ4W https://t.co/cpFEgtlyWs https://t.co/cgG9SIGFVc pic.twitter.com/u2aArID80T— you (@youtoy) November 28, 2021 試している時の様子 こんな感じで試せました。 MediaDevices.getUserMedia() で、画像のPTZ(パン・チルト・ズーム)を実現する仕組み、そのデモページ( https://t.co/HT8tAewsXd )を試している様子を動画にしてみた。 pic.twitter.com/COwt3IhDNU— you (@youtoy) November 28, 2021 自分の Webカメラは、カメラの土台が動くようなものではなく、固定の映像が取得できるだけのものです。 その中で実現しているというのを考えてみても、そして実際に使ってみた感じの挙動として、「カメラ全体の映像の一部を拡大している状況の場合 = カメラで取得できている画像の一部のみを表示している状態の時、その画面の一部の表示する範囲を移動させられる」、というような挙動になっているのかなと思います。 技術的な部分を見ていく プログラムの書き方 さらに技術的な部分を見ていきます。 ●mediacapture-image/ptz-explainer.md at main · w3c/mediacapture-image  https://github.com/w3c/mediacapture-image/blob/main/ptz-explainer.md 以下のようなサンプルがのっていました。 // User is prompted to grant both camera and PTZ access in a single call. // If the camera does not support PTZ or user denies PTZ permission, it falls // back to a regular camera prompt. const videoStream = await navigator.mediaDevices.getUserMedia({ // [NEW] Website asks to control camera PTZ as well. video: { pan: true, tilt: true, zoom: true }, }); // Show camera video stream to user. const video = document.querySelector("video"); video.srcObject = videoStream; // Get video track capabilities and settings. const [videoTrack] = videoStream.getVideoTracks(); const capabilities = videoTrack.getCapabilities(); const settings = videoTrack.getSettings(); // [NEW] Let the user control the camera pan motion if the camera supports it // and PTZ access is granted. if ("pan" in settings) { const input = document.querySelector("input[type=range]"); input.min = capabilities.pan.min; input.max = capabilities.pan.max; input.step = capabilities.pan.step; input.value = settings.pan; input.oninput = async (event) => { await videoTrack.applyConstraints({ advanced: [{ pan: input.value }], }); }; } // [NEW] Let the user control the camera tilt motion if the camera supports it // and PTZ access is granted. if ("tilt" in settings) { // similar to the pan motion above. } ポップアップによる許可の操作 「User is prompted to grant both camera and PTZ access in a single call.」と書かれた部分を見て、あらためてカメラ利用許可のポップアップを気にして見てみました。 カメラの許可のポップアップは 1つ、そしてカメラ利用の許可と「PTZ(パン・チルト・ズーム)の許可を同時に」という意味は、以下の画像のような形で確認できました(許可する対象の記載の後ろに、「移動」という記載も含む形になりました)。 MediaDevices.getUserMedia() で、画像のPTZ(パン・チルト・ズーム)を実現する仕組みの件、カメラ利用の許可のポップアップで「移動」の許可も求めるような記載になるか! pic.twitter.com/OqrJdnS1OT— you (@youtoy) November 28, 2021 単純にカメラ利用のみを求める場合は、以下の表示になり、「移動」に関する話は出てきません。 PTZ(パン・チルト・ズーム)を求めない場合の、カメラ利用のみ許可を求めるポップアップ。 pic.twitter.com/cHOOeQnTGF— you (@youtoy) November 28, 2021 値の変更 それと、PTZ(パン・チルト・ズーム)を適用する際の処理は、以下の部分が該当しているようです。 パーミッションの変更 パーミッションまわりの処理は、途中で変更したり、その変更を検出したりというのができそうな記載がありました。 おわりに 概要はつかめたかな、と思えたので、あとは何か使い道を考えつつ試せればと思っています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MediaDevices.getUserMedia() で PTZ(パン・チルト・ズーム)用の仕組みがあるのを知って試してみる

MediaDevices.getUserMedia() で PTZ(パン・チルト・ズーム)ができる仕組みを見かけて、とりあえず試してみたという話です。 ●Control camera pan, tilt, and zoom  https://web.dev/camera-pan-tilt-zoom/ デモページ 以下がデモページのようです。 ●ptz.glitch.me  https://ptz.glitch.me/ MediaDevices.getUserMedia() で、画像のPTZ(パン・チルト・ズーム)を実現する API の件、まずはデモページでお試し。●https://t.co/MPsfQsPQ4W https://t.co/cpFEgtlyWs https://t.co/cgG9SIGFVc pic.twitter.com/u2aArID80T— you (@youtoy) November 28, 2021 試している時の様子 こんな感じで試せました。 MediaDevices.getUserMedia() で、画像のPTZ(パン・チルト・ズーム)を実現する仕組み、そのデモページ( https://t.co/HT8tAewsXd )を試している様子を動画にしてみた。 pic.twitter.com/COwt3IhDNU— you (@youtoy) November 28, 2021 自分の Webカメラは、カメラの土台が動くようなものではなく、固定の映像が取得できるだけのものです。 その中で実現しているというのを考えてみても、そして実際に使ってみた感じの挙動として、「カメラ全体の映像の一部を拡大している状況の場合 = カメラで取得できている画像の一部のみを表示している状態の時、その画面の一部の表示する範囲を移動させられる」、というような挙動になっているのかなと思います。 技術的な部分を見ていく プログラムの書き方 さらに技術的な部分を見ていきます。 ●mediacapture-image/ptz-explainer.md at main · w3c/mediacapture-image  https://github.com/w3c/mediacapture-image/blob/main/ptz-explainer.md 以下のようなサンプルがのっていました。 // User is prompted to grant both camera and PTZ access in a single call. // If the camera does not support PTZ or user denies PTZ permission, it falls // back to a regular camera prompt. const videoStream = await navigator.mediaDevices.getUserMedia({ // [NEW] Website asks to control camera PTZ as well. video: { pan: true, tilt: true, zoom: true }, }); // Show camera video stream to user. const video = document.querySelector("video"); video.srcObject = videoStream; // Get video track capabilities and settings. const [videoTrack] = videoStream.getVideoTracks(); const capabilities = videoTrack.getCapabilities(); const settings = videoTrack.getSettings(); // [NEW] Let the user control the camera pan motion if the camera supports it // and PTZ access is granted. if ("pan" in settings) { const input = document.querySelector("input[type=range]"); input.min = capabilities.pan.min; input.max = capabilities.pan.max; input.step = capabilities.pan.step; input.value = settings.pan; input.oninput = async (event) => { await videoTrack.applyConstraints({ advanced: [{ pan: input.value }], }); }; } // [NEW] Let the user control the camera tilt motion if the camera supports it // and PTZ access is granted. if ("tilt" in settings) { // similar to the pan motion above. } ポップアップによる許可の操作 「User is prompted to grant both camera and PTZ access in a single call.」と書かれた部分を見て、あらためてカメラ利用許可のポップアップを気にして見てみました。 カメラの許可のポップアップは 1つ、そしてカメラ利用の許可と「PTZ(パン・チルト・ズーム)の許可を同時に」という意味は、以下の画像のような形で確認できました(許可する対象の記載の後ろに、「移動」という記載も含む形になりました)。 MediaDevices.getUserMedia() で、画像のPTZ(パン・チルト・ズーム)を実現する仕組みの件、カメラ利用の許可のポップアップで「移動」の許可も求めるような記載になるか! pic.twitter.com/OqrJdnS1OT— you (@youtoy) November 28, 2021 単純にカメラ利用のみを求める場合は、以下の表示になり、「移動」に関する話は出てきません。 PTZ(パン・チルト・ズーム)を求めない場合の、カメラ利用のみ許可を求めるポップアップ。 pic.twitter.com/cHOOeQnTGF— you (@youtoy) November 28, 2021 値の変更 それと、PTZ(パン・チルト・ズーム)を適用する際の処理は、以下の部分が該当しているようです。 パーミッションの変更 パーミッションまわりの処理は、途中で変更したり、その変更を検出したりというのができそうな記載がありました。 おわりに 概要はつかめたかな、と思えたので、あとは何か使い道を考えつつ試せればと思っています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Next.js/React】好きな本10選を作るWebアプリ作りました

ブックリストを作成し、オリジナル画像をSNSでシェアすることが出来るアプリを作りました。 URL: https://www.tennovels.com/lists/SANyAYlpvw0Y2KFM3RIX Twitter: @10novels 紹介動画:https://youtu.be/rAQD_ACqfjs 作ったもの アプリ概要 好きな本10選を作成し、オリジナル画像をSNSでシェアするアプリを作りました。ログイン、新規登録なしで使うことができます。 ローズウォーターさん、あなたに神のお恵みを/カート・ヴォネガットソラリス/スタニスワフ・レム1Q84(BOOK1(4月ー6月))/村上春樹銀の匙/中勘助細雪(上巻)改版/谷崎潤一郎沈黙/遠藤周作...#趣味は読書です@10novels より https://t.co/QltNFJwZWx— 趣味は読書です (@10novels) November 23, 2021 上のツイートのようなブックリストを作成しTwitterで画像をシェアすることができます。 タイトルと著者名は自動で記載されます。 文字数オーバーの場合は…と記載されます。 技術スタック こちらがインフラ構成です。 Front-end フロントエンドは Next/React。 Back-end バックエンドはFirebaseを使用しています。デプロイはVercel。 Firestore: データ管理 Functions: 設定したトリガーでバックエンド処理を自動的に実行 Storage: 画像を保存 無料プランではなく、従量制の Blaze プランを使用。 使用技術 Next/React Firebase (Firestore, Functions, Storage) Material UI Vercel Algolia Rakuten Total Books API
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

p5.jsでカラーパレット集"Chromotome"を使う

サマリとモチベーション? p5.jsでKjetil Golidさん(Twitter: @kGolid ,Web: Generated Space)が個人的に作っていらっしゃるカラーパレット集Chrometomeを使うためのノウハウです。元がNode.js用なので非Node環境での使い方がググっても出てこず、10分くらいハマってしまったので、簡単な内容ですが、備忘録的にメモを残します。 使い方 1. import方法 ローカル環境やp5.js Web Editorの場合 index.htmlにて、スケッチを読み込む前に、以下を挿入します。 <script src="https://unpkg.com/chromotome@1.19.1/dist/index.umd.js"></script> OpenProcessingの場合 画面右側のLibrariesのパネル上で+Add Custom Libraryを押して、 https://unpkg.com/chromotome@1.19.1/dist/index.umd.js を入力してください。こんな感じで表示され、importされるようになります。 2. ソース上での実装方法 ほとんどReadmeからのコピペですが、、、 // globalなインスタンス`chromotome`が定義されています。 const tome = chromotome; // .get()すると、ランダムにパレットを取得できます。.getRandom()でもOK。 let palette = tome.get(); // palette = tome.getRandom(); // also works. // パレットは名前指定でもOK! // パレット名称は `https://kgolid.github.io/chromotome-site/` を参照するか // tome.getNames(); // するとパレットの名前一覧を取得できます。 palette = tome.get('miradors'); // パレットobjectには、通常 `stroke`と`background`、そして実際のパレットである`colors`が含まれています。 console.log(palette.colors); // --> ['#ff6936', '#fddc3f', '#0075ca', '#00bb70'] console.log(palette.stroke); // --> '#ffffff' console.log(palette.background); // --> '#020202' 3. 実装例 Basic Example #Chrometome (from @kGolid) sample01#creativecoding #generativeart #p5js https://t.co/3FSnElBw0Y pic.twitter.com/PRDHYajDaz— Tetsunori NAKAYAMA | 中山 哲法 (@tetunori_lego) November 28, 2021 https://openprocessing.org/sketch/1370077 Advanced Example #Chrometome (from @kGolid) sample02#creativecoding #generativeart #p5js https://t.co/LlwEYdRjN5 pic.twitter.com/ngb7QMK6Nh— Tetsunori NAKAYAMA | 中山 哲法 (@tetunori_lego) November 28, 2021 https://openprocessing.org/sketch/1370086 メモ Chometome カラーパレット閲覧用サイト Chometome GitHubリポジトリ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript(ES6〜)の基本文法

この記事について 実務歴半年ほどのバックエンドエンジニアです。 Reactの学習を進めている中で、現在のJavaScriptの基本的な理解が足りていないと感じたので、振り返りも兼ねて基本的な文法をまとめていきます。 文法に関してはこちら「https://developer.mozilla.org/ja/docs/Web/JavaScript」にもしっかり記載されていますが、まずはこれからJavaScriptに触れていくといった方に向けた内容となります。 JavaScriptとは? ブラウザ上で動作するプログラミング言語。 もともと、発表当初は「LiveScript」という名称だったが、当時非常に人気のあった言語として「Java」がありました。その人気にあやかって「JavaScript」と名称を改めたとされています。 言語としては全く別の独立したものであり、現在は「ECMAScript」という言語仕様を持ち、2015年以降は1年ごとに言語仕様がアップデートされています。 ES6以前・以降 ES6とは、2015年に発表された「ECMAScriptのバージョン」のことで、このES6が発表されたことによってこれまで世間で言われていたJavaScriptの評判が一気に変化したとされています。 このES6で新たに発表された言語仕様として、 クラス、モジュール、イテレータ、for/ofループ、Pythonスタイルのジェネレータ、アロー関数、2進数および8進数の整数リテラル、Map、Set、WeakMap、WeakSet、プロキシ、テンプレート文字列、let、const、型付き配列、デフォルト引数、Symbol、Promise、分割代入、可変長引数 以上があります。要するに、機能がモリモリ追加されて、JavaScriptが超進化したみたいです。 こうして、モダンなフロントエンドを実装するためにReact.js、Vue.js、Angularなどどいったライブラリの開発も盛り上がり、どんどん発展していきました。 「モダンなJavaScript」というと、ES6以降というニュアンスになるかと思います。 この記事では、ES6以降に追加された言語仕様を中心に記述していきます。 変数宣言 var 従来(ES6以前)の宣言方法。 上書き可能で、再宣言も可能なため、プロジェクトが大きくなってくると意図しない動きをしてしまうことがあった。 そのため現在は主に、後述する2つの変数宣言が使用されています。 let 上書きが可能な変数宣言です。 varと違い、再宣言は不可。 letで宣言された変数はブロックスコープを持ち、スコープ内でしか書き換えやアクセスができなくなります。 if (true){ let foo = "bar"; } console.log(foo); // スコープの外で呼び出しているため、エラーになる const 上書き不可の変数宣言。厳密には変数ではなく定数。 現在、ほとんどの開発現場ではconstが使用されています。 オブジェクトを定義した場合は書き換え可能 const var4 = { name: "aaa", age: 29, }; var4.name = "変更"; // var4.nameの中身は"変更" になる。 分割代入 まずはオブジェクトの定義から const myPlrofile = { name: "keita", age: 29, }; 通常のオブジェクトの展開をします。 オブジェクトのプロパティにアクセスするには、[オブジェクト名].[プロパティ名]を記述します。 const message = `名前は${myProfile.name}です。年齢は${myProfile.age}歳です。` console.log(message); 出力結果 名前はkeitaです。年齢は29歳です。 分割代入で表すと、以下のようになります。 「.」が不要になり、スマートなコードになりました。 const { name, age } = myProfile; const message = `名前は${name}です。年齢は${age}歳です。` console.log(message); 出力結果 名前はkeitaです。年齢は29歳です。 配列の場合も、同様に分割代入が可能です。 const myProfile = ["keita", 29] const [ name, age ] = myProfile; const message = `名前は${name}です。年齢は${age}歳です。` console.log(message); スプレッド構文 ES6以降に追加された機能。スプレッド構文ともいわれ、配列やオブジェクトの中身を展開していく構文です。 スプレッドは「広げる」や「拡散する」といった意味を持っており、その意味の示すとおり [] や {} でくくられたものを外してくれます。 const array = [1, 2, 3, 4, 5] console.log(array); // 出力 > 1, 2, 3, 4, 5 例えば、以下のような関数「sum」があった場合、スプレッド構文として引数を渡すと、値を(1, 2, 3)と展開して渡すことができます。 function sum(a, b, c) { return a + b + c; } const numbers = [1, 2, 3]; const result = sum(...numbers); console.log(result); // 出力 > 6 JavaScriptでの関数 JavaScriptの学習において、理解が進みづらかったのが様々な関数の宣言方法でした。 JavaScriptにおける関数は、他の言語と違って重要度が段違いに高く、他の言語でいえるような「ただの処理の集まり」ではないとされています。 関数そのものは「データ型の一種」であり、変数やオブジェクトと同等に扱うことができます。 基本的には関数の内部にreturn(戻り値)を記載しますが、returnの記述を省略すると、戻り値は「undefined」という値になります。 Tips nullとundefined null 「null」という値がセットされた状態 undifined 「未定義」という意味 両者の違いは、あえて「null」という値をセットしたか、何もしていないか」の違い 関数の4つの定義方法 function命令による定義 function sum(a, b) { return Number(a) + Number(b); } Function()コンストラクタによる定義 実際に使用するメリットはない const sum = new Function('a', 'b', 'return Number(a) + Number(b);'); 関数リテラルによる定義 宣言した時点では名前を持たないので、匿名関数や無名関数とも呼ばれる const sum = function(a, b) { return Number(a) + Number(b); } アロー関数による定義 ES6より登場した新しい方法 const sum = (a, b) => { return Number(a) + Number(b) }; アロー関数とはつまり? 関数を「より少ない記述で宣言する」ための仕組みで、特にES6以前の無名関数を短く書けるという側面があります。 JavaScriptライブラリであるReact.jsやVue.jsでは、コンポーネントの定義で引数なしのアロー関数をたびたび使用します。 function() と アロー関数は何が違う? // function命令での定義 function sum(a, b) { return Number(a) + Number(b); } // アロー関数での定義 const sum = (a, b) => { return Number(a) + Number(b) }; 一見、ちょっとの違いなのでどっちでもいいように感じますが、この2つは「thisの取り扱い方」で違いがあります。 通常、JavaScriptでは文脈によってthisの指すものが変化します。 例えば以下の場合 text = "わーーーい"; function hoge() { console.log(this.text); } const object_1 = { text : "いえーーい", func : hoge, }; const object_2 = { text : "やっほーーーい", func : hoge, }; object_1.func(); => "いえーーい" object_2.func(); => "やっほーーーい" hogeで呼び出されるthisは、呼び出し元のオブジェクトを指します。 つまり、object_1内で呼び出されたhogeが指すthis.textは「object_1」にあるtext「いえーーい」です。 同様に、object_2の中で呼び出されたthis.textはobject_2にあるtextを見に行っているので、「やっほーーーい」と返されます。 この場合は、hoge関数が定義された段階で「this」の値は定まっておらず、hoge関数を呼び出した段階で初めて「this」の正体が決まるということになります。 では、これをアロー関数で定義してみるとどうなるでしょうか。 text = "わーーーい"; const hoge = () => { console.log(this.text); } const object_1 = { text : "いえーーい", func : hoge, }; const object_2 = { text : "やっほーーーい", func : hoge, }; object_1.func(); => "わーーーい" object_2.func(); => "わーーーい" どちらも出力は「わーーーい」となりました。 アロー関数式で宣言された関数は、宣言された時点でthisを確定させてしまいます。 そのため、どちらのオブジェクトから呼び出してもコードのはじめに宣言されているtextが表示されました。 このようにthisの挙動を確定させてしまうのが、アロー関数の大きな違いかと思います。 さいごに 以上、基本的な文法をさらっとまとめてみました。 今後もっと理解が深まって来たら、追記などもしていければと思います。 参考にさせていただいた記事 https://qiita.com/10mi8o/items/2da84ab650f4caffdeea https://qiita.com/mejileben/items/69e5facdb60781927929
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TradingViewのチャート(lightweight-chart)を使ってみた

lightweight-chartとは lightweight-chartとは、TradingView社が提供するチャートライブラリです。 ローソク足チャートとか描きたいけど、自分で実装すると面倒だしクオリティーも...という方におすすめです。 大体下の画像のような感じで使えます。TradingViewのサイトではトレンドラインとか引けるんですが、lightweight-chartは軽量版という事でチャートの描画のみが可能です。 (一応申請すればすべての機能がついてるライブラリが使えるみたいですが...) ライブラリ等のリンクは以下から参照してください。 https://jp.tradingview.com/lightweight-charts/ https://github.com/tradingview/lightweight-charts 基本的な使い方 インストール インストールにはnpmを用いる方法とCDN(unpkg)を用いる2通りの方法があります。 ここでは(楽なので)CDNを用いる方法でやります。 index.html <!DOCTYPE html> <html> <head> <title>installing lightweight-chart</title> </head> <body> <script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script> <script type="text/javascript" src="./index.js"></script> </body> </html> チャートを表示しよう lightweight-chartではラインチャート, バーチャート, ローソク足チャート, ヒストグラムなどが使えます。 ここではローソク足チャートの表示方法を解説します。 index.js var chart = LightweightCharts.createChart(document.body, { width: 400, height: 300, layout: { backgroundColor: '#131722', textColor: 'rgba(255, 255, 255, 0.9)', }, grid: { vertLines: { color: 'rgba(197, 203, 206, 0)', }, horzLines: { color: 'rgba(197, 203, 206, 0)', }, }, crosshair: { mode: LightweightCharts.CrosshairMode.Normal, }, rightPriceScale: { borderColor: 'rgba(197, 203, 206, 0.8)', }, timeScale: { borderColor: 'rgba(197, 203, 206, 0.8)', timeVisible: true, //5分足とか表示したい人はtrueにしてください secondsVisible: false, //秒足を使いたい人はtrueに }, }); //ローソク足に関する設定 var candleSeries = chart.addCandlestickSeries({ upColor: '#87cefa', downColor: '#fa8072', borderDownColor: '#fa8072', borderUpColor: '#87cefa', wickDownColor: '#fa8072', wickUpColor: '#87cefa', }); //チャートの描画はOHLCとタイムスタンプ(秒単位)を指定して行います //OHLC+Tのデータは連想配列の形式で指定します //例: {time: 1534204800, open: 6035, high: 6213, low: 5968, close: 6193} //githubの例だと時間の指定が'2019-04-11'となっていますが、タイムスタンプの方が便利かと思います candleSeries.setData([ {time: 1534204800, open: 6035, high: 6213, low: 5968, close: 6193}, {time: 1534291200, open: 6187.5, high: 6620.5, low: 6176, close: 6264.5}, {time: 1534377600, open: 6264, high: 6474, low: 6207.5, close: 6315}, {time: 1534464000, open: 6315, high: 6588.5, low: 6290.5, close: 6575.5}, {time: 1534550400, open: 6582, high: 6612.5, low: 6297, close: 6388}, {time: 1534636800, open: 6387.5, high: 6535.5, low: 6304.5, close: 6490.5}, {time: 1534723200, open: 6487, high: 6517, low: 6210, close: 6246.5}, {time: 1534809600, open: 6245, high: 6497, low: 6235.5, close: 6461.5}, {time: 1534896000, open: 6466, high: 6890.5, low: 6230.5, close: 6344.5}, {time: 1534982400, open: 6348.5, high: 6570.5, low: 6336.5, close: 6521.5}, {time: 1535068800, open: 6521.5, high: 6726, low: 6446, close: 6706.5} ]); ここまでで、下の画像のようなチャートが出来上がります。 リアルタイムチャート 上の例だと、データを読み込んだきりチャートが更新されません。 この記事にたどり着いてしまった方の半分くらいは外部のAPIと連携してリアルタイムでチャートを表示したいのではないかと思います。 まあ、リアルタイムで表示と言っても特に難しいことをするわけではありません。 OHLC+Tをどこかに保持しておいて、新しいデータがやってくる度にOHLC+Tを更新してsetData関数を呼び出すだけです。 あとがき それにしても、このチャートかなり使いやすいにもかかわらず使ってる人が全然いないですね... 情報もあまりなくintradayのチャートを表示する方法を探すのにも苦労しました。 国内証券会社の変なチャートもこれに変えてくれたらなぁと思うんですが...まあ如何せんそういう訳には行かないのでしょう(残念) 私もこれを使ってちょっとしたものを作ってみました。 https://github.com/Nikkei225Futures/deribitWebsocketSample (プログラミングが得意な訳ではないので、設計もコーディングもめちゃくちゃなのは内緒で())
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.js・ブラウザ(JavaScript)で処理時間を測る方法のいくつか

JavaScript で処理時間を計測する場合の話で、複数の方法があるので、そのいくつかをまとめてみました。 Node.js は 2通り、ブラウザ(Chrome を利用)は 3通りの方法を試しています。 Console API 最初は Console API です。 ●Console API reference - Chrome Developers  https://developer.chrome.com/docs/devtools/console/api/#time 以下、ブラウザと Node.js でそれぞれ動かしてみたものです。 ブラウザ <html> <head> <meta charset="UTF-8"> <title>test</title> </head> <body> </body> <script> console.log("start"); console.time("test"); let count; const num = 2000000000; for (let i = 0; i < num; i++) { let square = i ** 2; } console.timeEnd("test"); console.log("end"); </script> </html> 以下の出力が得られました。 Node.js console.log("start"); console.time("test"); let count; const num = 200000000; for (let i = 0; i < num; i++) { let square = i ** 2; } console.timeEnd("test"); console.log("end"); 実行結果は以下の通りです。 Performance 次は以下を試します。 ●Performance - Web API | MDN  https://developer.mozilla.org/ja/docs/Web/API/Performance ●Performance measurement APIs | Node.js v17.1.0 Documentation  https://nodejs.org/api/perf_hooks.html 以下、ブラウザと Node.js でそれぞれ動かしてみたものです。 Node.js の場合は const performance = require("perf_hooks").performance; という部分を追加しています。 ブラウザ <html> <head> <meta charset="UTF-8"> <title>test</title> </head> <body> </body> <script> console.log("start"); const startTime = performance.now(); let count; const num = 2000000000; for (let i = 0; i < num; i++) { let square = i ** 2; } console.log(`${performance.now() - startTime}`); console.log("end"); </script> </html> Node.js const performance = require("perf_hooks").performance; console.log("start"); const startTime = performance.now(); let count; const num = 200000000; for (let i = 0; i < num; i++) { let square = i ** 2; } console.log(`${performance.now() - startTime}`); console.log("end"); 出力は省略しています。 ブラウザ(Chrome)の開発者ツール 以下の記事に、Chrome の開発者ツールを使った例が書かれていたので、それを試してみました。 ●JavaScriptで任意の処理にかかる時間を計測する  https://sbfl.net/blog/2017/12/01/javascript-measure-time/ 手順は以下となります。 Chrome で開発者ツールを開く Performanceタブを開く ⇒ 左上にある丸型のアイコンかその隣のアイコンを押して計測開始4 Stopボタンを押す」 以下は、計測結果が表示された場所のキャプチャです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cssのみ(HTML+javascript)でcookie-Clickerをつくる!pt.4

こんにちは! 前回→ここをクリック 自作cookie-Clicker→ここをクリック 今回は、セーブ機能を作りました。 コード・js〜save&load〜 main.js const save = { "0":"Α", "1":"A", "2":"A", "3":"E", "4":"E", "5":"Ε", "6":"Υ", "7":"Y", "8":"Y", "9":"Τ", "_":"T", "x":".", "@":"T", "Α":"0", "A":"1", "A":"2", "E":"3", "E":"4", "Ε":"5", "Υ":"6", "Y":"7", "Y":"8", "Τ":"9", "T":"_", "T":"@" } ...(中略) function save_all_get() { var save_former=main_item["all_cookies"]+"_"+main_item["all_cookies_lvl"]+"_"+main_item["cookies"]+"_" save_former=save_former+main_item["lvl"]+"_"+main_item["salary"]+"_"+main_item["white_click"]+"_"+main_item["white_click_buy_cookie"]+"_" save_former=save_former+main_item["white_click_cookie"]+"_"+main_item["red_click"]+"_"+main_item["blue_click"]+"_"+main_item["yellow_click"]+"_@"; save_code=""; for(var i=1;i<=save_former.length;i++){ save_code=save_code+save[save_former.slice(i-1,i)] } } ...(中略) function save_code_download() { document.getElementById("save_ok_button").style.display ="block"; }; function save_get_ok(){ var string = save_code; var title = 'css_coookie_clicker_save_code.txt'; var blobType = 'text/plain'; var linkTagId = 'getLocal'; var linkTag = document.getElementById(linkTagId); var linkTagAttr = ['href','download']; var msSave = window.navigator; var stringObject = new Blob([string], { type: blobType }); var objectURL = window.URL.createObjectURL(stringObject); var UA = window.navigator.userAgent.toLowerCase(); if((UA.indexOf('msie') != -1 || UA.indexOf('trident') != -1)) { window.navigator.msSaveOrOpenBlob(stringObject, title); }else{ linkTag.setAttribute(linkTagAttr[0],objectURL); linkTag.setAttribute(linkTagAttr[1],title); }document.getElementById("save_ok_button").style.display ="none";} ...(中略) window.addEventListener('DOMContentLoaded', function(){ document.getElementById("loodfile").addEventListener('change', function(e){ var file_reader = new FileReader(); file_reader.addEventListener('load', function(e) { load_set(e.target.result);}); file_reader.readAsText(e.target.files[0]); });}); function main() { main_system_shop(); main_system_text(); main_cookie_unit(); save_all_get(); requestAnimationFrame(main); } requestAnimationFrame(main); コード・html〜load〜 index.html <a onclick="save_get_ok()" id="getLocal" href="#">2段階セーブコードダウンロードボタン</a> 本当に色々ありますが、ざっくり説明すると、 「save」がセーブコード作成用の配列で、 「save_all_get」で、セーブコードを作成し(loop)、 「save_code_download」で2段階認証して、 「save_get_ok」で、タイトルなどを決めてダウンロードする。 「window...」は、ファイルがアップロードされたら、読み込むというコードです。 HTMLコードは「a」タグにIDその他をつけてボタンを作っています。 皆さんにもCSSのイラストなどを作って欲しいです。お願いします!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

swiper.jsで自動再生しながらループするサムネイル付きスライダーを実装した時の備忘録

Swiper.js version 7.2.0 ~ 7.3.1 installation 以下の3択。 npm CDN assetsをDLする 7系にアップデートされた際、.swiper-containerが.swiperに代わり、CDN派一同騒然という事態が発生した事例があるので、CDNは要注意。 自動再生しながらループするサムネイル付きスライダー swiper.jsのサムネイル付きのループするスライドのdemoでは、サムネイルもhtmlで記述している。 でもサムネイルまで記述するのがめんどくさいし、スライドを変更することになった時に両方いじらないといけなくなる。 なので、メインスライドのスライドを複製する。 Swiper.jsのdemo Demo See the Pen Untitled by brassyk (@brassyk) on CodePen. index.html <!-- メインスライド --> <div style="--swiper-navigation-color: #fff; --swiper-pagination-color: #fff;" class="swiper mySwiper2"> <div class="swiper-wrapper"> <div class="swiper-slide"> <img src="https://swiperjs.com/demos/images/nature-1.jpg" /> </div> <div class="swiper-slide"> <img src="https://swiperjs.com/demos/images/nature-2.jpg" /> </div> <div class="swiper-slide"> <img src="https://swiperjs.com/demos/images/nature-3.jpg" /> </div> </div> <div class="swiper-button-next"></div> <div class="swiper-button-prev"></div> </div> <!-- サムネイル --> <div thumbsSlider="" class="swiper mySwiper"> <!-- .mySwiper2の.swiper-wrapperをcloneNodeしてここにappendChildする --> </div> サムネイルのスライドを生成する メインスライドのwrapperごと複製し、サムネイルのコンテナ内へ追加する。 メインスライドのスライドを複製する cloneNodeでwrapperごと複製する。このとき、deepをtrueにしていることを確認。 falseになっていると、小ノードが複製されない。 DOM4ではdefaultがtrueで省略可能だったが、現在は必須なので要注意。 サムネイルのコンテナへ追加 cloneNodeで複製したノードは、メソッドで追加するまで親ノードを持てずドキュメントの一部にもなれない。 .mySwierへ、appendChildで追加する。 swiper-config.js const mySwiper2_wrapper = document.querySelector(".mySwiper2 .swiper-wrapper"), mySwiper_container = document.querySelector(".mySwiper"), clone = mySwiper2_wrapper.cloneNode(true); mySwiper_container.appendChild(clone); Paramaterを設定する スライドのサイズはいつもCSSで指定しているので、slidesPerViewはauto。 slidesPerView: "auto"のとき、loopedSlidesを設定する必要がある。 さらに、サムネイルは1列で表示させたい。 しかし、スライドの数が増減したときにいちいち変えるのはめんどくさい。 今回はメインスライドを複製するためにwrapperを取得しているので、childNodes.lengthで子ノードの数を取得する。 あとは適当にいい感じにする。 swiper-config.js const swiper = new Swiper(".mySwiper", { loop: false, spaceBetween: 10, slidesPerView: mySwiper2_wrapper.childNodes.length, freeMode: true, watchSlidesProgress: true }); const swiper2 = new Swiper(".mySwiper2", { autoplay: { delay: 5000, disableOnInteraction: false }, slidesPerView: "auto", centeredSlides: true, loop: true, loopedSlides: mySwiper2_wrapper.childNodes.length, spaceBetween: 10, speed: 800, navigation: { nextEl: ".swiper-button-next", prevEl: ".swiper-button-prev" }, thumbs: { swiper: swiper } }); },false); ついでに IntersectionObserverでビューポートを監視し、 ビューポートにスライドが交差してからautoplay.start()させてみる。(ちょっと適当) swiper-config.js on: { afterInit: (mySwiper2) => { mySwiper2.autoplay.stop(); const options = { root: null, rootMargin: '-50%', thredhold: 0 }; const target = document.querySelector('.mySwiper2'); const observer = new IntersectionObserver(callback, options); observer.observe(target); function callback(entries, object) { entries.forEach(function(entry, i) { if (!entry.isIntersecting) return; const target = entry.target; mySwiper2.autoplay.start(); object.unobserve(target); }); }; }, }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む