- 投稿日:2019-03-02T21:05:04+09:00
Canvasから動画を生成するWebサービスを試作してみた。
はじめに
3年ぶりの投稿です。
最近、Codepenなどを使ってCanvasで遊んでいるのですが、Canvasの画像・動きを動画として残せないかと考えるようになりました。
調べてみると、CCapture.jsなど、Canvasから動画を生成するライブラリがあるようなので、Herokuを使って、簡単なCanvas動画生成サービスを作ってみました。作ったもの
Canvasから動画を生成するWebサービスを試作してみました。https://t.co/Nd265nEUGw pic.twitter.com/sweoAaaEZt
— t_mat (@t_mat) March 2, 2019使い方
htmlヘッダーに以下の行を追加します。なお、idはcanvasのid、fpsはフレームレート、timeは録画時間(ms)を指定します。
'C'キーを推すとCanvasのキャプチャが開始され、録画終了後、動画(webm)がダウンロードされます。
<script type="text/javascript" src="https://capcanvas.herokuapp.com/capcanvas/id=app&fps=60&time=10000"></script>
実装
■JavaScript
Jsは、CCaputureのサンプルコードを参考に、以下のように実装しました。なお、$URL
、$ID
、$FPS
、$TIME
はサーバー側で文字列に置換します。CapCanvas.jswindow.onload = function(){ var script = document.createElement("script"); script.type = "text/javascript"; script.src = "$URL"; document.body.appendChild(script); }; window.addEventListener('keydown', event => { if(event.key=='c'||event.key=='C'){ let canvas=document.getElementById("$ID"); startCapture(canvas,$FPS,$TIME); } }); let startCapture=function(canvas,fps,time){ let cap=new CCapture({format:'webm',framerate: fps,verbose: true}); let render=function(){ requestAnimationFrame(render); cap.capture( canvas ); }; requestAnimationFrame(render); cap.start(); setTimeout(function(){ cap.stop(); cap.save(); },time); };■サーバー
サーバー側はリクエストパラメータを解析し、CapCanvas.jsを書き換えて送信します。
個人的にお気に入りのSpark Frameworkで作成しています。Main.javapackage net.termat.webapp.capcanvas; import static spark.Spark.get; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.Optional; import spark.ModelAndView; import spark.Spark; import spark.template.mustache.MustacheTemplateEngine; public class Main { private static String code; public static void main(String[] args) { Spark.staticFileLocation("/public"); code=getJs(); Optional<String> optionalPort = Optional.ofNullable(System.getenv("PORT")); optionalPort.ifPresent(p -> { int port = Integer.parseInt(p); Spark.port(port); }); get("/", (request, response) -> { Map<String, Object> model = new HashMap<>(); return new ModelAndView(model, "index.mustache"); }, new MustacheTemplateEngine()); get("/capcanvas/:param", (request, response) -> { try{ String url="https://capcanvas.herokuapp.com/js/CCapture.all.min.js"; Map<String,String> map=paramMap(request.params("param")); String id=map.get("id"); String fps=map.get("fps"); String time=map.get("time"); response.status(200); String ret=new String(code); ret=ret.replace("$URL", url); ret=ret.replace("$ID", id); ret=ret.replace("$FPS", fps); ret=ret.replace("$TIME", time); return ret; }catch(Exception e){ e.printStackTrace(); response.status(400); response.type("application/json"); return ""; } }); } private static String getJs(){ try{ URL url=Main.class.getResource("CapCanvas.js"); BufferedReader br=new BufferedReader(new InputStreamReader(url.openStream())); StringBuffer buf=new StringBuffer(); String line=null; while((line=br.readLine())!=null){ buf.append(line); } return buf.toString(); }catch(Exception e){ return ""; } } private static Map<String,String> paramMap(String param) throws Exception{ Map<String,String> ret=new HashMap<String,String>(); String[] p=param.split("&"); for(int i=0;i<p.length;i++){ String[] k=p[i].split("="); if(k.length<2)continue; ret.put(k[0], k[1]); } return ret; } }デプロイ
herokuへは、heroku-maven-pluginを使用してデプロイしました。
Port番号の解決に気が付かず、実際に動作させるまで2~3時間かかってしまいました。
- 投稿日:2019-03-02T20:04:33+09:00
firebaseとvueでソーシャルログインを実装
タイトルままですが、firebaseとvueでソーシャルログインを実装しました。
各ルーター側の.vueに影響がないようにしました。
firebaseなのでここでホスティングもしてます。
実際にログインもできます。
https://authentication-sample-001.firebaseapp.com/
プロジェクトはこちら
- 投稿日:2019-03-02T20:04:33+09:00
FirebaseとVueでソーシャルログインを実装
タイトルままですが、FirebaseとVueでソーシャルログインを実装しました。
各ルーター側の.vueに影響がないようにしました。
Firebaseなのでここでホスティングもしてます。
実際にログインもできます。
https://authentication-sample-001.firebaseapp.com/
プロジェクトはこちら
- 投稿日:2019-03-02T19:51:16+09:00
JavaScriptで今風の配列やオブジェクトのコピーのやり方
JSを勉強し始めた人向けの記事です。
今っぽい感じの配列やオブジェクトのコピーのやり方を記載します。(今=2019年です)
配列のコピー
配列const array1 = [ 1, 2, 3 ] const arrayCopy = [ ...array1 ]スプレッド構文を使うことで
concat
とかslice
とかfor
とかを使わずに書けます。オブジェクトのコピー
オブジェクトconst obj1 = { a: 1, b: 2 } const objCopy = { ...obj1 }こちらもスプレッド構文を使ってコピーできます。
Object.assign
を使わずに書けます。注意点
どちらもShallow copyです。
Deep copyではありません!
まとめ
自分はこの方法を知らなかったので、初めてこの書き方を知ったときはシンプルさに驚きました。
スプレッド構文はコピー以外にもいろいろと便利なので知らなかった場合は詳細を調べてみることをオススメします!
参考
- 投稿日:2019-03-02T19:09:06+09:00
VBAでInternetExplore上のJavaScriptを無理やり動かすよ!
みんな大好きInternetExplore11のJavaScriptを、みんな大好きなExcelのVBAから動かします。
なにが嬉しいの?
・現在、表示されているIEのJavaScriptの保持している変数の中身を出力できるよ!
・無理やりJavaScriptで例外を発生させたときの挙動を確かめられるよ!
・サーバー側のソースコードを書き換えずにローカル環境でJavaScriptの関数をテスト用に置き換えられるよ!ExcelVBAなので、手足を縛ってプログラムを開発するのが性癖なマゾい会社でも使えるよ!やったぜ!
環境
InternetExploere
Windows10
Office16 Excel 32bitなお、64bitのExcelを使っている箇所はlongでなく、longPtrとかにしないと動かないと思うけど、ぶっちゃけ64bitマシン支給するようなブルジョワジーの組織だったらVisualStudio使わせてもらってC#で書いた方がいいと思います。
サンプル
IEAuto.basOption Explicit ' Microsoft HTML Object Libraryを参照設定すること Private Declare Function EnumWindows Lib "user32" (ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long Private Declare Function EnumChildWindows Lib "user32" (ByVal hWndParent As Long, ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long Private Declare Function RegisterWindowMessage Lib "user32" Alias "RegisterWindowMessageA" (ByVal lpString As String) As Long Private Declare Function SendMessageTimeout Lib "user32" Alias "SendMessageTimeoutA" (ByVal hwnd As Long, ByVal msg As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal fuFlags As Long, ByVal uTimeout As Long, lpdwResult As Long) As Long Private Declare Function ObjectFromLresult Lib "oleacc" (ByVal lResult As Long, riid As Any, ByVal wParam As Long, ppvObject As Object) As Long Private mIEWndList As Collection Private Type UUID Data1 As Long Data2 As Integer Data3 As Integer Data4(0 To 7) As Byte End Type ' MSHTML.IHTMLDocumentのキャッシュを検索する Public Sub RefreshBrowserCache() Set mIEWndList = New Collection Call EnumWindows(AddressOf EnumIEWndProc, 0) End Sub ' 指定の名称を含むタイトルのブラウザを検索する Public Function FindBrowser(ByVal title As String) As MSHTML.IHTMLDocument2 Dim r As MSHTML.IHTMLDocument2 If mIEWndList Is Nothing Then Call RefreshBrowserCache End If Set r = FindBrowserInCache(title) If Not r Is Nothing Then Set FindBrowser = r Exit Function End If Call RefreshBrowserCache Set FindBrowser = FindBrowserInCache(title) End Function ' キャッシュの中から指定の名称を含むタイトルのブラウザを検索する Private Function FindBrowserInCache(ByVal title As String) As MSHTML.IHTMLDocument2 Dim d As MSHTML.IHTMLDocument2 For Each d In mIEWndList If InStr(d.title, title) > 0 Then Set FindBrowserInCache = d Exit Function End If Next End Function ' コールバック関数 Private Function EnumIEWndProc(ByVal hwnd As Long, lParam As Long) As Boolean Const FRAME_NAME = "IEFrame" Const FRAME_NAME_EDGE = "TabWindowClass" 'Edgeの場合のクラス名 Dim strClassName As String * 128 Dim lngRet As Long strClassName = "" Call GetClassName(hwnd, strClassName, 128) Dim c As String c = Left(strClassName, Len(Trim(strClassName)) - 1) If c = FRAME_NAME Or c = FRAME_NAME_EDGE Then EnumChildWindows hwnd, AddressOf EnumIEServerWndProc, 0 End If 'Debug.Print "[" & Trim(strClassName) & "_]" EnumIEWndProc = True End Function ' コールバック関数 Private Function EnumIEServerWndProc(ByVal hwnd As Long, ByVal lParam As Object) As Long Const SERVER_NAME = "Internet Explorer_Server" Dim strClassName As String * 128 Dim lngRet As Long Dim doc As MSHTML.IHTMLDocument2 strClassName = "" Call GetClassName(hwnd, strClassName, Len(strClassName)) If Left(strClassName, Len(Trim(strClassName)) - 1) = SERVER_NAME Then Set doc = GetHTMLDocument(hwnd) If Not doc Is Nothing Then mIEWndList.Add doc End If End If EnumIEServerWndProc = 1 End Function Private Function GetHTMLDocument(ByVal hwnd As Long) As MSHTML.IHTMLDocument2 Dim doc As MSHTML.IHTMLDocument2 Dim lMsg As Long Dim lRet As Long Dim hr As Long Dim IID_IHTMLDocument As UUID With IID_IHTMLDocument .Data1 = &H626FC520 .Data2 = &HA41E .Data3 = &H11CF .Data4(0) = &HA7 .Data4(1) = &H31 .Data4(2) = &H0 .Data4(3) = &HA0 .Data4(4) = &HC9 .Data4(5) = &H8 .Data4(6) = &H26 .Data4(7) = &H37 End With lMsg = RegisterWindowMessage("WM_HTML_GETOBJECT") If lMsg <> 0 Then SendMessageTimeout hwnd, lMsg, 0, 0, &H2, 1000, lRet If lRet <> 0 Then If ObjectFromLresult(lRet, IID_IHTMLDocument, 0, doc) = 0 Then Set GetHTMLDocument = doc End If End If End If End FunctionサンプルPublic Sub test() Dim b As MSHTML.IHTMLDocument3 Dim w As Object Set b = IEAuto.FindBrowser("Google") If b Is Nothing Then Debug.Print "見つからない" Exit Sub End If Dim r As Variant Dim elems As MSHTML.IHTMLElementCollection Dim elem As MSHTML.IHTMLElement Set elems = b.getElementsByName("btnK") Debug.Print elems.Item(0).getAttribute("value") ' 以下のような感じでJavaScriptにアクセスできるがEdgeの場合はアクセス違反になる ' 管理者権限も無駄 Dim testId As Variant testId = Timer b.parentWindow.execScript ("function x() { result = 5+5+4; console.log('TEST'); var d = document.createElement('div'); d.id = 'test_elem" & testId & "'; d.innerHTML = result; document.getElementsByTagName('body').item(0).appendChild(d); }; x();") Set elem = b.getElementById("test_elem" & testId) Debug.Print elem.innerHTML End Sub解説
IEのウィンドウハンドルからMSHTML.IHTMLDocumentを取得してあとはよくあるIEの自動操作をしています。
IHTMLDocumentに変換できるウィンドウのクラス名は「Internet Explorer_Server」です。
IE11の場合は「IEFrame」クラスの子ウィンドウになっています。
Edgeの場合は「TabWindowClass」クラスの子ウィンドウになっています。プログラムとしては「IEAuto.FindBrowser("Google")」でタイトルにGoogleを含むものを検索してIHTMLDoucmentを取得します。
あとは、exeScriptでJavaScriptをたたくことが可能ですが、関数の仕様として戻り値を取得することはできません。そのため、JavaScriptから値をVBAに返す場合は、テスト用の要素に書き込むといいでしょう。
なお、すくなくとも当方の環境だとexeScriptはEdgeだとアクセス違反になりました。
参考
Cant create HTML document from Hwnd using C#
https://stackoverflow.com/questions/20873885/cant-create-html-document-from-hwnd-using-c-sharp【2017年1月版】Microsoft Edgeを操作するVBAマクロ(DOM編)
https://www.ka-net.org/blog/?p=7921
- 投稿日:2019-03-02T18:47:48+09:00
[ITP 2.0] Google広告でクロスドメインのコンバージョン計測をするために
はじめに:
・GTMコンバージョンリンカー
・gtagでset linker
いずれかの方法で、クロスドメインの設定が必要になります。※Googleアナリティクスの_gacというクッキーも使える。この投稿では一旦省略します。
① Googleタグマネージャーで設定する場合
・ドメイン間のリンクの有効可にチェック
・カンマつなぎで、複数ドメインを登録する
・ドメインまたぎの瞬間がフォームの送信(カートに入れた瞬間にドメインが変わる場合など)の時には、「装飾フォーム: true」にする必要あり
(これが結構罠。デフォルトtrueではダメなんか...)
・トリガーはページビュー: All pages② gtagでset linkerの設定する場合
・以下のコードでドメイン間のリンクを有効にしてあげます。
・が、かならず gtag('js', new Date());よりも早く呼ぶ必要があります。
(テストしましたが、クッキー引き継げなかったです。これも罠。)... gtag('set', 'linker', { 'domains': ['exampleA.com', 'exampleB.com'], 'decorate_forms': true // trueにしておきましょう。 }); gtag('js', new Date()); ...よって、最終的な形はこうなります。
<script async src="https://www.googletagmanager.com/gtag/js?id=AW-XXXXXXXX"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('set', 'linker', { 'domains': ['exampleA.com', 'exampleB.com'], 'decorate_forms': true }); gtag('js', new Date()); gtag('config', 'AW-XXXXXXXX'); </script>③ 本当にクロスドメインでクッキーを引き継げているのか?の検証
Google広告にて、コンバージョン計測に活用できるファーストパーティークッキーは2つです。
・_gcl_aw (Google広告のクッキー)
・_gac (※アナリティクスのクッキーです。アカウントのリンクが必要)よって、「自動タグ設定にて付与された_gcl_aw / _gacを、ドメインAからドメインBに引き継げているかどうか」の検証が必要ですね。
④ 検証方法
手順1. gclid付与(広告クリックの状態を疑似的に再現)
「?gclid=xxxxx」とドメインAにてURLに付与
手順2. クッキー確認
デベロッパーツールにて、「_gcl_aw」というクッキーが生成されたか確認
※_gcl_aw=xxxxx(gclidで付与した値) という形式のクッキーです
手順3. ドメインを移動して、URLのクエリを確認
ユーザーと同じ導線でドメインBへ。URLに「?_gl=1*xxxxxx*_gcl_aw*xxxxx」というクエリが付くのを確認してください!(この中にクッキーの情報が入っているイメージ)
手順4. ドメインBでクッキーを確認
ドメインBにて、手順2でチェックしたクッキーと同じものが引き継がれていれば、ドメインBへのファーストパーティークッキーの引き継ぎ完了です。
プラグイン SUGOI!Cookies: gclid tester for Google Ads
SUGOI!Cookies: gclid tester for Google Ads
・手動での検証方法が面倒なので、プラグインを作ってみました。 気になる方は試してみてください。
・あくまで個人の趣味で開発してますが、バグがあれば直すので一報いただけると嬉しいです。随時改善中です。⑤ 補足: コンバージョンデータ送信時のHTTP通信を見てみる
- もし本当の本当に「コンバージョン計測時にファーストパーティークッキーのデータを送信できているのかどうか」気になるのであれば、コンバージョン計測時のHTTPリクエストを確認してみましょう。
手順1 コンバージョンのHTTPリクエストを探す
デベロッパーツール=> Network
=> www.googleadservices.com/pagead/conversion/ で検索手順2 ファーストパーティークッキーが送られているか確認
この通信にて、「_gcl_aw / _gac」が送信されていれば安心です。
まとめ
・Google広告のコンバージョン計測で使えるファーストパーティーは「_gcl_aw」「_gac」の2つ。今回は「_gcl_aw」をクロスドメインで引き継ぐための話でした。
・クッキーを渡す側(ドメインA)、もらう側(ドメインB)のいずれもで、
「GTM」 OR 「gtag」でのクロスドメイン設定の実装が必要になります。以上です!
参考資料:
コンバージョン トラッキングの実装を検証する
- 投稿日:2019-03-02T18:47:48+09:00
[ITP 2.0] Google広告でクロスドメインのコンバージョン計測をするためのTODO
はじめに:
やりたいこと
・Safariブラウザでもコンバージョン計測がしたい。
日本のiPhone普及率を考えると、ここは無視できないところ。そのために
・ファーストパーティークッキー(後述しますが、_gcl_aw/_gac)を生成する。
・広告をクリックした直後のランディングページと、コンバージョン計測したいページのドメインが異なる場合(クロスドメイン)、特別な方法でクッキーを渡してあげる必要がある。しかし
・Googleの資料を見ても、クロスドメイントラッキングの話がなかなかない。
(これ解決しないと、ITP対応できないでしょうに!)
・Google 広告のコンバージョンをトラッキングする方法
こんなん読んでも、ファーストパーティー、どころか、クッキーの「ク」の字もない。そこで、
参考資料:
・コンバージョン トラッキングの実装を検証する
・Google アナリティクスによるウェブサイトでの Cookie の使用
・Google 広告の自動タグ設定が正常に機能するか確認する
・ドメインをまたぐカスタマー ジャーニーを測定するここらの資料をすべてまとめてみました。
ドメインをまたいでクッキーの引継ぎをするために
・GTMコンバージョンリンカー
・gtagでset linker
いずれかの方法で、クロスドメインの設定が必要になります。① Googleタグマネージャーで設定する場合
・ドメイン間のリンクの有効可にチェック
・カンマつなぎで、複数ドメインを登録する
・ドメインまたぎの瞬間がフォームの送信(カートに入れた瞬間にドメインが変わる場合など)の時には、「装飾フォーム: true」にする必要あり
(これが結構罠。デフォルトtrueではダメなんか...)
・トリガーはページビュー: All pages② gtagでset linkerの設定する場合
・以下のコードでドメイン間のリンクを有効にしてあげます。
・が、かならず gtag('js', new Date());よりも早く呼ぶ必要があります。
(テストしましたが、クッキー引き継げなかったです。これも罠。)... gtag('set', 'linker', { 'domains': ['exampleA.com', 'exampleB.com'], 'decorate_forms': true // trueにしておきましょう。 }); gtag('js', new Date()); ...よって、最終的な形はこうなります。
<script async src="https://www.googletagmanager.com/gtag/js?id=AW-XXXXXXXX"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('set', 'linker', { 'domains': ['exampleA.com', 'exampleB.com'], 'decorate_forms': true }); gtag('js', new Date()); gtag('config', 'AW-XXXXXXXX'); </script>③ 本当にクロスドメインでクッキーを引き継げているのか?の検証
Google広告にて、コンバージョン計測に活用できるファーストパーティークッキーは2つです。
・_gcl_aw (Google広告のクッキー)
・_gac (※アナリティクスのクッキーです。アカウントのリンクが必要)よって、「自動タグ設定にて付与された_gcl_aw / _gacを、ドメインAからドメインBに引き継げているかどうか」の検証が必要ですね。
④ 検証方法
手順1. gclid付与(広告クリックの状態を疑似的に再現)
「?gclid=xxxxx」とドメインAにてURLに付与
手順2. クッキー確認
デベロッパーツールにて、「_gcl_aw」というクッキーが生成されたか確認
※_gcl_aw=xxxxx(gclidで付与した値) という形式のクッキーです
手順3. ドメインを移動して、URLのクエリを確認
ユーザーと同じ導線でドメインBへ。URLに「?_gl=1*xxxxxx*_gcl_aw*xxxxx」というクエリが付くのを確認してください!(この中にクッキーの情報が入っているイメージ)
手順4. ドメインBでクッキーを確認
ドメインBにて、手順2でチェックしたクッキーと同じものが引き継がれていれば、ドメインBへのファーストパーティークッキーの引き継ぎ完了です。
プラグイン SUGOI!Cookies: gclid tester for Google Ads
SUGOI!Cookies: gclid tester for Google Ads
・手動での検証方法が面倒なので、プラグインを作ってみました。 気になる方は試してみてください。
・あくまで個人の趣味で開発してますが、バグがあれば直すので一報いただけると嬉しいです。随時改善中です。⑤ 補足: コンバージョンデータ送信時のHTTP通信を見てみる
- もし本当の本当に「コンバージョン計測時にファーストパーティークッキーのデータを送信できているのかどうか」気になるのであれば、コンバージョン計測時のHTTPリクエストを確認してみましょう。
手順1 コンバージョンのHTTPリクエストを探す
デベロッパーツール=> Network
=> www.googleadservices.com/pagead/conversion/ で検索手順2 ファーストパーティークッキーが送られているか確認
この通信にて、「_gcl_aw / _gac」が送信されていれば安心です。
※Googleアナリティクスのクロスドメイン設定ができている、かつ、アカウントのリンクができている場合、_gacというクッキーも活用できます。
まとめ
・Google広告のコンバージョン計測で使えるファーストパーティーは「_gcl_aw」「_gac」の2つ。今回は「_gcl_aw」をクロスドメインで引き継ぐための話でした。
・クッキーを渡す側(ドメインA)、もらう側(ドメインB)のいずれもで、
「GTM」 OR 「gtag」でのクロスドメイン設定の実装が必要になります。以上です!
- 投稿日:2019-03-02T18:40:49+09:00
【個人開発】何が何でも魚拓を見つけるChrome拡張を作った
デモ
特徴
- archive.is, archive.org, gyo.tc, googleのキャッシュ、と計四か所から魚拓のURLを探します!
- vue.js製(やってみたかった)
なぜ作ったか
ハテブで魚拓コメがトップだったりして、需要を感じた。
後普通に自分が使いたかった。
そして、Qiitaユーザーの皆さんも必要性を感じてくださっている事と思います^^。どう作ったか
Chorome拡張はHTML+CSS+javascriptの基本セットで作れます。
ファイル構成は以下のようになりました。
- manifest.json(どの拡張でも必須)
- popup.js
- popup.html
- popup.css
- jquery
- vue.js
- icons(フォルダ。配下にicon16.png, icon48.png, icon128.png)
詳しく見ていきましょう。
manifest.jsonは、Chrome拡張のまとめ役みたいな感じ。manifest.json{ "name": "FindGyotaku in Console", "version": "0.0.1", "manifest_version": 2,//2で固定する "permissions": ["tabs", "http://*/", "http://*/"],//tabsはアクティブなタブのURLを取得するため、http://*/,https://*/はChrome拡張からすべてのサイトへのアクセスを許可するために必要 "description": "Show all Gyotaku", "icons": { "16": "icons/icon16.png", "48": "icons/icon48.png", "128": "icons/icon16.png" }, "browser_action": { "default_popup": "popup.html"//ここでpopupのhtmlを指定 } }そして、popup.jsが今回の大事ポイントです。
popup.jslet vue; //https://gyo.tc/から function findGyotakuByUrl(url) { return new Promise(resolve => { console.log("findGyotakuByUrl Started"); $.get("https://gyo.tc/" + url, null, function (data) { console.log(data); let gyotakuList_ = new DOMParser() .parseFromString(data, "text/html") .querySelectorAll("[id^=fish]"); resolve(gyotakuList_) }, "html" ); }) } //https://archive.is/から function findInternetArchiveByUrl(url) { return new Promise(resolve => { $.get(`https://archive.is/${url}`, function (data) { let rowsList = new DOMParser() .parseFromString(data, "text/html") .querySelectorAll("[id^=row]"); let archivesList = []; rowsList.forEach(row => { archivesList.push( Array.from( row.querySelectorAll("[style^=text\\-decoration\\:none]") ) ) }); resolve(archivesList.flat()); console.log(`flatten archivesList is ${archivesList.flat()}`); }) }) } //googleのwebcacheから function findGoogleCache(url) { return new Promise(resolve => { $.get(`http://webcache.googleusercontent.com/search?q=cache:${url}`, function () { resolve(`http://webcache.googleusercontent.com/search?q=cache:${url}`); }) }) } //http://web.archive.orgから function findWaybackMachineLatest(url) { return new Promise(resolve => { //limit=-1で最新 $.get(`http://web.archive.org/cdx/search/cdx?limit=-1&fl=timestamp&url=${url}`, function (timestamp) { resolve(`https://web.archive.org/web/${timestamp}/${url}`); console.log(`waybackMachine response(latest) is ${timestamp}`); }) }) } //http://web.archive.orgから function findWaybackMachineOldest(url) { return new Promise(resolve => { //limit=1で最古 $.get(`http://web.archive.org/cdx/search/cdx?limit=1&fl=timestamp&url=${url}`, function (timestamp) { resolve(`https://web.archive.org/web/${timestamp}/${url}`); console.log(`waybackMachine response(latest) is ${timestamp}`); }) }) } console.log("popup.jsLoaded"); chrome.tabs.query({ active: true, currentWindow: true }, function (tabsArray) { console.log(`currentUrl is ${tabsArray[0].url}`); findGyotakuByUrl(tabsArray[0].url).then((gyotakuList) => { console.log(gyotakuList); gyotakuList.forEach(element => { vue.items.push(element) }); vue.isLoadingGyotaku = false }).catch((err) => { console.log(err); vue.isLoadingGyotaku = false }); findInternetArchiveByUrl(tabsArray[0].url).then((archivesList) => { console.log(archivesList); archivesList.forEach(element => { vue.items.push(element) }); vue.isLoadingArchives = false }).catch((err) => { console.log(err); vue.isLoadingArchives = false }); findGoogleCache(tabsArray[0].url).then((googleCacheUrl) => { console.log({ href: googleCacheUrl }); vue.items.push({ href: googleCacheUrl }); vue.isLoadingGoogleCache = false }).catch((err) => { console.log(err) }); findWaybackMachineLatest(tabsArray[0].url).then((waybackMachineUrl) => { console.log({ href: waybackMachineUrl }); vue.items.push({ href: waybackMachineUrl }); vue.isLoadingWaybackMachine = false }).catch((err) => { console.log(err) }); findWaybackMachineOldest(tabsArray[0].url).then((waybackMachineUrl) => { console.log({ href: waybackMachineUrl }); vue.items.push({ href: waybackMachineUrl }); vue.isLoadingWaybackMachine = false }).catch((err) => { console.log(err) }); }); $(function () { vue = new Vue({ el: '#example-1', data: { items: [], isLoadingGyotaku: true, isLoadingArchives: true, isLoadingGoogleCache: true, isLoadingWaybackMachine: true, } }) });分割して解説します。
function findGyotakuByUrl(url) { return new Promise(resolve => { $.get("https://gyo.tc/" + url, null, function ( let gyotakuList_ = new DOMParser() .parseFromString(data, "text/html") .querySelectorAll("[id^=fish]"); resolve(gyotakuList_) }, "html" ); }) }↑要するにスクレイピングですね。javascriptではquerySelectorが便利です。
https://gyo.tc/ 以下にURLを投げると魚拓一覧が返るので、開発者ツールから頑張って解析しました。function findInternetArchiveByUrl(url) { return new Promise(resolve => { $.get(`https://archive.is/${url}`, function (data) { let rowsList = new DOMParser() .parseFromString(data, "text/html") .querySelectorAll("[id^=row]"); let archivesList = []; rowsList.forEach(row => { archivesList.push( Array.from( row.querySelectorAll("[style^=text\\-decoration\\:none]") ) ) }); resolve(archivesList.flat()); }) }) }↑こちらもhttps://archive.is/ 以下にURLを投げると魚拓一覧が返るので、こちらも開発者ツールから頑張って解析しました。
Array.flat()はMDNで分かるように実験的なヤツで、配列を好きな次元に均せる関数です。デフォルトで一次元なのでそのまま使用しました。function findGoogleCache(url) { return new Promise(resolve => { $.get(`http://webcache.googleusercontent.com/search?q=cache:${url}`, function () { resolve(`http://webcache.googleusercontent.com/search?q=cache:${url}`); }) }) }↑言うことなし。思ったより簡単。
function findWaybackMachineLatest(url) { return new Promise(resolve => { //limit=-1で最新 $.get(`http://web.archive.org/cdx/search/cdx?limit=-1&fl=timestamp&url=${url}`, function (timestamp) { resolve(`https://web.archive.org/web/${timestamp}/${url}`); }) }) } function findWaybackMachineOldest(url) { return new Promise(resolve => { //limit=1で最古 $.get(`http://web.archive.org/cdx/search/cdx?limit=1&fl=timestamp&url=${url}`, function (timestamp) { resolve(`https://web.archive.org/web/${timestamp}/${url}`); console.log(`waybackMachine response(latest) is ${timestamp}`); }) }) }↑apiが有って助かりました。
WaybackMachineのアーカイブは膨大なので最古&最新のみ取得しています。
limit=1で最古、limit=-1で最新を指定、fl=timestampでレスポンスをtimestampのみに限定します。
WaybackMachineはtimestampとURLを指定すると(珍しく)個別ページのURLが分かるので、timestampだけで十分なのです。$(function () { vue = new Vue({ el: '#example-1', data: { items: [],//以下デフォルト値を指定しています isLoadingGyotaku: true, isLoadingArchives: true, isLoadingGoogleCache: true, isLoadingWaybackMachine: true, } }) });index.html<p v-if="isLoadingGyotaku"> 魚拓から読み込んでいます </p>↑出ましたvue.js!読み込み中のメッセージを出しています。
vue.jsを使うと、HTMLとjs間でバインディングができます。
例えば、HTMLのv-if属性にisLoadingGyotakuを指定
↓
js側でvueインスタンス作成(vueのdata属性に、isLoadinGyotakuをメンバにしたオブジェクトを付けたもの)
↓
vue.isLoadingGyotakuにアクセスできるように&
vue.isLoadingGyotakuがv-if="isLoadingGyotaku"とバインドされるって感じ。
つまり、isLoadingGyotaku=trueならpタグが見えて、falseなら見えなくなる!
これは素晴らしいですね。本当に素晴らしい。
ほかにもいろいろな属性が有ります。だいたいv-が付く。<ul> <li v-for="item in items"> <a href="{{ item.href }}" target='_newtab'> {{ item.href }} </a> </li> </ul>↑v-forとか。見つかったURLを表示するために使っています。
{{}}はテンプレートリテラルみたいに使えます。
↓ラストchrome.tabs.query({ active: true, currentWindow: true }, function (tabsArray) { console.log(`currentUrl is ${tabsArray[0].url}`); findGyotakuByUrl(tabsArray[0].url).then((gyotakuList) => { console.log(gyotakuList); gyotakuList.forEach(element => { vue.items.push(element) }); vue.isLoadingGyotaku = false }).catch((err) => { console.log(err); vue.isLoadingGyotaku = false }); findInternetArchiveByUrl(tabsArray[0].url).then((archivesList) => { console.log(archivesList); archivesList.forEach(element => { vue.items.push(element) }); vue.isLoadingArchives = false }).catch((err) => { console.log(err); vue.isLoadingArchives = false }); findGoogleCache(tabsArray[0].url).then((googleCacheUrl) => { console.log({ href: googleCacheUrl }); vue.items.push({ href: googleCacheUrl }); vue.isLoadingGoogleCache = false }).catch((err) => { console.log(err); vue.isLoadingGoogleCache = false }); findWaybackMachineLatest(tabsArray[0].url).then((waybackMachineUrl) => { console.log({ href: waybackMachineUrl }); vue.items.push({ href: waybackMachineUrl }); vue.isLoadingWaybackMachine = false }).catch((err) => { console.log(err) vue.isLoadingWaybackMachine = false }); findWaybackMachineOldest(tabsArray[0].url).then((waybackMachineUrl) => { console.log({ href: waybackMachineUrl }); vue.items.push({ href: waybackMachineUrl }); vue.isLoadingWaybackMachine = false }).catch((err) => { console.log(err) vue.isLoadingWaybackMachine = false }); });↑async/awaitで見やすくなるのは分かってるんだけどね……面倒なのでそのまま。
chrome.tabs.queryで今見てるタブのURLを取得後、非同期×5を回しています。
chrome.系はChrome拡張の時しか使えないapiで、ほかにも色々便利です。はまったポイント
最初
let fuga = true vue = new Vue({ el: '#example-1', data: { isLoadingGyotaku: fuga, } }) fuga = false//バインドされてるはずなのに動かない!!!って事が有って大変でしたね。本当は
vue = new Vue({ el: '#example-1', data: { isLoadingGyotaku: true, } }) vue.isLoadingGyotaku = false//バインドされてて動く!!!で良かったっていう。公式ドキュメントをちゃんと読みましょう。
後、動作確認にevent pagesを使ってしまって、popupのみで動くapiを動かせなかったりとか……
popupページで検証する方法を知らなかったとか……(左クリックでポップアップされたのを右クリ)計何時間使ったでしょうかね。今はどうでも良いけど。
最後に
2020/04に大学生になる(浪人しなければ!)ので、良かったらインターンに誘ってください。
github:https://github.com/negitorogithub
gmail:bakeratta35@gmail.comお読み頂きありがとうございました。 ~~旦⊂(・∀・ )
- 投稿日:2019-03-02T18:05:05+09:00
【備忘録】react-templatesについてまとめた
react-templates
- wixが提供しているライブラリ(http://wix.github.io/react-templates/)
- reactのテンプレート箇所を".rt" 拡張子ファイルとして切り分け、コンパイルしテンプレートとして出力する。
- HTML感覚で書ける。
- お試し: http://wix.github.io/react-templates/fiddle.html
install
https://www.npmjs.com/package/react-templates
npm i -g react-templatesgulp: https://www.npmjs.com/package/gulp-react-templates
npm i gulp-react-templatesgulp task code
サンプル
const gulp = require('gulp'); const rt = require('gulp-react-templates'); const rename = require("gulp-rename"); // 出力ファイル名を変更するため const rt_path = './src/rt/*.rt'; gulp.task('rt', () => { return gulp.src(rt_path) .pipe(rt({ modules: 'es6' })) // 吐き出す際の出力フォーマットを選択 .pipe(rename(name_info => { const new_basename = name_info.basename.replace('.rt', ''); // '.rt'を''に置換 name_info.basename = new_basename; })) .pipe(gulp.dest('./src/templates')); }); gulp.task("watch", () => { return gulp.watch(rt_path, gulp.series(['rt'])); });Command line Interface
source: https://github.com/wix/react-templates/blob/gh-pages/docs/cli.md
先ほどの出力する際のoptionの例を以下に記載
// temp.rt <h2>Hello React</h2>// es6 // rt [file.rt|glob]* -m es6 import * as React from 'react'; import * as _ from 'lodash'; export default function () { return React.createElement('h2', {}, 'Hello React'); }// commonJS // rt [file.rt|glob]* -m commonJS 'use strict'; var React = require('react'); var _ = require('lodash'); module.exports = function () { return React.createElement('h2', {}, 'Hello React'); };// none (default) // rt [file.rt|glob]* var tempRT = function () { return React.createElement('h2', {}, 'Hello React'); };reactに組み込む
▼ こんなrtファイルを書いて
// temp.rt <h2>Hello react-templates</h2>▼ コンパイル(es6)してこんな感じにして
// temp.js import * as React from 'react'; import * as _ from 'lodash'; export default function () { return React.createElement('h2', {}, 'Hello react-templates'); }▼ importしてrenderのなかでreturnする
// App.js import React, { Component } from 'react'; import Temp from './templates/temp.js'; class App extends Component { render() { return Temp(); } } ReactDOM.render(<App />, document.getElementById('root'));ブラウザに Hello react-templates が出力されればok
参考
- 投稿日:2019-03-02T17:42:57+09:00
TypeScript/JavaScriptの配列を一定の個数ごとに分割する
Ruby の each_slice、PHP の array_chunk みたいなやつです。
例えば [1, 2, 3, 4, 5, 6, 7] を3個ずつに分けて [[1, 2, 3], [4, 5, 6], [7]] を作りたい場合に使えます。TypeScript
function chunk<T>(arr: T[], size: number) { return arr.reduce( (newarr, _, i) => (i % size === 0 ? [...newarr, arr.slice(i, i + size)] : newarr), [] as T[][] ) } chunk([1, 2, 3, 4, 5, 6, 7], 3) // -> [[1, 2, 3], [4, 5, 6], [7]]JavaScript
function chunk(arr, size) { return arr.reduce( (newarr, _, i) => (i % size === 0 ? [...newarr, arr.slice(i, i + size)] : newarr), [] ) }
- 投稿日:2019-03-02T16:52:22+09:00
知識0でもプログラマーになれるたった1つの理由と3つの方法
- 投稿日:2019-03-02T16:10:56+09:00
selectとoptionタグをjQueryを使って動的に追加する
デモ(動画)
やりたいこと
上記のGIF通り、
カテゴリーが選択されたと同時に、親カテゴリーに紐づく子カテゴリーが出てくるようにしたい。
入れ子構造については以下を参照
【追記予定】awesome_nested_setを使った入れ子構造のDB設計実装概要
jQueryのajaxメソッドを使って、カテゴリーが選択されたら子カテゴリーがappendされるようにした。
コード
routing
route.rbRails.application.routes.draw do root 'products#new' resources :products, only: [:create] do <!-- 今回はsearchアクションをajaxメソッドで叩きます--> collection do get 'search' end end endview
new.html.haml= form_for @product do |f| = f.label :name = f.text_field :name, placeholder: "商品名を入力" #cat = f.select :category_id, Category.roots.map {|i| ["#{i.name}", i.id]}, { selected: @product.category_id, include_blank: true }, {id: "l_category"}js
category.js$(document).on('turbolinks:load', function() { // Mカテゴリーのselectを追加するHTML var cat_seach = $("#cat"); function appendMselect() { var html = `<select name="product[category_id]" id="m_category"> <option value>---</option> </select>` cat_seach.append(html) } // Sカテゴリーのselectを追加するHTML function appendSselect() { var html = `<select name="product[category_id]" id="s_category"> <option value>---</option> </select>` cat_seach.append(html) } // Mカテゴリーのoptionを追加するHTML function appendMcat(m_cat) { $("#m_category").append( $("<option>") .val($(m_cat).attr('id')) .text($(m_cat).attr('name')) ) } // Sカテゴリーのoptionを追加するHTML function appendScat(s_cat) { $("#s_category").append( $("<option>") .val($(s_cat).attr('id')) .text($(s_cat).attr('name')) ) } // Lカテゴリーが選択された時のアクション $("#l_category").on('change', function() { l_cat = $(this).val() $("#m_category").remove() $("#s_category").remove() // ajaxでリクエストを送信 $.ajax({ type: "GET", url: "/products/search", data: {l_cat: l_cat}, dataType: 'json' }) // doneメソッドでappendする .done(function(m_cat) { appendMselect() m_cat.forEach(function(m_cat) { appendMcat(m_cat) }) }) }) // Mカテゴリーが選択された時のアクション $(document).on('change', "#m_category", function() { m_cat = $(this).val() $("#s_category").remove() $.ajax({ type: "GET", url: "/products/search", data: {m_cat: m_cat}, dataType: 'json' }) .done(function(s_cat) { appendSselect() s_cat.forEach(function(s_cat) { appendScat(s_cat) }) }) }) })controller
categories_controller.rb<!--関係ないアクションは省略しています--> def search if params[:l_cat] @m_cat = Category.find(params[:l_cat]).children else @s_cat = Category.find(params[:m_cat]).children end respond_to do |format| format.html format.json end endjbuilder
search.json.jbuilderjson.array! @m_cat do |m_cat| json.id m_cat.id json.name m_cat.name end json.array! @s_cat do |s_cat| json.id s_cat.id json.name s_cat.name end勉強になったところ
optionタグの追加の方法
optionの追加$("#m_category").append( $("<option>") .val($(m_cat).attr('id')) .text($(m_cat).attr('name')) ) }こういう風に書くことで、selectタグのoptionが追加できることを知りました。
始めはselectを追加すればいいじゃん!と思ってましたが、配列形式で渡ってくるため、配列の中身分selectが増えてしまい、上手な方法が浮かばなかったので、こちらの方法にしました。参考記事
・jQuery でセレクトボックスのプルダウン項目(option 要素)を追加/削除する方法
https://webllica.com/jquery-select-option-add-del/まとめ
なんとか狙った機能は実装できましたが、MカテゴリーとSカテゴリーで二回書いているようなコードになっているので、リファクタリングしたいけどどうしていいのかわからん・・・
- 投稿日:2019-03-02T15:51:11+09:00
csv をパースする JavaScript
車輪の再発明です
CSV の仕様
改行は
CRLF
でなければならないそうです。実装例
function parseCSV(d) { let t = d.replace(/\r\n$/u, '') const r = [[]] let c = 0 let l, m, n while ([l, m, n] = /^(|[^",](?:[^,\r]|\r[^,\n])*\r?|"(?:[^"]|"")*")(,|\r\n|$)/u.exec(t)) { if ('"' === m[0] && '"' === m[m.length - 1]) m = m.slice(1, -1) r[c].push(m.replace(/""/gu, '"')) if (!n) break if (',' !== n) r[++c] = [] t = t.substring(l.length) } return r }実装例2
\n
や\r
も改行とみなす方が便利かと思います。function parseCSV(d) { let t = d.replace(/(\r?\n|\r)$/u, '') const r = [[]] let c = 0 let l, m, n while ([l, m, n] = /^(|[^",][^,\r\n]*|"(?:[^"]|"")*")(,|\r?\n|\r|$)/u.exec(t)) { if ('"' === m[0] && '"' === m[m.length - 1]) m = m.slice(1, -1) r[c].push(m.replace(/""/gu, '"')) if (!n) break if (',' !== n) r[++c] = [] t = t.substring(l.length) } return r }
- 投稿日:2019-03-02T15:37:42+09:00
jQueryの書き方 〜初級〜
jQueryの超初歩を備忘録としてメモしておきます。
言語を複数使っていると、書き方がこんがらがってしまったりするので、
『どう書くんだったかな』となった時用です〜(^o^)基本的な書き方
$('セレクタ'・ 'id' ・ 'class').メソッド;
htmlで使用しているセレクタ等を指定して、そのセレクタ等にメソッドを実行する。
以下のコードは、h1を徐々に隠せ!というコード。$(function() { $('h1').fadeOut(); });ちなみに書き出しのfunctionですが、HTMLの読み込みが完了してからjQueryの操作を開始するようにという指定です。
$(function() { // });下のような書き方もできますが、長いのでどちらかというと前者の方が使われますね。
$(document).ready(function(){ // });イベントのタイミングを指定する
$('セレクタ'・'id'・'class').イベント(function(){ hogehoge });
htmlで使用しているセレクタ等を指定して、そのセレクタ等に実行するメソッドを、
発火タイミングを指定して設定する。
以下のコードは、id=hide-text要素がクリックされたら、id=textを下から上にアニメーションで表示させて!というコード。$(function(){ $('#hide-text').click(function(){ $('#text').slideUp(); }); });自身を指定する
$(this)
発火イベントの時に自身を指定すると、イベントが起こった要素を取得することができる。
以下のコードは、クリックされたリストのcssを変更して!というコード。$(function(){ $('li').click(function(){ $(this).css('color', 'red'); }); });変数を宣言する
同じjQueryオブジェクト(これまでで$()で宣言したもの)を複数回使う時は、変数にしましょう!
処理が高速化されます。
いちいち$()と宣言していると、そのオブジェクトを都度読みにいきますが、変数にしていればそのオブジェクトを読みにいかず、変数に格納されているデータを使うからです。
変数宣言にはvarを使います。
以下のコードは \$('div') を\$divに格納しています。
jQueryオブジェクトを格納する時は、わかりやすいように先頭に$をつける慣習があります。var $div = $('div');メソッドチェーン
同じfunction内でメソッドを何回も使いたい場合、メソッドチェーンを使えばスマートに書くことができます。
例えば以下のようなコードは、cssとfadeOutを同じthisに対して使っている場合です。$(function(){ $('li').click(function(){ $(this).css('color', 'red'); $(this).fadeOut(); }); });これは以下のようにメソッドチェーンを使って書くことができます。
$(function(){ $('li').click(function(){ $(this).css('color', 'red').fadeOut(); }); });冗長ではなく綺麗になっていますね!
下階層の要素を取得
find()とchildren()がある。
find()は、要素の下階層にある指定した要素を取得する。
children()は、要素の"1階層下"の指定した要素を取得する。<div is="wrapper"> <a href="#">リンク1</a> <p> <a href="#">リンク2</a> </p> </div>$(function(){ $('#wrapper').find('a') }); //リンク1とリンク2が取得できる $(function(){ $('#wrapper').find('a') }); //リンク1だけ取得できる初級編は以上です!
中級編もまとめています(^^)/
- 投稿日:2019-03-02T15:34:19+09:00
Puppeteerのセットアップから使い方まで〜ブラウザ操作の自動化〜
はじめに
Puppeteerを使ってみたら、とても簡単にブラウザ操作が自動化できたことに感動しました。
セットアップの方法とよく使うPuppeteerのAPIについてまとめましたので、これから使ってみようかなと思っている方の参考になれば嬉しいです。Puppeteerとは
読み方は「ぱぺてぃあ」。日本語だと人形使いという意味です。
Chromeブラウザを操作できるNodeのライブラリで、Chrome DevToolsのチームが開発を行っています。Puppetterの特徴
ブラウザ操作ができるツールとしては、Selenium Webdriverが有名です。
Selenium Webdriverとの違いは、Puppeteerはヘッドレスブラウザを使うことができるので、高速に動作させることができます。
また、PuppeteerはChromeのブラウザしか操作ができません。使ってみる
セットアップ
Node.jsが動く環境が必要です。
$ npm i puppeteerサクッと試したいだけならPuppeteerがWebツールを提供してくれているので、そちらを使うと便利です。
テストを書く
簡単なテストシナリオを書いてみます。
下記はGoogleのトップページに遷移して、画面のスクリーンショットを撮るテストシナリオです。test.jsconst puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://www.google.com'); await page.screenshot({path: 'screenshot.png'}); await browser.close(); })();ベースはこんな感じです。
Browser
のインスタンスを作成した後にPuppeteerのAPIを使って、ページ内のリンクをクリックして別ページに遷移したり、フォームを操作したり、ブラウザを操作していきます。テストを実行する
下記コマンドを実行します。
$ node test.jsよく使うAPI
指定したURLにアクセスする
page.goto('https://www.google.com/');クリック
page.click('input[type="submit"]');クリックする要素の指定には、CSSセレクタを使用します。指定したセレクタが存在しない場合は、エラーになります。
$ node test.js UnhandledPromiseRejectionWarning: Error: Node is either not visible or not an HTMLElement at ElementHandle._clickablePointフォーム操作
<form method="post" action="/update"> <label for="name">名前</label> <input type="text" name="name" id="name"> <label for="email">メールアドレス</label> <input type="text" name="email" id="email"> <label for="gender">性別</label> <select id="gender" name="gender"> <option value="m">男性</option> <option value="f">女性</option> </select> <label for="inquire">問い合わせの内容</label> <input type="radio" name="inquire" value="1" id="inquire_1"> 商品に関する問い合わせ <input type="radio" name="inquire" value="2" id="inquire_2"> その他 <input type="submit" value="送信"> </form>例えば上記のフォームを操作するAPIは以下のとおりです。
// テキストフィールドに値を設定する page.type('input[name="name"]', 'kanoe'); page.type('input[name="email"]', 'kanoe@xxxx.xxx'); // プルダウンから選択する page.select('#gender', 'f'); // ラジオボタンを選択する page.click('#inquire_1');デバイス切り替え
Puppeteerには既にデバイスのリストを定義しているので、そちらを使って切り替えると便利です。
定義されているデバイスは下記コードから確認できます。
https://github.com/GoogleChrome/puppeteer/blob/master/DeviceDescriptors.jsconst puppeteer = require('puppeteer'); const devices = require('puppeteer/DeviceDescriptors'); // iPhone6を指定してみる const iPhone = devices['iPhone 6']; (async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.emulate(iPhone); await page.goto('https://www.ozmall.co.jp/'); await page.screenshot({path: 'example.png'}); await browser.close(); })();デバイスを指定する方法以外にも、
page.setUserAgent
を使うことでUserAgentを直接指定することも可能です。page.setUserAgent('Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30');待機
用途としては、JavaScriptのeventが起きるまで待機するために使うことが多いAPIです。
// imgタグが見つかるまで待機、見つかったらconsoleにログを出力する page.waitFor('img') .then(() => console.log('gazou attayo!')); // 1s待機 page.waitFor(1000)画面のキャプチャ
// fullPageのオプションを指定すると、フルページでスクリーンショットが撮れる page.screenshot({path: 'example.png', fullPage: true});特定の要素に対して操作する
特定の1要素だけ取得して操作する場合は、
page.$eval
を使います。// itemクラスを持つ最初の要素のテキストを取得する const tab = await page.$eval('.item', el => el.textContent);ページ内のセレクタを全て取得したい場合は、
page.$$eval
を使う必要があります。// ページ内の複数のセレクタの内、特定の要素だけをクリックする page.$$eval('#nav > .item', tabs => { tabs.filter(tab => tab.textContent === 'Contact')[0].click(); });感想
UIテストを書くとき、Selenium Webdriver + 何かしらのテストフレームワークでテストをかくことが多いと思います。
わたしも、以前Selenium WebdriverとCodeceptionを使ってE2Eテストを構築したことがあるのですが、ブラウザのドライバの仕様に引っ張られて苦労することが多々ありました。
PuppeteerはChrome開発チームが開発していることもあって、ストレスなくブラウザ操作が実現できるのがとてもよかったです。あと環境構築もとても簡単なので、ちょっと試したい時にすぐ使えるのは便利だなーと思いました。
- 投稿日:2019-03-02T15:18:09+09:00
JavaScriptで動画再生を操作する
動画の設置
<video>
要素の記述htmlの基本的な記述は以下の通り。
<video>
要素のsrc
属性で動画のパスを指定します。動画再生に非対応の古いブラウザ用に、代替テキストや画像を用意するとよいと言われています。その場合は、<video>
要素内に記述します。index.html<video src="./hoge.mp4"> <p>ご使用のブラウザでは動画再生に対応していません</p> </video>HTML5以降では、
<video>
要素内に<source>
要素を記述し、複数のソースを指定することが可能です。その場合は、上から順に<source>
をみていき、再生可能なものが利用されます。index.html<video> <source src="./hoge.mp4" type="video/mp4"> <source src="./hoge.webm" type="video/webm"> <p>ご使用のブラウザでは動画再生に対応していません</p> </video>
<video>
要素に指定できる属性
<video>
要素のcontrols
属性を使用することで、ブラウザに用意されたコントローラを使用することができます。インターフェースはブラウザに依存します。
コントローラーは使用せず、JavaScriptで操作する場合の記述は、次のセクションにまとめています。index.html<video src="./hoge.mp4" controls> <p>ご使用のブラウザでは動画再生に対応していません</p> </video>その他、
<video>
要素には、以下の属性を指定できます。
属性 機能 値 autoplay ロードされたら自動的に再生を開始 なし loop ループ再生する なし muted デフォルトで音量をゼロにする なし preload データのプリロードについての指定 "none"/"metadata"/"auto" poster データが再生可能になるまでに表示させる画像 画像のパス width 高さ 整数値 height 幅 整数値 参考: https://developer.mozilla.org/ja/docs/Web/HTML/Element/video
JavaScriptでの動画再生
JavaScriptで動画を操作する場合の主要な記述を以下にまとめています。
動画の再生操作
index.html<video id="video" src="./hoge.mp4"> <p>ご使用のブラウザでは動画再生に対応していません</p> </video>main.jsvar v = document.getElementById('video'); //再生 v.play(); //一時停止 v.pause(); //ロード v.load();play()
paused
属性をfalse
に設定する、必要に応じリソースをロードする、再生を開始するpause()
再生を中断する、
paused
属性をtrue
に設定する、必要に応じリソースをロードするload()
要素をリセットする、新たなリソースを選択しロードを開始する
JavaScriptでの再生位置の取得
main.js//再生位置の取得 v.addEventListener('timeupdate', function() { if (v.currentTime !== 0) { console.log(timeConvert(v.currentTime)); } else { console.log('0:00'); } }) //数値型から”00:00”表記への変換(秒、ミリ秒の場合) function timeConvert(time) { var sec = Math.floor(time); var msec = ((time - sec) * 100).toFixed(0); return sec + ':' + msec; }currentTimeプロパティ
再生位置を数値型データ(秒単位)で返す
durationプロパティ
メディアの再生時間を数値型データ(秒単位)で返す
再生状態に関するプロパティ
played
: 再生が完了した時間の長さを表すpaused
: 再生が一時停止されているかどうかをBoolean値で表すended
: 再生が終了しているかどうかをBoolean値で表すerror
: 直近で発生したメディアのダウンロード中のエラーをerror
オブジェクトで返す再生の設定に関するプロパティ
loop
: 繰り返し再生の有効・無効をBoolean値で表すcontrols
: 再生をコントロールするユーザインタフェースの表示・非表示をBoolean値で表すpreload
: メディアをプリロードすべきか指定する、none
、metadata
、auto
のいずれかの値をとるautoplay
: htmlのautoplay
属性を反映し、Boolean値で表す再生状態に関するイベントハンドラ
play
: 再生が開始したときtimeupdate
: 再生位置が変化したときpause
: 再生が中断したときplaying
: 再生中断状態から、ふたたび再生可能になったときwaiting
: 次のフレームの受信を待っているときended
: 再生が完了したときerror
: 再生中にエラーが発生したときabort
: エラー以外の原因で再生が停止したときメディアの読み込みステータスの取得
readyStateプロパティ
メディアリソースの読み込みの状態を取得する。戻り値は以下の通り。
0
:HAVE_NOTHING
リソースに関するいかなる情報も利用できない状態
1
: HAVE_METADATA
リソースの情報(ビデオ要素の場合、ビデオの高さ・幅など)を取得済み、再生位置に関するデータは未取得
2
: HAVE_CURRENT_DATA
再生位置に関するデータは取得済み、再生位置より先のフレームデータは未取得
3:
HAVE_FUTURE_DATA
再生位置より先のフレームデータも取得済みで、早送りができる状態
4
: DONE(完了)
3
の状態で、なおかつ、十分なデータがロードできていて、このまま再生しても、再生位置が読み込みデータを追い越さないような状態networkStateプロパティ
メディアリソースの読み込みのネットワークの状態を取得する。戻り値は以下の通り。
0
: NETWORK_EMPTY
リソースに関するいかなる情報も利用できない状態
(redayState
プロパティのHAVE_NOTHINGと同じ状態)
1
: NETWORK_EMPTY
要素がアクティブではあるが、ネットワークは使用されていない状態 (リソースが取得できている)
2
: NETWORK_LOADING
リソースの読み込み中
3
: NETWORK_NO_SOURCE
リソースが見つからないロード状態に関するイベントハンドラ
index.html<video id="video"> <source src="./hoge.mp4" type="video/mp4"> <p>ご使用のブラウザでは動画再生に対応していません</p> </video> <p id="state"></p>main.jsdocument.addEventListener('DOMContentLoaded', function() { var v = document.getElementById('video'); var state = document.getElementById('state'); //ロード開始 v.addEventListener('loadedmetadata', function() { state.textContent = 'ロードを開始しました'; }) //読み込み完了 v.addEventListener('loadeddata', function() { state.textContent = '読み込み完了しました'; }) //再生可能 v.addEventListener('canplay', function() { state.textContent = '再生可能です'; }) //再生中 v.addEventListener('playing', function() { state.textContent = '再生中です'; })参考
https://developer.mozilla.org/ja/docs/Web/HTML/Element/video
https://developer.mozilla.org/ja/docs/Web/API/HTMLMediaElement
- 投稿日:2019-03-02T14:31:05+09:00
object
https://javascript.info/object
オブジェクトの左側と右側の読み方がわからなかったので、JSの基礎を調べる。
{a: hoge}みたいなやつ結論
{key: value}でした。
ぐぐるワード
js object検索結果一番目
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects
オブジェクトだけど、このオブジェクトじゃない。次。ぐぐるワード
js object keys検索結果一番目
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keysThe Object.keys() method returns an array of a given object's own property names, in the same order as we get with a normal loop.
うーん、ちょっと理解に苦しむ。次、ぐぐるワード
js object key value pairs検索結果2番目
https://javascript.info/object
これっぽい。An object can be created with figure brackets {…} with an optional list of properties. A property is a “key: value” pair, where key is a string (also called a “property name”), and value can be anything.
keyとvalueのペアですっぽいこと言ってる。
これは5分で見つけた。いい感じ。
- 投稿日:2019-03-02T14:22:26+09:00
js {}
https://cointrader.co.jp/botdev/
勉強中です
疑問
const {trade} = require('cointrader')はい、意味が分かりません
ググったワード
const {}検索結果4番目
https://ushumpei.hatenablog.com/entry/2016/06/29/032906複雑で理解できませんでした。が、
・分割代入である
・分割代入 - JavaScript | MDN<=このページをみればよい
ということがわかりました。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
オブジェクトの分割代入という項目を発見
これこれ、こういうのが欲しいの簡単な例
var o = {p: 42, q: true}; var {p, q} = o; console.log(p); // 42 console.log(q); // true
完全に理解した。超簡単に書くと
{a} = {a: "hoge"}で、右辺にオブジェクト、左辺にオブジェクトのキーを書けば、値を取得できる。
ここで、objectってkeyとvalueのペアであっているのかという疑問が生じる。。。
私の頭は容量が少なすぎて、簡単な言葉で説明できないと記憶してくれません。てことで後で調べる。ということで試しにオンラインエディタで実行
https://playcode.io/online-javascript-editor
const {a} = {a: "hello"} console.log(a); --- helloはい、OK
ここまで10分でできたOK、OK
てことで次はObjectについて。
- 投稿日:2019-03-02T14:22:26+09:00
js 分割代入 {}
https://cointrader.co.jp/botdev/
勉強中です
疑問
const {trade} = require('cointrader')はい、意味が分かりません
ググったワード
const {}検索結果4番目
https://ushumpei.hatenablog.com/entry/2016/06/29/032906複雑で理解できませんでした。が、
・分割代入である
・分割代入 - JavaScript | MDN<=このページをみればよい
ということがわかりました。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
オブジェクトの分割代入という項目を発見
これこれ、こういうのが欲しいの簡単な例
var o = {p: 42, q: true}; var {p, q} = o; console.log(p); // 42 console.log(q); // true
完全に理解した。超簡単に書くと
{a} = {a: "hoge"}で、右辺にオブジェクト、左辺にオブジェクトのキーを書けば、値を取得できる。
ここで、objectってkeyとvalueのペアであっているのかという疑問が生じる。。。
私の頭は容量が少なすぎて、簡単な言葉で説明できないと記憶してくれません。てことで後で調べる。ということで試しにオンラインエディタで実行
https://playcode.io/online-javascript-editor
const {a} = {a: "hello"} console.log(a); --- helloはい、OK
ここまで10分でできたOK、OK
てことで次はObjectについて。
- 投稿日:2019-03-02T13:13:28+09:00
HTML canvas で画像を切り取る(crop) Javascript サンプルコード
HTML canvas & Javascript を使った画像の切り抜き「Crop」のサンプルコードです。
ブラウザ上で画像を加工しようとしたら、canvas しかないわけですが、
いちばんやりたいのは「切り抜き:Crop」なんだけど、まぁ、シンプルなコードがほしかったのに無いわけで。
無いときは自分で作るのがエンジニアの掟なわけです。切り取る(crop) Demo
中央の枠の中を切り出すタイプ。先に切り出す枠を決めておいて、画像の方を確認しながら切り出す。
なんと言いますか、マウスで角から角へドラッグしたり、枠をつまんで移動するUIよりも、このほうが直感的でわかりやすいし、スマホとかタブレットにも向いているUIだと思うのです。Crop してますか?
実際に触ってみてもらうのが早いでしょう。⬇︎
- canvas 上で、マウスをドラッグすると切り抜く場所を変えられます。
- canvas 上で、マウスのホイールをグリグリすると拡大率が変わります。
- 「CROP」ボタンで、赤枠の部分が切り抜きされます。
- 上部の「A」「B」「C」のボタンで画像が変わります。
See the Pen HTML crop canvas by yamazaki.3104 (@yamazaki3104) on CodePen.
実際のコードがこちら
Qiita を見ているひとたちに、細かい解説は不要だとは思うので、ざっと概要のみ書く
主な canvas は編集用の id='cvs' と、ボタン「CROP」を押したあとに編集結果が入る id='out' の2つ
ボタン「A」「B」「C」は入力画像の変更を行う load_img() を呼ぶだけ。読み込まれた画像は img に入っていて、img は読み出すだけで加工はしない。
スライダーは id='scal' 画像の拡大縮小率を変えられるように用意したが、最終的にホイールイベントに対応したので、UIとしてはあまり役に立っていないかもしれない
画像の切り抜き処理は drawImage() がやってます
特に難しいことは何もやっていない、平凡なコードだと思っています。マウスのドラッグで画像の位置を変えるコードがいちばん大変だった、、、とはいっても、HTMLも入れて90行程度なので、、、まぁ、半日くらいの作業量でしょうか。
<html> <body> <button onclick="load_img('https://lh3.ggpht.com/O0aW5qsyCkR2i7Bu-jUU1b5BWA_NygJ6ui4MgaAvL7gfqvVWqkOBscDaq4pn-vkwByUx=w300')">A</button> <button onclick="load_img('https://www.mozilla.org/media/img/logos/firefox/logo-quantum-wordmark-white.bd1944395fb6.png')">B</button> <button onclick="load_img('')">C</button><br> <input id='scal' type='range' value='' min='10' max='400' oninput="scaling(value)" style='width: 300px;'><br> <canvas id='cvs' width='300' height='400'></canvas><br> <button onclick='crop_img()'>CROP</button><br> <canvas id='out' width='200' height='200'></canvas> <script> const cvs = document.getElementById( 'cvs' ) const cw = cvs.width const ch = cvs.height const out = document.getElementById( 'out' ) const oh = out.height const ow = out.width let ix = 0 // 中心座標 let iy = 0 let v = 1.0 // 拡大縮小率 const img = new Image() img.onload = function( _ev ){ // 画像が読み込まれた ix = img.width / 2 iy = img.height / 2 let scl = parseInt( cw / img.width * 100 ) document.getElementById( 'scal' ).value = scl scaling( scl ) } function load_img( _url ){ // 画像の読み込み img.src = ( _url ? _url : 'https://1.bp.blogspot.com/-AoQB8vIvlcw/W8BOcXcEQ6I/AAAAAAABPZM/rXNbol90tXcxBZBlXsg__xix03b_F4nqwCLcBGAs/s800/pet_cat_omoi_sleep_man.png' ) } load_img() function scaling( _v ) { // スライダーが変った v = parseInt( _v ) * 0.01 draw_canvas( ix, iy ) // 画像更新 } function draw_canvas( _x, _y ){ // 画像更新 const ctx = cvs.getContext( '2d' ) ctx.fillStyle = 'rgb(200, 200, 200)' ctx.fillRect( 0, 0, cw, ch ) // 背景を塗る ctx.drawImage( img, 0, 0, img.width, img.height, (cw/2)-_x*v, (ch/2)-_y*v, img.width*v, img.height*v, ) ctx.strokeStyle = 'rgba(200, 0, 0, 0.8)' ctx.strokeRect( (cw-ow)/2, (ch-oh)/2, ow, oh ) // 赤い枠 } function crop_img(){ // 画像切り取り const ctx = out.getContext( '2d' ) ctx.fillStyle = 'rgb(200, 200, 200)' ctx.fillRect( 0, 0, ow, ow ) // 背景を塗る ctx.drawImage( img, 0, 0, img.width, img.height, (ow/2)-ix*v, (oh/2)-iy*v, img.width*v, img.height*v, ) } let mouse_down = false // canvas ドラッグ中フラグ let sx = 0 // canvas ドラッグ開始位置 let sy = 0 cvs.onmousedown = function ( _ev ){ // canvas ドラッグ開始位置 mouse_down = true sx = _ev.offsetX sy = _ev.offsetY } cvs.onmouseout = cvs.onmouseup = function ( _ev ){ // canvas ドラッグ終了位置 if ( mouse_down == false ) return mouse_down = false draw_canvas( ix += (sx-_ev.offsetX)/v, iy += (sy-_ev.offsetY)/v ) } cvs.onmousemove = function ( _ev ){ // canvas ドラッグ中 if ( mouse_down == false ) return draw_canvas( ix + (sx-_ev.offsetX)/v, iy + (sy-_ev.offsetY)/v ) } cvs.onmousewheel = function ( _ev ){ // canvas ホイールで拡大縮小 let scl = parseInt( document.getElementById( 'scal' ).value ) document.getElementById( 'scal' ).value = scl + _ev.wheelDelta scaling( scl + _ev.wheelDelta ) return false // イベントを伝搬しない } </script> </body> </html>動作確認環境
いまのところ
- Chrome バージョン: 72.0.3626.109(Official Build) (64 ビット)
- Safari バージョン 12.0.2 (14606.3.4)
でしか確認してません。ごめんなさい。
他の環境で確認できたら、追記します。2019/03/03: 追記
Firefox 65.0.2 で確認したらホイールのイベントが取れてなかった、、、なぜ??
(原因不明の助)
- 投稿日:2019-03-02T12:16:54+09:00
takeWhile はメモリリーク (無駄なメモリ消費) の原因になり得る
takeWhile
に参照透過でない式を渡すのは良くない。上記が分かっていれば下記を読む必要はありません。
経緯
React開発ノウハウメモ(随時更新)
上記記事を読んでunsubscribe
をcomponentWillUnmount
に書かない方法を知ったので調べてみた。1
takeWhile
はメモリリークの原因になり得る。
takeWhile
は値が来たときのみ実行されるので値が来なければunsubscribe
されない。
無駄なネットワーク通信やDBへのアクセスを続けてしまう。
takeWhile
は値に応じて処理を続けるべきか決まる場合で使うべき。
Observable
を流れる値と関係なく処理を止めたいならunsubscribe
を呼ぶかtakeUntil
を使う。テストしてみる
実際に試してみるためのコードを書いた。
それらで下記の 3 つの関数を使っている。utils.ts// s 秒待つ const sleep = (s: number) => new Promise(r => setTimeout(r, s * 1000)) // タイムスタンプをつけてログを出す const log = (...v: any[]) => { const ts = (performance.now() / 1000) | 0 console.log(...v, `(${ts})`) } // キャンセルできる Observable を生成 const make = (name: string) => { return new Observable<number>(s => { let canceled = false const push = (v: number) => { log(name, 'sub1', v, '- canceled', canceled) s.next(v) } Promise.resolve().then(async () => { push(12) // すぐに 12 を渡す await sleep(10) // 10 秒待ってから push(13) // 13 を渡す push(14) // 14 を渡す }) return () => { log(name, 'unsub1') canceled = true } }) }テスト内容
上記
make
でObservable
を生成したのち 1 秒後に処理を止める。
unsubscribe
する一般的なやりかたであると思われる。
問題なく動いている。test1.tsconst test1 = async () => { const read = make('test1').pipe( tap(v => log('test1', 'sub2', v)), ).subscribe() await sleep(1) read.unsubscribe() } test1() /* test1 sub1 12 - canceled false (0) test1 sub2 12 (0) test1 unsub1 (1) test1 sub1 13 - canceled true (10) test1 sub1 14 - canceled true (10) */
takeWhile
を使う1 秒後に止めようとしたが、その後 9 秒経って次の値が来るまで止まっていない。
test2.tsconst test2 = async () => { let alive = true make('test2').pipe( takeWhile(() => alive), tap(v => log('test2', 'sub2', v)), ).subscribe() await sleep(1) alive = false } test2() /* test2 sub1 12 - canceled false (0) test2 sub2 12 (0) test2 sub1 13 - canceled false (10) test2 unsub1 (10) test2 sub1 14 - canceled true (10) */
takeUntil
を使う問題なく動く。
test3.tsconst test3 = async () => { const unsub = new Subject<void>() make('test3').pipe( takeUntil(unsub), tap(v => log('test3', 'sub2', v)), ).subscribe() await sleep(1) unsub.next() unsub.complete() } test3() /* test3 sub1 12 - canceled false (0) test3 sub2 12 (0) test3 unsub1 (1) test3 sub1 13 - canceled true (10) test3 sub1 14 - canceled true (10) */
unsubscribe
とtakeUntil
、どちらを使うべきか?こちらの記事 (英語)では
takeUntil
がオススメされている。手続き的な
unsubscribe
よりも宣言的なtakeUntil
が良いという判断だろうか?補足
React
とRxJS
を組み合わせる場合大規模なアプリなら
redux-observable
を、小規模ならrxjs-hooks
を使うことを勧める。
古いReact
を使っているならrecompose
も良い。
useEffect
のある今のReact
でcomponentWillUnmount
のような古い方法を使う必要はないと思う。 ↩
- 投稿日:2019-03-02T12:11:04+09:00
書いて覚える ESLint ルールの作り方:キマリは通さない
はじめに
JavaScript の Linter である ESLint のルールを自作しそれを動かす方法を作りながら調べました。
この記事はその備忘録の第一回です。最初なのでまずは単純な ESLint ルールを作ります。そしてそれをローカルルールとして動かしてみます。
Linter とは
プログラムコードのクオリティを担保するため、開発チーム内でコーディングルールが決められます。
しかし、チームレビューなどの目視による確認方法では、見落としが発生することが容易に想像できます。そこでモダンな開発チームでは、静的コード解析ツール (Linter) を用いて自動的に機械にチェックさせています。そうすることで見落としを防ぐことができます。便利ですね。
例えるなら...
あなたのプロジェクトはガガゼト山です。ここに召喚士一行が通りかかりますが、あなたは次のルールを課しています:
召喚士は通す。 ガードも通す。 キマリは通さない。
しかし、24 時間 365 日、キマリが通るかどうかを一人で監視することは難しいです。立て看板を置いておきますか? キマリは無視して通ってしまうかもしれません。他のロンゾ族に伝えておきますか? もしかしたらそのロンゾ族は、反逆者である召喚士が己の身を捧げる覚悟を聞いてキマリを通してしまうかもしれません。
そこでビランとエンケに頼みます。ビランとエンケは絶対にキマリを通しません。召喚士を通しても、ガードを通しても、キマリは通しません。いつでもガガゼト山の道を一族の誇りをかけて監視しており、通りがかったキマリを見つけては過去の因縁を持ち出して弄ったり二対一で戦ったりして通せんぼします。二人はキマリを通さないことにかけてはエキスパートです。
しかし、二人はキマリの成長した強さに感服してキマリを通してしまいます。
というわけで
キマリを通さない ESLint ルールを作ります。
要件
すでに汎用的な ESLint のルールは広く公開されており、大抵の場合はそれらを組み合わせて適用すれば事足ります。ですがその中にキマリを通さない ESLint ルールはありません。なので自作しましょう。
ここではコード中の文字列リテラルに「キマリ」が含まれている場合にエラーにします。
ちなみに、文字列リテラル内で「キマリ」と連続していなければならないことにします。仮に「キ」と「マ」と「リ」を別個に宣言して後から結合しても、ガガゼト山を通ろうとしているのは「キマリ」ではなく「キ」と「マ」と「リ」に分割されたものだからです。
実装
まずは
eslint
を install しましょう。コードは AST (抽象構文木)と呼ばれる、コードをパースした JSON で ESLint に入ってきます(AST のわかりやすい解説)。コードは抽象化され、例えば
""
と''
のような書き方の差異は構文木の構造自体には現れなくなります。それに JSON 形式なのでプログラムで簡単に扱うことができます。今回の要件では
Literal
ノードを見て、そのvalue
が文字列かつ「キマリ」が含まれているかチェックするだけで OK です。エラー地点はLiteral
ノード、メッセージは「キマリは通さない」にします。rules/kimahri-not-pass.js"use strict"; module.exports = { create: function(context) { return { Literal: function(node) { if ( typeof node.value == "string" && node.value.indexOf("キマリ") !== -1 ) { context.report({ node: node, message: "キマリは通さない" }); } } }; } };テスト
正しく動くかどうかテストを書きます。テストには
mocha
を利用し、次のような形式で簡単に書くことができます。通すルールは
valid
に書きます。まずはvar a = 1;
という簡単な式が通ることを書いておきましょう。後は召喚士は通す、ガードも通すことも書いておきます。通さないルールである
invalid
でキマリを通さないことを記述します。tests/kimahri-not-pass.js"use strict"; const RuleTester = require("eslint").RuleTester; const rule = require("../rules/kimahri-not-pass"); const tester = new RuleTester(); tester.run("kiomahri-not-pass", rule, { valid: [{ code: "var a = 1;" }, { code: '"召喚士"' }, { code: '"ガード"' }], invalid: [{ code: '"キマリ"', errors: ["キマリは通さない"] }] });テストスクリプトは
mocha "tests/**/*.js" --reporter dot
です。無事通過すれば作成完了です。ルールを動かす
実際に開発しているような環境でルールを動かして確かめないと納得がいきません。実際に src ファイルを書いて、それに対して lint をかけてみます。
キマリを通さないのはガガゼト山だけでいいので、パッケージとしてではなくプロジェクト内のローカルルールとして動かします。ローカルルールを
.eslintrc.js
に書くためにeslint-plugin-rulesdir
を add します(--rulesdir
オプションでもいいんですが後述の VSCode で動かすために plugin で書きます)。
.eslintrc.js
は次のように書きます:.eslintrc.jsconst rulesDirPlugin = require("eslint-plugin-rulesdir"); rulesDirPlugin.RULES_DIR = "rules"; module.exports = { parserOptions: { ecmaVersion: 6 }, plugins: ["rulesdir"], rules: { "rulesdir/kimahri-not-pass": "error" } };src ファイルは次の通りです。
src/kimahri-not-pass.js"use strict"; const a = "キマリは通さない";
eslint ./src
を打てばちゃんとエラーとして検出されました。VSCode でルールを動かす
さらに VSCode の ESLint 拡張を使って動作するようにします。
上で書いた通りコマンドオプションでなく
eslint-plugin-rulesdir
でルールの場所を教えてあげているので ESLint 拡張もローカルルールを読み込むことができます。これが次のように表示されれば成功です。動かない時は VSCode を再起動するといいかと思います。
終わりに
今回、簡単な ESLint のルールを作って、それをローカルルールとして動作させる方法を学びました。
実際のプロダクトにおいてはキマリを通さないルールを課すことはないでしょうし、キマリは実力を示すことでガガゼト山を通過することを認められます。
これを足がかりに、次回はきちんとプロジェクトで使えそうなルールを書く予定です。
参考にしたサイト
・ルールの書き方
https://qiita.com/mysticatea/items/cc3f648e11368799e66c
https://techblog.yahoo.co.jp/javascript/how-to-create-eslint-rules・AST のわかりやすい解説
https://efcl.info/2016/03/06/ast-first-step/・実際にコードを書いて AST を見られるツール
https://astexplorer.net・公式
https://eslint.org/docs/developer-guide/working-with-rulesあと実際に公開されているルールのコードを見ると勝てます。
- 投稿日:2019-03-02T12:02:19+09:00
Javascriptで、公開鍵ペア生成・暗号化・復号をしてみた
node.jsでRSA公開鍵を使った鍵ペア生成や暗号化/復号をする例はたくさんあったのですが、javascriptでの例はあまりなかったのでやってみました。
途中、はまってしまったので、備忘録として残しておきます。とはいいつつも、なんてことはない、browserifyを使っただけです。。。
使わせてもらったnpmモジュールは「node-rsa」です。PureJavascriptという特徴に惹かれました。rzcoder/node-rsa
https://github.com/rzcoder/node-rsa作成するモジュールの仕様
ブラウザ上のJavascriptから以下関数を呼び出せるモジュールとします。
- RSA公開鍵ペアの生成
- 公開鍵による暗号化
- 秘密鍵による復号
- PEM形式をDER形式に変換
- DER形式をPEM形式に変換
ちなみに、暗号化時のパディング方式は「RSA_PKCS1_OAEP_PADDING」とします。PKCS#1 v2.0 with SHA-1のことです。
node.jsでモジュールを作成する
まずは、node.jsで期待する動作となることを確認しましょう。
npm init -y
npm install --save node-rsa
vi main.jsmain.jsconst NodeRSA = require('node-rsa'); function generateKeyPair(bits){ const key = new NodeRSA({b: bits}); var priv = key.exportKey('pkcs1-private-der'); var pub = key.exportKey('pkcs1-public-der'); return { public: pub, private: priv }; } function pem2der(scheme, pem){ const key = new NodeRSA(pem, scheme + '-pem'); return key.exportKey(scheme + '-der'); } function der2pem(scheme, key){ var der = Buffer.from(key); const rsa = new NodeRSA(der, scheme + '-der'); return rsa.exportKey(scheme + '-pem'); } function publicEncrypt(key, buffer){ var input = Buffer.from(buffer); var der = Buffer.from(key); const rsa = new NodeRSA(der, 'pkcs1-public-der', { encryptionScheme : 'pkcs1_oaep' }); var enc = rsa.encrypt(input); return enc; } function privateDecrypt(key, buffer){ var input = Buffer.from(buffer); var der = Buffer.from(key); const rsa = new NodeRSA(der, 'pkcs1-private-der', { encryptionScheme : 'pkcs1_oaep' }); var dec = rsa.decrypt(input); return dec; } module.exports = { generateKeyPair: generateKeyPair, publicEncrypt: publicEncrypt, privateDecrypt: privateDecrypt, der2pem: der2pem, pem2der: pem2der, };ちなみに、はまった点を示しておきます。
publicEncryptやprivateDecryptでは、以下のような一見不要な処理をしています。ですが、なぜかこの処理を入れないと、ブラウザのJavascriptから呼び出したときにうまく復号してくれなかったんです。原因不明です。とりあえず動いているのでいいですが。。
var input = Buffer.from(buffer);
次は、それを呼び出すサンプルコードです。
index.jsvar rsab = require('./main'); var key = rsab.generateKeyPair(2048); console.log('private=', key.private); console.log('public=', key.public); var buffer = new Uint8Array(10); var enc2 = rsab.publicEncrypt(key.public, buffer); console.log('enc2=', enc2); var dec2 = rsab.privateDecrypt(key.private, enc2); console.log('OK? dec2=', dec2); var pem = rsab.der2pem('pkcs1-private', key.private); console.log(pem); var der = rsab.pem2der('pkcs1-private', pem); console.log(der); var pem = rsab.der2pem('pkcs1-public', key.public); console.log(pem); var der = rsab.pem2der('pkcs1-public', pem); console.log(der);以下のような感じで出力されましたでしょうか?
private= <Buffer 30 82 02 5b 02 01 00 02 81 81 00 98 db 26 9b 37 a0 ec 95 d5 19 84 9e 51 64 96 0b 01 2a 90 aa ee b8 39 26 5d b0 b8 32 12 c1 dd 2d b8 cd 1b ea 69 1f 7c ... > public= <Buffer 30 81 89 02 81 81 00 98 db 26 9b 37 a0 ec 95 d5 19 84 9e 51 64 96 0b 01 2a 90 aa ee b8 39 26 5d b0 b8 32 12 c1 dd 2d b8 cd 1b ea 69 1f 7c 4c 1e d3 9a ... > enc2= <Buffer 1c fb e1 02 95 a6 4c b4 6f 4a 22 20 10 47 94 1d 0a ca d1 77 9a 4f 28 a8 8d 66 e7 cb 7c b7 cb 13 3e 07 3a a1 75 0d be 94 25 c7 ef fc 3e be de 6e f4 eb ... > OK? dec2= <Buffer 00 00 00 00 00 00 00 00 00 00> -----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQCY2yabN6DsldUZhJ5RZJYLASqQqu64OSZdsLgyEsHdLbjNG+pp H3xMHtOaJxrMOQh4hABGUiU5KS1gqT88c33ymvMw2hBm4ArdJ59n9y+PSIAKIdMO 9CNR5I4ONj59cbL3uaIZdPmmXZ2ApHU83nUVv1pqflULRMoEflGsMgfaRwIDAQAB AoGATmllZYxk1diKx5tbZ590xNJlVm8JJKHUh41HABDVODPjL/yZBDItnhLWM6bJ vWeoa8IzLd+nzqnL6GEJ2mXYflUiTTmeHr6Tgd+AtiMzmw9F9M6SDtKbLo9thHjz ss31l6q18/o4lhOHxltcA1k0ns/ou2Ys5nre90/YtO65vdECQQDmbJdNw7OaLSyC z1HRnfQ1wPTSsRtUzDPVVcirnssRgzPQXjwX+sJonG6Plt6Ymk3raAKYfj4/4eKp wXSKgGWPAkEAqdJ+QealDvocx9n3L5WIcN1QKsQBbjW/lCZW9eBLY5J2eX2/KXCH 1UB4ycL4/sGJnnrKiEJcmMwAfJ7cuymTyQJAaN3O1kNUf7xDX5z+vYlHVRdl1GVp 7OOqS1LpKsHv5R5y/EeGzdr/kyBuaMbes1C2WYZSeBgsOGKTB3LVmRrqHQJAUrux tvlewIUpMFQIEK6Q4itvuuXyrHaS9uIWy4KSN9hKz7VrRA0Gn+Xg8qqCE9rF6Od5 dBigc/Au7IeYkbYXwQJAcBdeq8OZvdQ93aZkBWntcYFV4N82XfulIYD3EbP+Fi9G dUWWUtKzsJuby9vrb5yCiJgHO16GTH7RqGFJyd124A== -----END RSA PRIVATE KEY----- <Buffer 30 82 02 5b 02 01 00 02 81 81 00 98 db 26 9b 37 a0 ec 95 d5 19 84 9e 51 64 96 0b 01 2a 90 aa ee b8 39 26 5d b0 b8 32 12 c1 dd 2d b8 cd 1b ea 69 1f 7c ... > -----BEGIN RSA PUBLIC KEY----- MIGJAoGBAJjbJps3oOyV1RmEnlFklgsBKpCq7rg5Jl2wuDISwd0tuM0b6mkffEwe 05onGsw5CHiEAEZSJTkpLWCpPzxzffKa8zDaEGbgCt0nn2f3L49IgAoh0w70I1Hk jg42Pn1xsve5ohl0+aZdnYCkdTzedRW/Wmp+VQtEygR+UawyB9pHAgMBAAE= -----END RSA PUBLIC KEY----- <Buffer 30 81 89 02 81 81 00 98 db 26 9b 37 a0 ec 95 d5 19 84 9e 51 64 96 0b 01 2a 90 aa ee b8 39 26 5d b0 b8 32 12 c1 dd 2d b8 cd 1b ea 69 1f 7c 4c 1e d3 9a ... >当然ながら、実行ごとに鍵ペアの鍵値や暗号文の値は異なります。
browserify化する
まだbrowserifyをインストールしていない場合は、インストールしておきます。
npm install browserify -g
それではJavascriptのモジュールを作成します。
browserify -r ./main.js:node-rsa-js -o node-rsa-js.js
これで、node-rsa-js.jsというJavascriptファイルが出来上がりました。-rで指定した「node-rsa-js」がJavascriptからrequireする名前になります。
ブラウザから使ってみる
以下のようなサンプルHTMLファイルを作成します。
index.html<html> <head> </head> <body> <script src="node-rsa-js.js"></script> <script> var rsab = require('node-rsa-js'); var key = rsab.generateKeyPair(1024); console.log('private=', key.private); console.log('public=', key.public); var buffer = new Uint8Array(10); var enc2 = rsab.publicEncrypt(key.public, buffer); console.log('enc2=', enc2); var dec2 = rsab.privateDecrypt(key.private, enc2); console.log('OK? dec2=', dec2); var pem = rsab.der2pem('pkcs1-private', key.private); console.log(pem); var der = rsab.pem2der('pkcs1-private', pem); console.log(der); var pem = rsab.der2pem('pkcs1-public', key.public); console.log(pem); var der = rsab.pem2der('pkcs1-public', pem); console.log(der); </script> </body> </html>この
index.html
と同じフォルダに先ほど作成したnode-rsa-js.js
を置きます。早速、ブラウザから表示してみましょう。その時、各ブラウザのConsoleをのぞいてみてください。
以下のような感じで表示されれば成功です。private= Uint8Array(607) [48, 130, 2, 91, 2, 1, 0, 2, 129, 129, 0, 135, 247, 194, 56, 19, 233, 127, 19, 147, 129, 65, 237, 175, 243, 152, 253, 56, 138, 53, 125, 92, 80, 238, 56, 162, 176, 145, 208, 171, 170, 67, 7, 180, 223, 138, 45, 41, 210, 92, 189, 81, 30, 52, 182, 180, 204, 22, 121, 80, 52, 11, 15, 134, 141, 167, 65, 149, 99, 59, 220, 57, 95, 87, 118, 213, 114, 177, 49, 174, 18, 88, 184, 11, 165, 220, 15, 137, 140, 159, 116, 207, 152, 39, 71, 161, 24, 217, 108, 192, …] (index):10 public= Uint8Array(140) [48, 129, 137, 2, 129, 129, 0, 135, 247, 194, 56, 19, 233, 127, 19, 147, 129, 65, 237, 175, 243, 152, 253, 56, 138, 53, 125, 92, 80, 238, 56, 162, 176, 145, 208, 171, 170, 67, 7, 180, 223, 138, 45, 41, 210, 92, 189, 81, 30, 52, 182, 180, 204, 22, 121, 80, 52, 11, 15, 134, 141, 167, 65, 149, 99, 59, 220, 57, 95, 87, 118, 213, 114, 177, 49, 174, 18, 88, 184, 11, 165, 220, 15, 137, 140, 159, 116, 207, 152, 39, 71, 161, 24, 217, 108, 192, 225, 181, 95, 94, …] (index):15 enc2= Uint8Array(128) [29, 169, 149, 166, 146, 167, 162, 135, 62, 94, 187, 65, 22, 38, 205, 152, 249, 187, 73, 173, 52, 92, 96, 143, 31, 10, 48, 169, 210, 168, 227, 253, 216, 79, 159, 110, 13, 66, 85, 133, 22, 120, 234, 216, 188, 133, 149, 29, 252, 221, 146, 158, 98, 149, 83, 28, 85, 97, 17, 46, 246, 216, 100, 119, 134, 119, 31, 152, 39, 23, 30, 125, 97, 189, 242, 22, 215, 106, 44, 189, 26, 110, 241, 140, 146, 235, 12, 116, 119, 120, 230, 12, 195, 135, 246, 164, 13, 163, 141, 115, …] (index):17 OK? dec2= Uint8Array(10) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] (index):20 -----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQCH98I4E+l/E5OBQe2v85j9OIo1fVxQ7jiisJHQq6pDB7Tfii0p 0ly9UR40trTMFnlQNAsPho2nQZVjO9w5X1d21XKxMa4SWLgLpdwPiYyfdM+YJ0eh GNlswOG1X14XtKAWQNJmSlu4toOmTMHwheoXK9r0H3V2oKhzVW7FBQBaEwIDAQAB AoGACSWw6kbkqYjunn6J+BFiVH7ORYT2hfdQ1hkc5jX2UWYeU/+exDaDyLTBXHiC N0y3wp6Ns/CwbPUrkK6BRVbVsm7NUAKVJqhjUEGlsSjoTqnvBNCBTe/hqbpfZNFr VVUOkPHxOSDVttJtPJydExwDBq/dbibaTOqlAsmIRrfNMhECQQDEG19ngx1L/1V5 QAzxs5jylRV8AdAFHBZIIjhkfTv0jvY1AvzP3x/GTdTQfsgm0i79Q/dajvTgqWZL 0uXVDS8bAkEAsX5oJmuu/o5Jxdc+4sewgNp9H2RVCytj0viNmM0SxXj/9z8BT1rh +TTjFlJi5Nzf08W6J4X2HI2SFiWVNeCYaQJAYsdy593oPOaRNDDqFDklulMT7lGv aVN7ebwa8asPLyFvrMYjd8Vo6Oc9YgPqZ3uJhbLdfjTx7dQGHf1OHWG1rwJAF19j 65tGgfLOuCSt/UL6dR1QWK/nmv7tuDxOuQ5YKHTUkARV8dMv6pcSSEA1EZocR1DE QKwdSolB8XSlf2DB8QJAX5IGTRZCz3qKQ9ghPfYKa45QT9M2uB0MX0cazgPUNrQf 0IGlzH1L7LSVGaGNkM3XYR2RxIVGn46Dv3g1REwAhQ== -----END RSA PRIVATE KEY----- (index):22 Uint8Array(607) [48, 130, 2, 91, 2, 1, 0, 2, 129, 129, 0, 135, 247, 194, 56, 19, 233, 127, 19, 147, 129, 65, 237, 175, 243, 152, 253, 56, 138, 53, 125, 92, 80, 238, 56, 162, 176, 145, 208, 171, 170, 67, 7, 180, 223, 138, 45, 41, 210, 92, 189, 81, 30, 52, 182, 180, 204, 22, 121, 80, 52, 11, 15, 134, 141, 167, 65, 149, 99, 59, 220, 57, 95, 87, 118, 213, 114, 177, 49, 174, 18, 88, 184, 11, 165, 220, 15, 137, 140, 159, 116, 207, 152, 39, 71, 161, 24, 217, 108, 192, …] (index):24 -----BEGIN RSA PUBLIC KEY----- MIGJAoGBAIf3wjgT6X8Tk4FB7a/zmP04ijV9XFDuOKKwkdCrqkMHtN+KLSnSXL1R HjS2tMwWeVA0Cw+GjadBlWM73DlfV3bVcrExrhJYuAul3A+JjJ90z5gnR6EY2WzA 4bVfXhe0oBZA0mZKW7i2g6ZMwfCF6hcr2vQfdXagqHNVbsUFAFoTAgMBAAE= -----END RSA PUBLIC KEY----- (index):26 Uint8Array(140) [48, 129, 137, 2, 129, 129, 0, 135, 247, 194, 56, 19, 233, 127, 19, 147, 129, 65, 237, 175, 243, 152, 253, 56, 138, 53, 125, 92, 80, 238, 56, 162, 176, 145, 208, 171, 170, 67, 7, 180, 223, 138, 45, 41, 210, 92, 189, 81, 30, 52, 182, 180, 204, 22, 121, 80, 52, 11, 15, 134, 141, 167, 65, 149, 99, 59, 220, 57, 95, 87, 118, 213, 114, 177, 49, 174, 18, 88, 184, 11, 165, 220, 15, 137, 140, 159, 116, 207, 152, 39, 71, 161, 24, 217, 108, 192, 225, 181, 95, 94, …]関数名を見れば、だいたい処理内容は理解いただけると思います。
1点だけ注意ですが、publicEncryptとprivateDecryptで指定する公開鍵はDER形式としています。
私がDER形式を使うことが多いためこのようにしていますが、PEM形式とすることは簡単だと思いますので、適宜修正してください。以上
- 投稿日:2019-03-02T12:02:19+09:00
Javascriptで公開鍵ペア生成・暗号化・復号をしてみた
node.jsでRSA公開鍵を使った鍵ペア生成や暗号化/復号をする例はたくさんあったのですが、javascriptでの例はあまりなかったのでやってみました。
途中、はまってしまったので、備忘録として残しておきます。とはいいつつも、なんてことはない、browserifyを使っただけです。。。
使わせてもらったnpmモジュールは「node-rsa」です。Pure Javascriptという特徴に惹かれました。rzcoder/node-rsa
https://github.com/rzcoder/node-rsa(2019/3/2 追記)
せっかくなので、署名付与・署名検証も追加しておきました。作成するモジュールの仕様
ブラウザ上のJavascriptから以下関数を呼び出せるモジュールとします。
- RSA公開鍵ペアの生成
- 公開鍵による暗号化
- 秘密鍵による復号
- 秘密鍵による署名付与
- 公開鍵による署名検証
- PEM形式をDER形式に変換
- DER形式をPEM形式に変換
ちなみに、暗号化時のパディング方式は「RSA_PKCS1_OAEP_PADDING」とします。PKCS#1 v2.0 with SHA-1のことです。
node.jsでモジュールを作成する
まずは、node.jsで期待する動作となることを確認しましょう。
npm init -y
npm install --save node-rsa
vi main.jsmain.jsconst NodeRSA = require('node-rsa'); function generateKeyPair(bits){ const key = new NodeRSA({b: bits}); var priv = key.exportKey('pkcs1-private-der'); var pub = key.exportKey('pkcs1-public-der'); return { public: pub, private: priv }; } function pem2der(scheme, pem){ const key = new NodeRSA(pem, scheme + '-pem'); return key.exportKey(scheme + '-der'); } function der2pem(scheme, key){ var der = Buffer.from(key); const rsa = new NodeRSA(der, scheme + '-der'); return rsa.exportKey(scheme + '-pem'); } function publicEncrypt(key, buffer){ var input = Buffer.from(buffer); var der = Buffer.from(key); const rsa = new NodeRSA(der, 'pkcs1-public-der', { encryptionScheme : 'pkcs1_oaep' }); var enc = rsa.encrypt(input); return enc; } function privateDecrypt(key, buffer){ var input = Buffer.from(buffer); var der = Buffer.from(key); const rsa = new NodeRSA(der, 'pkcs1-private-der', { encryptionScheme : 'pkcs1_oaep' }); var dec = rsa.decrypt(input); return dec; } function sign(key, buffer){ var input = Buffer.from(buffer); var der = Buffer.from(key); const rsa = new NodeRSA(der, 'pkcs1-private-der'); var sig = rsa.sign(input); return sig; } function verify(key, buffer, signature){ var input = Buffer.from(buffer); var der = Buffer.from(key); var sig = Buffer.from(signature) const rsa = new NodeRSA(der, 'pkcs1-public-der'); var result = rsa.verify(input, sig); return result; } module.exports = { generateKeyPair: generateKeyPair, publicEncrypt: publicEncrypt, privateDecrypt: privateDecrypt, sign: sign, verify: verify, der2pem: der2pem, pem2der: pem2der, };ちなみに、はまった点を示しておきます。
publicEncryptやprivateDecryptでは、以下のような一見不要な処理をしています。ですが、なぜかこの処理を入れないと、ブラウザのJavascriptから呼び出したときにうまく復号してくれなかったんです。原因不明です。とりあえず動いているのでいいですが。。
var input = Buffer.from(buffer);
次は、それを呼び出すサンプルコードです。
index.jsvar rsab = require('./main'); var key = rsab.generateKeyPair(1024); console.log('private=', key.private); console.log('public=', key.public); var buffer = new Uint8Array(10); var enc2 = rsab.publicEncrypt(key.public, buffer); console.log('enc2=', enc2); var dec2 = rsab.privateDecrypt(key.private, enc2); console.log('OK? dec2=', dec2); var pem = rsab.der2pem('pkcs1-private', key.private); console.log(pem); var der = rsab.pem2der('pkcs1-private', pem); console.log(der); var pem = rsab.der2pem('pkcs1-public', key.public); console.log(pem); var der = rsab.pem2der('pkcs1-public', pem); console.log(der); var sig = rsab.sign(key.private, 'Hello World'); console.log('sig=', sig); var result = rsab.verify(key.public, 'Hello World', sig); console.log('result=', result);以下のような感じで出力されましたでしょうか?
private= <Buffer 30 82 02 5c 02 01 00 02 81 81 00 86 d6 34 4f d8 d8 2d 33 75 1e 04 95 bc a8 a0 24 30 c8 a0 d3 c6 b7 73 fe c1 81 8c 05 25 75 f1 1e a9 c5 3e 75 c5 04 2a ... > public= <Buffer 30 81 89 02 81 81 00 86 d6 34 4f d8 d8 2d 33 75 1e 04 95 bc a8 a0 24 30 c8 a0 d3 c6 b7 73 fe c1 81 8c 05 25 75 f1 1e a9 c5 3e 75 c5 04 2a d5 65 a3 b4 ... > enc2= <Buffer 22 b0 af e7 8c 2e 02 eb 73 f1 f1 d0 28 c0 a3 4b c5 cf 46 d1 82 f2 3d 12 32 d0 bf 95 e4 13 61 46 f1 79 32 ce 10 b9 ce 5d 8d 4e e6 0c d8 5c 3c 67 e0 96 ... > OK? dec2= <Buffer 00 00 00 00 00 00 00 00 00 00> -----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQCG1jRP2NgtM3UeBJW8qKAkMMig08a3c/7BgYwFJXXxHqnFPnXF BCrVZaO0LtPLmeV3HeqtK9W67TyBe/FMgXLdZ6cpxOco4NLBWp/7QP2MwVesf9g/ fm4LZIxeN9GXM/HIQOvbObyKRmd30CKHxjWkbnMTi72SG+Rc2RqhPJJPvwIDAQAB AoGAV9VDQFwt/cvOV95+t+VUZB7PIkyx3qEV63F7B4MugAIMbytPxiX/zQCnkeEL IE7AtkZrr6ClWl3dky9ssPyGGJJIVr7byMU1VdJlse2DIfIXW4kl+jo/MipXzb4I dr1D/AT043WS7hpyDKNDyZzmf4dwJsp9cpOm/AqUz+LfdIECQQDlh0+EuihKpaYs y4vtlHHWjZJPoLQQAtk57NnMJaqmEYuQYCnYs7RAuGoCRSc6DdgukZp+wSZ0WBn3 u0sSHTb/AkEAlmMtDN8y6nzLZvGzxQDgNxyzkNrSwmFb7fAa0+o2Pea0ps1ByrpX vOuebwze69kk5bldyVM+npwQZ1xiKrmnQQJBAKEXMmwI6zZYxCQ0R2TbBnp6qfFQ 7I9AMI1C+ikZVodvUPBnTXdVyHCT/XLSbhGEnfExJ6lGjmKhYrhHrwxrjKkCQF+6 T7Hy3eE/gOZNksYjUZYjUfYyJJiRCsiB30Hnw5FRqsrGu0uFpFXgkeBUjA4LEh6d CSMfNywVYae5uc9CkEECQH1xO+qrkWeGrrX3JaKjRmo13mYMfO9wwNOtINrlb9so 1om826G3vYPX4qrcWYC4pr0t8/7hN+4P80UNEnHCiac= -----END RSA PRIVATE KEY----- <Buffer 30 82 02 5c 02 01 00 02 81 81 00 86 d6 34 4f d8 d8 2d 33 75 1e 04 95 bc a8 a0 24 30 c8 a0 d3 c6 b7 73 fe c1 81 8c 05 25 75 f1 1e a9 c5 3e 75 c5 04 2a ... > -----BEGIN RSA PUBLIC KEY----- MIGJAoGBAIbWNE/Y2C0zdR4ElbyooCQwyKDTxrdz/sGBjAUldfEeqcU+dcUEKtVl o7Qu08uZ5Xcd6q0r1brtPIF78UyBct1npynE5yjg0sFan/tA/YzBV6x/2D9+bgtk jF430Zcz8chA69s5vIpGZ3fQIofGNaRucxOLvZIb5FzZGqE8kk+/AgMBAAE= -----END RSA PUBLIC KEY----- <Buffer 30 81 89 02 81 81 00 86 d6 34 4f d8 d8 2d 33 75 1e 04 95 bc a8 a0 24 30 c8 a0 d3 c6 b7 73 fe c1 81 8c 05 25 75 f1 1e a9 c5 3e 75 c5 04 2a d5 65 a3 b4 ... > sig= <Buffer 08 9f 2c 45 f2 e9 b4 8a f6 1e 88 e4 16 71 1a 07 18 fc 36 a5 8b d0 66 7d c6 b6 bc ff cf 51 b2 ca 5f d7 bb 7c 86 2e a0 be 97 15 4a 85 f6 f8 46 83 f2 db ... > result= true当然ながら、実行ごとに鍵ペアの鍵値や暗号文の値は異なります。
browserify化する
まだbrowserifyをインストールしていない場合は、インストールしておきます。
npm install browserify -g
それではJavascriptのモジュールを作成します。
browserify -r ./main.js:node-rsa-js -o node-rsa-js.js
これで、node-rsa-js.jsというJavascriptファイルが出来上がりました。-rで指定した「node-rsa-js」がJavascriptからrequireする名前になります。
ブラウザから使ってみる
以下のようなサンプルHTMLファイルを作成します。
index.html<html> <head> </head> <body> <script src="node-rsa-js.js"></script> <script> var rsab = require('node-rsa-js'); var key = rsab.generateKeyPair(1024); console.log('private=', key.private); console.log('public=', key.public); var buffer = new Uint8Array(10); var enc2 = rsab.publicEncrypt(key.public, buffer); console.log('enc2=', enc2); var dec2 = rsab.privateDecrypt(key.private, enc2); console.log('OK? dec2=', dec2); var pem = rsab.der2pem('pkcs1-private', key.private); console.log(pem); var der = rsab.pem2der('pkcs1-private', pem); console.log(der); var pem = rsab.der2pem('pkcs1-public', key.public); console.log(pem); var der = rsab.pem2der('pkcs1-public', pem); console.log(der); var sig = rsab.sign(key.private, 'Hello World'); console.log('sig=', sig); var result = rsab.verify(key.public, 'Hello World', sig); console.log('result=', result); </script> </body> </html>この
index.html
と同じフォルダに先ほど作成したnode-rsa-js.js
を置きます。早速、ブラウザから表示してみましょう。その時、各ブラウザのConsoleをのぞいてみてください。
以下のような感じで表示されれば成功です。private= Uint8Array(609) [48, 130, 2, 93, 2, 1, 0, 2, 129, 129, 0, 145, 2, 86, 33, 88, 50, 157, 251, 230, 51, 254, 9, 56, 64, 126, 169, 20, 2, 90, 3, 220, 32, 203, 53, 160, 62, 57, 209, 184, 183, 216, 203, 55, 131, 173, 205, 41, 174, 161, 16, 8, 17, 240, 238, 164, 117, 161, 187, 111, 10, 85, 116, 163, 25, 228, 14, 233, 56, 203, 16, 14, 234, 62, 49, 183, 171, 82, 140, 225, 101, 207, 84, 92, 47, 222, 212, 69, 102, 196, 226, 197, 38, 106, 237, 73, 6, 228, 221, 36, …] (index):10 public= Uint8Array(140) [48, 129, 137, 2, 129, 129, 0, 145, 2, 86, 33, 88, 50, 157, 251, 230, 51, 254, 9, 56, 64, 126, 169, 20, 2, 90, 3, 220, 32, 203, 53, 160, 62, 57, 209, 184, 183, 216, 203, 55, 131, 173, 205, 41, 174, 161, 16, 8, 17, 240, 238, 164, 117, 161, 187, 111, 10, 85, 116, 163, 25, 228, 14, 233, 56, 203, 16, 14, 234, 62, 49, 183, 171, 82, 140, 225, 101, 207, 84, 92, 47, 222, 212, 69, 102, 196, 226, 197, 38, 106, 237, 73, 6, 228, 221, 36, 198, 25, 31, 221, …] (index):15 enc2= Uint8Array(128) [8, 132, 229, 218, 1, 167, 51, 85, 250, 19, 215, 124, 177, 59, 76, 160, 142, 151, 103, 170, 200, 101, 222, 190, 148, 79, 70, 88, 63, 92, 227, 244, 220, 185, 117, 131, 98, 113, 73, 130, 247, 156, 254, 244, 198, 154, 109, 12, 135, 158, 125, 88, 186, 121, 244, 194, 18, 1, 180, 236, 159, 169, 51, 170, 195, 172, 166, 178, 122, 230, 209, 78, 45, 122, 16, 126, 2, 156, 65, 215, 100, 22, 63, 211, 127, 220, 51, 173, 106, 232, 80, 109, 228, 250, 245, 32, 137, 217, 236, 96, …] (index):17 OK? dec2= Uint8Array(10) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] (index):20 -----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQCRAlYhWDKd++Yz/gk4QH6pFAJaA9wgyzWgPjnRuLfYyzeDrc0p rqEQCBHw7qR1obtvClV0oxnkDuk4yxAO6j4xt6tSjOFlz1RcL97URWbE4sUmau1J BuTdJMYZH91cLvedEwc5lc7SNHrmZytOaE4jxFACXdi9tHyu8fENhL4x1QIDAQAB AoGAQassNDeL3K3J53vA0x+p/InaMseStasxIttrNcWQRHZrMo/P3HN/7xGohlKc WcUfa77jSknenL//8D9Ni2ObPBstwhAIVu/n6UEOL19XSkTxGKExboI3zXXX8Uc5 5NTz6UQ12BPr4/cOmu19pAMuSRCA4QF/nfv3hpRGWNuWspUCQQDWcom+nT2DVzsx B1mfPjV3d95049+p/WiWyRbxBh+G4MZs1PShT2cuchJNumrcgjZU6BFo9StSdSS8 DlVpa75rAkEArRtgpzBNLYqoJGBsq8bRZzgDlU72a7qYnxbmz/mBjQHIC4MUk3FD rrrGfMBlQoK8poQBL9BZiPYNEB+N+JlgvwJBAJh8X3f8BUaEW6GUUWULbidiQ/uo IV2VxK4blUWTjg1xfYbbsouVk5ASKvO8T8o2iP28+sxAMSr0A0f5hUBuDbsCQHbI NHhEkpDPdjUP3UG5uXLkYsEPX9PoRFXV9yd6g8ToFgagOXw62kCJdS2hL1qGL0Dr j4zpoKZ0f94yaM7PIC0CQQDL1VPh6oJ9MSDjoVGjVcjpxffXe/fNS/nnqRTn6lmZ VT6KNQ+KKenp5RBqlYJjS2S9Jhnx1by3bdZOJlkIY34k -----END RSA PRIVATE KEY----- (index):22 Uint8Array(609) [48, 130, 2, 93, 2, 1, 0, 2, 129, 129, 0, 145, 2, 86, 33, 88, 50, 157, 251, 230, 51, 254, 9, 56, 64, 126, 169, 20, 2, 90, 3, 220, 32, 203, 53, 160, 62, 57, 209, 184, 183, 216, 203, 55, 131, 173, 205, 41, 174, 161, 16, 8, 17, 240, 238, 164, 117, 161, 187, 111, 10, 85, 116, 163, 25, 228, 14, 233, 56, 203, 16, 14, 234, 62, 49, 183, 171, 82, 140, 225, 101, 207, 84, 92, 47, 222, 212, 69, 102, 196, 226, 197, 38, 106, 237, 73, 6, 228, 221, 36, …] (index):24 -----BEGIN RSA PUBLIC KEY----- MIGJAoGBAJECViFYMp375jP+CThAfqkUAloD3CDLNaA+OdG4t9jLN4OtzSmuoRAI EfDupHWhu28KVXSjGeQO6TjLEA7qPjG3q1KM4WXPVFwv3tRFZsTixSZq7UkG5N0k xhkf3Vwu950TBzmVztI0euZnK05oTiPEUAJd2L20fK7x8Q2EvjHVAgMBAAE= -----END RSA PUBLIC KEY----- (index):26 Uint8Array(140) [48, 129, 137, 2, 129, 129, 0, 145, 2, 86, 33, 88, 50, 157, 251, 230, 51, 254, 9, 56, 64, 126, 169, 20, 2, 90, 3, 220, 32, 203, 53, 160, 62, 57, 209, 184, 183, 216, 203, 55, 131, 173, 205, 41, 174, 161, 16, 8, 17, 240, 238, 164, 117, 161, 187, 111, 10, 85, 116, 163, 25, 228, 14, 233, 56, 203, 16, 14, 234, 62, 49, 183, 171, 82, 140, 225, 101, 207, 84, 92, 47, 222, 212, 69, 102, 196, 226, 197, 38, 106, 237, 73, 6, 228, 221, 36, 198, 25, 31, 221, …] (index):29 sig= Uint8Array(128) [80, 85, 255, 196, 23, 196, 40, 61, 23, 239, 13, 112, 221, 241, 216, 229, 45, 164, 241, 41, 212, 252, 55, 15, 86, 107, 42, 245, 86, 152, 155, 134, 37, 37, 255, 72, 93, 191, 63, 149, 144, 207, 72, 64, 80, 212, 92, 6, 74, 116, 62, 32, 25, 142, 155, 200, 42, 33, 31, 155, 163, 232, 219, 121, 99, 175, 164, 132, 140, 121, 145, 242, 147, 191, 90, 131, 195, 48, 193, 75, 3, 157, 98, 83, 161, 206, 221, 44, 132, 16, 144, 143, 103, 59, 233, 4, 87, 235, 15, 198, …] (index):32 result= true関数名を見れば、だいたい処理内容は理解いただけると思います。
1点だけ注意ですが、publicEncryptとprivateDecryptで指定する公開鍵はDER形式としています。
私がDER形式を使うことが多いためこのようにしていますが、PEM形式とすることは簡単だと思いますので、適宜修正してください。(2019/3/2 追記)
言うのを忘れましたが、node-rsa-js.jsのファイルサイズは「でかい」です。768KBです。以上
- 投稿日:2019-03-02T12:02:19+09:00
Javascriptで公開鍵ペア生成・暗号化/復号をしてみた
node.jsでRSA公開鍵を使った鍵ペア生成や暗号化/復号をする例はたくさんあったのですが、javascriptでの例はあまりなかったのでやってみました。
途中、はまってしまったので、備忘録として残しておきます。とはいいつつも、なんてことはない、browserifyを使っただけです。。。
使わせてもらったnpmモジュールは「node-rsa」です。Pure Javascriptという特徴に惹かれました。rzcoder/node-rsa
https://github.com/rzcoder/node-rsa(2019/3/2 追記)
せっかくなので、署名付与・署名検証も追加しておきました。作成するモジュールの仕様
ブラウザ上のJavascriptから以下関数を呼び出せるモジュールとします。
- RSA公開鍵ペアの生成
- 公開鍵による暗号化
- 秘密鍵による復号
- 秘密鍵による署名付与
- 公開鍵による署名検証
- PEM形式をDER形式に変換
- DER形式をPEM形式に変換
ちなみに、暗号化時のパディング方式は「RSA_PKCS1_OAEP_PADDING」とします。PKCS#1 v2.0 with SHA-1のことです。
node.jsでモジュールを作成する
まずは、node.jsで期待する動作となることを確認しましょう。
npm init -y
npm install --save node-rsa
vi main.jsmain.jsconst NodeRSA = require('node-rsa'); function generateKeyPair(bits){ const key = new NodeRSA({b: bits}); var priv = key.exportKey('pkcs1-private-der'); var pub = key.exportKey('pkcs1-public-der'); return { public: pub, private: priv }; } function pem2der(scheme, pem){ const key = new NodeRSA(pem, scheme + '-pem'); return key.exportKey(scheme + '-der'); } function der2pem(scheme, key){ var der = Buffer.from(key); const rsa = new NodeRSA(der, scheme + '-der'); return rsa.exportKey(scheme + '-pem'); } function publicEncrypt(key, buffer){ var input = Buffer.from(buffer); var der = Buffer.from(key); const rsa = new NodeRSA(der, 'pkcs1-public-der', { encryptionScheme : 'pkcs1_oaep' }); var enc = rsa.encrypt(input); return enc; } function privateDecrypt(key, buffer){ var input = Buffer.from(buffer); var der = Buffer.from(key); const rsa = new NodeRSA(der, 'pkcs1-private-der', { encryptionScheme : 'pkcs1_oaep' }); var dec = rsa.decrypt(input); return dec; } function sign(key, buffer){ var input = Buffer.from(buffer); var der = Buffer.from(key); const rsa = new NodeRSA(der, 'pkcs1-private-der'); var sig = rsa.sign(input); return sig; } function verify(key, buffer, signature){ var input = Buffer.from(buffer); var der = Buffer.from(key); var sig = Buffer.from(signature) const rsa = new NodeRSA(der, 'pkcs1-public-der'); var result = rsa.verify(input, sig); return result; } module.exports = { generateKeyPair: generateKeyPair, publicEncrypt: publicEncrypt, privateDecrypt: privateDecrypt, sign: sign, verify: verify, der2pem: der2pem, pem2der: pem2der, };ちなみに、はまった点を示しておきます。
publicEncryptやprivateDecryptでは、以下のような一見不要な処理をしています。ですが、なぜかこの処理を入れないと、ブラウザのJavascriptから呼び出したときにうまく復号してくれなかったんです。原因不明です。とりあえず動いているのでいいですが。。
var input = Buffer.from(buffer);
次は、それを呼び出すサンプルコードです。
index.jsvar rsab = require('./main'); var key = rsab.generateKeyPair(1024); console.log('private=', key.private); console.log('public=', key.public); var buffer = new Uint8Array(10); var enc2 = rsab.publicEncrypt(key.public, buffer); console.log('enc2=', enc2); var dec2 = rsab.privateDecrypt(key.private, enc2); console.log('OK? dec2=', dec2); var pem = rsab.der2pem('pkcs1-private', key.private); console.log(pem); var der = rsab.pem2der('pkcs1-private', pem); console.log(der); var pem = rsab.der2pem('pkcs1-public', key.public); console.log(pem); var der = rsab.pem2der('pkcs1-public', pem); console.log(der); var sig = rsab.sign(key.private, 'Hello World'); console.log('sig=', sig); var result = rsab.verify(key.public, 'Hello World', sig); console.log('result=', result);以下のような感じで出力されましたでしょうか?
private= <Buffer 30 82 02 5c 02 01 00 02 81 81 00 86 d6 34 4f d8 d8 2d 33 75 1e 04 95 bc a8 a0 24 30 c8 a0 d3 c6 b7 73 fe c1 81 8c 05 25 75 f1 1e a9 c5 3e 75 c5 04 2a ... > public= <Buffer 30 81 89 02 81 81 00 86 d6 34 4f d8 d8 2d 33 75 1e 04 95 bc a8 a0 24 30 c8 a0 d3 c6 b7 73 fe c1 81 8c 05 25 75 f1 1e a9 c5 3e 75 c5 04 2a d5 65 a3 b4 ... > enc2= <Buffer 22 b0 af e7 8c 2e 02 eb 73 f1 f1 d0 28 c0 a3 4b c5 cf 46 d1 82 f2 3d 12 32 d0 bf 95 e4 13 61 46 f1 79 32 ce 10 b9 ce 5d 8d 4e e6 0c d8 5c 3c 67 e0 96 ... > OK? dec2= <Buffer 00 00 00 00 00 00 00 00 00 00> -----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQCG1jRP2NgtM3UeBJW8qKAkMMig08a3c/7BgYwFJXXxHqnFPnXF BCrVZaO0LtPLmeV3HeqtK9W67TyBe/FMgXLdZ6cpxOco4NLBWp/7QP2MwVesf9g/ fm4LZIxeN9GXM/HIQOvbObyKRmd30CKHxjWkbnMTi72SG+Rc2RqhPJJPvwIDAQAB AoGAV9VDQFwt/cvOV95+t+VUZB7PIkyx3qEV63F7B4MugAIMbytPxiX/zQCnkeEL IE7AtkZrr6ClWl3dky9ssPyGGJJIVr7byMU1VdJlse2DIfIXW4kl+jo/MipXzb4I dr1D/AT043WS7hpyDKNDyZzmf4dwJsp9cpOm/AqUz+LfdIECQQDlh0+EuihKpaYs y4vtlHHWjZJPoLQQAtk57NnMJaqmEYuQYCnYs7RAuGoCRSc6DdgukZp+wSZ0WBn3 u0sSHTb/AkEAlmMtDN8y6nzLZvGzxQDgNxyzkNrSwmFb7fAa0+o2Pea0ps1ByrpX vOuebwze69kk5bldyVM+npwQZ1xiKrmnQQJBAKEXMmwI6zZYxCQ0R2TbBnp6qfFQ 7I9AMI1C+ikZVodvUPBnTXdVyHCT/XLSbhGEnfExJ6lGjmKhYrhHrwxrjKkCQF+6 T7Hy3eE/gOZNksYjUZYjUfYyJJiRCsiB30Hnw5FRqsrGu0uFpFXgkeBUjA4LEh6d CSMfNywVYae5uc9CkEECQH1xO+qrkWeGrrX3JaKjRmo13mYMfO9wwNOtINrlb9so 1om826G3vYPX4qrcWYC4pr0t8/7hN+4P80UNEnHCiac= -----END RSA PRIVATE KEY----- <Buffer 30 82 02 5c 02 01 00 02 81 81 00 86 d6 34 4f d8 d8 2d 33 75 1e 04 95 bc a8 a0 24 30 c8 a0 d3 c6 b7 73 fe c1 81 8c 05 25 75 f1 1e a9 c5 3e 75 c5 04 2a ... > -----BEGIN RSA PUBLIC KEY----- MIGJAoGBAIbWNE/Y2C0zdR4ElbyooCQwyKDTxrdz/sGBjAUldfEeqcU+dcUEKtVl o7Qu08uZ5Xcd6q0r1brtPIF78UyBct1npynE5yjg0sFan/tA/YzBV6x/2D9+bgtk jF430Zcz8chA69s5vIpGZ3fQIofGNaRucxOLvZIb5FzZGqE8kk+/AgMBAAE= -----END RSA PUBLIC KEY----- <Buffer 30 81 89 02 81 81 00 86 d6 34 4f d8 d8 2d 33 75 1e 04 95 bc a8 a0 24 30 c8 a0 d3 c6 b7 73 fe c1 81 8c 05 25 75 f1 1e a9 c5 3e 75 c5 04 2a d5 65 a3 b4 ... > sig= <Buffer 08 9f 2c 45 f2 e9 b4 8a f6 1e 88 e4 16 71 1a 07 18 fc 36 a5 8b d0 66 7d c6 b6 bc ff cf 51 b2 ca 5f d7 bb 7c 86 2e a0 be 97 15 4a 85 f6 f8 46 83 f2 db ... > result= true当然ながら、実行ごとに鍵ペアの鍵値や暗号文の値は異なります。
browserify化する
まだbrowserifyをインストールしていない場合は、インストールしておきます。
npm install browserify -g
それではJavascriptのモジュールを作成します。
browserify -r ./main.js:node-rsa-js -o node-rsa-js.js
これで、node-rsa-js.jsというJavascriptファイルが出来上がりました。-rで指定した「node-rsa-js」がJavascriptからrequireする名前になります。
ブラウザから使ってみる
以下のようなサンプルHTMLファイルを作成します。
index.html<html> <head> </head> <body> <script src="node-rsa-js.js"></script> <script> var rsab = require('node-rsa-js'); var key = rsab.generateKeyPair(1024); console.log('private=', key.private); console.log('public=', key.public); var buffer = new Uint8Array(10); var enc2 = rsab.publicEncrypt(key.public, buffer); console.log('enc2=', enc2); var dec2 = rsab.privateDecrypt(key.private, enc2); console.log('OK? dec2=', dec2); var pem = rsab.der2pem('pkcs1-private', key.private); console.log(pem); var der = rsab.pem2der('pkcs1-private', pem); console.log(der); var pem = rsab.der2pem('pkcs1-public', key.public); console.log(pem); var der = rsab.pem2der('pkcs1-public', pem); console.log(der); var sig = rsab.sign(key.private, 'Hello World'); console.log('sig=', sig); var result = rsab.verify(key.public, 'Hello World', sig); console.log('result=', result); </script> </body> </html>この
index.html
と同じフォルダに先ほど作成したnode-rsa-js.js
を置きます。早速、ブラウザから表示してみましょう。その時、各ブラウザのConsoleをのぞいてみてください。
以下のような感じで表示されれば成功です。private= Uint8Array(609) [48, 130, 2, 93, 2, 1, 0, 2, 129, 129, 0, 145, 2, 86, 33, 88, 50, 157, 251, 230, 51, 254, 9, 56, 64, 126, 169, 20, 2, 90, 3, 220, 32, 203, 53, 160, 62, 57, 209, 184, 183, 216, 203, 55, 131, 173, 205, 41, 174, 161, 16, 8, 17, 240, 238, 164, 117, 161, 187, 111, 10, 85, 116, 163, 25, 228, 14, 233, 56, 203, 16, 14, 234, 62, 49, 183, 171, 82, 140, 225, 101, 207, 84, 92, 47, 222, 212, 69, 102, 196, 226, 197, 38, 106, 237, 73, 6, 228, 221, 36, …] (index):10 public= Uint8Array(140) [48, 129, 137, 2, 129, 129, 0, 145, 2, 86, 33, 88, 50, 157, 251, 230, 51, 254, 9, 56, 64, 126, 169, 20, 2, 90, 3, 220, 32, 203, 53, 160, 62, 57, 209, 184, 183, 216, 203, 55, 131, 173, 205, 41, 174, 161, 16, 8, 17, 240, 238, 164, 117, 161, 187, 111, 10, 85, 116, 163, 25, 228, 14, 233, 56, 203, 16, 14, 234, 62, 49, 183, 171, 82, 140, 225, 101, 207, 84, 92, 47, 222, 212, 69, 102, 196, 226, 197, 38, 106, 237, 73, 6, 228, 221, 36, 198, 25, 31, 221, …] (index):15 enc2= Uint8Array(128) [8, 132, 229, 218, 1, 167, 51, 85, 250, 19, 215, 124, 177, 59, 76, 160, 142, 151, 103, 170, 200, 101, 222, 190, 148, 79, 70, 88, 63, 92, 227, 244, 220, 185, 117, 131, 98, 113, 73, 130, 247, 156, 254, 244, 198, 154, 109, 12, 135, 158, 125, 88, 186, 121, 244, 194, 18, 1, 180, 236, 159, 169, 51, 170, 195, 172, 166, 178, 122, 230, 209, 78, 45, 122, 16, 126, 2, 156, 65, 215, 100, 22, 63, 211, 127, 220, 51, 173, 106, 232, 80, 109, 228, 250, 245, 32, 137, 217, 236, 96, …] (index):17 OK? dec2= Uint8Array(10) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] (index):20 -----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQCRAlYhWDKd++Yz/gk4QH6pFAJaA9wgyzWgPjnRuLfYyzeDrc0p rqEQCBHw7qR1obtvClV0oxnkDuk4yxAO6j4xt6tSjOFlz1RcL97URWbE4sUmau1J BuTdJMYZH91cLvedEwc5lc7SNHrmZytOaE4jxFACXdi9tHyu8fENhL4x1QIDAQAB AoGAQassNDeL3K3J53vA0x+p/InaMseStasxIttrNcWQRHZrMo/P3HN/7xGohlKc WcUfa77jSknenL//8D9Ni2ObPBstwhAIVu/n6UEOL19XSkTxGKExboI3zXXX8Uc5 5NTz6UQ12BPr4/cOmu19pAMuSRCA4QF/nfv3hpRGWNuWspUCQQDWcom+nT2DVzsx B1mfPjV3d95049+p/WiWyRbxBh+G4MZs1PShT2cuchJNumrcgjZU6BFo9StSdSS8 DlVpa75rAkEArRtgpzBNLYqoJGBsq8bRZzgDlU72a7qYnxbmz/mBjQHIC4MUk3FD rrrGfMBlQoK8poQBL9BZiPYNEB+N+JlgvwJBAJh8X3f8BUaEW6GUUWULbidiQ/uo IV2VxK4blUWTjg1xfYbbsouVk5ASKvO8T8o2iP28+sxAMSr0A0f5hUBuDbsCQHbI NHhEkpDPdjUP3UG5uXLkYsEPX9PoRFXV9yd6g8ToFgagOXw62kCJdS2hL1qGL0Dr j4zpoKZ0f94yaM7PIC0CQQDL1VPh6oJ9MSDjoVGjVcjpxffXe/fNS/nnqRTn6lmZ VT6KNQ+KKenp5RBqlYJjS2S9Jhnx1by3bdZOJlkIY34k -----END RSA PRIVATE KEY----- (index):22 Uint8Array(609) [48, 130, 2, 93, 2, 1, 0, 2, 129, 129, 0, 145, 2, 86, 33, 88, 50, 157, 251, 230, 51, 254, 9, 56, 64, 126, 169, 20, 2, 90, 3, 220, 32, 203, 53, 160, 62, 57, 209, 184, 183, 216, 203, 55, 131, 173, 205, 41, 174, 161, 16, 8, 17, 240, 238, 164, 117, 161, 187, 111, 10, 85, 116, 163, 25, 228, 14, 233, 56, 203, 16, 14, 234, 62, 49, 183, 171, 82, 140, 225, 101, 207, 84, 92, 47, 222, 212, 69, 102, 196, 226, 197, 38, 106, 237, 73, 6, 228, 221, 36, …] (index):24 -----BEGIN RSA PUBLIC KEY----- MIGJAoGBAJECViFYMp375jP+CThAfqkUAloD3CDLNaA+OdG4t9jLN4OtzSmuoRAI EfDupHWhu28KVXSjGeQO6TjLEA7qPjG3q1KM4WXPVFwv3tRFZsTixSZq7UkG5N0k xhkf3Vwu950TBzmVztI0euZnK05oTiPEUAJd2L20fK7x8Q2EvjHVAgMBAAE= -----END RSA PUBLIC KEY----- (index):26 Uint8Array(140) [48, 129, 137, 2, 129, 129, 0, 145, 2, 86, 33, 88, 50, 157, 251, 230, 51, 254, 9, 56, 64, 126, 169, 20, 2, 90, 3, 220, 32, 203, 53, 160, 62, 57, 209, 184, 183, 216, 203, 55, 131, 173, 205, 41, 174, 161, 16, 8, 17, 240, 238, 164, 117, 161, 187, 111, 10, 85, 116, 163, 25, 228, 14, 233, 56, 203, 16, 14, 234, 62, 49, 183, 171, 82, 140, 225, 101, 207, 84, 92, 47, 222, 212, 69, 102, 196, 226, 197, 38, 106, 237, 73, 6, 228, 221, 36, 198, 25, 31, 221, …] (index):29 sig= Uint8Array(128) [80, 85, 255, 196, 23, 196, 40, 61, 23, 239, 13, 112, 221, 241, 216, 229, 45, 164, 241, 41, 212, 252, 55, 15, 86, 107, 42, 245, 86, 152, 155, 134, 37, 37, 255, 72, 93, 191, 63, 149, 144, 207, 72, 64, 80, 212, 92, 6, 74, 116, 62, 32, 25, 142, 155, 200, 42, 33, 31, 155, 163, 232, 219, 121, 99, 175, 164, 132, 140, 121, 145, 242, 147, 191, 90, 131, 195, 48, 193, 75, 3, 157, 98, 83, 161, 206, 221, 44, 132, 16, 144, 143, 103, 59, 233, 4, 87, 235, 15, 198, …] (index):32 result= true関数名を見れば、だいたい処理内容は理解いただけると思います。
1点だけ注意ですが、publicEncryptとprivateDecryptで指定する公開鍵はDER形式としています。
私がDER形式を使うことが多いためこのようにしていますが、PEM形式とすることは簡単だと思いますので、適宜修正してください。(2019/3/2 追記)
言うのを忘れましたが、node-rsa-js.jsのファイルサイズは「でかい」です。768KBです。以上
- 投稿日:2019-03-02T10:43:12+09:00
React Nativeでピアノを作る
はじめに
React Nativeでピアノを作ったのでソースコードの説明をします。
Githubのリポジトリはこちらです。I made the piano with React Native !
— nabehide (@____nabehide) 2019年2月27日
React Nativeでピアノを実装しました!
ソースコードはGithubに置きました。https://t.co/3WHPpVzS4L pic.twitter.com/Mdb7iuqfc0プロジェクトの立ち上げ
react-nativeのコマンドでプロジェクトを作ります。
react-native init piano cd piano
ピアノ音源の準備
mp3形式で音源を準備します。
Githubのリポジトリにアップロードしているので、こちらをお使いください。audio/ ├── A.mp3 ├── As.mp3 ├── B.mp3 ├── C.mp3 ├── Cs.mp3 ├── D.mp3 ├── Ds.mp3 ├── E.mp3 ├── F.mp3 ├── Fs.mp3 ├── G.mp3 └── Gs.mp3
react-native-soundのインストール
React Nativeで音を鳴らすためのライブラリreact-native-soundをインストールします。
yarn add react-native-sound react-native link react-native-sound
iOSではxcode上でプロジェクトファイルの中に音源をドラッグ&ドロップします。
Androidでは音源を android/app/src/main/res/raw のフォルダに入れます。
mkdir android/app/src/main/res/raw cp audio/* android/app/src/main/res/raw/
ソースコード
ソースコードは以下になります。
順番に説明していきます。App.jsximport React from 'react'; import { StyleSheet, Text, TouchableOpacity, View, } from 'react-native'; import Sound from 'react-native-sound'; export default class App extends React.Component { constructor( props ){ super( props ); this.state = { colorC : "white", colorCs: "black", colorD : "white", colorDs: "black", colorE : "white", colorF : "white", colorFs: "black", colorG : "white", colorGs: "black", colorA : "white", colorAs: "black", colorB : "white", } this.sound = {}; const soundList = [ "C", "Cs", "D", "Ds", "E", "F", "Fs", "G", "Gs", "A", "As", "B" ] soundList.forEach(note => { this.sound[note] = new Sound( note + ".mp3", Sound.MAIN_BUNDLE, error => { if ( error ) { console.log("failed to load the sound.", error); } }) }); } stroke ( note ) { switch ( note ) { case "C": this.setState({ colorC: "rgba(1, 1, 1, 0.1)" }) break; case "Cs": this.setState({ colorCs: "rgba(0, 0, 0, 0.5)" }) break; case "D": this.setState({ colorD: "rgba(1, 1, 1, 0.1)" }) break; case "Ds": this.setState({ colorDs: "rgba(0, 0, 0, 0.5)" }) break; case "E": this.setState({ colorE: "rgba(1, 1, 1, 0.1)" }) break; case "F": this.setState({ colorF: "rgba(1, 1, 1, 0.1)" }) break; case "Fs": this.setState({ colorFs: "rgba(0, 0, 0, 0.5)" }) break; case "G": this.setState({ colorG: "rgba(1, 1, 1, 0.1)" }) break; case "Gs": this.setState({ colorGs: "rgba(0, 0, 0, 0.5)" }) break; case "A": this.setState({ colorA: "rgba(1, 1, 1, 0.1)" }) break; case "As": this.setState({ colorAs: "rgba(0, 0, 0, 0.5)" }) break; case "B": this.setState({ colorB: "rgba(1, 1, 1, 0.1)" }) break; } setTimeout( () => { this.sound[note].play(success => { if ( success ) { console.log("successfully finished playing."); } else { console.log("failed to play the sound."); } }); }, 1); } stop( note ) { switch ( note ) { case "C": this.setState( { colorC: "white" } ) break; case "Cs": this.setState( { colorCs: "black" } ) break; case "D": this.setState( { colorD: "white" } ) break; case "Ds": this.setState( { colorDs: "black" } ) break; case "E": this.setState( { colorE: "white" } ) break; case "F": this.setState( { colorF: "white" } ) break; case "Fs": this.setState( { colorFs: "black" } ) break; case "G": this.setState( { colorG: "white" } ) break; case "Gs": this.setState( { colorGs: "black" } ) break; case "A": this.setState( { colorA: "white" } ) break; case "As": this.setState( { colorAs: "black" } ) break; case "B": this.setState( { colorB: "white" } ) break; } setTimeout( () => { for (let i=0; i<2000; i++) { this.sound[note].setVolume( 1.0-i/2000. ); } this.sound[note].stop(); this.sound[note].setVolume( 1.0 ); }, 1 ) } render () { return ( <View style={styles.container}> <View style={{ flex: 1, flexDirection: "column", alignItems: "center" }}> <View style={{ flexDirection : "row", alignItems: "center", justifyContent: "center" }}> <View style={{ backgroundColor: "white", height: 100, width: 32, borderLeftWidth: 1, borderTopWidth: 1,}} > </View > <View onTouchStart={() => this.stroke("Cs")} onTouchEnd={() => this.stop("Cs")} style={{ backgroundColor: this.state.colorCs, height: 100, width: 32, borderTopWidth: 1, borderLeftWidth: 1,}} > </View > <View style={{ backgroundColor: "white", height: 100, width: 16, borderTopWidth: 1, }} > </View > <View onTouchStart={() => this.stroke("Ds")} onTouchEnd={() => this.stop("Ds")} style={{ backgroundColor: this.state.colorDs, height: 100, width: 32, borderTopWidth: 1, borderLeftWidth: 1,}} > </View > <View style={{ backgroundColor: "white", height: 100, width: 32, borderTopWidth: 1, }} > </View > <View style={{ backgroundColor: "white", height: 100, width: 32, borderTopWidth: 1, borderLeftWidth: 1, }} > </View > <View onTouchStart={() => this.stroke("Fs")} onTouchEnd={() => this.stop("Fs")} style={{ backgroundColor: this.state.colorFs, height: 100, width: 32, borderTopWidth: 1, }} > </View > <View style={{ backgroundColor: "white", height: 100, width: 16, borderTopWidth: 1, }} > </View > <View onTouchStart={() => this.stroke("Gs")} onTouchEnd={() => this.stop("Gs")} style={{ backgroundColor: this.state.colorGs, height: 100, width: 32, borderTopWidth: 1, }} > </View > <View style={{ backgroundColor: "white", height: 100, width: 16, borderTopWidth: 1, }} > </View > <View onTouchStart={() => this.stroke("As")} onTouchEnd={() => this.stop("As")} style={{ backgroundColor: this.state.colorAs, height: 100, width: 32, borderTopWidth: 1, }} > </View > <View style={{ backgroundColor: "white", height: 100, width: 32, borderRightWidth: 1, borderTopWidth: 1, }} > </View > </View> <View style={{ flexDirection : "row", alignItems: "center", justifyContent: "center" }}> <View onTouchStart={() => this.stroke("C")} onTouchEnd={() => this.stop("C")} style={{ backgroundColor: this.state.colorC, height: 100, width: 48, borderBottomWidth: 1, borderLeftWidth: 1 }} > </View > <View onTouchStart={() => this.stroke("D")} onTouchEnd={() => this.stop("D")} style={{ backgroundColor: this.state.colorD, height: 100, width: 48, borderBottomWidth: 1, borderLeftWidth: 1 }} > </View > <View onTouchStart={() => this.stroke("E")} onTouchEnd={() => this.stop("E")} style={{ backgroundColor: this.state.colorE, height: 100, width: 48, borderBottomWidth: 1, borderLeftWidth: 1 }} > </View > <View onTouchStart={() => this.stroke("F")} onTouchEnd={() => this.stop("F")} style={{ backgroundColor: this.state.colorF, height: 100, width: 48, borderBottomWidth: 1, borderLeftWidth: 1 }} > </View > <View onTouchStart={() => this.stroke("G")} onTouchEnd={() => this.stop("G")} style={{ backgroundColor: this.state.colorG, height: 100, width: 48, borderBottomWidth: 1, borderLeftWidth: 1 }} > </View > <View onTouchStart={() => this.stroke("A")} onTouchEnd={() => this.stop("A")} style={{ backgroundColor: this.state.colorA, height: 100, width: 48, borderBottomWidth: 1, borderLeftWidth: 1 }} > </View > <View onTouchStart={() => this.stroke("B")} onTouchEnd={() => this.stop("B")} style={{ backgroundColor: this.state.colorB, height: 100, width: 48, borderBottomWidth: 1, borderLeftWidth: 1, borderRightWidth: 1 }} > </View > </View> </View> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', flexDirection: "row", }, });鍵盤の表示
Viewで鍵盤を作ってきます。
鍵盤に触れた時に音が鳴り、離した時に音が鳴り終わるように、OnTouchStartとOnTouchEndに処理を書いていきます。
また、鍵盤に触れている時にbackgroundColorを変えたいので、stateにしておきます。constructor( props ){ super( props ); this.state = { colorC : "white", colorCs : "black", ... } ... } render() { ... <View onTouchStart={() => this.stroke("Cs")} onTouchEnd={() => this.stop("Cs")} style={{ backgroundColor: this.state.colorCs, height: 100, width: 32, borderTopWidth: 1, borderLeftWidth: 1,}} > </View > .... }音を鳴らす(鍵盤に触れる)
コンストラクタで音源を読み込んでおきます。
鍵盤に触れた時、鍵盤のbackgroundColorをstateで変更して、音を鳴らします。
音が鳴らないことがあるため、setTimeoutが必要です。constructor( props ){ super( props ); ... this.sound = {}; const soundList = [ "C", "Cs", "D", "Ds", "E", "F", "Fs", "G", "Gs", "A", "As", "B" ] soundList.forEach(note => { this.sound[note] = new Sound( note + ".mp3", Sound.MAIN_BUNDLE, error => { if ( error ) { console.log("failed to load the sound.", error); } }) }); } stroke ( note ) { switch ( note ) { case "C": this.setState({ colorC: "rgba(1, 1, 1, 0.1)" }) break; ... } setTimeout( () => { this.sound[note].play(success => { if ( success ) { console.log("successfully finished playing."); } else { console.log("failed to play the sound."); } }); }, 1); }音を止める(鍵盤から指を離す)
同様にstateで鍵盤の色を元に戻します。
音をいきなり止めてしまうとブツッと鳴ってしまうので、徐々にボリュームを下げてから止めるようにします。
止めた後は、次に音を鳴らす時のためにボリュームを元に戻しておきます。stop( note ) { switch ( note ) { case "C": this.setState( { colorC: "white" } ) break; ... } setTimeout( () => { for (let i=0; i<2000; i++) { this.sound[note].setVolume( 1.0-i/2000. ); } this.sound[note].stop(); this.sound[note].setVolume( 1.0 ); }, 1 ) }まとめ
React Nativeでピアノを実装しました。
パフォーマンスは良いとは言えませんが、アプリにちょっとした鍵盤を実装したい場合(?)に試してみてください!
- 投稿日:2019-03-02T03:02:28+09:00
Church数でfizzbuzz
「javascriptで難読fizzbuzz」っていうお題で書き始めました。
rnという関数でChurch数をjavascriptの整数に変換していますが、これは結果の表示用であって計算は全てChurch数のまま行ってます。
実行時の効率は考えてないので、結果が表示されるまで数秒かかります。
fizzbuzz.jsconst z=f=>(x=>f(y=>x(x)(y)))(x=>f(y=>x(x)(y))) //Zコンビネーター const id=x=>x //恒等関数 const sc=n=>f=>x=>f(n(f)(x)) //succ const pr=n=>f=>x=>n(g=>h=>h(g(f)))(tr(x))(id) //pred const mn=m=>n=>n(pr)(m) //引き算 const pl=m=>n=>n(sc)(m) //足し算 const ml=m=>n=>n(pl(m))(zr) //掛け算 const zr=f=>x=>x //0 const th=f=>x=>f(f(f(x))) //3 const fv=f=>x=>f(f(f(f(f(x))))) //5 const ft=ml(th)(fv) //15 const hd=ml(ml(fv)(fv))(sc(th)) //100 const tr=t=>f=>t //true const fl=t=>f=>f //false const iz=n=>n(tr(fl))(tr) //isZero const nt=b=>b(fl)(tr) //not const an=b=>c=>b(c(tr)(fl))(fl) //and const or=b=>c=>nt(an(nt(b))(nt(c))) //or const lt=z(f=>a=>b=>or(iz(a))(iz(b))(()=>nt(iz(b)))(()=>f(pr(a))(pr(b)))()) //less than const md=z(f=>m=>n=>lt(m)(n)(()=>m)(()=>f(mn(m)(n))(n))()) //割り算のあまり const cn=x=>y=>f=>f(x)(y) //cons //Church数からjavascriptの整数に変換(表示用) const rn=z(f=>n=> iz(n)(()=>0)(()=>f(pr(n))+1)()) //Church数を受け取って、fizz, buzz, javascriptの整数のいずれかを返す関数 const fb=n=>iz(md(n)(ft))("FizzBuzz")(iz(md(n)(fv))("Buzz")(iz(md(n)(th))("Fizz")(rn(n)))) //100から0までの fizzbuzz の結果のリスト(降順) const fbs=z(f=>n=>iz(n)(()=>cn(cn(zr)(zr))(zr))(()=>cn(cn(n)(fb(n)))(f(pr(n))))())(hd) //結果のリストを逆順に出力 const ma=z(f=>ls=>iz(ls(tr)(tr))(()=>zr)(()=>(f(ls(fl)),console.log(ls(tr)(fl))))())(fbs)以下はワンライナーver.
fizzbuzz2.js(f=>(x=>f(y=>x(x)(y)))(x=>f(y=>x(x)(y))))(f=>ls=>(n=>n((t=>f=>t)(t=>f=>f))(t=>f=>t))(ls(t=>f=>t)(t=>f=>t))(()=>(f=>x=>x))(()=>(f(ls(t=>f=>f)),console.log(ls(t=>f=>t)(t=>f=>f))))())((f=>(x=>f(y=>x(x)(y)))(x=>f(y=>x(x)(y))))(f=>n=>(n=>n((t=>f=>t)(t=>f=>f))(t=>f=>t))(n)(()=>(x=>y=>f=>f(x)(y))((x=>y=>f=>f(x)(y))(f=>x=>x)(f=>x=>x))(f=>x=>x))(()=>(x=>y=>f=>f(x)(y))((x=>y=>f=>f(x)(y))(n)((n=>(n=>n((t=>f=>t)(t=>f=>f))(t=>f=>t))(((f=>(x=>f(y=>x(x)(y)))(x=>f(y=>x(x)(y))))(f=>m=>n=>((f=>(x=>f(y=>x(x)(y)))(x=>f(y=>x(x)(y))))(f=>a=>b=>(b=>c=>b(t=>f=>t)(c))((n=>n((t=>f=>t)(t=>f=>f))(t=>f=>t))(a))((n=>n((t=>f=>t)(t=>f=>f))(t=>f=>t))(b))(()=>(b=>b(t=>f=>f)(t=>f=>t))((n=>n((t=>f=>t)(t=>f=>f))(t=>f=>t))(b)))(()=>f((n=>f=>x=>n(g=>h=>h(g(f)))((t=>f=>t)(x))(x=>x))(a))((n=>f=>x=>n(g=>h=>h(g(f)))((t=>f=>t)(x))(x=>x))(b)))()))(m)(n)(()=>m)(()=>f((m=>n=>n(n=>f=>x=>n(g=>h=>h(g(f)))((t=>f=>t)(x))(x=>x))(m))(m)(n))(n))()))(n)((m=>n=>n((m=>n=>n(n=>f=>x=>f(n(f)(x)))(m))(m))(f=>x=>x))(f=>x=>f(f(f(x))))(f=>x=>f(f(f(f(f(x))))))))("FizzBuzz")((n=>n((t=>f=>t)(t=>f=>f))(t=>f=>t))(((f=>(x=>f(y=>x(x)(y)))(x=>f(y=>x(x)(y))))(f=>m=>n=>((f=>(x=>f(y=>x(x)(y)))(x=>f(y=>x(x)(y))))(f=>a=>b=>(b=>c=>b(t=>f=>t)(c))((n=>n((t=>f=>t)(t=>f=>f))(t=>f=>t))(a))((n=>n((t=>f=>t)(t=>f=>f))(t=>f=>t))(b))(()=>(b=>b(t=>f=>f)(t=>f=>t))((n=>n((t=>f=>t)(t=>f=>f))(t=>f=>t))(b)))(()=>f((n=>f=>x=>n(g=>h=>h(g(f)))((t=>f=>t)(x))(x=>x))(a))((n=>f=>x=>n(g=>h=>h(g(f)))((t=>f=>t)(x))(x=>x))(b)))()))(m)(n)(()=>m)(()=>f((m=>n=>n(n=>f=>x=>n(g=>h=>h(g(f)))((t=>f=>t)(x))(x=>x))(m))(m)(n))(n))()))(n)(f=>x=>f(f(f(f(f(x)))))))("Buzz")((n=>n((t=>f=>t)(t=>f=>f))(t=>f=>t))(((f=>(x=>f(y=>x(x)(y)))(x=>f(y=>x(x)(y))))(f=>m=>n=>((f=>(x=>f(y=>x(x)(y)))(x=>f(y=>x(x)(y))))(f=>a=>b=>(b=>c=>b(t=>f=>t)(c))((n=>n((t=>f=>t)(t=>f=>f))(t=>f=>t))(a))((n=>n((t=>f=>t)(t=>f=>f))(t=>f=>t))(b))(()=>(b=>b(t=>f=>f)(t=>f=>t))((n=>n((t=>f=>t)(t=>f=>f))(t=>f=>t))(b)))(()=>f((n=>f=>x=>n(g=>h=>h(g(f)))((t=>f=>t)(x))(x=>x))(a))((n=>f=>x=>n(g=>h=>h(g(f)))((t=>f=>t)(x))(x=>x))(b)))()))(m)(n)(()=>m)(()=>f((m=>n=>n(n=>f=>x=>n(g=>h=>h(g(f)))((t=>f=>t)(x))(x=>x))(m))(m)(n))(n))()))(n)(f=>x=>f(f(f(x)))))("Fizz")(((f=>(x=>f(y=>x(x)(y)))(x=>f(y=>x(x)(y))))(f=>n=> (n=>n((t=>f=>t)(t=>f=>f))(t=>f=>t))(n)(()=>0)(()=>f((n=>f=>x=>n(g=>h=>h(g(f)))((t=>f=>t)(x))(x=>x))(n))+1)()))(n)))))(n)))(f((n=>f=>x=>n(g=>h=>h(g(f)))((t=>f=>t)(x))(x=>x))(n))))())((m=>n=>n((m=>n=>n(n=>f=>x=>f(n(f)(x)))(m))(m))(f=>x=>x))((m=>n=>n((m=>n=>n(n=>f=>x=>f(n(f)(x)))(m))(m))(f=>x=>x))(f=>x=>f(f(f(f(f(x))))))(f=>x=>f(f(f(f(f(x)))))))((n=>f=>x=>f(n(f)(x)))(f=>x=>f(f(f(x)))))))ワンライン(2571文字)
- 投稿日:2019-03-02T01:10:32+09:00
トラッキングの方法
Webのトラッキングは、GoogleAnalyticsなどのアクセス解析ツールや、アフィリエイトで使われていますが、その方法について分かっていることをつらつら書きます。
トラッキングとは
アクセス解析ツールのトラッキング、アフィリエイトのトラッキングはだいたい方法は同じ。
その前にまず、アフィリエイトとは、サイト運営者が自サイトに貼りだした広告をサイト訪問者がクリックし、商品購入や会員登録などに繋がった場合に報酬を支払うというもので、
- 広告クリック時にCookieを発行し(ex: Cookie発行ドメインは「*.アフィリエイトサービスプロバイダ.com」)
- 商品購入完了や会員登録完了ページでそのCookieを送信する(ex: https://アフィリエイトサービスプロバイダ.com/readCookie を実行)
という2点で広告が踏まれたこと、なにかしらの目標達成を検知している。
つまり 広告クリック と 〇〇完了ページでのCookie送信 の2点が大事でその途中のページ遷移はどうでもいい、異なるドメイン間で移動してようが関係なく、Cookieの性質を利用することでトラッキング(クロスドメインも)をしていたということになるが、そのトラッキング方法ができなくなることがあるのがAppleのITP対応であり、ASP(アフィリエイトサービスプロバイダ)各社はCookieに代わるトラッキングの方法を検討し導入しているという状況。
クロスドメインの問題
異なるドメイン間でデータをやり取りは基本できない。
ただ、それでは困るので解決するための手段はいくつかあり、その際に「クロスドメインはどう対応する?」
などとよく使われている。と思う。
トラッキングでもクロスドメインはもちろん問題になり、例としては、ECサイトとショッピングカートでドメインが異なる場合に、アクセス解析ツールがドメインをまたいだ瞬間(ex:商品をカートにいれた瞬間)トラッキングができないことが問題になる。
トラッキングに使っていたCookieは、クロスドメインでデータをやり取りできない(※1)ため。といってもアクセス解析ツールはどこもそんなことにはならないようにしてあるはずで、GoogleAnalyticsでは埋め込みコードにクロスドメインするドメインを列挙しておくことで対応できます。細かいやり方は本家の資料をみてください。
※1 Cookieの発行ドメインが、「*.example.com」で、クロスドメイン先のドメイン、上記の例だとショッピングカートのドメインが「cart.example.com」ならトラッキングできる。
トラッキングの方法一覧
サードパーティCookieで点と点のトラッキング
クリックと〇〇完了だけを特定すればいいアフィリエイトで用いられやすい、クロスドメインの問題も気にしない。(でもITPがね・・)
ファーストパーティCookieでユーザの動きを連続してトラッキング
GoogleAnalyticsはこの方法。クロスドメイントラッキングが必要な場合は、そのドメインを列挙する必要あり。
(しかしITP2.1(JavaScriptが扱えるcookieが7日間までしか使えなくなる)がね ・・噂によるとCookie+LocalStorageの合わせ技をすることになるのか?)LocalStorage
あまり使ったことがないのでわからない..。
デバイス推定技術?
アフィリエイト大手のValueCommerceが発表している方法。具体的に発表されていない?実際にトラッキングされてみて試してみようかな?
https://www.valuecommerce.co.jp/news/c_press/4263/最後に
ほかにもあると思いますし、勢いで書いたので間違っているところがありそうです。。
思いついたり、気づいたら追記・編集していきます。
- 投稿日:2019-03-02T00:40:33+09:00
【今日から携わる】Javascript入門(3)応用
Javascript入門(1)初級-中級
Javascript入門(2)上級
Javascript入門(3)応用←いまここJavascript入門のため、Progateをやってみました
学習コース JavaScript Ⅴ
モジュールを組み合わせよう
このレッスンでは、JavaScriptのコードを複数のファイルに分割し、
それらを組み合わせる方法を学びます。
複数ファイルに分割することで、維持・更新のしやすいコードを書くことができます。
また、パッケージと呼ばれる便利な機能を使うための方法も学びます。最後の演習では、
readline-sync
を使って、コンソールでの質問に答えるプログラムを書きます。ファイルの分割
コードの量が増えてくると1つのファイルで管理するのが大変になるため、複数のファイルでコードを管理することがあります。
今回は、メインのプログラムを実行する「script.js」とAnimalクラスを定義する「animal.js」、Dog クラスを定義する「dog.js」の3つのファイルにコードを分けてみましょう。エラーをおこさないためにすること
ファイルを分割したときのエラーは、それぞれのファイルを関連づけし、必要な値を渡すことで解決できます。
今回の場合「dog.js」でAnimalクラスを、「script.js」でDogクラスを使用できるように設定する必要があります。▼演習
・「animal.js」では、”Animal”クラスをエクスポートする
・「dog.js」では、”animal.js”を読み込み、”Dog”クラスをエクスポートする
・「script.js」では、”dog.js”を読み込むanimal.jsclass Animal { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log("こんにちは"); } info() { this.greet(); console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); } } // Animalクラスをエクスポートしてください export default Animal;dog.js// Animalクラスをインポートしてください import Animal from "./animal"; class Dog extends Animal { constructor(name, age, breed) { super(name, age); this.breed = breed; } info() { this.greet(); console.log(`名前は${this.name}です`); console.log(`犬種は${this.breed}です`); console.log(`${this.age}歳です`); const humanAge = this.getHumanAge(); console.log(`人間年齢で${humanAge}歳です`); } getHumanAge() { return this.age * 7; } } // Dogクラスをエクスポートしてください export default Dog;script.js// Dogクラスをインポートしてください import Dog from "./dog"; const dog = new Dog("レオ", 4, "チワワ"); dog.info();値のエクスポート
クラスのエクスポートを行いましたが、エクスポート出来るのはクラスだけではありません。文字列や数値や関数など、どんな値でもエクスポートが可能です。
エクスポートする際は、下図のように「export default 定数名」とします。インポートする際は「import 定数名 from "./ファイル名"」とします。▼演習
「dogData.js」という新しいファイルを作成しました。
「script.js」から「dogData.js」にdogインスタンスを定義する部分を移動させてみましょう。script.js// 定数dogをインポートしてください import dog from "./dogData.js"; dog.info();dogData.js// Dogクラスのインポートと定数dogを以下に張り付けてください import Dog from "./dog"; const dog = new Dog("レオ", 4, "チワワ"); // 定数dogをエクスポートしてください export default dog;dog.jsimport Animal from "./animal"; class Dog extends Animal { constructor(name, age, breed) { super(name, age); this.breed = breed; } info() { this.greet(); console.log(`名前は${this.name}です`); console.log(`犬種は${this.breed}です`); console.log(`${this.age}歳です`); const humanAge = this.getHumanAge(); console.log(`人間年齢で${humanAge}歳です`); } getHumanAge() { return this.age * 7; } } export default Dog;animal.jsclass Animal { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log("こんにちは"); } info() { this.greet(); console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); } } export default Animal;名前付きエクスポート
export defaultはデフォルトエクスポートと呼ばれ、そのファイルがインポートされると自動的に「export default 値」の値がインポートされます。そのためエクスポート時の値の名前と、インポート時の値の名前は違っても問題ありません。
デフォルトエクスポートは1ファイル1つの値のみ使えます。このファイルをインポートする際には、デフォルトエクスポートの値を自動でインポートするため、値が1つのみとなっています。
複数の値をエクスポートしたい場合は、次のスライドで紹介する「名前付きエクスポート」を用います。「import { 値の名前 } from "./ファイル名"」名前付きエクスポートした値をインポートする場合は、エクスポート時と同じ名前で値を指定します。インポートする値は、エクスポート時と同様に、「import { 値の名前 } from "./ファイル名"」と{}で囲んで指定します。
「export { 名前1, 名前2 }」名前付きエクスポートは、デフォルトエクスポートと違い、複数の定数やクラスを指定してエクスポートが出来ます。
また、左の図のように、「export { 名前1, 名前2 }」という形で書くことにより、1つのファイルから複数のエクスポートが出来ます。
インポートの際も、コンマで区切ることで複数のインポートができます。▼演習
「dogData.js」で定数dogを、2つの定数dog1とdog2に書きかえておきました。
・「dogData.js」に、定義した定数dog1とdog2をエクスポートしていきましょう。
・「script.js」に、import dog
ではなく、dog1
dog2
をインポートするように書き換えましょう
・dog1
dog2
の情報を出力するようにしましょうdogData.jsimport Dog from "./dog"; // 定数dog1, dog2を確認してください const dog1 = new Dog("レオ", 4, "チワワ"); const dog2 = new Dog("ベン", 2, "プードル"); // 以下を書き換えて、定数dog1, dog2をエクスポートしてください export {dog1, dog2};script.js// 以下を書き換えて、定数dog1, dog2をインポートしてください import { dog1, dog2} from "./dogData"; // 定数dog1とdog2を出力するように左からコピーして書き換えてください console.log("----------"); dog1.info(); console.log("----------"); dog2.info();相対パス
これまでファイルの指定は「./ファイル名」としてきました。
これは相対パスと言い、記述されているファイルからみた位置関係を示しています。▼ファイルを分ける
.src ├── class │ ├── animal.js │ └── dog.js ├── data │ └── dogDate.js └── script.jsパッケージ(1)
JavaScriptの世界では、誰かが作った便利なプログラムがパッケージという形で公開されています。また、JavaScriptの機能を使うことで、このパッケージを自分のプログラムの中に組み込んで使うことができます。
使い方
定義import 定義名 from "パッケージ名";使い方import chalk from "chalk"; console.log(chalk.yellow("Hello World")); console.log(chalk.bgCyan("Hello World"));パッケージ(2)
コンソールから値を入力出来るようにするためのパッケージを使ってみましょう。
readline-sync
コンソールへの値の入力と、その入力された値をプログラムの中で使うことができるようになります。
▼演習
readline-sync
を使って、コンソールでの質問に答えるプログラムを書いてみようdogDate.js// readline-syncをインポートしてください import readlineSync from "readline-sync"; import Dog from "../class/dog"; const dog1 = new Dog("レオ", 4, "チワワ"); // readlineSync.questionを使って書き換えてください // const name = "ベン"; const name = readlineSync.question("名前を入力してください: "); // readlineSync.questionIntを使って書き換えてください // const age = 2; const age = readlineSync.questionInt("年齢を入力してください: "); // readlineSync.questionを使って書き換えてください // const breed = "プードル"; const breed = readlineSync.question("犬種を入力してください: "); const dog2 = new Dog(name, age, breed); export { dog1, dog2 };つぎの章
学習コース JavaScript Ⅵ
このレッスンでは「配列を操作するメソッド」について学習していきましょう。
配列の操作を学ぶと、データを柔軟に扱えるようになります。
これから学ぶ便利なメソッドはJavaScriptを実践的に使う場合に必須の知識です。pushメソッド
▼演習
pushメソッドを使って、配列に新しい要素を追加しましょう。演習const characters = ["にんじゃわんこ", "ベイビーわんこ", "ひつじ仙人"]; console.log(characters); // pushメソッドを使って配列charactersに、文字列「とりずきん」を追加してください characters.push("とりずきん"); // 配列charactersを出力してください console.log(characters);forEachメソッド
forEachメソッドは配列の中の要素を1つずつ取り出して、全ての要素に繰り返し同じ処理を行うメソッドです。
forEachの仕組み
定義配列 = [要素1,要素2,要素3]; 配列.forEach((引数) => {処理});//アロー関数を使用 //配列の中の要素を1つずつ取り出して同じ処理をする▼演習
forEachメソッドを使って、配列charactersの中身をすべて出力してください演習const characters = ["にんじゃわんこ", "ベイビーわんこ", "ひつじ仙人", "とりずきん"]; // forEachメソッドを使って、配列charactersの中身をすべて出力してください characters.forEach((character) => { console.log(character); });findメソッド
findメソッドとは、アロー関数の処理部分に記述した条件式に合う1つ目の要素を配列の中から取り出すメソッドです。
とりあえず例文const numbers = [1,3,5,7]; const foundNumber = numbers.find((number) => { //number:引数 return number > 3; // number> 3:条件 }); console.log(foundNumber);//5 //条件に合う最初の要素が取り出されている▼演習
1.findメソッドを使って配列numbersから3の倍数を見つけ、定数foundNumberに代入して出力してください
2.定数charactersからidが3のオブジェクトを見つけ、定数foundCharacterに代入して出力してください演習const numbers = [1, 3, 5, 7, 9]; // findメソッドを使って配列numbersから3の倍数を見つけ、定数foundNumberに代入してください const foundNumber = numbers.find((number) => { return number % 3 === 0; }); // foundNumberを出力してください console.log(foundNumber); const characters = [ {id: 1, name: "にんじゃわんこ", age: 6}, {id: 2, name: "ベイビーわんこ", age: 2}, {id: 3, name: "ひつじ仙人", age: 100}, {id: 4, name: "とりずきん", age: 21} ]; // 定数charactersからidが3のオブジェクトを見つけ、定数foundCharacterに代入してください const foundCharacter = characters.find((character) => { return character.id === 3; }); // foundCharacterを出力してください console.log(foundCharacter);filterメソッド
filterメソッドとは記述した条件に合う要素のみを取り出して新しい配列を作成するメソッドです。
▼演習
・filterメソッドを使ってnumbersから偶数を取り出し、定数evenNumbersに代入して出力してください
・charactersから20歳未満のキャラクターを取り出し、定数underTwentyに代入して出力してください演習const numbers = [1, 2, 3, 4]; // filterメソッドを使ってnumbersから偶数を取り出し、定数evenNumbersに代入してください const evenNumbers = numbers.filter((number) => { return number % 2 === 0; }); // evenNumbersを出力してください console.log(evenNumbers); const characters = [ {id: 1, name:"にんじゃわんこ", age: 14}, {id: 2, name:"ベイビーわんこ", age: 5}, {id: 3, name:"ひつじ仙人", age: 100} ]; // charactersから20歳未満のキャラクターを取り出し、定数underTwentyに代入してください const underTwenty = characters.filter((character) => { return character.age < 20; }); // underTwentyを出力してください console.log(underTwenty);mapメソッド
mapメソッドとは、配列内のすべての要素に処理を行い、その戻り値から新しい配列を作成するメソッドです。
▼演習
・定数numbersにmapメソッドを使って配列を作り、定数doubledNumbersに代入して出力してください
・定数namesにmapメソッドを使って新しい配列を作り、定数fullNamesに代入して出力してください演習const numbers = [1, 2, 3, 4]; // 定数numbersにmapメソッドを使って配列を作り、定数doubledNumbersに代入してください const doubledNumbers = numbers.map((number) => { return number * 2; }); // 定数doubledNumbersを出力してください console.log(doubledNumbers); const names = [ {firstName: "Kate", lastName: "Jones"}, {firstName: "John", lastName: "Smith"}, {firstName: "Denis", lastName: "Williams"}, {firstName: "David", lastName: "Black"} ]; // 定数namesにmapメソッドを使って新しい配列を作り、定数fullNamesに代入してください const fullNames = names.map((name) => { return name.firstName + name.lastName; }); // 定数fullNamesを出力してください console.log(fullNames);Javascript入門(1)初級-中級
Javascript入門(2)上級
Javascript入門(3)応用←いまここ
- 投稿日:2019-03-02T00:40:33+09:00
【今日から携わる】Javascript入門のため、Progateをやってみた(3)
Javascript入門のため、Progateをやってみた(1)
Javascript入門のため、Progateをやってみた(2)
Javascript入門のため、Progateをやってみた(3)←いまここ学習コース JavaScript Ⅴ
モジュールを組み合わせよう
このレッスンでは、JavaScriptのコードを複数のファイルに分割し、
それらを組み合わせる方法を学びます。
複数ファイルに分割することで、維持・更新のしやすいコードを書くことができます。
また、パッケージと呼ばれる便利な機能を使うための方法も学びます。最後の演習では、
readline-sync
を使って、コンソールでの質問に答えるプログラムを書きます。ファイルの分割
コードの量が増えてくると1つのファイルで管理するのが大変になるため、複数のファイルでコードを管理することがあります。
今回は、メインのプログラムを実行する「script.js」とAnimalクラスを定義する「animal.js」、Dog クラスを定義する「dog.js」の3つのファイルにコードを分けてみましょう。エラーをおこさないためにすること
ファイルを分割したときのエラーは、それぞれのファイルを関連づけし、必要な値を渡すことで解決できます。
今回の場合「dog.js」でAnimalクラスを、「script.js」でDogクラスを使用できるように設定する必要があります。▼演習
・「animal.js」では、”Animal”クラスをエクスポートする
・「dog.js」では、”animal.js”を読み込み、”Dog”クラスをエクスポートする
・「script.js」では、”dog.js”を読み込むanimal.jsclass Animal { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log("こんにちは"); } info() { this.greet(); console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); } } // Animalクラスをエクスポートしてください export default Animal;dog.js// Animalクラスをインポートしてください import Animal from "./animal"; class Dog extends Animal { constructor(name, age, breed) { super(name, age); this.breed = breed; } info() { this.greet(); console.log(`名前は${this.name}です`); console.log(`犬種は${this.breed}です`); console.log(`${this.age}歳です`); const humanAge = this.getHumanAge(); console.log(`人間年齢で${humanAge}歳です`); } getHumanAge() { return this.age * 7; } } // Dogクラスをエクスポートしてください export default Dog;script.js// Dogクラスをインポートしてください import Dog from "./dog"; const dog = new Dog("レオ", 4, "チワワ"); dog.info();値のエクスポート
クラスのエクスポートを行いましたが、エクスポート出来るのはクラスだけではありません。文字列や数値や関数など、どんな値でもエクスポートが可能です。
エクスポートする際は、下図のように「export default 定数名」とします。インポートする際は「import 定数名 from "./ファイル名"」とします。▼演習
「dogData.js」という新しいファイルを作成しました。
「script.js」から「dogData.js」にdogインスタンスを定義する部分を移動させてみましょう。script.js// 定数dogをインポートしてください import dog from "./dogData.js"; dog.info();dogData.js// Dogクラスのインポートと定数dogを以下に張り付けてください import Dog from "./dog"; const dog = new Dog("レオ", 4, "チワワ"); // 定数dogをエクスポートしてください export default dog;dog.jsimport Animal from "./animal"; class Dog extends Animal { constructor(name, age, breed) { super(name, age); this.breed = breed; } info() { this.greet(); console.log(`名前は${this.name}です`); console.log(`犬種は${this.breed}です`); console.log(`${this.age}歳です`); const humanAge = this.getHumanAge(); console.log(`人間年齢で${humanAge}歳です`); } getHumanAge() { return this.age * 7; } } export default Dog;animal.jsclass Animal { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log("こんにちは"); } info() { this.greet(); console.log(`名前は${this.name}です`); console.log(`${this.age}歳です`); } } export default Animal;名前付きエクスポート
export defaultはデフォルトエクスポートと呼ばれ、そのファイルがインポートされると自動的に「export default 値」の値がインポートされます。そのためエクスポート時の値の名前と、インポート時の値の名前は違っても問題ありません。
デフォルトエクスポートは1ファイル1つの値のみ使えます。このファイルをインポートする際には、デフォルトエクスポートの値を自動でインポートするため、値が1つのみとなっています。
複数の値をエクスポートしたい場合は、次のスライドで紹介する「名前付きエクスポート」を用います。「import { 値の名前 } from "./ファイル名"」名前付きエクスポートした値をインポートする場合は、エクスポート時と同じ名前で値を指定します。インポートする値は、エクスポート時と同様に、「import { 値の名前 } from "./ファイル名"」と{}で囲んで指定します。
「export { 名前1, 名前2 }」名前付きエクスポートは、デフォルトエクスポートと違い、複数の定数やクラスを指定してエクスポートが出来ます。
また、左の図のように、「export { 名前1, 名前2 }」という形で書くことにより、1つのファイルから複数のエクスポートが出来ます。
インポートの際も、コンマで区切ることで複数のインポートができます。▼演習
「dogData.js」で定数dogを、2つの定数dog1とdog2に書きかえておきました。
・「dogData.js」に、定義した定数dog1とdog2をエクスポートしていきましょう。
・「script.js」に、import dog
ではなく、dog1
dog2
をインポートするように書き換えましょう
・dog1
dog2
の情報を出力するようにしましょうdogData.jsimport Dog from "./dog"; // 定数dog1, dog2を確認してください const dog1 = new Dog("レオ", 4, "チワワ"); const dog2 = new Dog("ベン", 2, "プードル"); // 以下を書き換えて、定数dog1, dog2をエクスポートしてください export {dog1, dog2};script.js// 以下を書き換えて、定数dog1, dog2をインポートしてください import { dog1, dog2} from "./dogData"; // 定数dog1とdog2を出力するように左からコピーして書き換えてください console.log("----------"); dog1.info(); console.log("----------"); dog2.info();相対パス
これまでファイルの指定は「./ファイル名」としてきました。
これは相対パスと言い、記述されているファイルからみた位置関係を示しています。▼ファイルを分ける
.src ├── class │ ├── animal.js │ └── dog.js ├── data │ └── dogDate.js └── script.jsパッケージ(1)
JavaScriptの世界では、誰かが作った便利なプログラムがパッケージという形で公開されています。また、JavaScriptの機能を使うことで、このパッケージを自分のプログラムの中に組み込んで使うことができます。
使い方
定義import 定義名 from "パッケージ名";使い方import chalk from "chalk"; console.log(chalk.yellow("Hello World")); console.log(chalk.bgCyan("Hello World"));パッケージ(2)
コンソールから値を入力出来るようにするためのパッケージを使ってみましょう。
readline-sync
コンソールへの値の入力と、その入力された値をプログラムの中で使うことができるようになります。
▼演習
readline-sync
を使って、コンソールでの質問に答えるプログラムを書いてみようdogDate.js// readline-syncをインポートしてください import readlineSync from "readline-sync"; import Dog from "../class/dog"; const dog1 = new Dog("レオ", 4, "チワワ"); // readlineSync.questionを使って書き換えてください // const name = "ベン"; const name = readlineSync.question("名前を入力してください: "); // readlineSync.questionIntを使って書き換えてください // const age = 2; const age = readlineSync.questionInt("年齢を入力してください: "); // readlineSync.questionを使って書き換えてください // const breed = "プードル"; const breed = readlineSync.question("犬種を入力してください: "); const dog2 = new Dog(name, age, breed); export { dog1, dog2 };つぎの章
学習コース JavaScript Ⅵ
このレッスンでは「配列を操作するメソッド」について学習していきましょう。
配列の操作を学ぶと、データを柔軟に扱えるようになります。
これから学ぶ便利なメソッドはJavaScriptを実践的に使う場合に必須の知識です。pushメソッド
▼演習
pushメソッドを使って、配列に新しい要素を追加しましょう。演習const characters = ["にんじゃわんこ", "ベイビーわんこ", "ひつじ仙人"]; console.log(characters); // pushメソッドを使って配列charactersに、文字列「とりずきん」を追加してください characters.push("とりずきん"); // 配列charactersを出力してください console.log(characters);forEachメソッド
forEachメソッドは配列の中の要素を1つずつ取り出して、全ての要素に繰り返し同じ処理を行うメソッドです。
forEachの仕組み
定義配列 = [要素1,要素2,要素3]; 配列.forEach((引数) => {処理});//アロー関数を使用 //配列の中の要素を1つずつ取り出して同じ処理をする▼演習
forEachメソッドを使って、配列charactersの中身をすべて出力してください演習const characters = ["にんじゃわんこ", "ベイビーわんこ", "ひつじ仙人", "とりずきん"]; // forEachメソッドを使って、配列charactersの中身をすべて出力してください characters.forEach((character) => { console.log(character); });findメソッド
findメソッドとは、アロー関数の処理部分に記述した条件式に合う1つ目の要素を配列の中から取り出すメソッドです。
とりあえず例文const numbers = [1,3,5,7]; const foundNumber = numbers.find((number) => { //number:引数 return number > 3; // number> 3:条件 }); console.log(foundNumber);//5 //条件に合う最初の要素が取り出されている▼演習
1.findメソッドを使って配列numbersから3の倍数を見つけ、定数foundNumberに代入して出力してください
2.定数charactersからidが3のオブジェクトを見つけ、定数foundCharacterに代入して出力してください演習const numbers = [1, 3, 5, 7, 9]; // findメソッドを使って配列numbersから3の倍数を見つけ、定数foundNumberに代入してください const foundNumber = numbers.find((number) => { return number % 3 === 0; }); // foundNumberを出力してください console.log(foundNumber); const characters = [ {id: 1, name: "にんじゃわんこ", age: 6}, {id: 2, name: "ベイビーわんこ", age: 2}, {id: 3, name: "ひつじ仙人", age: 100}, {id: 4, name: "とりずきん", age: 21} ]; // 定数charactersからidが3のオブジェクトを見つけ、定数foundCharacterに代入してください const foundCharacter = characters.find((character) => { return character.id === 3; }); // foundCharacterを出力してください console.log(foundCharacter);filterメソッド
filterメソッドとは記述した条件に合う要素のみを取り出して新しい配列を作成するメソッドです。
▼演習
・filterメソッドを使ってnumbersから偶数を取り出し、定数evenNumbersに代入して出力してください
・charactersから20歳未満のキャラクターを取り出し、定数underTwentyに代入して出力してください演習const numbers = [1, 2, 3, 4]; // filterメソッドを使ってnumbersから偶数を取り出し、定数evenNumbersに代入してください const evenNumbers = numbers.filter((number) => { return number % 2 === 0; }); // evenNumbersを出力してください console.log(evenNumbers); const characters = [ {id: 1, name:"にんじゃわんこ", age: 14}, {id: 2, name:"ベイビーわんこ", age: 5}, {id: 3, name:"ひつじ仙人", age: 100} ]; // charactersから20歳未満のキャラクターを取り出し、定数underTwentyに代入してください const underTwenty = characters.filter((character) => { return character.age < 20; }); // underTwentyを出力してください console.log(underTwenty);mapメソッド
mapメソッドとは、配列内のすべての要素に処理を行い、その戻り値から新しい配列を作成するメソッドです。
▼演習
・定数numbersにmapメソッドを使って配列を作り、定数doubledNumbersに代入して出力してください
・定数namesにmapメソッドを使って新しい配列を作り、定数fullNamesに代入して出力してください演習const numbers = [1, 2, 3, 4]; // 定数numbersにmapメソッドを使って配列を作り、定数doubledNumbersに代入してください const doubledNumbers = numbers.map((number) => { return number * 2; }); // 定数doubledNumbersを出力してください console.log(doubledNumbers); const names = [ {firstName: "Kate", lastName: "Jones"}, {firstName: "John", lastName: "Smith"}, {firstName: "Denis", lastName: "Williams"}, {firstName: "David", lastName: "Black"} ]; // 定数namesにmapメソッドを使って新しい配列を作り、定数fullNamesに代入してください const fullNames = names.map((name) => { return name.firstName + name.lastName; }); // 定数fullNamesを出力してください console.log(fullNames);Javascript入門のため、Progateをやってみた(1)
Javascript入門のため、Progateをやってみた(2)
Javascript入門のため、Progateをやってみた(3)←いまここ