20210416のJavaScriptに関する記事は22件です。

Electron:ver12のcontextBridgeとipcRenderer.invoke

 本記事では、ElectronのVer.12以降でセキュアにモジュールを使用する際に躓きがちな「ContextBridge.ipcMainWorld」と「ipcRenderer.invoke」について解説します!  さらに、具体例にSlackAPIによるメッセージの送信を使用することで、一緒に使い方を覚えてしまおうという魂胆です! (注意)  非プログラマーの独学&初投稿になりますのでどうぞお手柔らかに。  ここ直すといいよ!というアドバイスいただけたら幸いです! 目次 作成する機能 セキュアにモジュールを使用するには 実際にやってみる SlackAPIでメッセージ送信 まとめ 1.作成する機能  「ボタンをクリックしたら、Slackにあらかじめ決められたメッセージ(かめはめ波)を飛ばす」だけの非常にシンプルな機能を作成します。  という建前のもと、デバックの取り方わからなかったので、代わりにAPIでメッセージを投げます(笑) electronについてはこちら 2.セキュアにモジュールを使用するには electronでは、以前からXSSの危険性が指摘されています。 バージョン更新のたびにデフォルトの設定が変更になるなど、 私を含め初心者にとってややこしい状況かと。 まずは公式ドキュメントを見てみましょう。また併せて日本語でも検索します。 それぞれ共通するキーワードをピックアップします。 preload.js ContextBridge.ipcMainWorld ipcRenderer.invoke ipcMain.handle これらを組み合わせることにより、従来より指摘されていたXSSの危険性を回避しつつ、モジュールを使用することができるっぽいですね。 全体のイメージはこんな感じでしょうか。 3.実際にやってみる まずはじめに、画面となるHTMLを準備します。 test.html <html lang="ja"> <head> <meta charset="UTF-8"> <meta content="width=device-width" name="viewport"> <title>テスト</title> </head> <body> <p><input type="button" value="かめはめ波" id=Button></p> <script src="./renderer.js"></script> </body> </html>  rendererプロセス側から「window.任意のAPIキー.任意のAPI名(引数)」という形でpreload.jsの処理を呼び出します。 renderer.js const button = document.getElementById("Button"); button.addEventListener('click',async () => { const result = await window.electron.sendToMain("かめはめ波"); console.log(result); })  次にpreload.jsでは、「contextBridge.exposeInMainWorld('任意のAPIキー', {任意のAPI名:処理内容})」という形で窓口を用意します。  この時、処理内容には「ipcRenderer.invoke('任意の名前', 引数)」を記述し、mainプロセスの処理を呼びに行きます。 preload.js const {contextBridge,ipcRenderer} = require("electron"); contextBridge.exposeInMainWorld( 'electron', { sendToMain: async (skill) => { const result = await ipcRenderer.invoke('invoke-test', skill) ; return result; } })  最後にmainプロセスでは、preload.jsに対応する「ipcMain.handle('任意の名前',async(event,data) => { 処理内容...}」と記述し、呼び出しの受け取りと処理の実行を行います。 main.js ipcMain.handle('invoke-test', async(event,data) => { const Result = await sendMessageSlack(); return Result }); const sendMessageSlack = async() => { //処理 } SlackAPIでメッセージ送信 SlackのApiの使用に関してはこちら  上記のMainプロセスで呼び出される関数を以下のように記述します。TokenやChannel名は態々外に出さなくてもいいかもですが、後から見てわかりやすいかなと思ってconstしてます。 main.js const { WebClient, LogLevel } = require("@slack/web-api"); const apiToken = "Slackの設定で取得したApiToken"; const chatGroup = "任意のチャンネル名"; const client = new WebClient(apiToken, {logLevel: LogLevel.DEBUG}); const sendMessageSlack = async(text) => { try { const result = await client.chat.postMessage({ token: apiToken, channel: chatGroup, text: text }); console.log(result); } catch (error) { console.error(err); } } まとめ その他の参考記事 所感  初心者にとって、async/awaitが入れ子になっている上に、他では使わない概念の理解やMethodの使用が求められ、なかなか大変でした。  すでに同様の内容を書かれている記事も見ましたが、うまく動かなかったり、 かゆいところに届かなかったりで、自分で試行錯誤して何とかたどり着きました。  もし同じような境遇の方がこれをタタキにして、やりたかったことができると?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

復習 Javascript きほんの 「き」 初心者    if文

理解の仕方が間違っていたら教えてください!よろしくお願い致します if文 //0~100のランダムな数字を取得 var rand = Math.floor(Math.random() * 101);//ランダムな数字を 取得できる書き方 document.write('<p>rand: ' + rand + '</p>');//rand=ランダムのこと //↑rand: ランダムな数字 の形でブラウザに表示 if (rand >= 80) { document.write('<p>randは80以上</p>'); } else if (rand <= 30) { document.write('<p>randは30以下</p>'); } else { document.write('<p>randは30より大きく80より小さい</p>'); } ブラウザ表示 rand: 15 randは30以下 rand: 75 randは30より大きく80より小さい if文書き方================================================ if (条件1を書く) { 条件1に一致した場合に実行するプログラム  } else if (条件2番目をかく){ 条件1に一致しなくて、条件2に一致した時に実行するプログラム } else {  全ての条件に一致しなかった場合に実行するプログラム } じゃあ当てはめてみる var rand = Math.floor(Math.random() * 101);//ランダムな数字を 取得できる書き方 document.write('<p>rand: ' + rand + '</p>'); if (rand >= 80) {//()のなかが条件1 randが80より大きい時。 document.write('<p>randは80以上</p>');//書き出しは randは80以上 } else if (rand <= 30) {//条件2 randが30より小さい時。条件1に一致しなくて条件2に一致した時。 document.write('<p>randは30以下</p>');//書き出し randは30以下 } else {//全部の条件に一致しなかった時 document.write('<p>randは30より大きく80より小さい</p>'); }//書き出し randは30より大きく80より小さい 条件に一致したかで処理内容を分けることができる。  if文は、else if . else はぶくことができる。 //0~100のランダムな数字を取得 var rand = Math.floor(Math.random() * 101); //80以上の場合 if (rand >= 80) { document.write('受験合格!!'); } これはifのみ、変数randが80以上の場合は受験合格と表示されますが、80より小さい場合は何も表示されないです //0~1のランダムな数字を取得 var rand = Math.floor(Math.random() * 2); //1の場合 if (rand === 1) { document.write('1マス進む'); //1以外 } else { document.write('1回休み'); } ifとelseだけ使った文 0~1のランダムな数字を取得して 1の場合は 1マス進む それ以外は 1回休みと表示
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

中学1年生がCADソフトを自作した話

noteで書きました→ https://note.com/ittakun/n/nd529757c6ff1
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node+WebSocket-Nodeで動画データ送信時にEPIPEが発生する

はじめに Node+WebSocket-Nodeでクライアントにある動画ファイル(.mp4)をサーバーに送信するプログラムを書き動かしている際、とある送信タイミングで(クライアント側で)EPIPEが発生したので、その原因と対応内容について記載します。 送信していたデータ クライアントから送信する動画ファイル(.mp4)は以下のようなものでした。 -rwxrwxrwx 1 root root 164514 Apr 16 14:13 /path/data/20210416/14/1311.mp4 -rwxrwxrwx 1 root root 157882 Apr 16 14:13 /path/data/20210416/14/1313.mp4 -rwxrwxrwx 1 root root 266566 Apr 16 14:13 /path/data/20210416/14/1315.mp4 -rwxrwxrwx 1 root root 296955 Apr 16 14:13 /path/data/20210416/14/1317.mp4 -rwxrwxrwx 1 root root 175276 Apr 16 14:13 /path/data/20210416/14/1319.mp4 上記の1311.mp4, 1313.mp4, 1315.mp4の送信は問題なく行えますが、(とある送信タイミング==)1317.mp4を送信するタイミングでEPIPEが発生しています。 サーバー側のソース myServer.js const WebSocketServer = require('websocket').server; const http = require('http'); var server = http.createServer(function(request, response) { console.log((new Date()) + ' Received request for ' + request.url); response.writeHead(404); response.end(); }); server.listen(port, function() { console.log((new Date()) + ' Server is listening on port ' + port); }); var wsServer = new WebSocketServer({ httpServer: server, autoAcceptConnections: false, }); wsServer.on('request', function(request) { if (!originIsAllowed(request.origin)) { request.reject(); return; } var connection = request.accept('echo-protocol', request.origin); connection.on('message', function(message) { }); connection.on('close', function(reasonCode, description) { }); }); クライアント側のソース myClient.js client = new WebSocketClient(); client.connect(wss_server, 'echo-protocol', origin, wss_headers); client.on('connect', function(connection) { console.log('Event : Connected'); connection.on('error', function(error) { console.log("Connection Error: " + error.toString()); }); connection.on('close', function() { console.log('Connection Closed'); }); エラーになった時のクライアント側のコンソールログ クライアント側のコンソールログ Connection Error: Error: write EPIPE Connection Closed 原因 先に書いてしまいますが、原因は送信したデータのサイズが、WebSocket-Node(WebSocketServer)の最大メッセージサイズ(maxReceivedMessageSize)を超えていたためでした。 コンポーネント パラメータ デフォルト値 実際のデータサイズ WebSocketServer maxReceivedMessageSize 0x100000(1MB) 1MB以上??? WebSocketClient maxReceivedMessageSize 0x800000(8MB) - 今回問題となったのは送信時(サーバーが受信した時)ですのでWebSocketServerのmaxReceivedMessageSizeのサイズが影響してきますが、参考までにWebSocketClient のmaxReceivedMessageSizeも載せておきます。サーバーからの送信時(クライアント側で受信した時)にEPIPEが発生した場合は、この辺りを疑ってください。 解決策 WebSocketServerのmaxReceivedMessageSizeを3MBに変更します。(今回のケースでは2MBでも大丈夫だとは思いますが、ひとまず3MBにしました) WebSocketClientの最大受信メッセージサイズ(maxReceivedMessageSize)のデフォルトは8MBです。 なぜWebSocketServerのmaxReceivedMessageSizeのデフォルトは1MBなのか、、、。 Webサーバーとブラウザの関係から来ているのでしょうかね。(Webサーバーにある動画ファイルをブラウザで再生するようなケースを想定しているなど) 対応コード WebSocketServerのconfigにて、maxReceivedMessageSizeに3MB(0x100000 * 3)を追加しました。 myServer.js var wsServer = new WebSocketServer({ httpServer: server, autoAcceptConnections: false, maxReceivedMessageSize: 0x100000 * 3 ・・・ この行を追加 }); maxReceivedMessageSizeに設定する値は10進で 3145728 でもよいですが、WebSocketServerのソースを見ると以下のようになっていましたので、踏襲した形で 0x100000 * 3 としました。 WebSocket-Node:WebSocketServer.js // 1MiB max message size, only applicable if // assembleFragments is true maxReceivedMessageSize: 0x100000, 解決するまでの経緯 参考までに解決するまでの経緯を書きたいと思います。 WebSocket-Node のソースを取得しチェック WebSocket-NodeのGithubにあるREADMEだけでは情報が不足していますのでソースをgit cloneし眺めてみます。EPIPEがヒットしないか検索したり、最大受信メッセージサイズをチェックしたり。 実は最初っから最大受信メッセージサイズ辺りを疑っていました。 ただ最初にも記載しましたが、送信していた動画ファイルのサイズが300KB弱であることと、maxReceivedMessageSizeのデフォルト値が1MBだったので問題ないと判断し、ここではスルーしてしまいました。(最終的にはここにたどり着くのですが。詳細はまた後記) デバッグログの追記 WebSocket-Nodeのソースを眺めても問題なさそうだったので、もう少し挙動を確認するため、ログを埋め込むことにしました。 クライアント側のソース(myClient.js)ではエラーの内容を出力していたのと、コンソールログに"Connection Closed"とも出ていたので、そうなるとクライアント↔サーバー間の接続が切れている==サーバー側でも何かしらログが取れるかも、ということでサーバー側のロジック(myServer.js)を見てみます。 すると、、、 myServer.js connection.on('close', function(reasonCode, description) { }); reasonCode, descriptionでもう少し詳細なログが取れそうなことが分かりました。そこで、、、 myServer.js connection.on('close', function(reasonCode, description) { console.log('disconnected : reasonCode =', reasonCode, ", description =", description); }); として動作確認を行います。すると、、、 サーバー側のコンソールログ disconnected : reasonCode = 1009 , description = Maximum message size exceeded. が出ました。”WebSocket 1009”で検索してもヒットしますが、description にある通りメッセージのサイズオーバーが原因のようです。(あれあれ?やはり動画ファイルのサイズが影響している???) 改めて送信側(myClient.js)のロジックを見直してみると、、、 myClient.jsの送信ロジックあたり fs.readFile(movieFilePath, function(err, data) { if (err) { console.error("Failed to publish : " + err); if (callback) callback(); return; } var content = { "from": myId(), "to": getRequesterId(), "clientType": "client", "request": "send_movie_file", "fileKey": fileKey, "movieIndex": movieIndex, "data": msgpack.encode(data) }; // Websocket通知 wsAgent.sendContent(JSON.stringify(content)); !! データのサイズが1MBを超えたのは msgpack.encode(data) を行っているためですね。 実は動画ファイルのデータだけではなく、その動画ファイルに対する付随情報も送信する必要があったため上記のロジックのようにJSON形式で送信していました。JSONでは動画データ(バイナリ)をそのまま送れないのでmsgpackを利用しています。 BASE64などでもそうですが、msgpackでもエンコードの過程でエスケープ文字などが付加されるため、最終的なデータのサイズは元のサイズより増えます。元は300KB弱の動画ファイルのデータがmsgpack化により1MBを超えてしまったのでしょう。 実際にJSON.stringify(content)のサイズをログに出してみると、、、 JSON.stringify(content)のサイズ data size = 1070254 のようにmaxReceivedMessageSizeのデフォルト値である1MB(1,048,576Byte)に対して20KBオーバーになっていました。 そこで改めてWebSocket-Nodeのソースを再確認し、"対応コード"で記載した設定を追記しました。 するとEPIPEで送信できなかった1317.mp4も無事に送信できるようになりました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

軽量JavaScriptエンジン “QuickJS” を試す (2021-04-16)

「qiita:C/C++に組み込める軽量JavaScriptエンジン “QuickJS” を試す」を追体験する。 (環境は Windows10 pro + docker desktop からの Debian busterである) https://bellard.org/quickjs/ C:\> docker run -it debian:latest root@2afe0cfe2c13:~# apt-get update root@2afe0cfe2c13:~# apt-get install gcc git root@2afe0cfe2c13:~# git clone https://github.com/bellard/quickjs.git root@2afe0cfe2c13:~# cd quickjs root@2afe0cfe2c13:~/quickjs# make install root@2afe0cfe2c13:~/quickjs# ls -la $(which qjs) 956 -rwxr-xr-x 1 root root 978248 Apr 16 10:04 /usr/local/bin/qjs 小さい。かわいい。 続いて JS ファイルをコンパイルする を試す。 root@2afe0cfe2c13:~# ls -la a.out -rwxr-xr-x 1 root root 5478048 Apr 16 10:31 a.out root@2afe0cfe2c13:~# strip a.out root@2afe0cfe2c13:~# ls -la a.out -rwxr-xr-x 1 root root 888104 Apr 16 10:33 a.out root@2afe0cfe2c13:~# ./a.out Hello, Alice! 興味があるのは、これポータビリティあるかどうかということ。 もう一個、まっさらなdebian:latest を起動してコピーして実行してみた。動くじゃないか!! root@87bbb47e078a:~# ./a.out Hello, Alice! root@87bbb47e078a:~# qjs bash: qjs: command not found これは面白そう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

回転する見た目も実装、jQueryで作るコイントスシステム

どうも7noteです。コインが回転するコイントスシステムを作った コイントスで調べると、表と裏がランダムで表示されるレベルのコイントスシステムはあったのですが、実際にコインが空中で回転するような見た目のコイントスはなかったので作ってみました。 コイントスシステムのソース index.html <div class="coin"></div> style.css .coin { width: 50px; /* コインの幅 */ height: 50px; /* コインの高さ */ background: url(omote.png) no-repeat center / 100% auto; /* 背景にコインの画像を設定 */ position: relative; /* 表示位置を変更するために必要 */ cursor: pointer; /* hover時にカーソルを指の形にする */ } .coin.toss { animation : toss 0.3s linear infinite,toss2 0.3s infinite steps(1, end),updown 2s ease; /* anime「toss」、anime「toss2」、anime「updown」 */ } /* X軸を基準に回転させるアニメーション */ @keyframes toss{ 0% { transform:rotateX(0deg); } 100% { transform:rotateX(360deg); } } /* 表裏を切り替えるアニメーション */ @keyframes toss2{ 25% { background-image: url(ura.png); } 75% { background-image: url(omote.png); } } /* コインを上下させるアニメーション */ @keyframes updown{ 0% { top: 0; } 50% { top: -100px; } 100% { top: 0; } } script.js $(function(){ $('.coin').not(':animated').on('click',function(){ /* ランダムで0か1を取得 */ rndm = Math.floor( Math.random() * 2 ); /* アニメーションを開始させる */ $(this).addClass('toss'); /* 1.9秒後にアニメーションを停止し、コインの裏表を表示する */ setTimeout(function(){ $('.coin').removeClass('toss'); if(rndm == 1){ $('.coin').css(`background-image`,`url(ura.png)`); } else { $('.coin').css(`background-image`,`url(omote.png)`); } },1900); }); }); ※jQueryを使用しています。jQueryについてはこちら 解説 ・3種類のcssアニメーション ・jsでの表裏のランダム、アニメーションの起動 大きくこの2つに分けて解説します。 ▼ 3種類のcssアニメーション アニメーションは3つのアニメーションをカンマ区切りで書いて1行にしているだけですのでこれも分けて解説していきます。 「toss」 .coin { animation : toss 0.3s linear infinite; /* 0.3秒ごとにアニメーションを繰り返す */ } /* X軸を基準に回転させるアニメーション */ @keyframes toss{ 0% { transform:rotateX(0deg); } 100% { transform:rotateX(360deg); } } X軸、つまり コインを(ー)←この向きを軸として回転をさせるアニメーションです。 「toss2」 .coin { animation : toss2 0.3s infinite steps(1, end); /* 0.3秒ごとにアニメーションを繰り返す。かつ徐々にではなくコマ割りで動かす。 */ } /* 表裏を切り替えるアニメーション */ @keyframes toss2{ 25% { background-image: url(ura.png); } 75% { background-image: url(omote.png); } } 表のコインを回転させるだけでは、コインが回転してもずっと表の絵が見えてしまうので、コインが反転したタイミング、つまりコインが回転する際に横一線になる瞬間の25%時間経過したタイミングで裏に、75%時間経過したタイミングでまた表に戻すことで表裏のあるコインが回転しているように見せることができます。 通常backgroundはアニメーションを指定すると徐々に変化してしまうので、表の文字がどんどん薄くなる・・・みたいなことになってしまうのでそうならないようにsteps(1, end)を指定することで、コマ撮りのような瞬時に切り替わる設定を入れています。 「updown」 .coin { animation : updown 2s ease; /* 2秒かけて1回アニメーションする */ } /* コインを上下させるアニメーション */ @keyframes updown{ 0% { top: 0; } 50% { top: -100px; } 100% { top: 0; } } topの指定を変更することで、コインを空中に飛ばすようなアニメーションになります。 ▼ jsでの表裏のランダム、アニメーションの起動 jsに関してはほぼコメントを入れている通りの処理を順番に行っているだけです。 $(function(){ $('.coin').not(':animated').on('click',function(){ /* ランダムで0か1を取得 */ rndm = Math.floor( Math.random() * 2 ); /* アニメーションを開始させる */ $(this).addClass('toss'); /* 1.9秒後にアニメーションを停止し、コインの裏表を表示する */ setTimeout(function(){ $('.coin').removeClass('toss'); if(rndm == 1){ $('.coin').css(`background-image`,`url(ura.png)`); } else { $('.coin').css(`background-image`,`url(omote.png)`); } },1900); }); }); コインがクリックされた時 ↓ ランダムで0か1を決定、かつtossアニメーションを開始させるためにクラス「toss」を付与 ↓ 1.9秒後にアニメーションを停止させるためにクラス「toss」を取り除く。かつ0か1の値を取得してそれに合わせて表示する画像を決定。 こんな感じの流れになりますね。 まとめ 本当はcssだけで作りたかったのですがランダムを再現しようと思うとさすがに骨が折れそうだったので、js便りになってしまいました。 cssマスターならきっとランダムっぽい再現もできるのかもしれませんがまだまだ勉強不足を感じます。 私的にはきれいにアニメーションを作ることができたので、webのオンラインゲーム等を作成するときにはぜひ使ってみたいですね。 jsを追加して、これまで出た表か裏かを画面端に記録していくようなシステムなんかも追加してもいいかもしれませんね。 画像データ:いらすとやよりお借りしました。 https://www.irasutoya.com/2017/12/blog-post_410.html おそまつ! ~ Qiitaで毎日投稿中!! ~ 【初心者向け】WEB制作のちょいテク詰め合わせ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

復習 Javascript きほんの 「き」 初心者 代入演算子編 6 

代入演算子 何回かにわたって利用してきた「=」が代入演算子になるよ =の右側にある値を =の左側に描かれた名前の変数に代入する。 代入演算子は 算術演算子と組み合わせて利用できる。 算術演算子 +,-,*,% 復習はこちら↓ 復習 Javascript きほんの 「き」 初心者 算術演算子編 4 var num = 10; document.write('<p>num : ' + num + '</p>'); // 値を加算して変数に代入 num += 6; // num = num + 6 document.write('<p>num += 6 :' + num + '</p>'); ブラウザ表示 num : 10 num += 6 :16 ん?ちょっと頭の整理を 解体新書 var num = 10; numに10を入れる document.write('num : ' + num + ''); これは、ブラウザには''は文字列だから、 ブラウザ表示はnum : なって numで何も囲まれてないところは、10が入るから num : 10 と表示。 次 num += 6; これが定義されてる。 document.write('num += 6 :' + num + '') num += 6: まで文字列でブラウザ表示されて なんも囲まれてないnumには10+6してあげて num += 6:16 となる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsでSlackみたいなメンションと入力中の名前色付けを作った完成形コードとハマった話と解決法

Repsona LLCの@GussieTechです。「理想のプロジェクト管理ツール」Repsonaを開発しています。 RepsonaではSlackとかTeamsみたいにメンションでお知らせできるようになっています。受信トレイの新規開発に合わせて、メンションの体感もカイゼンしようということで立ち向かったので、その完成形コードと、そこに至るまでのハマりポイントや解決法などをまとめておきます。色付きメンションの記事は案外ないのでお役に立てれば嬉しいです。 完成形 忙しい人へ 完成形のコード抜粋をCodeSandboxにつくりました。 環境 Vue v2.x + Bootstrap v4.x + vue-mention ハマった話と解決法 もくじ メンションポップアップ Tributeが期待した位置に表示されてくれない vue-mentionが表示されない vue-mentionの表示位置がおかしい 全角@で期待通りに動いてくれない 入力中の名前色付け textarea内の文字に色をつける方法がない スクロールバーの有無で色付け位置がズレる メンションポップアップ Tributeが期待した位置に表示されてくれない リリース当初よりTributeでメンションの動作を実装していました。通常の文字数や表示位置では問題なく動作していたのですが、画面全体のスクロール位置や入力文字数によって期待した位置に表示されない問題がありました。かなり試行錯誤をしたのですがうまくいかなかったため、vue-mentionに乗り換えることにしました。 vue-mentionが表示されない 乗り換えたところ、なぜか表示されませんでした。これがかなりハマりました。vue-mentionはv-tooltipに依存しているのですが、Bootstrapの.tooltipクラスとぶつかって、なんとopacity: 0;が採用されてしまっていました。 v-tooltip内の.tooltipに関しては、opacity: 1;が適用されるようにCSSを設定して回避しました。 [id^="popover_"].tooltip { opacity: 1; } vue-mentionの表示位置がおかしい いや違う、左側ではなく右側にでて欲しいのです。デモでは右に出ているのです。 細かい原因は調べませんでしたが、きっとまたBootstrapのCSSと競合しているのだろうと思いました。そこでまるっとv-tooltipのCSSが優先して適用されれば良いのではないかと考えました。 CSSは、対象を詳しく指定しているセレクタが優先されるので、idと合わせて指定することでオーバーライドしてやります([id^="popover_"].tooltip)。v-tooltipのデフォルトのCSSで上書きすることで、うまく解決しました。 [id^="popover_"].tooltip { display: block !important; z-index: 10000; .tooltip-inner { background: black; color: white; border-radius: 16px; padding: 5px 10px 4px; } .tooltip-arrow { width: 0; height: 0; border-style: solid; position: absolute; margin: 5px; border-color: black; z-index: 1; } &[x-placement^="top"] { margin-bottom: 5px; .tooltip-arrow { border-width: 5px 5px 0 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; bottom: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="bottom"] { margin-top: 5px; .tooltip-arrow { border-width: 0 5px 5px 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-top-color: transparent !important; top: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="right"] { margin-left: 5px; .tooltip-arrow { border-width: 5px 5px 5px 0; border-left-color: transparent !important; border-top-color: transparent !important; border-bottom-color: transparent !important; left: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &[x-placement^="left"] { margin-right: 5px; .tooltip-arrow { border-width: 5px 0 5px 5px; border-top-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; right: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &.popover { $color: #f9f9f9; .popover-inner { background: $color; color: black; padding: 24px; border-radius: 5px; box-shadow: 0 5px 30px rgba(black, .1); } .popover-arrow { border-color: $color; } } &[aria-hidden='true'] { visibility: hidden; opacity: 0; transition: opacity .15s, visibility .15s; } &[aria-hidden='false'] { visibility: visible; opacity: 1; transition: opacity .15s; } } きました!(幅やカラー指定の調整は省略しています) 全角@で期待通りに動いてくれない 全角@でポップアップが出て、確定するとなんだか@がいっぱい出てきてしまいます。 ドキュメントに記載はないのですが、どうやら隠しオプションみたいなものがあって、omitKeyというのを見つけました。 これをセットすることで、起動キーを排除した上で、valueをセットできるようになりました。valueには@も含めることで、期待通り動作します。でも、まだもうひとつ@が残ってしまいます。 IMEの変換確定とメンションの決定が重なって、決定後に@が残ってしまっています。IME確定後のみ決定を発動させる必要がありそうです。 vue-mentionのコードをみてみるとonKeyDown()が担っているようです。この部分ですね。 if ((e.key === 'Enter' || e.key === 'Tab' || e.keyCode === 13 || e.keyCode === 9) && 非常にdirtyですがe.key === 'Enter' || e.key === 'Tab'を無効にするオーバーライドをすることで回避しました。しかし、keyCodeはdeprecatedなので、いずれ期待通り動かなくなるかもしれません。他サービスで全角@が気持ちよく動作しないものがあるのは、きっとこの辺りの事情があるのでしょう。英語圏の人たちには関係ないでしょうし・・。ところで、正しくはどう実装するべきなのだろう。 入力中の名前色付け textarea内の文字に色をつける方法がない さまざまなサービスで簡単そうに実現しているのですが、甘くみていました。textarea css font-colorなどとググってみても、どうやらtextarea内の特定の文字に色をつける方法はないようです。 そうするとcontenteditableが頭をよぎりますが、プレーンテキストとして取り扱うのはかなりしんどさがあります(以前とんでもなくハマったのですがここでは省略します)。 そこで、textareaに、入力中の文字とまったく同じ文字を表示するdivを重ねる方法を採用しました。 ピンクで表示したdiv内にtextareaで記述した内容をそのまま透明文字で表示して、必要に応じて<span>等でカラーをつける作戦です。仮実装してすぐ期待通りに動いたので、簡単そうに見えました。しかし・・・ スクロールバーの有無で色付け位置がズレる ああ・・なるほど。スクロールバーさんを忘れていました。サイズ変更などでも表示されますね。スクロールバー表示有無を検出してスクロールバー幅分をpaddingしてやる必要がありそうです。 const barWidth = this.$refs.comment.$el.getBoundingClientRect().width - this.$refs.comment.$el.clientWidth (実際はtextareaに適用されたボーダーなどのスタイルも加味して計算しています) うまくいきました。 完成形 <template> <div id="app"> <div class="p-3"> <div class="position-relative"> <div ref="textareaCover" class="textarea-cover" v-html="commentMention" ></div> <mentionable :keys="['@', '@']" :items="users" insert-space omit-key> <textarea ref="comment" class="form-control comment" rows="4" v-model="comment" @keyup="commentScroll" @scroll="commentScroll" /> <template #item="{ item }"> <div class="user"> <span class="font-weight-bold"> {{ item.value }} </span> <span class="ml-2"> {{ item.firstName }} </span> </div> </template> </mentionable> </div> </div> </div> </template> <script> import { Mentionable } from "vue-mention"; Mentionable.methods.onKeyDown = function (e) { if (this.key) { if (e.key === "ArrowDown" || e.keyCode === 40) { this.selectedIndex++; if (this.selectedIndex >= this.displayedItems.length) { this.selectedIndex = 0; } this.cancelEvent(e); } if (e.key === "ArrowUp" || e.keyCode === 38) { this.selectedIndex--; if (this.selectedIndex < 0) { this.selectedIndex = this.displayedItems.length - 1; } this.cancelEvent(e); } if ( (e.keyCode === 13 || e.keyCode === 9) && this.displayedItems.length > 0 ) { this.applyMention(this.selectedIndex); this.cancelEvent(e); } if (e.key === "Escape" || e.keyCode === 27) { this.closeMenu(); this.cancelEvent(e); } } }; export default { name: "App", components: { Mentionable, }, data() { return { comment: "", users: [ { value: "@akryum", firstName: "Guillaume", }, { value: "@posva", firstName: "Eduardo", }, { value: "@atinux", firstName: "Sébastien", }, ], }; }, computed: { commentMention() { if (typeof this.comment?.replaceAll !== "function") { return this.comment; } const replaced = this.comment ?.replaceAll("&", "&amp;") ?.replaceAll(">", "&gt;") ?.replaceAll("<", "&lt;") + "\n\n"; const search = new RegExp( this.users .slice() .sort((a, b) => b.value?.length - a.value?.length) .map((user) => user.value) .join("|"), "g" ); return replaced.replace(search, (match, offset) => { return `<span class="mention-str">${match}</span>`; }); }, }, methods: { mounted() { setTimeout(() => { this.resize(); window.addEventListener("resize", this.resize); this.$once("hook:beforeDestroy", () => { window.removeEventListener("resize", this.resize); }); }); }, resize() { const barWidth = this.$refs.comment.getBoundingClientRect().width - this.$refs.comment.clientWidth - 2; // border this.$refs.textareaCover.style.paddingRight = `calc(12px + ${barWidth}px)`; }, commentScroll() { this.$refs.textareaCover.scrollTop = this.$refs.comment.scrollTop; this.resize(); }, }, }; </script> [id^="popover_"].tooltip { display: block !important; z-index: 10000; .tooltip-inner { background: black; color: white; border-radius: 16px; padding: 5px 10px 4px; } .tooltip-arrow { width: 0; height: 0; border-style: solid; position: absolute; margin: 5px; border-color: black; z-index: 1; } &[x-placement^="top"] { margin-bottom: 5px; .tooltip-arrow { border-width: 5px 5px 0 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; bottom: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="bottom"] { margin-top: 5px; .tooltip-arrow { border-width: 0 5px 5px 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-top-color: transparent !important; top: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="right"] { margin-left: 5px; .tooltip-arrow { border-width: 5px 5px 5px 0; border-left-color: transparent !important; border-top-color: transparent !important; border-bottom-color: transparent !important; left: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &[x-placement^="left"] { margin-right: 5px; .tooltip-arrow { border-width: 5px 0 5px 5px; border-top-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; right: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &.popover { $color: #f9f9f9; .popover-inner { background: $color; color: black; padding: 10px; border-radius: 5px; box-shadow: 0 5px 30px rgba(black, 0.1); } .popover-arrow { border-color: $color; } } &[aria-hidden="true"] { visibility: hidden; opacity: 0; transition: opacity 0.15s, visibility 0.15s; } &[aria-hidden="false"] { visibility: visible; opacity: 1; transition: opacity 0.15s; } } .mention-item { padding: 4px 10px; border-radius: 4px; } .mention-selected { background: #00ABE7; color: white; } .textarea-cover { z-index: 1; position: absolute; color: transparent; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; white-space: pre-wrap; overflow-wrap: anywhere; overflow-y: hidden; padding: 6px 12px; border: 1px solid transparent; } .comment { width: 100%; } .mention-str { color: #00ABE7; background-color: rgba(0, 171, 231, 0.05); } 実際のサービス上は血の滲むようなCSSの調整をがんばっております。もしかしたらまだバグがあるかもしれません。何かお気づきの点がありましたらお知らせいただけますと幸いでございます。 まとめ vue-mention + Bootstrap は css をオーバーライド(リセット)して使う 全角@問題はkeyCodeで解決する(ただしdepricatedなのが悩ましい) textareaに色付けできないので上に文字を重ねて表現する 簡単そうで実は難しい「普通の動き」の実装にとっても苦労してハマりました。これで気持ちよくメンションして受信トレイでサラサラと通知を管理できるようになりそうです。ますます便利になった「プロジェクト管理と情報共有のためのツール(ガントチャート無料)」Repsona、ぜひお試しください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsでSlackみたいなメンションと入力中の名前色付けを作った完成形コードとハマった話

Repsona LLCの@GussieTechです。「理想のプロジェクト管理ツール」Repsonaを開発しています。 作ったもの これを作るのに大変苦労しました。textareaの特定文字に色はつけられません。ではどうするか?そんなお話です。 忙しい人へ 完成形のコード抜粋をCodeSandboxにつくりましたのでご利用ください。でも・・僕の苦労話も是非読んでいってください・・ 環境 Vue v2.x + Bootstrap v4.x + vue-mention ハマった話と解決法 RepsonaではSlackみたいにメンションでお知らせできるようになっています。受信トレイの新規開発に合わせて、メンションの体感もカイゼンしようということで立ち向かいました。色付きメンションの記事は案外ないのでお役に立てれば嬉しいです。 もくじ メンションポップアップ Tributeが期待した位置に表示されてくれない vue-mentionが表示されない vue-mentionの表示位置がおかしい 全角@で期待通りに動いてくれない 入力中の名前色付け textarea内の文字に色をつける方法がない スクロールバーの有無で色付け位置がズレる メンションポップアップ Tributeが期待した位置に表示されてくれない リリース当初よりTributeでメンションの動作を実装していました。通常の文字数や表示位置では問題なく動作していたのですが、画面全体のスクロール位置や入力文字数によって期待した位置に表示されない問題がありました。かなり試行錯誤をしたのですがうまくいかなかったため、vue-mentionに乗り換えることにしました。 vue-mentionが表示されない 乗り換えたところ、なぜか表示されませんでした。これがかなりハマりました。vue-mentionはv-tooltipに依存しているのですが、Bootstrapの.tooltipクラスとぶつかって、なんとopacity: 0;が採用されてしまっていました。 v-tooltip内の.tooltipに関しては、opacity: 1;が適用されるようにCSSを設定して回避しました。 [id^="popover_"].tooltip { opacity: 1; } vue-mentionの表示位置がおかしい いや違う、左側ではなく右側にでて欲しいのです。デモでは右に出ているのです。 細かい原因は調べませんでしたが、きっとまたBootstrapのCSSと競合しているのだろうと思いました。そこでまるっとv-tooltipのCSSが優先して適用されれば良いのではないかと考えました。 CSSは、対象を詳しく指定しているセレクタが優先されるので、idと合わせて指定することでオーバーライドしてやります([id^="popover_"].tooltip)。v-tooltipのデフォルトのCSSで上書きすることで、うまく解決しました。 [id^="popover_"].tooltip { display: block !important; z-index: 10000; .tooltip-inner { background: black; color: white; border-radius: 16px; padding: 5px 10px 4px; } .tooltip-arrow { width: 0; height: 0; border-style: solid; position: absolute; margin: 5px; border-color: black; z-index: 1; } &[x-placement^="top"] { margin-bottom: 5px; .tooltip-arrow { border-width: 5px 5px 0 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; bottom: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="bottom"] { margin-top: 5px; .tooltip-arrow { border-width: 0 5px 5px 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-top-color: transparent !important; top: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="right"] { margin-left: 5px; .tooltip-arrow { border-width: 5px 5px 5px 0; border-left-color: transparent !important; border-top-color: transparent !important; border-bottom-color: transparent !important; left: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &[x-placement^="left"] { margin-right: 5px; .tooltip-arrow { border-width: 5px 0 5px 5px; border-top-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; right: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &.popover { $color: #f9f9f9; .popover-inner { background: $color; color: black; padding: 24px; border-radius: 5px; box-shadow: 0 5px 30px rgba(black, .1); } .popover-arrow { border-color: $color; } } &[aria-hidden='true'] { visibility: hidden; opacity: 0; transition: opacity .15s, visibility .15s; } &[aria-hidden='false'] { visibility: visible; opacity: 1; transition: opacity .15s; } } きました!(幅やカラー指定の調整は省略しています) 全角@で期待通りに動いてくれない 全角@でポップアップが出て、確定するとなんだか@がいっぱい出てきてしまいます。 ドキュメントに記載はないのですが、どうやら隠しオプションみたいなものがあって、omitKeyというのを見つけました。 これをセットすることで、起動キーを排除した上で、valueをセットできるようになりました。valueには@も含めることで、期待通り動作します。でも、まだもうひとつ@が残ってしまいます。 IMEの変換確定とメンションの決定が重なって、決定後に@が残ってしまっています。IME確定後のみ決定を発動させる必要がありそうです。 vue-mentionのコードをみてみるとonKeyDown()が担っているようです。この部分ですね。 if ((e.key === 'Enter' || e.key === 'Tab' || e.keyCode === 13 || e.keyCode === 9) && 非常にdirtyですがe.key === 'Enter' || e.key === 'Tab'を無効にするオーバーライドをすることで回避しました。しかし、keyCodeはdeprecatedなので、いずれ期待通り動かなくなるかもしれません。他サービスで全角@が気持ちよく動作しないものがあるのは、きっとこの辺りの事情があるのでしょう。英語圏の人たちには関係ないでしょうし・・。ところで、正しくはどう実装するべきなのだろう。 入力中の名前色付け textarea内の文字に色をつける方法がない さまざまなサービスで簡単そうに実現しているのですが、甘くみていました。textarea css font-colorなどとググってみても、どうやらtextarea内の特定の文字に色をつける方法はないようです。 そうするとcontenteditableが頭をよぎりますが、プレーンテキストとして取り扱うのはかなりしんどさがあります(以前とんでもなくハマったのですがここでは省略します)。 そこで、textareaに、入力中の文字とまったく同じ文字を表示するdivを重ねる方法を採用しました。 ピンクで表示したdiv内にtextareaで記述した内容をそのまま透明文字で表示して、必要に応じて<span>等でカラーをつける作戦です。仮実装してすぐ期待通りに動いたので、簡単そうに見えました。しかし・・・ スクロールバーの有無で色付け位置がズレる ああ・・なるほど。スクロールバーさんを忘れていました。サイズ変更などでも表示されますね。スクロールバー表示有無を検出してスクロールバー幅分をpaddingしてやる必要がありそうです。 const barWidth = this.$refs.comment.$el.getBoundingClientRect().width - this.$refs.comment.$el.clientWidth (実際はtextareaに適用されたボーダーなどのスタイルも加味して計算しています) うまくいきました。 完成形 <template> <div id="app"> <div class="p-3"> <div class="position-relative"> <div ref="textareaCover" class="textarea-cover" v-html="commentMention" ></div> <mentionable :keys="['@', '@']" :items="users" insert-space omit-key> <textarea ref="comment" class="form-control comment" rows="4" v-model="comment" @keyup="commentScroll" @scroll="commentScroll" /> <template #item="{ item }"> <div class="user"> <span class="font-weight-bold"> {{ item.value }} </span> <span class="ml-2"> {{ item.firstName }} </span> </div> </template> </mentionable> </div> </div> </div> </template> <script> import { Mentionable } from "vue-mention"; Mentionable.methods.onKeyDown = function (e) { if (this.key) { if (e.key === "ArrowDown" || e.keyCode === 40) { this.selectedIndex++; if (this.selectedIndex >= this.displayedItems.length) { this.selectedIndex = 0; } this.cancelEvent(e); } if (e.key === "ArrowUp" || e.keyCode === 38) { this.selectedIndex--; if (this.selectedIndex < 0) { this.selectedIndex = this.displayedItems.length - 1; } this.cancelEvent(e); } if ( (e.keyCode === 13 || e.keyCode === 9) && this.displayedItems.length > 0 ) { this.applyMention(this.selectedIndex); this.cancelEvent(e); } if (e.key === "Escape" || e.keyCode === 27) { this.closeMenu(); this.cancelEvent(e); } } }; export default { name: "App", components: { Mentionable, }, data() { return { comment: "", users: [ { value: "@akryum", firstName: "Guillaume", }, { value: "@posva", firstName: "Eduardo", }, { value: "@atinux", firstName: "Sébastien", }, ], }; }, computed: { commentMention() { if (typeof this.comment?.replaceAll !== "function") { return this.comment; } const replaced = this.comment ?.replaceAll("&", "&amp;") ?.replaceAll(">", "&gt;") ?.replaceAll("<", "&lt;") + "\n\n"; const search = new RegExp( this.users .slice() .sort((a, b) => b.value?.length - a.value?.length) .map((user) => user.value) .join("|"), "g" ); return replaced.replace(search, (match, offset) => { return `<span class="mention-str">${match}</span>`; }); }, }, methods: { mounted() { setTimeout(() => { this.resize(); window.addEventListener("resize", this.resize); this.$once("hook:beforeDestroy", () => { window.removeEventListener("resize", this.resize); }); }); }, resize() { const barWidth = this.$refs.comment.getBoundingClientRect().width - this.$refs.comment.clientWidth - 2; // border this.$refs.textareaCover.style.paddingRight = `calc(12px + ${barWidth}px)`; }, commentScroll() { this.$refs.textareaCover.scrollTop = this.$refs.comment.scrollTop; this.resize(); }, }, }; </script> [id^="popover_"].tooltip { display: block !important; z-index: 10000; .tooltip-inner { background: black; color: white; border-radius: 16px; padding: 5px 10px 4px; } .tooltip-arrow { width: 0; height: 0; border-style: solid; position: absolute; margin: 5px; border-color: black; z-index: 1; } &[x-placement^="top"] { margin-bottom: 5px; .tooltip-arrow { border-width: 5px 5px 0 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; bottom: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="bottom"] { margin-top: 5px; .tooltip-arrow { border-width: 0 5px 5px 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-top-color: transparent !important; top: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="right"] { margin-left: 5px; .tooltip-arrow { border-width: 5px 5px 5px 0; border-left-color: transparent !important; border-top-color: transparent !important; border-bottom-color: transparent !important; left: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &[x-placement^="left"] { margin-right: 5px; .tooltip-arrow { border-width: 5px 0 5px 5px; border-top-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; right: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &.popover { $color: #f9f9f9; .popover-inner { background: $color; color: black; padding: 10px; border-radius: 5px; box-shadow: 0 5px 30px rgba(black, 0.1); } .popover-arrow { border-color: $color; } } &[aria-hidden="true"] { visibility: hidden; opacity: 0; transition: opacity 0.15s, visibility 0.15s; } &[aria-hidden="false"] { visibility: visible; opacity: 1; transition: opacity 0.15s; } } .mention-item { padding: 4px 10px; border-radius: 4px; } .mention-selected { background: #00ABE7; color: white; } .textarea-cover { z-index: 1; position: absolute; color: transparent; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; white-space: pre-wrap; overflow-wrap: anywhere; overflow-y: hidden; padding: 6px 12px; border: 1px solid transparent; } .comment { width: 100%; } .mention-str { color: #00ABE7; background-color: rgba(0, 171, 231, 0.05); } 実際のサービス上は血の滲むようなCSSの調整をがんばっております。もしかしたらまだバグがあるかもしれません。何かお気づきの点がありましたらお知らせいただけますと幸いでございます。 まとめ vue-mention + Bootstrap は css をオーバーライド(リセット)して使う 全角@問題はkeyCodeで解決する(ただしdepricatedなのが悩ましい) textareaに色付けできないので上に文字を重ねて表現する 簡単そうで実は難しい「普通の動き」の実装にとっても苦労してハマりました。これで気持ちよくメンションして受信トレイでサラサラと通知を管理できるようになりそうです。ますます便利になった「プロジェクト管理と情報共有のためのツール(ガントチャート無料)」Repsona、ぜひお試しください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

復習 Javascript きほんの 「き」 初心者 算術演算子編 5 データ型 論理型 null型 undefined型

データ型 var num = 1; //数値型 値が1。 データ型は? 数値型 変数numは、値を''とか何にも囲っていない 数字をそのまま書くと 数値型になる。 var str = '1'; //文字列型 値が1。 データ型は? 文字列型 変数str・・・見た目は数字だけど シングルクォート'' ダブルクォート""で囲うと、文字列型になる 次行きましょ // 数値型(1)+ 数値型(1)  !!!! +がある 加算か結合か注意 sum = num + num; document.write("<p>1 + 1 = " + sum + "</p>"); // 文字列型('1') + 文字列型('1') sum = str + str; document.write("<p>'1' + '1' = " + sum + "</p>"); ブラウザ表示 1 + 1 = 2      ・数字型同士は加算 '1' + '1' = 11   ・文字列同士は文字列結合 私ここまで理解おっけ じゃあ 数値型と文字列型 結合したらどうなるのか・・・ // 数値型(1) + 文字列型('1') sum = num + str; document.write("<p>1 + '1' = " + sum + "</p>"); ブラウザ表示 1 + '1' = 11 数値型(1) + 文字列型('1') 異なる値に +を使ったら 数値型は文字列型扱いになって 文字列結合が行われるのかぁ ===================================================== 他にもあるよ さわりだけ 論理型 var flag; flag = true; //真 flag = false; //偽 true/falseの二つの値を持つ形式 null型 var num = 0; //数値型 var str = '';//文字列型 var nothing = null; //null型 var nothing = nullこれがnull型、値がない、データとしてなんもないことを表現。 undefined型 var undef; document.write(undef); //undefined 変数undef宣言後 値を何も代入しないで変数の表示をすると undefined と表示。 値が未定義のものはundefined型。 ん? nullと何が違うんだ null・・・値がないと定義 undefined・・・未定義
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

`preinstall: typesync`がgit clone直後にエラーになるのを防ぐ

はじめに npm scriptsにpreinstallを書いておくと、npm installとかyarn addの前に自動でスクリプトを実行してくれます。 TypeScriptで開発する場合、typesyncをpreinstall内で流すと、インストール時に@types/のパッケージがあれば自動インストールしてくれて便利です。 { "scripts": { "preinstall": "typesync" } } 問題点 preinstallが走る時点でtypesyncがインストールされていないと、スクリプトの実行がエラーになり、続くインストールが止まってしまいます。 そのため、typesyncがdevDependenciesに含まれてるプロジェクトをgit cloneしてきた場合、clone直後のnpm installは失敗しちゃうんですね。 単独でtypesyncだけ先に入れるとか、そもそもグローバルインストールしておけば解決しますが、ローカルインストールでもgit clone && npm installが普通に成功する方法があります。 解決策 { "scripts": { "preinstall": "typesync || true" } } または { "scripts": { "preinstall": "typesync || :" } } true は何もせず、終了ステータスとして'成功'を意味する 0 を返す。 本コマンドはシェルスクリプト中で正常終了するコマンドが必要な場合に 使われる。なお,シェルのビルトインコマンド ':' (コロン)は 同じ動作をより速く実行する。 スクリプト全体の終了ステータスが0であればインストールが実行されるので、シェル芸でcommand || trueすればcommandが失敗してもインストールは続くわけです。try-catchでエラーを握りつぶす感覚に近いですね。 typesyncに関しては、clone直後のインストールであれば、その時点で@types/が新規追加されることはなくて、package.json内の全パッケージが入ればOKですからね。 もちろん、typesync以外ではちゃんと異常終了を検知すべき場合も多いので、やみくもにエラーを握りつぶさないようには気をつけましょう。 では今日はこのへんで!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptのHoistingについてメモ

この記事について Hoistingができるタイプの変数とできないタイプの変数があるので整理する。 ■Hoistingとは 宣言をする前でもその変数を使えることができる仕組み 変数のタイプ ① 関数宣言(Function Declaration) (関数に名前があるタイプ) 例)function calc(a, b) {return a + b } ② varでの変数宣言 ③ const や letでの変数宣言 ④ 無名関数(Function expression or arrow function) (関数に名前がないタイプ) 例)const calc = function(a, b) { return a + b } Hoistingができるのは... ① 関数宣言 関数宣言の書き方では、宣言する前に関数を呼んでいるのに、ちゃんと戻り値5で返ってくるんです。(Hoistingが機能する) example1 console.log(calc(2, 3)); function calc(a, b) { return a + b; } コンソール上でのログ↓ Hostingによって宣言前でもエラーにならず動くのですね。 ② varで宣言した変数 変数を定義する前にcalc変数を使ってみます。 example2 console.log(calc); var calc = 2 + 3; コンソール上の結果はこちら エラーにはならないものの、undefinedで結果が表示されます。 ③letやconstで宣言した変数 ②と同じコードをletで書くと example3 console.log(calc); let calc = 2 + 3; コンソール上の結果はこちら エラーになりますね。 これはletやconstには、Temporal Dead Zone(TDZ)が適用されるから。 TDZは、そのスコープ内で、使いたい変数が定義される(この場合let calc = 2+3)以前に書かれたコードに対してはその変数は使えないよという意味。 なので上記の例では、「initializationの前には'calc'は使えません」ってエラーが出ています。 ④無名関数 ①と同じコードを無名関数で書くと example4 console.log(calc(2, 3)); let calc = function (a, b) { return a + b; } console.logで結果を確認すると、エラーになっています。 ③で見たエラーと同じですね。letで書いているのでTDZが適用されています。 ちなみに、無名関数をvarで書くとこのようなエラーになります。 ②で見た例では、var宣言したものはエラーにならずundefinedと表示されました。 これは、varで定義するとundefinedとなりますが、undefinedという名前の関数のようになってしまい、「calc is not a function」とエラーが出てしまっています。 Hoistingが適用されるとこんなことにも... example5 if(!num) delete(); var num = 10; function delete() { console.log(`deleted!`); } ↑はvarの定義をif文より上に書けば問題ないのですが、もし間違えて下に書いてしまった時の例です。 if文で0の初期値はfalseなのを利用して「!でfalse->trueにして0だったらdelete()を実行、0以外の数字だとfalseになりdelete()は実行しない」という意図で上記コードを書いたとします。 「numは10が入るのでdelete関数は動かない」なんて思ってしまったらバグになってしまいます。varで定義されているためHoistingが適用されて最初のif文でのnumはundefinedが入るんです。 undefinedの初期値はfalseなので、!で反転してtrueになり、delete関数が動きコンソール上に「deleted!」と表示されてしまいます。 一方constやletで書いていたとするとTDZが適用されてエラーになってくれます。 感想 letやconstを使っているならエラーが出るので大丈夫ですが、varを使用して意図せずHoistingが適用されりすると見つかりにくいバグができたりするのかなーと思いました。 Hoistingの仕組みを知っているとバグ調査の時も、「varで書かれてるからもしやHoistingが適用されているのかな?」と疑ってみることもできるので、大事な知識だと感じました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

復習 Javascript きほんの 「き」 初心者 算術演算子編 4 

算術演算子 +(加算) ー(減算) /(除算) %(剰余)・・・割り算の余りを求める計算5➗2なら 剰余は 1。2余り1だから。 があって、計算に使う。 var num; //加算 num = 3 + 3; //numに3+3を入れる。6 document.write('<p>3 + 3 = ' + num + '</p>'); //減算 num = 5 - 3; //2 document.write('<p>5 - 3 = ' + num + '</p>'); //乗算 num = 2 * 3; //6 document.write('<p>2 * 3 = ' + num + '</p>'); //除算 num = 9 / 3; //3 document.write('<p>9 / 3 = ' + num + '</p>'); //剰余  9➗5で 剰余は4。1余り4だから。 num = 9 % 5 //4 document.write('<p>9 % 5 = ' + num + '</p>'); ブラウザに表示してみよう 3 + 3 = 6 5 - 3 = 2 2 * 3 = 6 9 / 3 = 3 9 % 5 = 4 計算式が表示できました! +って加算だけじゃなくて、文字列の結合もするんだな
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript 正規表現 HTMLの特定のタグの中のみ置換や削除

正規表現で抜き出した部分のみを対象として、さらに正規表現で置換をかけるサンプルです。 タイトルつけるのが下手くそなので、伝わりにくいかもしれないですね。 まずはこいつを見てほしい。 <div> hoge<br> fuga <p> piyo<br> piyo </p> <p> foo<br> <br> bar </p> </div> すごく適当です。 今回説明するのは、上記のHTMLから、pタグの中のbrタグだけを削除する方法です。 (divタグ直下のbrは残す) つまり、以下の結果を期待する、と。 <div> hoge<br> fuga <p> piyo piyo </p> <p> foo bar </p> </div> で、行った処理がこちら const str = "<div>\n hoge<br>\n fuga\n <p>\n piyo<br>\n piyo\n </p>\n <p>\n foo<br>\n <br>\n bar\n </p>\n</div>" const replaced = str.replace(/<p>(.*)<\/p>/sg, function(match){ return match.replace(/<br>/g, '') }) console.log(replaced); まあ、String.prototype.replace()を当然利用するわけなんですが String.prototype.replace replaceの第2引数には、関数を指定可能であると。 で、正規表現にグローバルフラグ /正規表現/g <- コレ を付けていると、マッチした回数だけ、無名関数の第一引数 match にマッチした部分文字列が入った状態で実行されるわけです。 示した例だと、pタグが2つあるので、2回実行されます。 var str2 = "<div>\n hoge<br>\n fuga\n <p>\n piyo<br>\n piyo\n </p>\n <p>\n foo<br>\n <br>\n bar\n </p>\n</div>" var replaced2 = str.replace(/<p>(.*)<\/p>/sg, function(match){ console.log(match) }) //// 1回目の無名関数実行の第一引数matchのvalue // <p> // piyo<br> // piyo // </p> //// 2回目の無名関数実行の第一引数matchのvalue // <p> // foo<br> // <br> // bar // </p> HTMLの場合なんかだと、タグ間の改行も考慮が必要なので、 sフラグ /正規表現/s <- これ も付けます。 /<p>(.*)<\/p>/sg // <- つまり、sフラグとgフラグをつけてこうなる この無名関数内で、returnした値が、matchした箇所と置換されることになります。 なので、matchに対して、再びプロトタイプのreplace関数を実行したものを returnすると match.replace(/<br>/g, '') // brタグを削除する置換 意外と情報がなかったので備忘録として。 情報がなかったのは、正規表現を利用しないもっと良いやり方があるのかもしれない。。。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

復習 Javascript きほんの 「き」 初心者 変数編  3  変数の使い方

変数の使い方 // 変数hpを定義 var hp; // 変数hpに値を代入 hp = 200; // 変数mpを定義し値に代入 var mp = 80; // HPとMPを表示 document.write('<p>現在のHPは' + hp + '現在のMPは' + mp + '</p>'); // HPとMPを表示 document.write('<p>現在のHPは' + hp + '現在のMPは' + mp + '</p>'); ↑「hp」 「mp」と2つの変数をシングルクォーテーションなしで書くことで、変数として認識されて箱の中の値を取り出すことができる。 ・「+」プラスを利用すると文字列と結合できる。 さあ、もっと詰めていきましょう 変数の取り出しと文字列の結合はこんなふうになっている document.write('<p>現在のHPは' + hp + '現在のMPは' + mp + '</p>');       //↓     //これで、hpには200入れてる。 mpには80入れてる。      var hp; // 変数hpに値を代入 hp = 200; // 変数mpを定義し値に代入 var mp = 80;            document.write('<p>現在のHPは' + 200 + '現在のMPは' + 80 + '</p>');      // ↓ //4つの文字列を結合して1つにする     document.write('<p>現在のHPは200現在のMPは80</p>'); ==================================================================== また、変数の値を変更することが可能で、同じ変数に別な値を代入すると、代入された値が変数の値として上書記されるよ やってみよう私。 // 変数hpを定義 var hp; // 変数hpに値を代入 hp = 200; // 変数mpを定義し値に代入 var mp = 80; // HPとMPを表示 document.write('<p>現在のHPは' + hp + '現在のMPは' + mp + '</p>'); //変数hpの値を上書きしてみまーす! hp = 530000; //HPとMPを表示 document.write('<p>現在のHPは' + hp + '現在のMPは' + mp + '</p>'); ブラウザに表示してみました! ↓ 現在のHPは200現在のMPは80 現在のHPは530000現在のMPは80 HPが53万に上書きされました。(ちなみにあるお方の戦闘力を入れてみた) 変数hpの値が200から530000に上書きされて 変数hpをdocument.write()で表した結果が前と後で異なる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

復習 Javascript きほんの 「き」 初心者 変数編  2  変数名書き方

復習 Javascript きほんの 「き」 初心者 変数編  2 「変数名の書き方」 ・var宣言を行う ・名前に利用できるのは英数字か_(アンダースコア)、$(ドル記号)で、さらに数字が使えるのは2文字目以降 var abc; //これはOK! var a12; //これはOK! var 12a; //NG!!!!だめ var _1a; //これはOK! var test_kihonnnoki; //アンダースコア var testKihonnnoki; //単語の始めのみ大文字(キャメルケースって言うんだって) ・Javascriptで予約されている名称は名前に利用できない(????これqiitaで質問してみます) 予約語例:if,for,float,class,new,true,var ・変数名が長くなる場合、単語の区切りで _アンダースコアか 単語の初めのみ大文字(キャメルケース)を利用することが一般的
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JavaScript] フォームの内容を文字列化⇔復元する

HTMLのフォームの内容を文字列化したり、その文字列を元にフォームの内容を復元する知見。 ソースコード フォーム文字列化 第1引数にフォーム要素を渡します。戻り値はJSON文字列です。 idがある要素のみ文字列化されます。 文字列化 function form_toJSON(form){ const json = {} for(const el of form.querySelectorAll('[id]')){ if(['file','submit','reset'].includes(el.type)){ continue } else if(el.type === 'checkbox' || el.type === 'radio'){ json[el.id] = el.checked } else if(el.tagName === 'SELECT'){ json[el.id] = {} for(const option of el.options){ json[el.id][option.value] = option.selected } } else{ json[el.id] = el.value } } return JSON.stringify(json) } フォーム復元化 第1引数にフォーム要素、第2引数にフォーム内容を文字列化したものを渡します。 復元化 function form_restore(form, json){ json = JSON.parse(json ?? '{}') for(const el of form.querySelectorAll('[id]')){ const value = json[el.id] if(value === undefined || ['file','submit','reset'].includes(el.type)){ continue } else if(el.type === 'checkbox' || el.type === 'radio'){ el.checked = value } else if(el.tagName === 'SELECT'){ for(const option of el.options){ if(option.value in value){ option.selected = value[option.value] } } } else{ el.value = value } } } ※主要なフォーム部品には対応したつもりですが、フォーム部品は多すぎて全てに対応できているかは不明です。非対応の部品があれば教えてくれると有難いです。 使用例 ページ離脱時にフォーム内容をセッションストレージに保存し、ページ読み込み時に復元するサンプル。 addEventListener('beforeunload', event => sessionStorage.setItem('sample', form_toJSON(form))) addEventListener('DOMContentLoaded', event => form_restore(form, sessionStorage.getItem('sample'))) メモ フォームの文字列化にあたって最初はFormDataの活用を考えましたが、checkedやselectedがfalseの場合に文字列化されないという欠点があった。復元作業においてこの情報欠落が問題になる場合があったので、没となりました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JavaScript] オブジェクトの値で配列をソートする

オブジェクトを格納した配列に対して,オブジェクトの特定のプロパティを基準にソートする方法を紹介します。 この配列を age でソートしたい const arr = [ {name: 'David', age: 25}, {name: 'Bob', age: 30}, {name: 'John', age: 25}, {name: 'Tom', age: 37}, {name: 'Rachel', age: 28}, {name: 'Sam', age: 40}, {name: 'Alice', age: 25}, ]; 結論 const arr = [ {name: 'David', age: 25}, {name: 'Bob', age: 30}, {name: 'John', age: 25}, {name: 'Tom', age: 37}, {name: 'Rachel', age: 28}, {name: 'Sam', age: 40}, {name: 'Alice', age: 25}, ]; const newArr = [...arr].sort((a, b) => (a.age > b.age) ? 1 : -1); console.log(newArr); [ { name: 'Alice', age: 25 }, { name: 'John', age: 25 }, { name: 'David', age: 25 }, { name: 'Rachel', age: 28 }, { name: 'Bob', age: 30 }, { name: 'Tom', age: 37 }, { name: 'Sam', age: 40 } ] age が同じ場合は name でソート const arr = [ {name: 'David', age: 25}, {name: 'Bob', age: 30}, {name: 'John', age: 25}, {name: 'Tom', age: 37}, {name: 'Rachel', age: 28}, {name: 'Sam', age: 40}, {name: 'Alice', age: 25}, ]; const newArr = [...arr].sort((a, b) => (a.age > b.age) ? 1 : (a.age === b.age) ? ((a.name > b.name) ? 1 : -1) : -1); console.log(newArr); [ { name: 'Alice', age: 25 }, { name: 'David', age: 25 }, { name: 'John', age: 25 }, { name: 'Rachel', age: 28 }, { name: 'Bob', age: 30 }, { name: 'Tom', age: 37 }, { name: 'Sam', age: 40 } ] 文字列の大小比較は辞書順ですが「大文字は小文字より小さい」などの直感的にわからない規則もあるので注意が必要です。 解説 MDN Web Docs の Array.prototype.sort() を読むのが早いです。 比較関数 compare(a, b) の使い方を簡単に言うと a, b の順 → -1 b, a の順 → 1 コールバック関数の処理部分が少しややこしいです。 (a.age > b.age) ? 1 : (a.age === b.age) ? ((a.name > b.name) ? 1 : -1) : -1 if文で書くとこうなります(分かりやすいように冗長な記述にしています)。 if(a.age > b.age){ return 1; }else{ if(a.age === b.age){ if(a.name > b.name){ return 1; }else{ return -1; } }else{ return -1; } } 蛇足(配列のコピーについて) sort() は配列そのものを変更する破壊的なメソッドです。 元の配列を維持するために [...arr] とコピーしてから使いました。 配列のコピーは arr.slice() や arr.concat() でもできます。 但し,いずれのコピーも内部のオブジェクトは shallow copy であることに注意してください。 const arr = [ {name: 'David', age: 25}, {name: 'Bob', age: 30}, {name: 'John', age: 25}, {name: 'Tom', age: 37}, {name: 'Rachel', age: 28}, {name: 'Sam', age: 40}, {name: 'Alice', age: 25}, ]; const newArr = [...arr]; // 配列のコピーを作成 newArr[0].name = 'Hello'; // 'David' を 'Hello' に変更 console.log(arr); [ { name: 'Hello', age: 25 }, { name: 'Bob', age: 30 }, { name: 'John', age: 25 }, { name: 'Tom', age: 37 }, { name: 'Rachel', age: 28 }, { name: 'Sam', age: 40 }, { name: 'Alice', age: 25 } ] オブジェクトを deep copy したい時は,lodash の cloneDeep() を使うのがおすすめです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

相席居酒屋風時計を作ってみた

はじめに 大阪を歩いていると、相席居酒屋の看板が目に入る時ありますよね。 お店によっては、現時点での男性客と女性客の人数が表示されてるのですが、 ほとんど女性客のほうが多いイメージがあって、これ実は時計なんじゃね...?って思ったんで 最近javascriptの勉強を始めだしたこともあり、勉強がてら作成してみました。 作成物 javascript やってることは、1秒毎に現在の時間、分を特定のIDを持つ場所に出力しているだけです。 const hoursClock = () => { const now = new Date(); const hours = now.getHours(); document.getElementById("hours").innerHTML = hours } const minutesClock = () => { const now = new Date(); const minutes = now.getMinutes(); document.getElementById("minutes").innerHTML = minutes } hoursClock(); minutesClock(); setInterval(hoursClock, 1000); setInterval(minutesClock, 1000); 終わりに すごく簡単なものではありますが、寝る前に思いついたものを1時間程度で形にできたので、楽しかったです。 更に勉強を重ねて、いつかは社会の役に立ちそうなものを作りたい。。。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nuxt.jsでFirebase Realtime Databaseの読み書きを行う

Nuxt.jsのWebアプリから、FirebaseのRealtimeDatabaseの読み書きを行ったので、手順をまとめておきます。 Firebase Realtime Databaseの作成 事前にFirebaseコンソールからプロジェクトを作成し、Webアプリを追加します。 その後、Realtime Databaseを作成してください。 作成時の各設定は任意に行ってOKです。 Nuxt.js側にFirebaseの設定 Nuxt.js側でFirebaseを操作するため、必要なモジュールをインストールして設定していきます。 今回は、Nuxt.jsでのFirebaseの操作に便利な nuxtjs/firebaseもインストールします。 下記コマンドを実行してください。 npm install --save firebase @nuxtjs/firebase 次に、 nuxt.config.js に下記設定を追加します。 nuxt.config.js export default { modules: [ [ '@nuxtjs/firebase', { config: { apiKey: '<apiKey>', authDomain: '<authDomain>', projectId: '<projectId>', storageBucket: '<storageBucket>', messagingSenderId: '<messagingSenderId>', appId: '<appId>', measurementId: '<measurementId>' }, services: { database: true // Realtime Databaseの使用を宣言 } } ] ], } <apiKey> など、それぞれの項目に指定する値は、Firebaseコンソールの「プロジェクトの設定」から、下記のように確認可能です。 (Nuxt.jsのプロジェクトに追記する際はenvファイルを利用するなどしてください) データの読み書き 基本的にデータの読み書きはドキュメント通りです。 今回は例として、下記のように実装します。 pages/index.vue <template> <div> <button @click="increment">send</button> <span>count:</span> <span>{{ count }}</span> </div> </template> <script> export default { data() { return { count: 0 }; }, mounted() { // RealtimeDatabaseの更新を検知 this.$fire.database.ref('count').on('value', (snapshot) => { this.count = snapshot.val(); }) }, methods: { // RealtimeDatabaseを更新 increment() { this.$fire.database.ref('count').set( ++this.count ); } } } </script> ページを確認すると、このように表示されます。 「send」ボタンを押すことで、 increment() が呼ばれ、Realtime Database上の値が更新されます。 Realtime Database上の値が更新されると、Webアプリ側は更新を検知して自動で値が同期されます。 これだけです! さくっと導入でき、とても簡単ですね!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ビットコインブロックチェーンのアドレスを生成してみた

勉強内容のアウトプットです。 // ライブラリ読み込み const bitcoinjs = require('bitcoinjs-lib') const crypto = require('crypto') const base58Check = require('bs58check') // 乱数(秘密鍵)を生成し、生成した秘密鍵から公開鍵を作成(非圧縮公開鍵) const keyPair = bitcoinjs.ECPair.makeRandom({rng: crypto.randomBytes, compressed: false}) const publicKey = keyPair.publicKey console.log('Public Key: ' + publicKey.toString('hex')) // 公開鍵からビットコインブロックチェーンのアドレスを生成する。 // 最初に公開鍵のSHA256-RIPEMD160ダブルハッシュを生成。 const preHash = sha256(publicKey) const publicKeyHash = ripemd160(preHash) // 作成するアドレスは1から始まる(シングルシグアドレス)とし、この場合バージョンバイトは00となる。 const versionByte = Buffer.from('00', 'hex') // 「バージョンバイト + 公開鍵SHA256-RIPEMDハッシュ」を作る。 let payload = Buffer.concat([versionByte, publicKeyHash]) console.log('Public Key Hash: ' + payload.toString('hex')) // 今回はチェックサムを付与する payload = concatCheckSum(payload) console.log('Payload: ' + payload.toString('hex')) // Base58エンコードして完成 const address = base58Check.encode(payload) console.log('Address: ' + address) // 関数群 // SHA256生成関数 function sha256(data) { return crypto.createHash('sha256').update(data).digest() } // RIPEMD160生成関数 function ripemd160(data) { return crypto.createHash('ripemd160').update(data).digest() } // チェックサム付与関数 function concatCheckSum(data) { const checkSum = sha256(sha256(data)).slice(0, 4) console.log('Check Sum: ' + checkSum.toString('hex')) return Buffer.concat([data, checkSum]) }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Spark AR】javascriptで時刻を表示⏰

エミリと申します? 今日は、spark Ar hubを使ってインスタやFacebookで使えるフィルターを作る際に、Javascriptを使ってフィルター上に時刻を表示する方法を紹介します! 記事が少なく、さらに日本語の情報は少ないので書いてみました! 完成イメージ↓ ※この記事は、ある程度spark ARを使ったことがある方向けです。 初めての方はこちらから↓ クリスマスの激盛れフィルターを作ろう! spark ARの基本的な使い方から書いてあります! 目次 1.spark AR側の準備 / scriptを用意する 2.scripting / scripting APIについて 3.実際に時間を表示する 4.おまけ フォントに変更。 1.spark AR側の準備 時刻を表示するためのオブジェクトを追加します。 時刻なので、textを使います。今回は2Dのものにしました Add object > 2D Text わかりやすくするために、time と名前を変更しておきます。(後で使うので、自分がわかるものにしておいてください。) 名前変更 続いて、scriptをお用意します。 今回は、Java Scriptでやります。Type Scriptでもできます!操作は同じで、コードの書き方が違うだけです。 Add Asset > Script > Jave Script クリックすると、javascriptの新しいファイルができるので、リネームしてわかりやすくしておきましょう。 今回は以下のようにtime.jsとしておきました。 time.jsのファイルをダブルクリックすると、何かしらのアプリケーションが開いてコードを編集できます! これで準備完了です?? 2 scripting / scripting APIについて ここで少しSpark AR hubにおけるscriptingとscripting API について話します。ただ真似して時間を表示できればよい!と言う方は次のステップに飛んでください! 公式サイトのlearn > scripting / scripting APIのところにいろいろ乗っているので興味がある方はこちらもみてみてください! 公式サイトへとぶ ・モジュール Java Script上でspark AR hubで使っているものをもろもろ使うためにmoduleをロードする必要があります。その際にrequireを使うので注意しましょう! 使えるモジュールは全てscripting APIのところに書いてあります! 今回は、DiagnosticsとSceneと言うモジュールが必要になります。(後にフォントのモジュールも使います。) //必要なモジュールを使えるようにする。 const Diagnostics = require('Diagnostics'); const Scene = require('Scene'); ・プロパティとメソッド それぞれのモジュールに対して使えるメソッドが決まっています。(メソッドがないものもあります。) 今回は、DiagnosticsのlogメソッドとSceneのrootプロパティを使います。 Diagnosticsのlogメソッドは、spark ARのコンソールに表示するときに使います Diagnostics.log("hello"); このコードで、spark ARのコンソールにhelloと表示できます。 Sceneのrootプロパティは、セットしたオブジェクトを使うときに使います。(後のコードを参照。) scriptingの基本的な使い方は、moduleをロードして、メソッドやプロパティ、クラスをつかって行けばなんでもできます! 結局公式ドキュメントに全てが詰まっています? 3 実際に時間を表示する コメントアウトで説明を書いたのでここでは割愛します! わからないところは一つ前のステップで説明しているので参考になるかと思います! 以上になります! 時間を表示するだけだとだいぶシンプルですが、周りに何かつけたり、アレンジし放題です!? 何か不明点、補足した方が良いところがあったら教えてください!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む