20190203のJavaScriptに関する記事は30件です。

初心者がダメにならないアプリを作る道③ガンガン削る、そしてドッグフーディング

前回と前々回はこちら。
- 初心者が『だめにならないアプリ』を作る道①

現状

https://nomoredame.firebaseapp.com/home.html

使うことを優先してメイン機能をブラッシュアップし他を削る

まぁ当初想定していた機能には全然足りていないのですが、作っているうちに「やっぱり大事なのメインの機能とコンテンツでありその他はクリティカルではない」と思い始め、そして「メイン以外の部分データベースとかいって作るの難しい…」となったので、メインの部分をよくして、思い切って他の部分は削った。
今の時点で作り込むよりは、何が必要なのかを明らかにする方が筋が良さそう。

  • 追加した部分
    • ボタンを押した後のUIをボタンの色が変わる→メッセージ付きのモーダルウィンドウに変更
    • コンテンツ(行動の種類)の追加
    • コンテンツのデータはデータベースにおくのが理想だが、ちょっと難しかったのでコード内にベタがきしている
  • 削った部分
    • コンテンツをユーザーが編集・追加する機能
    • 通知機能
    • それに従ってナビゲーションバーも削った

どれも表側は作っていたのでもったいない気持ちはあったが、削ったことで全体としての完成度は上がった。

今後

  • JavaScriptの基礎文法進める
  • しばらく実際に使う&使ってみた上で機能を考え直す
  • 通知機能の実装方法について調査
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

役にたつか立たないかわからないInteliJIDEの話 【随時更新していくよ!!】

はじめに

勢いでIntelliJ All Products Pack買いました!
衝動買い楽しい。
せっかくだしちゃんと勉強しながら色々と使いこなせるようにメモします。
この機能の紹介をぜひー!とかこれ忘れてないなどあればどんどんお教えいただけますと幸いです。追記していきます!!
あと、サムライズムさんとかIntelliJとかお金もらってかいているわけではないです。

色々あるけど・・・

いろんな機能があって色々と扱えるものも違ってきます。
ちなみに最強はIntelliJ IDEA Ultimate Editionです。
plugin で追加さえすればめちゃめちゃ捗ります。

僕がweb開発とアプリする人間なので、それ以外にどうするべきなのかはわからないです。
値段は年間ですが、3年目まで毎年安くなっていきます。

JavaScripter

webStormで要件を満たせます。
年間6300円で、三年目だと3700円です。

webStormは、HTML/CSS、js, ts, coffe scriptを扱うことができます。なので、フロントエンドだろうが、バックエンド(nodeでの話)だろうがこれ一個で大丈夫です。
goに関してはプラグインでなんとかできるけど辛いみたい・・・。
そして、WebStormはかなり安いです。

ただし、こいつでDBを覗き見たりするのはpluginでできなくもないですが、Data Gripを買った方がいいかと。
二郎を食べたことない人が二郎の美味しさがわからないてきな感じでData Gripを触らない限りは他のフリーツールでも満足してやっていけます。
ただし、free trialでもData Grip触ろうものなら、もう戻ってこれなくなります。

pythoner

pyCharm professional Edition(PE)で要件を満たせます。
年間9600円で三年目だと5700円です**

pythonはCommunity Edition(CE)って無償のバージョンがありますが、これは基本的にはデータサイエンス向けでweb開発用に転用していくのは結構無理がでます。データサイエンスだけのためならCEでよいです!jupyterも動かせます!
そして、こいつはなんとHTML/CSS, js, ts, CoffeScriptも扱うことができます!
なのでこれを買えば、フロンエンドだろうが、バックエンド(python or node)だろうがこれ一個で大丈夫です。
ただし、値段はちょっと高いです。

phper

phpStormです。
年間9600円で三年目だと5700円です
webStormの上位互換ににあたります。
そして、こいつもなんとHTML/CSS, js, ts, CoffeScriptも扱うことができます!
なのでこれを買えば、フロンエンドだろうが、バックエンド(php or node)だろうがこれ一個で大丈夫です。
上位互換なので値段はやっぱちょっと高いです。

Rubyer

RubyMineです。
年間9600円で三年目だと5700円です**
僕はあんまりRubyに関して明るくないです・・・。
が、Rubyを扱うことができ、こいつもなんとHTML/CSS, js, ts, CoffeScriptも扱うことができます!
ということでこれもフロンエンドだろうが、バックエンド(ruby or node)だろうがこれ一個で大丈夫です。

go langer

go landです。
年間9600円で三年目だと5700円です

かっこいいですね。
これはHTML/CSS, js, tsを扱うことができます!

javer

IntelliJ IDEA ultimate Edition(UE)を使うことができます。
年間16100円で三年目だと9600円です

一応無償のCommunity Edition(UE)もありますが、で開店としてはHTML/CSS, js, markdownとかに対応していない点です。
java, kotolin, androidとかならCEで十分ですが、フロント周りをやろうとするならこれだとできないです。
pulanginをいれることで、多少python、Goなどに対して耐性をつけることもできます。
これだけ機能がたくさんあるためややお高い値段設定になっています。

swifter Objective-Cer

AppCodeがあります。
年間9600円で三年目だと5700円です

いままでXCode使ってきたので使用感はちょっと不明です・・・。というかReact Nativeエンジニアなので、そこまで必要なものでもない。
これは、swift, Objective-C/C++のほか、HTML/CSS、JavaScriptを扱えますが、ts、Coffieはできません。
なのでReact Nativeやる人からしてみてもAppCode買うかは微妙なところですね。

SQLer

Data Gripがあります。
年間9600円で三年目だと5700円です
これはめっちゃ良くてかなり重宝しています。

All Product Packは買いなの?

はっきし言ってAll Product Packは結構たかいです。
年間26900円で三年目だと16100円です

普通に開発しててもなんだかんだで2言語ぐらいできたら結構十分かと思います。
基本的に元を取ろうと思ったら3製品はちゃんと使わないともったいないです。
ですが、js系のものはだいたいどれもあつかえるので3製品使おうとするのは結構難しいです。

たとえば、僕の場合、

  • フロントエンドやる
  • アプリやるけどReact Nativeなのでswift、javaはそんなに触らない
  • バックエンド node or python
  • データサイエンスやる
  • データベースみる

とかになってくると、pyCharmとDataGripあれば十分かなと言ったところです。
それと、基本的にアップデートの速さは固有の製品の方が早いみたいなので、前線をいく人は前線をいけるような製品チョイスを心がけた方がいいかもしれません。
なので中なかなかに絶妙な値段設定をしている感じがしますね・・・。

値段のまとめ

26900円コース

All Product Pack

16100円コース

IntelliJ IDEA Ultimate Edition

9600円コース

phpStorm, RubyMine, PyCharm, DataGrip, Goland, AppCode

6300円コース

webStorm

と言った感じです。なので、ここから自分に何が必要なのかを吟味して選んでいくといいかと思います!
あと、僕みたいに心配性だったり考えるのめんどくさ買ったりする人はAll Product Packを買うといいと思います。

intention Actions

option + enter
いろんなことができます。
例えば、勢いでコード書いたけどファイルつくってなかった時とか、ここからさささっと作ることができます。

nazo2.gif

post completion

あと出しでコードを書く機能。
みてもらったほうが早い機能。そんなにいらないんじゃないって思うかもですが、なんだかんだ言って覚えれば便利な機能です。

nazo2.gif

一覧はpreference > editor > general > Postfix Completionからみれます。

スクリーンショット 2019-02-05 14.41.30.png

独自定義もできます。

スクリーンショット 2019-02-05 14.48.19.png

割と便利な

console.log({
  a,
  b,
});

を作りました。
nazo2.gif

live template

色々な機能がある。アロー関数をfarrowとして追加してみた。
こちらを参考にした。

タブを押すたびに引数、処理と言ったことができる。

nazo2.gif

スクリーンショット 2019-02-03 13.56.22.png

こんな感じで追加する。ワーニングっぽいのでてたらどの言語でできているかわからないっていっているので、どの言語で使えるかを設定してあげましょう。

($PARAMS$) => ({
    $BODY$
})$END$;

こんな感じで定義すると、param -> body -> endにtabを押すたびに遷移する。
一応、テンプレート的なのは、範囲を選択 -> tools -> save as livetemplateっていうのを押すと、選択範囲のコードが入った状態で始められる親切設計。

expand selection

いい感じに選択範囲をする機能です。
option + ↑で回数に応じて範囲が広がります。逆に戻したい場合はoption + ↓です。

nazo2.gif

変数化とインライン化

名前的に変数化したかったり、戻したりを一瞬でできます。

cmd + option + v 変数化します。
cmd + option + n で戻します。

nazo2.gif

parameter hint name

若干物議をかもすこの機能。
スクリーンショット 2019-02-05 15.14.10.png

こんな形でパラーメーター名を表示できます。

設定はここで切り替えられます。

スクリーンショット 2019-02-05 15.13.44.png

ショートカット

効果 コマンド
Undo cmd + z
Redo cmd + shift + z
検索 cmd + f or cmd + shift + f (プロジェクト全体)
置換 cmd + r or cmd + shift + r (プロジェクト全体)
複製 cmd + d
プロジェクトにwindowを開く閉じる cmd + 1
ファイル作成 cmd + n
コードの折りみと展開 cmd + .
preferenceを開く cmd + ,
help or Action cmd + shift + a
expand selection option + ↑ (回数で変わる)
予測補完を出す(macだとデフォで行けないので後述。ここに書いたのは自分用メモ) cmd + 4
smart補完を出す(macだとデフォで行けないので後述。ここに書いたのは自分用メモ) cmd + 5
intention Actions option + enter
変数化 cmd + option + v
インライン化 cmd + option + n
パラメーターヒント cmd + p
定義元ジャンプ(とにかく素晴らしい) cmd + b
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

QiitaにはてなブックマークとPocketボタンを設置するための�スクリプトを作成してみた

Qiitaの記事にもはてなブックマークとPocketのボタンが欲しいですよね。
そこで、それらのボタンと同じ機能を持つリンクを生成するためのJavaScriptスクリプトを書きました。
Qiitaの記事編集画面で開発者用ツールを開き、以下のJavaScriptをコピペして実行して下さい。
実行すると結果がクリップボードにコピーされます。

snippet.js
const userId = Qiita.user.url_name
const itemId = window.location.href.split('/').splice(-2,1)[0]
const hatebuURL = `//b.hatena.ne.jp/add?mode=confirm&url=https://qiita.com/${userId}/items/${itemId}`
const pocketURL = `//getpocket.com/edit?url=https://qiita.com/${userId}/items/${itemId}`
const  htmlToElement = (html)=> {
  var template = document.createElement('template');
  html = html.trim(); // Never return a text node of whitespace as the result
  template.innerHTML = html;
  return template.content.firstChild;
}
const copyToClipboard = str => {
    if(!str || typeof(str) != "string") {
      return "";
    }
    //elmはtextareaノード
    var elm =htmlToElement("<textarea id=\"tmp_copy\" style=\"position:fixed;right:100vw;font-size:16px;\" readonly=\"readonly\">" + str + "</textarea>")
    //strを含んだtextareaをbodyタグの末尾に設置
    document.body.appendChild(elm);
    //select()でtextarea内の文字を選択
    elm.select();
    //rangeでtextarea内の文字を選択
    var range = document.createRange();
    range.selectNodeContents(elm);
    var sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
    elm.setSelectionRange(0, 999999);
    //execCommandを実施
    document.execCommand("copy");
    //textareaを削除
    document.body.removeChild(elm);
  };

copyToClipboard(`## はてなブックマーク・Pocketはこちらから
[はてなブックマークに追加](${hatebuURL})
[Pocketに追加](${pocketURL})`)
alert('コピーしました。')

コピーされた内容をQiitaのエディターの一番下に貼り付けて下さい。
このようになります。

はてなブックマーク・Pocketはこちらから

はてなブックマークに追加
Pocketに追加

 最後に

いかがでしたか?
どなたかこのコードを元にしてChrome拡張を作って頂けると泣いて喜びます。
さて、今後はこのリンクを記事の一番下に掲載しておくことにします。ありがとうございました。

はてなブックマーク・Pocketはこちらから

はてなブックマークに追加
Pocketに追加

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

秒で使える!時短スニペットコレクション

誰でもカンタン。
ライブラリやタグマネの存在確認、ページ内データ抽出などに使える時短スニペット。
Chrome DevToolsから調査したいことをすぐに調べるための時短スニペットコレクション。

おすすめ使用方法

  • Chrome DevToolsのConsole機能からサクッと使う
  • Chrome DevToolsのSourceタブのSnipets機能に保存して楽々呼び出し
  • Chrome DevToolsのSourcesタブのWatch機能で常に監視

スニペット

汎用

HTTPリファラ情報を調べる

今のページにどのページから移動してきたかを示す情報。metaタグやHTTP ヘッダで上書きされたり、ブラウザによって消されたりすることもある。

document.referrer;

実行結果
image.png

URLパラメータを一覧表示

URLパラメータがゴチャゴチャしてて見づらいときに

Array.from(new URLSearchParams(location.search.slice(1)));

実行結果
image.png

ページからaタグで貼られているリンクを一覧表示

ちょっとExcelに抜き出したいときに便利。

Array.from(document.querySelectorAll('a'),a=>a.href).filter((x, i, self) => self.indexOf(x) === i).sort();

実行結果
image.png

ページからaタグで貼られている外部リンクを一覧表示

同じくちょっとExcelに抜き出したいときに便利。

Array.from(document.querySelectorAll('a'),a=>a.href).filter((x, i, self) => self.indexOf(x) === i && x.indexOf(location.origin)).sort();

実行結果
image.png

黒丸で表示されているパスワードの中身を取得

ブラウザに保存されている自動入力されたパスワードの中身を簡単に確認&コピペ
Chromeの設定画面から確認するのが面倒なときに

Array.from(document.querySelectorAll("input[type='password']"), e => e.value);

実行結果
image.png

ページ内Cookie表示

Applicationタブの方から調べた方が良いことは知っている。。。

document.cookie.split(';').map(v => v.split('=').map(v => v.trim()));

実行結果
image.png

読み込まれているライブラリの有無・バージョン確認

jQueryのバージョン確認

jQuery.fn.jquery;
// "2.1.4"

Prototype.jsのバージョン確認

Prototype.Version;
// "1.5.0_rc0"

Angular.jsのバージョン確認

angular.version.full;
// "1.7.5"

Vue.jsのバージョン確認

Vue.version;
// "2.5.21"

タグマネジメント系

GoogleタグマネージャコンテナID確認

Object.keys(google_tag_manager).filter((key) => !key.indexOf('GTM-'));

実行結果
image.png

dataLayerの内容確認

dataLayer;

Yahoo!タグマネージャーのsite ID確認

BrightTag.instance.site;

実行結果
image.png

あとがき

基本無精なので、こういうスニペットを使って業務のちょっとした時間を短縮してます。
便利そうなスニペットがあれば後から増やしていく予定です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

秒で使える時短Chromeスニペットコレクション

誰でもカンタン。
Chrome DevToolsから調査したいことをすぐに調べるための時短スニペットコレクション。

おすすめ使用方法

  • Chrome DevToolsのConsole機能からサクッと使う
  • Chrome DevToolsのSourceタブのSnipets機能に保存して楽々呼び出し
  • Chrome DevToolsのSourcesタブのWatch機能で常に監視

スニペット

汎用

HTTPリファラ情報を調べる

今のページにどのページから移動してきたかを示す情報。metaタグやHTTP ヘッダで上書きされたり、ブラウザによって消されたりすることもある。

document.referrer;

実行結果
image.png

URLパラメータを一覧表示

URLパラメータがゴチャゴチャしてて見づらいときに

Array.from(new URLSearchParams(location.search.slice(1)));

実行結果
image.png

ページからaタグで貼られているリンクを一覧表示

ちょっとExcelに抜き出したいときに便利。

Array.from(document.querySelectorAll('a'),a=>a.href).filter((x, i, self) => self.indexOf(x) === i).sort();

実行結果
image.png

ページからaタグで貼られている外部リンクを一覧表示

同じくちょっとExcelに抜き出したいときに便利。

Array.from(document.querySelectorAll('a'),a=>a.href).filter((x, i, self) => self.indexOf(x) === i && x.indexOf(location.origin)).sort();

実行結果
image.png

黒丸で表示されているパスワードの中身を取得

ブラウザに保存されている自動入力されたパスワードの中身を簡単に確認&コピペ
Chromeの設定画面から確認するのが面倒なときに

Array.from(document.querySelectorAll("input[type='password']"), e => e.value);

実行結果
image.png

ページ内Cookie表示

Applicationタブの方から調べた方が良いことは知っている。。。

document.cookie.split(';').map(v => v.split('=').map(v => v.trim()));

実行結果
image.png

読み込まれているライブラリの有無・バージョン確認

jQueryのバージョン確認

jQuery.fn.jquery;
// "2.1.4"

Prototype.jsのバージョン確認

Prototype.Version;
// "1.5.0_rc0"

Angular.jsのバージョン確認

angular.version.full;
// "1.7.5"

Vue.jsのバージョン確認

Vue.version;
// "2.5.21"

タグマネジメント系

GoogleタグマネージャコンテナID確認

Object.keys(google_tag_manager).filter((key) => !key.indexOf('GTM-'));

実行結果
image.png

dataLayerの内容確認

dataLayer;

Yahoo!タグマネージャーのsite ID確認

BrightTag.instance.site;

実行結果
image.png

あとがき

基本無精なので、こういうスニペットを使って業務のちょっとした時間を短縮してます。
便利そうなスニペットがあれば後から増やしていく予定です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

秒で使える時短スニペットコレクション

誰でもカンタン。
Chrome DevToolsから調査したいことをすぐに調べるための時短スニペットコレクション。

おすすめ使用方法

  • Chrome DevToolsのConsole機能からサクッと使う
  • Chrome DevToolsのSourceタブのSnipets機能に保存して楽々呼び出し
  • Chrome DevToolsのSourcesタブのWatch機能で常に監視

スニペット

汎用

HTTPリファラ情報を調べる

今のページにどのページから移動してきたかを示す情報。metaタグやHTTP ヘッダで上書きされたり、ブラウザによって消されたりすることもある。

document.referrer;

実行結果
image.png

URLパラメータを一覧表示

URLパラメータがゴチャゴチャしてて見づらいときに

Array.from(new URLSearchParams(location.search.slice(1)));

実行結果
image.png

ページからaタグで貼られているリンクを一覧表示

ちょっとExcelに抜き出したいときに便利。

Array.from(document.querySelectorAll('a'),a=>a.href).filter((x, i, self) => self.indexOf(x) === i).sort();

実行結果
image.png

ページからaタグで貼られている外部リンクを一覧表示

同じくちょっとExcelに抜き出したいときに便利。

Array.from(document.querySelectorAll('a'),a=>a.href).filter((x, i, self) => self.indexOf(x) === i && x.indexOf(location.origin)).sort();

実行結果
image.png

黒丸で表示されているパスワードの中身を取得

ブラウザに保存されている自動入力されたパスワードの中身を簡単に確認&コピペ
Chromeの設定画面から確認するのが面倒なときに

Array.from(document.querySelectorAll("input[type='password']"), e => e.value);

実行結果
image.png

ページ内Cookie表示

Applicationタブの方から調べた方が良いことは知っている。。。

document.cookie.split(';').map(v => v.split('=').map(v => v.trim()));

実行結果
image.png

読み込まれているライブラリの有無・バージョン確認

jQueryのバージョン確認

jQuery.fn.jquery;
// "2.1.4"

Prototype.jsのバージョン確認

Prototype.Version;
// "1.5.0_rc0"

Angular.jsのバージョン確認

angular.version.full;
// "1.7.5"

Vue.jsのバージョン確認

Vue.version;
// "2.5.21"

タグマネジメント系

GoogleタグマネージャコンテナID確認

Object.keys(google_tag_manager).filter((key) => !key.indexOf('GTM-'));

実行結果
image.png

dataLayerの内容確認

dataLayer;

Yahoo!タグマネージャーのsite ID確認

BrightTag.instance.site;

実行結果
image.png

あとがき

基本無精なので、こういうスニペットを使って業務のちょっとした時間を短縮してます。
便利そうなスニペットがあれば後から増やしていく予定です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptでストップウォッチ作ってみる

参考

javascriptでストップウォッチを作ってみる。忘備録

https://qiita.com/ryomaDsakamoto/items/c49a9d4cd2017405af1b

完成形

stopwatch.png

シンプルなストップウォッチです.

HTML

最低限必要な表示する領域とボタンを配置します.

stopwatch.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>StopWatch</title>
  </head>
  <body>
    <div id="timer">00:00:000</div>
    <button id="start">start</button> <button id="stop">stop</button>
    <button id="reset">reset</button>
    <script src="js/main.js"></script>
  </body>
</html>

JavaScript(ES6)でロジックを書く

main.js
const timer = document.getElementById("timer");
const start = document.getElementById("start");
const stop = document.getElementById("stop");
const reset = document.getElementById("reset");

// 経過時間を保存する変数(単位:ミリ秒)
let elapsedTime;
// スタートボタンを押したときのUnix Epoch
let startTime;
// タイマーのID
let timerId;
// 以前 stop したタイミングまでの計測時間
let timeToAdd = 0;

// 表示される内容をアップデートする関数
const updateTimeText = () => {
  // 1分 = 1000 ミリ秒 * 60秒
  let m = Math.floor(elapsedTime / (1000 * 60));
  // 1分に満たなかったミリ秒のうち,秒となったもの
  let s = Math.floor((elapsedTime % (1000 * 60)) / 1000);
  // 1秒になれなかったもの
  let ms = elapsedTime % 1000;

  // ゼロパディング
  m = `0${m}`.slice(-2);
  s = `0${s}`.slice(-2);
  ms = `00${ms}`.slice(-3);

  timer.textContent = `${m}:${s}:${ms}`;
};

// 経過時間の管理と計算を行う関数
const countUp = () => {
  timerId = setTimeout(() => {
    elapsedTime = Date.now() - startTime + timeToAdd;
    updateTimeText();
    countUp();
  }, 10);
};

start.addEventListener("click", () => {
  startTime = Date.now();
  countUp();
});

stop.addEventListener("click", () => {
  clearTimeout(timerId);
  timeToAdd += Date.now() - startTime;
});

reset.addEventListener("click", () => {
  elapsedTime = 0;
  timeToAdd = 0;
  // 00:00:000 を表示
  updateTimeText();
});

ポイント

const,let

定数は const ,変数は let を使います.

変数の意図しない変更を防ぐことができます.また,スコープが自身が定義されたブロックとなります.

アロー関数

毎度 function と書くのは面倒です. () => {} というように書くと見た目がすっきりします.

今回は関係ありませんが,this の扱いが少し違うので注意が必要です.

https://azu.github.io/what-is-this/

テンプレート文字列

"string text " + expression + " string text" は, `string text ${expression} string text` というように書くことができます.テンプレート文字列を使うことによってダブルクオートやシングルクオートを使う回数を減らすことができ,可読性が上がります.

デバッグ

上のコードにはバグがあるので修正します.

スタートボタンの連続クリック

このアプリケーションでは,連続で2回クリックをしたとき2つのタイマーイベント( countUp )が発生してしまいます.

この問題を解決する方法としていくつかのアプローチがあります.

  1. スタートボタンとストップボタンを統合する
  2. 連続で押せないように無効化する

一般的な(物理)ストップウォッチでは,1の選択肢が取られています.

ただ,手法としては2もあるのでどちらも紹介します.

スタートボタンとストップボタンを統合する

HTML

HTMLはスタートボタンとストップボタンが統合されたことにより,buttonタグが一つ減ります.

stopwatch.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>StopWatch</title>
  </head>
  <body>
    <div id="timer">00:00:000</div>
    <button id="start-stop">start</button> <button id="reset">reset</button>
    <script src="js/main.js"></script>
  </body>
</html>

JavaScript

現在の状態が動作中か停止中かを条件分けして,ボタンの処理を変える必要があります.

main.js
const timer = document.getElementById("timer");
const startStop = document.getElementById("start-stop");
const reset = document.getElementById("reset");

// 経過時間を保存する変数(単位:ミリ秒)
let elapsedTime;
// スタートボタンを押したときのUnix Epoch
let startTime;
// タイマーのID
let timerId = null;
// 以前 stop したタイミングまでの計測時間
let timeToAdd = 0;

// 表示される内容をアップデートする関数
const updateTimeText = () => {
  // 1分 = 1000 ミリ秒 * 60秒
  let m = Math.floor(elapsedTime / (1000 * 60));
  // 1分に満たなかったミリ秒のうち,秒となったもの
  let s = Math.floor((elapsedTime % (1000 * 60)) / 1000);
  // 1秒になれなかったもの
  let ms = elapsedTime % 1000;

  // ゼロパディング
  m = `0${m}`.slice(-2);
  s = `0${s}`.slice(-2);
  ms = `00${ms}`.slice(-3);

  timer.textContent = `${m}:${s}:${ms}`;
};

// 経過時間の管理と計算を行う関数
const countUp = () => {
  timerId = setTimeout(() => {
    elapsedTime = Date.now() - startTime + timeToAdd;
    updateTimeText();
    countUp();
  }, 10);
};

startStop.addEventListener("click", () => {
  // タイマーIDが停止中
  if (timerId === null) {
    startTime = Date.now();
    countUp();
    // ボタンのテキストをstartからstopに更新
    startStop.textContent = "stop";
  } else {
    // タイマーが動作中
    clearTimeout(timerId);
    timerId = null;
    timeToAdd += Date.now() - startTime;
    // ボタンのテキストをstopからstartに更新
    startStop.textContent = "start";
  }
});

reset.addEventListener("click", () => {
  elapsedTime = 0;
  timeToAdd = 0;
  // 00:00:000 を表示
  updateTimeText();
});

連続で押せないように無効化する

button タグには,disabled という属性を付けることができます.
JavaScript から disabled 属性をコントロールすることによってボタンを無効化します.

HTML

はじめのHTMLと同じコードですが,初期状態としてstopボタンを無効化しておきます.

stopwatch.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>StopWatch</title>
  </head>
  <body>
    <div id="timer">00:00:000</div>
    <button id="start">start</button> <button id="stop" disabled>stop</button>
    <button id="reset">reset</button>
    <script src="js/main.js"></script>
  </body>
</html>

JavaScript

はじめのコードに ボタンを有効化/無効化するコードを追加します.

main.js
const timer = document.getElementById("timer");
const start = document.getElementById("start");
const stop = document.getElementById("stop");
const reset = document.getElementById("reset");

// 経過時間を保存する変数(単位:ミリ秒)
let elapsedTime;
// スタートボタンを押したときのUnix Epoch
let startTime;
// タイマーのID
let timerId;
// 以前 stop したタイミングまでの計測時間
let timeToAdd = 0;

// 表示される内容をアップデートする関数
const updateTimeText = () => {
  // 1分 = 1000 ミリ秒 * 60秒
  let m = Math.floor(elapsedTime / (1000 * 60));
  // 1分に満たなかったミリ秒のうち,秒となったもの
  let s = Math.floor((elapsedTime % (1000 * 60)) / 1000);
  // 1秒になれなかったもの
  let ms = elapsedTime % 1000;

  // ゼロパディング
  m = `0${m}`.slice(-2);
  s = `0${s}`.slice(-2);
  ms = `00${ms}`.slice(-3);

  timer.textContent = `${m}:${s}:${ms}`;
};

// 経過時間の管理と計算を行う関数
const countUp = () => {
  timerId = setTimeout(() => {
    elapsedTime = Date.now() - startTime + timeToAdd;
    updateTimeText();
    countUp();
  }, 10);
};

start.addEventListener("click", () => {
  startTime = Date.now();
  countUp();
  // スタートボタンを無効化
  start.disabled = true;
  // ストップボタンを有効化
  stop.disabled = false;
});

stop.addEventListener("click", () => {
  clearTimeout(timerId);
  timeToAdd += Date.now() - startTime;
  // スタートボタンを有効化
  start.disabled = false;
  // ストップボタンを無効化
  stop.disabled = true;
});

reset.addEventListener("click", () => {
  elapsedTime = 0;
  timeToAdd = 0;
  // 00:00:000 を表示
  updateTimeText();
});

まとめ

プログラムは自分の書いたようにしか動きません.
プログラムを書くにはまず必要な機能を考え言語化し,そのとおりにプログラムを書くと動きます.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Qiita で いいね したら草生えるページ(β版)を作った話

ユーザー毎のいいねした日時をGithub風カレンダーヒートマップで可視化するページを作りました。

作ったもの

https://qiiner.tag1216.net/likes-heatmap

データはユーザーの いいねした記事 のページから取得しています。
ただし、いいねした日はこのページからはわからないので、いいねした記事の投稿日から推測します。(詳細は後述)

データ取得には数秒から数分程度かかりQiitaへの負荷も高いので、取得結果は数時間キャッシュして再取得は行わないようにしています。

作った動機

Qiitaいいな〜ランキングでも書きましたが、Qiitaでは記事を書くだけでなく他の人が書いた良い記事にいいねしていく事もとても大切だと思っています。
可視化される事でいいねすることへのモチベーションが上がるのではないかと思い作ってみました。

使っているもの

  • Firebase
    • Hosting
      • フロントWebアプリ
    • Functions
      • Qiitaからデータを取得してスクレイピングする処理
    • Firestore
      • Qiitaから取得したデータのキャッシュ
      • 同時実行数制限のためのキュー
  • フロント(Firebase Hosting)
    • TypeScript
    • React
    • Material-UI
    • react-calendar-heatmap
  • バックエンド(Firebase Functions)
    • TypeScript
    • node-fetch
    • cheerio

Firebaseは色々と制約はあるものの簡単に作れていいですね。

いいねした日の推定方法

  • いいねした記事のページはいいねした日時の降順(新しい順)で表示されている
  • 記事の投稿日より前にいいねすることはできない

この2つの前提を元に以下のロジックでいいねした日を推定しています。

例えば、いいねした記事の投稿日が以下のようになっていたとします。

  • ⑤ 2019/01/17
  • ④ 2019/01/11
  • ③ 2019/01/10
  • ② 2019/01/16
  • ① 2019/01/15

いいねした日は以下のように推測していきます。

  • 一番古い①は投稿日をいいねした日とします。
  • ②の投稿日は①以降なので、②の投稿日をいいねした日とします。
  • ③の投稿日は②よりも前なので、②の投稿日をいいねした日とします。
  • ④も投稿日が②よりも前なので、②の投稿日をいいねした日とします。
  • ⑤の投稿日は②以降なので、⑤の投稿日をいいねした日とします。
No. 投稿日 いいねした日の推測日
2019/01/17 2019/01/17
2019/01/11 2019/01/16
2019/01/10 2019/01/16
2019/01/16 2019/01/16
2019/01/15 2019/01/15

正式版に向けて

今回作ったものはいいねした日は正確ではないのでβ版という扱いにしています。

Qiitaいいな〜分析 で書いたように Qiita API で記事のいいねを取得すれば正確な日時がわかるので、正式版ではこちらから取得する予定です。

参考

参考までに、過去にQiitaに書かれていた類似サービスの紹介です。

これらは記事を書くと草が生えるサービスです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「条件がどれかに一致」を(少しでも)分かりやすく書きたい

始めに

分岐の条件がいつの間にか増えていき、最終的に書いた本人がしんどくなる事がありました。
そういうコードは、コメントを残してもつらいです。
また保守もしんどくなります。

今回直したのはこんな条件です。

before

// xが判定したいもの
if(x==='hoge' || x==='huga' || x==='piyo'){
  statement
}

・・・最初は1つでしたが、どんどん増えてこうなりました。

こちらの投稿を参考に直しました。
https://qiita.com/yassh/items/12b7e685dc35824819f9

after

const conds = ['hoge', 'huga', 'piyo'];
if(conds.includes(x)){
  statement1
}else{
  statement2
}

使用しているメソッド

Array.prototype.includes()
これは、配列に特定の要素が含まれているかを true または false で返します

// 例
['condition-1', 'condition-2', 'condition-3'].includes('比較対象');

// 返り値はboolean

見通しが良くなったのと、後の保守が楽になりそうです。
もちろん条件次第で使えない場合があるので、そこは臨機応変に使い分けたいと思います。

参考

https://qiita.com/yassh/items/12b7e685dc35824819f9
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/includes

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで「条件のどれかに一致」を(少しでも)簡単に書きたい

始めに

分岐の条件がいつの間にか増えていき、最終的に書いた本人がしんどくなる事がありました。
そういうコードは、コメントを残してもつらいです。
また保守もしんどくなります。

今回直したのはこんな条件です。

before

// xが判定したいもの
if(x==='hoge' || x==='huga' || x==='piyo'){
  statement
}

・・・最初は1つでしたが、どんどん増えてこうなりました。

こちらの投稿を参考に直しました。
https://qiita.com/yassh/items/12b7e685dc35824819f9

after

const conds = ['hoge', 'huga', 'piyo'];
if(conds.includes(x)){
  statement1
}else{
  statement2
}

使用しているメソッド

Array.prototype.includes()
これは、配列に特定の要素が含まれているかを true または false で返します

// 例
['condition-1', 'condition-2', 'condition-3'].includes('比較対象');

// 返り値はboolean

見通しが良くなったのと、後の保守が楽になりそうです。
もちろん条件次第で使えない場合があるので、そこは臨機応変に使い分けたいと思います。

参考

https://qiita.com/yassh/items/12b7e685dc35824819f9
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/includes

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

私がよく使うJSからの外部JSの読み込み方法

前置き

外部JSの読み込みって様々な方法があるけど、違いがよくわからなかったり、できることが違ったり、使いやすさが違ったり...するので自分なりにまとめてみました。
私はjQueryを使用するときにしょっちゅう使っています

main.jsimport.jsを呼び出すというものになります。

コード

私がよく使うのは4つあります。
1. HTML(静的)
2. HTML(動的)
3. Fetch (3'としてxhr)
4. Module

早速書いていきます。

1.HTML経由(静的)

単純。多分最初に思いつくやつ。

<script src="import.js"></script>
<script src="main.js"></script>

main.jsで使うもの(import.js,例えば変数や関数など)はmain.jsの読み込み前に読み込む必要があります。

ダメな例

<script src="main.js"></script>
<script src="import.js"></script>

これでは○○ is not definedと出て使えないです。
main.jsで使うものがimport.jsを読み込むことで定義されるからです。

(ちなみに、これ書いているときに変数も外部からのものを使えることを知りました。)

2.動的なHTML要素作成

これはHTML経由とほぼ同じなのですが、動的にできたりとか、ファイル名を変数化できたりとかできます。実行が終わったらその部分のコードを消すことも可能です。

var script = document.createElement('script'); //変数名は適当なものにでも
script.src = "import.js"; //ファイルパス
document.head.appendChild(script); //<head>に生成
// document.body.appendChild(script); /*<body>に生成する場合はこちら*/

これで、動的に<script src="import.js"></script>を生成したことになります。

もし実行が終了したときには、
document.removeChild(script)で要素を消去可能です。

3.Fetch

順番に実行可能。JSのみならず、HTMLとかCSSも読み込めます。
私はよくjQueryの読み込みに使用します。
jQueryのファイル読み込んだ後にこのコードを実行してくださいなといった具合に。
ただしCORSの関係で他ドメインのファイルはできない(...と思う)。

fetch('./import.js').then(r=>{return r.text()}).then(t=>{
    // "t"にimport.jsのファイル内容が格納されているので
    eval(t); //その内容を実行する
});

こんな感じで私はjQueryの読み込みに使う。

fetch('./jQuery.js').then(r=>{return r.text()}).then(t=>eval(t)).then(()=>{
    //jQueryのコード
});

こっちでは変数は使えないようです(少なくとも私の環境では)

3'.XMLHttpRequest(xhr)

私はxhrを使わないけど、仕様的にはFetchと同じ...?

var xhr = new XMLHttpRequest();
xhr.addEventListener('load',function(){ //thisを使っているのでアロー関数は使えない
    eval(this.responseText);
});
oReq.open("GET", "./import.js");
oReq.send();

XMLHttpRequest の利用 - Web API | MDNをもとに編集。

4.Module

JSのみでImport/Export可能。とてもシンプルな構文だけどtype="module"をHTML側につけなければならない。

index.html
<script type="module" src="main.js">
main.js
import txt from "./import" //これが必要になります
console.log(txt);
import.js
const txt = "Hello,World";
export default txt; //これが必要になります

このように、Import/Exportの1文が必要となります。
export default (変数や定数、関数など)をもとにimport.jsに書き、
import (変数など) from (ファイル名)とやればその変数を取り込めます。

あとがき

こんな感じに書いていきましたが、アドバイス等もいただけると幸いです。
ミスもあればコメントよろしくお願いいたします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Three.jsでCurl Noise

はじめに

GLSLを触り始めるとやっぱりやりたくなりますよね。Curl Noise。
残念なことにWebGLではまだCompute Shaderは対応されてない状況です。(WebGL2.0から対応するらしい)
ですが、Three.jsにはGPUComputationRendererというものございまして、誰でも簡単に粉を飛ばせます。

今回作成するものです。gitはこちら

また、今回はCurl Noiseの説明というよりかはThree.jsでGPUComputationRendererを使ってGPGPUをすることについて解説します。

実装の大まかな説明

Curl Noiseのようなパーティクルは少なくとも1万くらいのパーティクルを出すことが多いですが、普通にThree.jsでオブジェクトを生成、位置の更新を行うと非常に重い、というか生成すらままならないかもしれません。

そこで、GLSLの出番です。

1万個のオブジェクトを生成するのは大変ですが、1万頂点くらいの3Dモデル1つなら今時のPC、スマホならだいたい余裕で動作すると思います。
そこでこれから実装するパーティクルは、一つのオブジェクトの頂点座標をGLSLでいじいじするという考え方です。

ただ、GLSLでは、頂点座標を更新してもその座標を保存しておくことができません。
Curl NoiseはSimplex Noiseで得たノイズを随時足していくため、前フレームの頂点座標が必要となります。
では、どうするかというと、GLSLで計算した値はテクスチャに保存してしまうのです。
テクスチャとはマジでテクスチャです。画像です。

図に示すとこんな感じです。
スクリーンショット 2019-02-04 17.49.13.png

よって実装の大まかな流れとしては

  1. 位置、速度を保存するテクスチャを作成
  2. 各テクスチャを制御するシェーダーの作成
  3. パーティクルオブジェクトの作成、テクスチャを渡す

こんなかんじですかね。

準備

GPUComputationRendererをゲットします。自分はローカルに保存したものをWebpackで読み込ませました。

Particle.js
import GPUComputationRenderer from '../../plugins/GPUComputationRenderer';

実装

では実装を解説していきます。

1.計算結果保存のためのテクスチャ作成

Particle.js
this.comTexs = {
    position:{
        texture: null,
        uniforms: null,
    },
    velocity:{
        texture: null,
        uniforms: null,
    },
}

コンストラクタ内でcomputeRendererでのテクスチャとuniforms変数の対応づけがわかりやすいように連想配列でまとめておきます。

Particle.js
initComputeRenderer(){
        //computeRendererの初期化
        this.computeRenderer = new GPUComputationRenderer(this.computeTextureWidth,this.computeTextureWidth,this.renderer);

        //テクスチャを作成
        let initPositionTex = this.computeRenderer.createTexture();
        let initVelocityTex = this.computeRenderer.createTexture();

        //各テクスチャを初期化
        this.initPosition(initPositionTex);
        this.initVelocity(initVelocityTex);

        //computeRendererに各テクスチャとそれに対応するシェーダーを登録する
        this.comTexs.position.texture = this.computeRenderer.addVariable("texturePosition",comShaderPosition,initPositionTex);
        this.comTexs.velocity.texture = this.computeRenderer.addVariable("textureVelocity",comShaderVelocity,initVelocityTex);

        //uniformを登録
        this.computeRenderer.setVariableDependencies( this.comTexs.position.texture, [ this.comTexs.position.texture, this.comTexs.velocity.texture] );
        this.comTexs.position.uniforms = this.comTexs.position.texture.material.uniforms;
        this.comTexs.position.uniforms.time =  { type:"v3", value : this.time};

        this.computeRenderer.setVariableDependencies( this.comTexs.velocity.texture, [ this.comTexs.position.texture, this.comTexs.velocity.texture] );  
        this.comTexs.velocity.uniforms = this.comTexs.velocity.texture.material.uniforms;

        this.computeRenderer.init();
    }
Particle.js
    initPosition(tex){
        var texArray = tex.image.data;
        let range = new THREE.Vector3(1,1,1);
        for(var i = 0; i < texArray.length; i +=4){
            texArray[i + 0] = Math.random() * range.x - range.x / 2;
            texArray[i + 1] = Math.random() * range.y - range.y / 2;
            texArray[i + 2] = Math.random() * range.z - range.z / 2;
            texArray[i + 3] = 0.0;
        }
    }

    initVelocity(tex){
        var texArray = tex.image.data;
        for(var i = 0; i < texArray.length; i +=4){
            texArray[i + 0] = Math.random() * 20 - 10;
            texArray[i + 1] = Math.random() * 20 - 10;
            texArray[i + 2] = Math.random() * 20 - 10;
            texArray[i + 3] = 0;
        }
    }

テクスチャを作ったりシェーダーに送るuniform変数を定義しています。

computeRendererの初期化の時にテクスチャのサイズを指定していますが、計算できるパーティクルの数はこのテクスチャのサイズとなります。
なので1万出したかったら、

var computeRenderer = new GPUComputationRenderer(100,100,renderer);

みたいなかんじです。

uniform変数の定義はThree.jsのShader Materialと一緒ですね。シェーダーの取り込み方は後述します。
(timeというuniform変数を定義してますが、サンプル用に作っただけで実際は使っていません。)

また、この中で重要なポイントなのが以下の2箇所箇所です。

this.computeRenderer.setVariableDependencies( this.comTexs.position.texture, [ this.comTexs.position.texture, this.comTexs.velocity.texture] );

this.computeRenderer.setVariableDependencies( this.comTexs.velocity.texture, [ this.comTexs.position.texture, this.comTexs.velocity.texture] );  

これ、何しているかというとcomputeRendererの中のテクスチャを他のshaderにuniformで参照できるようにしています。

this.computeRenderer.setVariableDependencies( 対象のテクスチャ, [ 送るテクスチャたち ] );

こんな文法ですね。
ただよく見てみると、送るテクスチャの中に対象のテクスチャ自身も入っています。
なんとこれ、シェーダーが前フレームで書き出した自身のテクスチャをそのまま参照できちゃうんですよ...。
生WebGLだとこれができないので非常に嬉しいです。

2.テクスチャを制御するシェーダーの作成

そんでもってシェーダーを書きます。

computePosition.glsl
void main() {
    vec2 uv = gl_FragCoord.xy / resolution.xy;

    vec3 pos = texture2D( texturePosition, uv ).xyz;  
    vec3 vel = texture2D( textureVelocity, uv ).xyz;

    pos += vel * 0.01;

    gl_FragColor = vec4(pos,1.0);
}

位置を更新するシェーダーです。テクスチャは1ピクセルにR,G,B,Aの4つのチャンネルがあるので、格納できるデータの個数も4つです。

今回はRチャンネルにX軸、GチャンネルにY軸、BチャンネルにZ軸を格納しました。Aチャンネルは使ってません。

処理としては速度テクスチャがuniformで送られてきているのでそれを読み取って前フレームの座標に足しているだけです。

フラグメントシェーダーと同じくgl_FragColorにデータを格納します。

computeVelocity.glsl
vec3 snoise3D(vec3 pos){
    float weight = 0.5;
    float x = snoise(pos * weight + 61.0) ;
    float y = snoise(pos * weight + 236.0);
    float z = snoise(pos * weight + 0.0);

    return vec3(x,y,z);
}

vec3 snoiseDelta(vec3 pos){
    float dlt = 0.0001;
    vec3 a = snoise3D(pos);
    vec3 b = snoise3D(vec3(pos.x + dlt,pos.y + dlt,pos.z + dlt));
    vec3 dt = vec3(a.x - b.x,a.y - b.y,a.z - b.z) / dlt;
    return dt;
}

void main() {
    vec2 uv = gl_FragCoord.xy / resolution.xy;

    vec3 pos = texture2D( texturePosition, uv ).xyz;
    vec3 vel = texture2D( textureVelocity, uv ).xyz;
    float idParticle = uv.y * resolution.x + uv.x;

    vel = vel + snoiseDelta(pos * 3.0) * 0.1;
    vel *= 0.92;

    gl_FragColor = vec4( vel.xyz, 1.0 );
}

速度を更新するシェーダーです。snoise関数はStefan Gustavsonさんのwebgl-noiseを使用させていただきました。

作成したシェーダーはshader-loaderを使えば簡単に取り込めます。

import comShaderPosition from './shaders/computePosition.glsl';
import comShaderVelocity from './shaders/computeVelocity.glsl';

3. パーティクルオブジェクトの作成

最後にThree.jsでパーティクルとなるオブジェクトを作成します。

Particle.js
createParticleObj(){
        let geo = new THREE.BufferGeometry();

        //ジオメトリ初期化用の配列
        var pArray = new Float32Array(this.numParticle * 3);
        for(var i = 0; i < pArray.length; i++){
            pArray[i] = 0;
        }

        //テクスチャ参照用のuvを取得
        var uv = new Float32Array(this.numParticle * 2);
        var p = 0;
        for(var i = 0;i < this.computeTextureWidth; i ++){
            for(var j = 0;j < this.computeTextureWidth; j ++){
                uv[p++] = i / ( this.computeTextureWidth - 1);
                uv[p++] = j / ( this.computeTextureWidth - 1);
            }
        }
        geo.addAttribute('position', new THREE.BufferAttribute( pArray, 3 ) );
        geo.addAttribute('uv', new THREE.BufferAttribute( uv, 2 ) );

        this.uni = {
            texturePosition : {value: null},
            cameraConstant: { value: 4.0},
            color:{ value: this.color},
        }

        let mat = new THREE.ShaderMaterial({
            uniforms: this.uni,
            vertexShader: vert,
            fragmentShader: frag,
            transparent: true,
        });

        this.obj = new THREE.Points(geo,mat);
        this.obj.matrixAutoUpdate = false;
        this.obj.updateMatrix();
    }

uniform変数で位置を記録したテクスチャを渡せるようにしておきます。また、位置テクスチャのどのピクセルを参照するかを表すuvをattributeで持たせておきます。

vertex.glsl
uniform sampler2D texturePosition;

uniform vec3 color;
varying vec4 vColor;

void main() {
    vec4 posTemp = texture2D( texturePosition, uv );
    vec3 pos = posTemp.xyz;

    vColor = vec4(color,1.0);

    vec4 mvPosition = modelViewMatrix * vec4( pos + position, 1.0 );

    gl_PointSize = 2.0;
    gl_Position = projectionMatrix * mvPosition;
}
fragment.glsl
varying vec4 vColor;
void main() {
    gl_FragColor = vColor;
}

パーティクルオブジェクトのシェーダーです。位置テクスチャを読んで足しているだけですね。

最後にcomputeRendererを走らせ、パーティクルオブジェクトに送るテクスチャを更新すれば完成です!

Particle.js
update(){
        this.time += this.clock.getDelta();

        //computeRendererを走らせる
        this.computeRenderer.compute();

        //パーティクルオブジェクトに送るテクスチャを更新
        this.uni.texturePosition.value = this.computeRenderer.getCurrentRenderTarget(this.comTexs.position.texture).texture;
    }

最後に

GPUComputationRendereを使えば簡単にThree.jsでGPGPUができました。
データをテクスチャに保存するのは最初はとっつきにくいですが、一回組んでしまえばいろんなことに使えます。

WebGL2.0ではTransform Feedbackが使えるようになり、わざわざこんなことしなくてもよくなるらしいです。たのしみですね!

GPUをフル活用してヌルサクなWebGL表現、追求していきたいですね!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

サブライムテキストでJavascript の予測変換機能を追加してみた②

「サブライムテキストでJavascriptの予測変換機能を追加してみた①」の続き。

参考にした記事
- https://qiita.com/akakuro43/items/600e7e4695588ab2958d)
- https://qiita.com/omega999/items/6f65217b81ad3fffe7e6
- JavaScriptのためのSublime Textプラグイン

①jsHintをインストール。

以下のコマンドを実行。

MyMac:~ takawaki$ npm install -g jshint

-gというオプションは、「グローバルインストール」というもので、npmのインストール場所にパッケージをインストールする。
つけなかったら、ローカルインストールでカレントディレクトリのnode_modules内にインストールされるらしい。
参考記事:npmのグローバルインストールとローカルインストール

②SublimeLinterをインストール

SublimeLinter-jshintを使うためにはSublimeLinter-jshintを動かすためのSublimeLinter というプラグインが必要とのこと。
sublimeTextにて、インストールパッケージを起動して、SublimeLinterをインストール。

③SublimeLinter-jshintをインストール

インストールパッケージを起動して、SublimeLinterjshintをインストール。
これで、予測変換機能が使えるようになった。
SublimeLinter-jshint_add.gif

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

サブライムテキストでJavascriptの予測変換機能を追加してみた②

「サブライムテキストでJavascriptの予測変換機能を追加してみた①」の続き。

参考にした記事
- https://qiita.com/akakuro43/items/600e7e4695588ab2958d)
- https://qiita.com/omega999/items/6f65217b81ad3fffe7e6
- JavaScriptのためのSublime Textプラグイン

①jsHintをインストール。

以下のコマンドを実行。

MyMac:~ takawaki$ npm install -g jshint

-gというオプションは、「グローバルインストール」というもので、npmのインストール場所にパッケージをインストールする。
つけなかったら、ローカルインストールでカレントディレクトリのnode_modules内にインストールされるらしい。
参考記事:npmのグローバルインストールとローカルインストール

②SublimeLinterをインストール

SublimeLinter-jshintを使うためにはSublimeLinter-jshintを動かすためのSublimeLinter というプラグインが必要とのこと。
sublimeTextにて、インストールパッケージを起動して、SublimeLinterをインストール。

③SublimeLinter-jshintをインストール

インストールパッケージを起動して、SublimeLinterjshintをインストール。
これで、予測変換機能が使えるようになった。
SublimeLinter-jshint_add.gif

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JS】三角関数を使用した回転アニメーション

回転するアニメーションに挑戦してみました。

◾️Demo
回転アニメーション

処理の内容

  • div.box.box0○の自動挿入。
  • 三角関数を用い、座標を取得後回転させる。
simple01.html
    <div id="boxArea"></div>
simple01.pug
    #boxArea
simple01.js
// ----- アニメーション実行 ----- //
  for (let i = 1; i < 6; i++) {

    const boxWrap = document.getElementById('boxArea'); // 追加したい要素の親要素を取得
    const createBox = document.createElement('div'); // divの作成
    createBox.classList.add('box', 'box0'+i); // divにクラスを付与
    boxWrap.appendChild(createBox); // div.boxの追加

    // 動作の対象
    const box = document.querySelector('.box0' + i);

    // 角度の設定
    let degree = 0;

    // 動作対象の各縦横幅取得
    const boxWid = box.clientWidth; // 横幅
    const boxHei = box.clientHeight; // 縦幅
    const boxWidHalf = boxWid / 2;
    const boxHeiHalf = boxHei / 2;

    // ループ
    loop();

    function loop() {

      // 回転させたい角度
      const rotation = (degree * Math.PI) / 180;

      // 回転位置
      const targetX = window.innerWidth / 2 + boxWid * Math.cos(rotation) - boxWidHalf;
      const targetY = window.innerHeight / 2 + boxHei * Math.sin(rotation) - boxHeiHalf;

      // stykeの付与
      box.style.left = `${targetX}px`;
      box.style.top = `${targetY}px`;

      // 増加させる角度(値の増減で回転スピード調整)
      degree -= 2;

      // 次の画面更新した際にLoop()を実行
      requestAnimationFrame(loop);
    }
  }

本当はなんとかトレインみたくしてみたかったのですが、タイムアップにより断念。
あとは、地球の周りを何か走らせたりしてみたかったですので、今後の課題ににします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

サブライムテキストでJavascript の予測変換機能を追加してみた①

WEBサイト制作で使っているsublimeText(サブライムテキスト)で、Javascriptの予測変換機能を追加してみた。

これで、querySelectorといった長ったらしいメソッドも楽に入力できるに違いない。

でも、どうやらそのためには、「jshint」というプラグインがあるらしいが、前提としてNode.jsをインストールしておく必要があるとのこと。
Node.jsというのは、「サーバーサイドで動くJavascript」のこと。詳細を調べると、複雑すぎて(⁰▿⁰)だったので、とりあえずは左のような浅い理解で今はOKとさせてください。
参照サイト
後々、ドットインストール等で勉強します。
以下に、Node.jsインストール・jshint追加までの手順を記します。

参考にした記事
- https://qiita.com/akakuro43/items/600e7e4695588ab2958d)
- https://qiita.com/omega999/items/6f65217b81ad3fffe7e6
- JavaScriptのためのSublime Textプラグイン

① homebrewのインストール

homebrewというのは、Mac用のパッケージマネージャ。→公式サイト
パッケージマネージャーというのは、OSという一つの環境で各種のソフトウェアの管理、ソフトウェア同士やライブラリとの依存関係を管理するシステム。(Wikipediaから引用)
⬇︎
Macターミナルを起動して、以下のコマンドを実行。
※homebrew公式サイトの「インストール」項目を参照

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

インストールできたか確認するために、「brew doctor」と打ち込んだら、変な表示がされた。
「brew doctor」というのは、インストールされたhomebrewの問題をチェックしてくれるコマンド。

Please note that these warnings are just used to help the Homebrew maintainers
with debugging if you file an issue. If everything you use Homebrew for is
working fine: please don't worry or file an issue; just ignore this. Thanks!

Warning: "config" scripts exist outside your system or Homebrew directories.
`./configure` scripts often look for *-config scripts to determine if
software packages are installed, and what additional flags to use when
compiling and linking.

Having additional scripts in your path can confuse software installed via
Homebrew if the config script overrides a system or Homebrew provided
script of the same name. We found the following "config" scripts:
  /anaconda3/bin/icu-config
  /anaconda3/bin/freetype-config
  /anaconda3/bin/xslt-config
  /anaconda3/bin/libpng16-config
  /anaconda3/bin/python3.7-config
  /anaconda3/bin/libpng-config
  /anaconda3/bin/xml2-config
  /anaconda3/bin/python3.7m-config
  /anaconda3/bin/python3-config
  /anaconda3/bin/curl-config
  /anaconda3/bin/ncursesw6-config
  /anaconda3/bin/pcre-config

Warning: Broken symlinks were found. Remove them with `brew cleanup`:
  /usr/local/bin/mono-boehm
  /usr/local/bin/prj2make

Warningということは「警告」ってこと?
一つ目の警告から、調べてみた。
①. 「Warning: "config" scripts exist outside your system or Homebrew directories.」
(警告: "config"スクリプトはあなたのシステムまたは自作ディレクトリの外にあります。)
解決方法はここを参考にしたけどうまくできませんでした。そもそもbashなどといったシェルの知識を持ち合わせていないから、参考記事のコードをコピペしても何をしているのかさっぱり分かりません。
とりあえず致命的な警告ではないので、ここは飛ばします。
シェルスクリプトは、後々ドットインストール等で勉強します。
https://dotinstall.com/lessons/basic_shellscript_v2

二つ目の警告は、
②. 「Warning: Broken symlinks were found. Remove them with brew cleanup
(壊れたシンボリックリンクが見つかりました。 brew cleanupでそれらを削除してください。)
これは、メッセージ通りに、

MyMac:~ takawaki$ brew cleanup

を実行した後は、警告が出なくなった。

シンボリックリンク
OSのファイルシステムの機能の一つで、特定のファイルやディレクトリを指し示す別のファイルを作成し、それを通じて本体を参照できるようにする仕組み。UNIX系OSでよく用いられるもので、Windowsでも利用することができる。
ショートカットと同じじゃないの?と思ったが厳密には違うみたいです。→参考サイト

②nodebrewのインストール

nodebrewとは、node.jsを自分のマシン内でversion管理するためのツール。
プロジェクトごとにnode.jsのversionが違っていたりすると、不具合が起こるのでそれを解決してくれる。

参考記事

以下のコマンドを実行して、確認のためバージョン確認。

brew install nodebrew
nodebrew -v

⬇︎
実行結果

nodebrew 1.0.1

Usage:
    nodebrew help                         Show this message
    nodebrew install <version>            Download and install <version> (from binary)
    nodebrew compile <version>            Download and install <version> (from source)
    nodebrew install-binary <version>     Alias of `install` (For backword compatibility)
    nodebrew uninstall <version>          Uninstall <version>
    nodebrew use <version>                Use <version>
    nodebrew list                         List installed versions
    nodebrew ls                           Alias for `list`
    nodebrew ls-remote                    List remote versions
    nodebrew ls-all                       List remote and installed versions
    nodebrew alias <key> <value>          Set alias
    nodebrew unalias <key>                Remove alias
    nodebrew clean <version> | all        Remove source file
    nodebrew selfupdate                   Update nodebrew
    nodebrew migrate-package <version>    Install global NPM packages contained in <version> to current version
    nodebrew exec <version> -- <command>  Execute <command> using specified <version>

Example:
    # install
    nodebrew install v8.9.4

    # use a specific version number
    nodebrew use v8.9.4

③Node.jsのインストール

以下のコマンドを実行

nodebrew innstall-binary latest

そしたら
以下のエラーが返ってきた

MyMac:~ takawaki$ nodebrew install-binary latest
Fetching: https://nodejs.org/dist/v11.9.0/node-v11.9.0-darwin-x64.tar.gz
Warning: Failed to create the file                                             
Warning: /Users/takafumi/.nodebrew/src/v11.9.0/node-v11.9.0-darwin-x64.tar.gz: 
Warning: No such file or directory
                                                                            0.0%
curl: (23) Failed writing body (0 != 846)
download failed: https://nodejs.org/dist/v11.9.0/node-v11.9.0-darwin-x64.tar.gz

解決策を調べた。
Node.jsのインストールに失敗する時の解決策(No such file or directory)
No such file or directoryは、「こんなファイルを知りません」というメッセージ。
だから以下のコマンドで非表示ディレクトリを作成

mkdir -p ~/.nodebrew/src

mkidirはディレクトリを作成するコマンド
-pはparentオプションで、エラーを表示せず記述したディレクトリが存在しなければ作成することができる。
ディレクトリ名の最初に、「.」をつけるのは非表示ディレクトリにするため。
作成したら、nodebrew innstall-binary latestを再実行。

MyMac:~ takawaki$ nodebrew install-binary latest
Fetching: https://nodejs.org/dist/v11.9.0/node-v11.9.0-darwin-x64.tar.gz
######################################################################### 100.0%
Installed successfully

インストールが成功した!念のため確認。
「nodebrew list」は、インストールされているバージョンを確認できる。
以下のコマンドでは、version11.9.0を使っていることが分かる。
```
MyMac:~ takawaki$ nodebrew list
v11.9.0

current: v11.9.0
```

④実行パスを通す

以下のコマンドでnodeコマンドへパスをbashrcへ保存。

$ echo 'export PATH=$PATH:/Users/takafumi/.nodebrew/current/bin' >> ~/.bashrc

だが、インストール確認したら次のメッセージが出た

node -v
-bash: node: command not found

なので、参考記事通りに「.bash_profile」に以下を記述
参考記事①

そして、再度確認したら、node.jsが使えるようになった!

MyMac:~ takawaki$ node -v
v11.9.0

⑤npmを使えるか確認

npmは、Node.jsのパッケージ管理システム。

MyMac:~ takawaki$ npm -v
6.5.0

インストールされていることを確認。

今回、なんどもパスを通すためにファイルをいじったけど、そもそもパスを通すということはどういうことなのかも調べた。
→参考記事:PATHを通す方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

サブライムテキストでJavascriptの予測変換機能を追加してみた①

WEBサイト制作で使っているsublimeText(サブライムテキスト)で、Javascriptの予測変換機能を追加してみた。

これで、querySelectorといった長ったらしいメソッドも楽に入力できるに違いない。

でも、どうやらそのためには、「jshint」というプラグインがあるらしいが、前提としてNode.jsをインストールしておく必要があるとのこと。
Node.jsというのは、「サーバーサイドで動くJavascript」のこと。詳細を調べると、複雑すぎて(⁰▿⁰)だったので、とりあえずは左のような浅い理解で今はOKとさせてください。
参照サイト
後々、ドットインストール等で勉強します。
以下に、Node.jsインストール・jshint追加までの手順を記します。

参考にした記事
- https://qiita.com/akakuro43/items/600e7e4695588ab2958d)
- https://qiita.com/omega999/items/6f65217b81ad3fffe7e6
- JavaScriptのためのSublime Textプラグイン

①homebrewのインストール

homebrewというのは、Mac用のパッケージマネージャ。→公式サイト
パッケージマネージャーというのは、OSという一つの環境で各種のソフトウェアの管理、ソフトウェア同士やライブラリとの依存関係を管理するシステム。(Wikipediaから引用)
⬇︎
Macターミナルを起動して、以下のコマンドを実行。
※homebrew公式サイトの「インストール」項目を参照

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

インストールできたか確認するために、「brew doctor」と打ち込んだら、変な表示がされた。
「brew doctor」というのは、インストールされたhomebrewの問題をチェックしてくれるコマンド。

Please note that these warnings are just used to help the Homebrew maintainers
with debugging if you file an issue. If everything you use Homebrew for is
working fine: please don't worry or file an issue; just ignore this. Thanks!

Warning: "config" scripts exist outside your system or Homebrew directories.
`./configure` scripts often look for *-config scripts to determine if
software packages are installed, and what additional flags to use when
compiling and linking.

Having additional scripts in your path can confuse software installed via
Homebrew if the config script overrides a system or Homebrew provided
script of the same name. We found the following "config" scripts:
  /anaconda3/bin/icu-config
  /anaconda3/bin/freetype-config
  /anaconda3/bin/xslt-config
  /anaconda3/bin/libpng16-config
  /anaconda3/bin/python3.7-config
  /anaconda3/bin/libpng-config
  /anaconda3/bin/xml2-config
  /anaconda3/bin/python3.7m-config
  /anaconda3/bin/python3-config
  /anaconda3/bin/curl-config
  /anaconda3/bin/ncursesw6-config
  /anaconda3/bin/pcre-config

Warning: Broken symlinks were found. Remove them with `brew cleanup`:
  /usr/local/bin/mono-boehm
  /usr/local/bin/prj2make

Warningということは「警告」ってこと?
一つ目の警告から、調べてみた。
①. 「Warning: "config" scripts exist outside your system or Homebrew directories.」
(警告: "config"スクリプトはあなたのシステムまたは自作ディレクトリの外にあります。)
解決方法はここを参考にしたけどうまくできませんでした。そもそもbashなどといったシェルの知識を持ち合わせていないから、参考記事のコードをコピペしても何をしているのかさっぱり分かりません。
とりあえず致命的な警告ではないので、ここは飛ばします。
シェルスクリプトは、後々ドットインストール等で勉強します。
https://dotinstall.com/lessons/basic_shellscript_v2

二つ目の警告は、
②. 「Warning: Broken symlinks were found. Remove them with brew cleanup
(壊れたシンボリックリンクが見つかりました。 brew cleanupでそれらを削除してください。)
これは、メッセージ通りに、

MyMac:~ takawaki$ brew cleanup

を実行した後は、警告が出なくなった。

シンボリックリンク
OSのファイルシステムの機能の一つで、特定のファイルやディレクトリを指し示す別のファイルを作成し、それを通じて本体を参照できるようにする仕組み。UNIX系OSでよく用いられるもので、Windowsでも利用することができる。
ショートカットと同じじゃないの?と思ったが厳密には違うみたいです。→参考サイト

②nodebrewのインストール

nodebrewとは、node.jsを自分のマシン内でversion管理するためのツール。
プロジェクトごとにnode.jsのversionが違っていたりすると、不具合が起こるのでそれを解決してくれる。

参考記事

以下のコマンドを実行して、確認のためバージョン確認。

brew install nodebrew
nodebrew -v

⬇︎
実行結果

nodebrew 1.0.1

Usage:
    nodebrew help                         Show this message
    nodebrew install <version>            Download and install <version> (from binary)
    nodebrew compile <version>            Download and install <version> (from source)
    nodebrew install-binary <version>     Alias of `install` (For backword compatibility)
    nodebrew uninstall <version>          Uninstall <version>
    nodebrew use <version>                Use <version>
    nodebrew list                         List installed versions
    nodebrew ls                           Alias for `list`
    nodebrew ls-remote                    List remote versions
    nodebrew ls-all                       List remote and installed versions
    nodebrew alias <key> <value>          Set alias
    nodebrew unalias <key>                Remove alias
    nodebrew clean <version> | all        Remove source file
    nodebrew selfupdate                   Update nodebrew
    nodebrew migrate-package <version>    Install global NPM packages contained in <version> to current version
    nodebrew exec <version> -- <command>  Execute <command> using specified <version>

Example:
    # install
    nodebrew install v8.9.4

    # use a specific version number
    nodebrew use v8.9.4

③Node.jsのインストール

以下のコマンドを実行

nodebrew innstall-binary latest

そしたら
以下のエラーが返ってきた

MyMac:~ takawaki$ nodebrew install-binary latest
Fetching: https://nodejs.org/dist/v11.9.0/node-v11.9.0-darwin-x64.tar.gz
Warning: Failed to create the file                                             
Warning: /Users/takafumi/.nodebrew/src/v11.9.0/node-v11.9.0-darwin-x64.tar.gz: 
Warning: No such file or directory
                                                                            0.0%
curl: (23) Failed writing body (0 != 846)
download failed: https://nodejs.org/dist/v11.9.0/node-v11.9.0-darwin-x64.tar.gz

解決策を調べた。
Node.jsのインストールに失敗する時の解決策(No such file or directory)
No such file or directoryは、「こんなファイルを知りません」というメッセージ。
だから以下のコマンドで非表示ディレクトリを作成

mkdir -p ~/.nodebrew/src

mkidirはディレクトリを作成するコマンド
-pはparentオプションで、エラーを表示せず記述したディレクトリが存在しなければ作成することができる。
ディレクトリ名の最初に、「.」をつけるのは非表示ディレクトリにするため。
作成したら、nodebrew install-binary latestを再実行。

MyMac:~ takawaki$ nodebrew install-binary latest
Fetching: https://nodejs.org/dist/v11.9.0/node-v11.9.0-darwin-x64.tar.gz
######################################################################### 100.0%
Installed successfully

インストールが成功した!念のため確認。
「nodebrew list」は、インストールされているバージョンを確認できる。
以下のコマンドでは、version11.9.0を使っていることが分かる。

MyMac:~ takawaki$ nodebrew list
v11.9.0

current: v11.9.0

④実行パスを通す

以下のコマンドでnodeコマンドへパスをbashrcへ保存。

$ echo 'export PATH=$PATH:/Users/takafumi/.nodebrew/current/bin' >> ~/.bashrc

だが、インストール確認したら次のメッセージが出た

node -v
-bash: node: command not found

なので、参考記事通りに「.bash_profile」に以下を記述
参考記事①

そして、再度確認したら、node.jsが使えるようになった!

MyMac:~ takawaki$ node -v
v11.9.0

⑤npmを使えるか確認

npmは、Node.jsのパッケージ管理システム。

MyMac:~ takawaki$ npm -v
6.5.0

インストールされていることを確認。

今回、なんどもパスを通すためにファイルをいじったけど、そもそもパスを通すということはどういうことなのかも調べた。
→参考記事:PATHを通す方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ゼロからVue.jsを触ってみる 第5回

前回の続きでdirectives紹介をしていきます。

Directivesの続き

v-on

DOMのイベントをlistenして、イベント発火時にメソッドをトリガーできます。

<template>
  <p v-on:click="handleClick">Click me</p>
</template>

<script>
export default {
  methods: {
    handleClick: function() {
      alert('test')
    }
  }
}
</script>

この例の場合、Click meというところをクリックするとhandleClickというメソッドが発火してtestというアラートが表示されます。

このv-onにはショートハンドが用意されており、v-onと書く代わりに@で代用できます。

ショートハンドで書き直した例
<a @click="handleClick">Click me!</a>

(Qiitaのシンタックスハイライトには対応してないっぽいですね。)

click以外にもイベントはあり、submitなどもよく使います。

Event Modifiers

v-onのイベントに対してEvent Modifiersというものが用意されています。

例えば.preventというEvent Modifierを使うと自動的にpreventDefault()を呼んでくれます。

他にも.stop .prevent .capture .self .once .passiveなどがあります。

ここでは解説しません。詳細は公式ドキュメントを参考にしてください。

元のイベントオブジェクトにアクセスする

$eventという変数を使って元のイベントオブジェクトをメソッドに渡すことができます。

<template>
  <p @click="handleClick($event)">Click me</p>
</template>

<script>
export default {
  methods: {
    handleClick: function(event) {
      console.log(event);
    }
  }
}
</script>

上記の例ではClick meをクリックするとコンソールにMouseEventのオブジェクトが表示されます。

このMouseEventオブジェクトに関していえば例えばShiftキーを押しながらのクリックかどうか?みたいなことだったり、ほかにもいろんな値が入ってるのでそういったイベント関連の情報を引っ張ってきて処理をしたいときに便利です。

v-show

表示したり隠したりできます。値がTruthyなら表示、Falsyなら非表示(display: none)になります。

<template>
  <p v-show="isThree(3)">3です</p>
</template>

<script>
export default {
  methods: {
    isThree: function(num) {
      return num === 3;
    }
  }
};
</script>

上記の例ではisThree()の引数に3を与えるとTrueになって「3です」と表示されます。引数を3以外にするとFalseになって何も表示されません。


Directivesに関してはこれで大体一通り見たかな?と思うので今回はここまで。もっと細かいところは公式ドキュメントなどを参考にお願いします。

次回はそろそろDirectives以外を見ていきます。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js 基礎 [クリックイベント/データバインド]

Vue.jsが気になっていたので、猫本を買ってきました。
しばらく自分のOUTPUTに記事を書いてこうと思います。

クリックイベントとデータバインド

hoge.js
var app = new Vue({
    el: '#app',
    data: {
        show :true,
        message :'HelloWorld!!',
    },
    methods: {
        handleClick: function(event) {
            if( app.show == false)
            {
                app.show = true;
            }
            else
            {
                app.show = false;
            }
        }
    }
})
hoge.html
<div>
    <p v-if="show">{{message}}</p>
    <input v-if="show" v-model="message">
    <button v-on:click="handleClick">button</button>
</div>

理解

  • divタグのid"app"と、jsの"#app"が紐づく
  • dataはモデル、これがバインドされる
  • methodsはイベント、ここにクリックイベントとか書くらしい

ボタンを押すとラベルと入力フォームが消えたり表示されたりします。
入力フォームの値が、ラベルにバインドされているのでリアルタイムで連動します。

バインドがWPFのMVVMよりわかりやすくてとても良い。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js 基礎 【クリックイベント/データバインド】

Vue.jsが気になっていたので、猫本を買ってきました。
しばらく自分のOUTPUTに記事を書いてこうと思います。

クリックイベントとデータバインド

hoge.js
var app = new Vue({
    el: '#app',
    data: {
        show :true,
        message :'HelloWorld!!',
    },
    methods: {
        handleClick: function(event) {
            if( app.show == false)
            {
                app.show = true;
            }
            else
            {
                app.show = false;
            }
        }
    }
})
hoge.html
<div>
    <p v-if="show">{{message}}</p>
    <input v-if="show" v-model="message">
    <button v-on:click="handleClick">button</button>
</div>

理解

  • divタグのid"app"と、jsの"#app"が紐づく
  • dataはモデル、これがバインドされる
  • methodsはイベント、ここにクリックイベントとか書くらしい
  • {{}}のことをMustacheという

ボタンを押すとラベルと入力フォームが消えたり表示されたりします。
入力フォームの値が、ラベルにバインドされているのでリアルタイムで連動します。

バインドがWPFのMVVMよりわかりやすくてとても良い。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactとAudioの付き合い方

Image from Gyazo

このプレイヤーの実装方法がお題です。

はじめに :smiley:

この記事は、React コンポーネントで内部状態をもつ要素を扱うための How を書いたものです。

今回のネタでいうと、内部状態をもつ要素とは、audio 要素のことです。

ここでいう内部状態をもつ要素はどんなものかというと、

const audio = new Audio("sample.mp3") // コンストラクタでaudio要素を生成

audio.play() // 再生
audio.currentTime = 50 // 経過時間を50秒にする

的なやつです。

内部状態を持っている要素は React の setState で更新する対象にできないし、しないべきです。
(↑ このテーマは 大きくて書ききれないので割愛します)

そうなると出てくる問題として、audio 要素の状態を直接更新しても、React コンポーネントは再描画されない んですよね。

はい。今回の問題は上記の通りで、 目指すのは、

audio 要素の状態を更新すると React コンポーネントが再描画される です。

そのために今回、render prop という React コンポーネントの実装パターンを使います。

render prop について :tophat:

render prop について今回は詳しく説明しませんが、以前書いた記事で使用例などを交えて説明しています。

簡単に説明すると 「コンポーネントの render メソッドを外部から定義するためのテクニック」 です。

本テーマ: 内部状態をもつ要素を React で扱うための How :writing_hand:


まず render prop パターンを使って何がしたいかというと、

audio 要素(内部状態をもつ厄介なやつ)の状態管理と画面描画の責務を分離したいんです。
audio 要素の状態を知ってるヤツには見た目の知識を与えず、その逆もまた然りとする感じです。

なので、実装としては、大きく二つのコンポーネントを用意します。

  • AudioProvider: audio の内部状態を知ってるし、変更できるけど、audio 要素が画面でどう使われるかは知らないコンポーネント

  • AudioPlayer: Provider から audio の情報(経過時間とか)を受け取って、画面を表示するコンポーネント

AudioPlayer (audio 情報を受け取って見た目を作るコンポーネント)

まず、audio 要素の 情報を受け取って画面表示する側の実装をお見せします

今回は冒頭の GIF でお見せした通り、再生・停止と 30 秒ジャンプができるだけのシンプルなプレイヤーを作りました。

AudioPlayer.jsx
const AudioPlayer = () => (
  <AudioProvider
    url="path/to/audioUrl"
    render={({ currentTime, paused, play, pause, jump }) => (
      <div>
        <p>currenttime: {currentTime}</p>

        <button onClick={paused ? play : pause}>
          {paused ? "Play" : "Pause"}
        </button>

        <button onClick={() => jump(30)}>30sec ▶︎</button>
      </div>
    )}
  />
);

render={({ currentTime, paused, play, pause, jump }) => ... の部分ですが、

render という名前の props に 引数を受け取ってコンポーネントを返す関数 を渡しています。

実際、このコンポーネントはそんな重要ではないです。 値や関数を受け取って画面に表示してるだけなので。

ただよくみると、受け取っている引数が currentTime(音声の経過時間) とか play(再生するための関数) とかですが、これは AudioProvider から渡されます。

それでは本題の AudioProvider 側の実装を 通して、 引数どっからきてるの? を 紹介します

AudioProvider (audio の内部状態を扱うコンポーネント)

class AudioProvider extends React.Component {
  audio = new Audio(this.props.url); // audio要素の生成

  componentDidMount = () => {
    /**
     * audioの内部状態に変化があったときに再描画するための処理
     * ex.) this.playメソッドが実行されると、"play" を登録してるイベントリスナーが反応し、
     *      コンポーネントが強制的に再描画される
     */
    this.audio.addEventListener("play", this.forceUpdate);
    this.audio.addEventListener("pause", this.forceUpdate);
    this.audio.addEventListener("ended", this.forceUpdate);
    this.audio.addEventListener("timeupdate", this.forceUpdate);
  };

  componentWillUnmount = () => {
    this.audio.removeEventListener("play", this.forceUpdate);
    this.audio.removeEventListener("pause", this.forceUpdate);
    this.audio.removeEventListener("ended", this.forceUpdate);
    this.audio.removeEventListener("timeupdate", this.forceUpdate);
  };

  // --------状態変更用のコールバック関数--------
  play = () => this.audio.play();
  pause = () => this.audio.pause();
  jump = value => (this.audio.currentTime += value);

  render = () =>
    this.props.render({
      currentTime: this.audio.currentTime,
      paused: this.audio.paused,
      play: this.play,
      pause: this.pause,
      jump: this.jump
    });
}

AudioProvider が

  • audio 要素を保持し、
  • 変更をゴリゴリ行い、
  • 変更を検知して自分自身(とその配下の AudioPlayer コンポーネント)を再描画し、
  • 新しい状態を render prop に渡す

などなど、汚れ仕事をたくさんしています。

ここで大事なのは、 audio要素自体をrender propに渡さない ということです。

audio 要素の変更がAudioProviderのおかげでブラックボックスにできているのに、
audio要素自体を渡してしまうと、渡された側(AudioPlayer 側)でaudio要素の状態を変えたりできちゃいます。

それを防ぐために、
audio 要素を分解して currentTime や paused などを ただの値として render prop に渡しているワケです。

  render = () =>
    this.props.render({
      currentTime: this.audio.currentTime,
      paused: this.audio.paused,
      play: this.play,
      pause: this.pause,
      jump: this.jump
    });

さいごに :airplane:

audio 要素だけではなくて、 内部状態をもつ要素をReactコンポーネントで綺麗に扱いたい 場合はこのパターンでだいたい乗り切れる気がします。

ただ、解決策は今回の render prop パターンだけではないと思うので、みなさんのプラクティスも、ぜひ教えて下さい

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptでお手軽にランダム文字列の生成

お手軽さを重視しているため、作り方に偏りがある事が分かっている物もある。

以下の環境で確認

  • Node: v10.15.0
  • ブラウザ: Chrome 71.0.3578.98, Firefox 64.0.2

Edgeやモバイルの動作確認はしてない。

Node/ブラウザ共通

短いコードで

[a-v][0-9]の32文字で12桁以下なら、これでそれっぽいのが作れる。StackOverflowで知った

しかし、末端の0がくると省略されるため、任意の桁数が得られるとは限らない。以下のコードでも100万回ぐらい生成を繰り返すと6桁のものがでてきたりする。

Math.random().toString(32).substring(2) // 'a6dpgjqlq8g' 等

任意の桁数・任意の文字で

var S="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
var N=16
Array.from(Array(N)).map(()=>S[Math.floor(Math.random()*S.length)]).join('')

一番最初に考えたやり方がこれだった。

なお、Array(N)だけでは長さNの配列になっているが要素が無い状態のため、mapが機能しないらしい。
そのため、Array.from(Array(N))としてやることで、undefinedな要素が入りmapが機能するようになる。(ES6以降なら、Array.fromの代わりに[...Array(N)]が使える。以降はES6/ES5ごちゃまぜになっている。)

ブラウザ

よりセキュアな乱数生成器で

「Math.randomはセキュアではないのでは?」という難癖をつける場合、crypto.getRandomValuesがあるのでこちらを使う。結構古いブラウザもサポートしてる模様。IE11もmsCryptoから使える。

以下の方法は除算を使うので特定の文字に偏りが起きる。なのでセキュア志向なら使ったらダメ。(以下の例だと[a-h]が出やすい)

var S="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
var N=16
Array.from(crypto.getRandomValues(new Uint8Array(N))).map((n)=>S[n%S.length]).join('')

なお、Uint8ArrayからUint32Arrayにすると偏りは少しはマシになる。後述。

これよりも任意の文字を偏りなく出す方法を他のAPIで出来ないか考えたけど、すぐに思い浮かばなかったので諦めてます。

Base64で

文字を用意するのが面倒な場合、String.fromCharCodeで変換してからbtoaでBase64変換を使う手がある。

複雑な記号は使えないが、除算を使わない分こっちのほうが偏りも少ない気がする。(未調査)

var N=16
btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(N)))).substring(0,N) 

Nodeのみ

よりセキュアな乱数生成器で

Nodeにはブラウザとは異なるAPIを持つcryptoモジュールがある。

ブラウザのcrypto.getRandomValuesと同じ動作をするcrypto.randomFillSyncがあるのでこれで。

以下の方法も同じように除算を使ってるので偏ります。

const crypto = require('crypto')
const S="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
const N=16
Array.from(crypto.randomFillSync(new Uint8Array(N))).map((n)=>S[n%S.length]).join('')

Base64で

Base64も同じ要領で...と思ったが、btoaがないので、ブラウザのようには行えない。

代わりにNodeにはcrypto.randomBytesがある。これはN個のランダムなバイト列を作り、Node固有のBufferという型で返す。(crypto.randomFillSync(new Uint8Array(N))に似ているが、戻り値の型が違う。)

そして、このBufferがBase64変換に対応している。なので、もう少し短く書ける。

const crypto = retuire('crypto')
const N = 16
crypto.randomBytes(N).toString('base64').substring(0, N)

Nodeはこれが一番スマートな気がする。

おまけ:剰余計算による偏り

質の良い乱数生成期を使ったとしても、剰余を求めるやり方を行ってしまうと、偏りが起きる場合がある。

const S="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" // 62文字
let p=Array(S.length).fill(0)

// 100個づつ乱数を取り出し集計する処理を6200回繰り返す(620000回行う)

for(let i=0;i<100*S.length; ++i){
  const arr = crypto.randomFillSync(new Uint8Array(100))
  for(let j=0; j<arr.length; ++j){
    p[arr[j]%S.length]++;
  }
}
p // => 均等にばらければ全部が10000前後になるはずだが、あきらかに0-7番目の要素のカウントが大きいことが分かる。

これは、0~255(256個)がランダムで得られたとしても、剰余計算によって0~61(62個)のいずれかになる。
すると、0~61,62~123, 124~185,186~247の間は均等に出現するが、残りの248~255は0~7しか出現しえないため、この分が他と比べて多く出る。これで偏りが起きる。
逆に256を割り切れる数(2,4,8...64,128)で割れば計算による偏りは起きない。もし偏る場合は元の乱数の問題。

また、ランダムで得られる範囲0-255と狭いせいというのもあるので、この範囲を十分に大きくすると剰余計算による偏りは減らすことができる。
上記のコードなら、Uint8Array => Uint32Array にしてみると、偏りが目立たなくなることがわかる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VueLoaderPluginのせいでエラーが起きていると言われたら確認したいこと

概要

土曜日なので、vueはじめました。
はじめてのwebpackに戸惑いながらもSPAに挑戦しています。

しかし、さっそくのビルドエラーに遭遇してしまいました。
なにやらvue-loaderが悪さをしているようです。

ERROR in ./src/pages/About.vue
Module Error (from ./node_modules/vue-loader/lib/index.js):
vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config.

今回はわたしが解決した方法をご紹介します。

原因

https://vue-loader.vuejs.org/migrating.html
どうやらvue-loaderには最近著しい変更があり、v15からプラグインとして読み込んであげてねってことになったようです。

ちなみに、自分の環境だとバージョンは"vue-loader": "^15.6.2",
まさにこれでした。

解決方法

https://vue-loader.vuejs.org/migrating.html
以下のようにconstで定義して、module.exportsのなかのpluginsにぽこっとnewしてあげればいいようです。

// webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
  // ...
  plugins: [
    new VueLoaderPlugin()
  ]
}

私はこれで無事に解決できました。
もし、同じエラーが出た場合はお試しくださいね!

おまけ

次はcss-loaderを解決しなくては..汗
今までgulp一本な人生だったので、webpackは不思議な感覚です。(かといって、gulpをそんなに高い頻度で使っているわけでもないw)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

loadしたhtmlにjsを適用させる

loadしたhtmlにjsを適用させる

前提:フォルダ階層

階層
┣ static
┃ ┣ css
┃ ┃ ┗ menu.css
┃ ┣ js
┃ ┃ ┣common.js
┃ ┃ ┗menu.js
┃ ┣ html
┃ ┃ ┗ menu.html
┗ templates
  ┗ main.html

やりたかったこと

機能

現在いるページと対応するメニューの色を変更させたい。

コード

htmlで複数ページを作る時に、メニューのところのhtmlが長いのでmenu.htmlとして外部ファイルを作った。
それをcommon.jsで読み込み、main.htmlにmenu.htmlを適用させた。
更に今いるページと対応するメニューの色を変更するために
特定のタグをactiveにする処理を書いたmenu.jsをmenu.htmlに適応させたい。

main.html
<body>
    <div id="menu"></div>

    <!--jQueryの読み込みとか-->
    <script type="text/javascript"
        src="/webjars/jquery/3.3.1/jquery.min.js" charset="utf-8">  
    </script>
    <!--javaScriptの読み込み-->
    <script type="text/javascript" src="/js/common.js"></script>
    <script type="text/javascript" src="/js/menu.js"></script>
</body>
menu.html
<ul id="nav"> 
   <li><a href="/menu1">MENU1</a></li> 
   <li><a href="/menu2">MENU2</a></li> 
   <li><a href="/menu3">MENU3</a></li> 
   <li><a href="/menu4">MENU4</a></li> 
 </ul> 
common.js
$(window).on('load', function() {
      //main.htmlのmenu要素についてmenu.htmlを適用させる
    $("#menu").load('/html/menu.html');
});
menu.js
//現在のページのurlとメニューのurlが同じだったら、そこのタグをactiveにする
$(function() {
    $('#nav li a').each(function() {
        var $href = $(this).attr('href');
        if (location.href.match($href)) {
            $(this).addClass('active');
        } else {
            $(this).removeClass('active');
        }
    });
});
menu.css
/*activeになってる要素navの[li a]タグの文字を赤にする*/
#nav li a.active {
    color: red;
}

問題1

menu.jsはちゃんと読み込まれているっぽいけど、activeにならない。
コンソール上のエラーは発生してないが、なんかうまく適用されていない気がする

実際にやってみたこと

menu.html上で直接jsを呼んでみる

menu.html
<!--変更したところ↓-->
<script type="text/javascript" src="/js/menu.js"></script>
<!--変更したところ↑-->
<ul id="nav"> 
   <li><a href="/menu1">MENU1</a></li> 
   <li><a href="/menu2">MENU2</a></li> 
   <li><a href="/menu3">MENU3</a></li> 
   <li><a href="/menu4">MENU4</a></li> 
 </ul> 

問題2

とりあえず、jsは読み込まれて適用された。が、
コンソールに以下のエラー

error
[[Deprecation] Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org/.

よくわらかないけどエラーが出るってことはダメな気がする。気持ち悪いし
あと、ググったらエンドユーザーに影響がどうのって書いてたらかダメなんだと思う

解決方法

menu.htmlをロードする時に一緒にjsもロードしてあげる!

common.js
$(window).on('load', function() {
      //main.htmlのmenu要素についてmenu.htmlを適用させる
    $("#menu").load('/html/menu.html');
    //変更したところ↓
    $.getScript("/js/menu.js", function() {
    });
    //変更したところ↑
});

これでエラーなくちゃんとactiveにすることができた

参考にしたサイト

http://mugen00.moo.jp/web/jquery/getscript-2

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JS】setTimeoutを用いた、非同期処理入門

概要

setTimeoutを用いて、非同期処理の処理順をまとめました。
コールバック -> Promise -> async/await の順に説明する。

setTimeout

基本構文

setTimeout('コールバック関数', 'タイムアウト時間')

1秒後にhogeを出力する。

function callback(){
    console.log('hoge')
}

setTimeout(callback, 1000)
出力結果
hoge

alt

無名関数を用いて書く。

setTimeout(function(){ console.log('hoge') }, 1000)

アロー演算子を用いて書く。

setTimeout(() => { console.log('hoge') }, 1000)

アロー演算子を用いて書く。(中括弧を省略)

setTimeout(() => console.log('hoge'), 1000)

処理順

非同期処理の処理順を確認する。

console.log(1)
setTimeout(() => console.log(2), 1000)
console.log(3)
出力結果
1
3
2

alt

timeout時間 0秒 でも、処理順は変わらない。

console.log(1)
setTimeout(() => console.log(2), 0)
console.log(3)
出力結果
1
3
2

alt

コールバック地獄

非同期処理が複数重なると、コールバック地獄に陥る。

setTimeout(() => {
    console.log(1)
    setTimeout(() => {
        console.log(2)
        setTimeout(() => {
            console.log(3)
        }, 1000)
    }, 1000)
}, 1000)
出力結果
1
2
3

alt

コールバック地獄の回避方法

Promise化すると、thenを用いたメソッドチェーンで処理を繋げられる。

new Promise(function(resolve) {
    resolve('Hello')
}).then(function(value) {
    console.log(value)    // Hello
    return 'World'
}).then(function(value) {
    console.log(value)    // World
})
出力結果
Hello
World

resolve、returnの値が、コールバック関数の引数になる。
アロー演算子を用いて書き換えてみる。

new Promise(resolve => resolve('Hello'))
.then(value => {
    console.log(value)    // Hello
    return 'World'
}).then(value => {
    console.log(value)    // World
})
出力結果
Hello
World

コールバック地獄を、Promiseで書き直してみる。

function sleep(ms) {
    return new Promise(function(resolve) { 
        setTimeout(function() { 
            resolve() 
        }, ms)
    })
} 

sleep(1000)
.then(function() {
    console.log(1)
    return sleep(1000)
}).then(function() {
    console.log(2)
    return sleep(1000)
}).then(function() {
    console.log(3)
})
出力結果
1
2
3

Promiseとアロー演算子を用いて書き換えてみる。

const sleep = ms => new Promise(resolve => setTimeout(() => resolve(), ms))

sleep(1000)
.then(() => {
    console.log(1)
    return sleep(1000)
}).then(() => {
    console.log(2)
    return sleep(1000)
}).then(() => {
    console.log(3)
})
出力結果
1
2
3

alt

async関数内で、Promise関数にawaitを用いると、同期的に処理できる。

function sleep(ms) {
    return new Promise(function(resolve) {
        setTimeout(resolve, ms)
    })
}

async function asyncFunc() {
    await sleep(1000)
    console.log(1)
    await sleep(1000)
    console.log(2)
    await sleep(1000)
    console.log(3)
}

asyncFunc()
出力結果
1
2
3

async/await とアロー演算子を用いた場合

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

const asyncFunc = async () => {
    await sleep(1000)
    console.log(1)
    await sleep(1000)
    console.log(2)
    await sleep(1000)
    console.log(3)
}

asyncFunc()
出力結果
1
2
3

更に、即時関数を用いて書くと、簡潔に書ける。

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

!(async () => {
    await sleep(1000)
    console.log(1)
    await sleep(1000)
    console.log(2)
    await sleep(1000)
    console.log(3)
})()
出力結果
1
2
3

alt

まとめ

非同期処理の処理順をまとめました。
async/await を用いることで、とても簡潔に書けます。
間違い・指摘等があればコメントお願いします。

参考文献

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

setTimeoutを用いた、非同期処理入門

概要

setTimeoutを用いて、非同期処理の処理順をまとめました。
コールバック -> Promise -> async/await の順に説明する。

setTimeout

基本構文

setTimeout(コールバック関数, タイムアウト時間)

1秒後にhogeを出力する

function callback(){
    console.log('hoge')
}

setTimeout(callback, 1000)

alt

無名関数を用いて書く

setTimeout(function(){ console.log('hoge') }, 1000)

アロー演算子を用いて書く

setTimeout(() => { console.log('hoge') }, 1000)

アロー演算子を用いて書く(中括弧を省略)

setTimeout(() => console.log('hoge'), 1000)

処理順

非同期処理の処理順を確認する

console.log(1)
setTimeout(() => console.log(2), 1000)
console.log(3)

// 1 -> 3 -> 2

alt

timeout時間 0秒 でも、処理順は変わらない

console.log(1)
setTimeout(() => console.log(2), 0)
console.log(3)

// 1 -> 3 -> 2

alt

同期的に処理したい場合

Promise化すると、thenを用いたメソッドチェーンで処理を繋げられる

const promiseTimeout = new Promise(function(resolve) {
    setTimeout(function() {
        console.log(2)
        resolve()
    }, 1000)
})

console.log(1)
promiseTimeout.then(function() { console.log(3) })

// 1 -> 2 -> 3

Promiseとアロー演算子を用いた場合

const promiseTimeout = new Promise(resolve =>
    setTimeout(() => {
        console.log(2)
        resolve()
    }, 1000)
)

console.log(1)
promiseTimeout.then(() => console.log(3))

// 1 -> 2 -> 3

alt

async関数内で、Promise関数にawaitを用いると、同期的に処理できる

function promiseTimeout() {
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log(2)
            resolve()
        }, 1000)
    }) 
}

async function asyncFunc() {
    console.log(1)
    await promiseTimeout()
    console.log(3)
}

asyncFunc()

async/await とアロー演算子を用いた場合

const promiseTimeout = () => 
    new Promise(resolve => 
        setTimeout(() => {
            console.log(2)
            resolve()
        }, 1000)
    )

const asyncFunc = async () => {
    console.log(1)
    await promiseTimeout()
    console.log(3)
}

asyncFunc()

alt

まとめ

非同期処理の処理順をまとめました。
async/await を用いることで、とても簡潔に書けます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

setTimeoutを用いた非同期処理入門

概要

setTimeoutを用いて、非同期処理の処理順をまとめました。
コールバック -> Promise -> async/await の順に説明する。

setTimeout

基本構文

setTimeout(コールバック関数, タイムアウト時間)

1秒後にhogeを出力する

function callback(){
    console.log('hoge')
}

setTimeout(callback, 1000)

alt

無名関数を用いて書く

setTimeout(function(){ console.log('hoge') }, 1000)

アロー演算子を用いて書く

setTimeout(() => { console.log('hoge') }, 1000)

アロー演算子を用いて書く(中括弧を省略)

setTimeout(() => console.log('hoge'), 1000)

処理順

非同期処理の処理順を確認する

console.log(1)
setTimeout(() => console.log(2), 1000)
console.log(3)

// 1 -> 3 -> 2

alt

timeout時間 0秒 でも、処理順は変わらない

console.log(1)
setTimeout(() => console.log(2), 0)
console.log(3)

// 1 -> 3 -> 2

alt

コールバック地獄

非同期処理が複数重なると、コールバック地獄に陥る

setTimeout(() => {
    console.log(1)
    setTimeout(() => {
        console.log(2)
        setTimeout(() => {
            console.log(3)
        }, 1000)
    }, 1000)
}, 1000)

// 1 -> 2 -> 3

alt

コールバック地獄の回避方法

Promise化すると、thenを用いたメソッドチェーンで処理を繋げられる

const promiseTimeout = new Promise(function(resolve) {
    setTimeout(function() {
        console.log(2)
        resolve()
    }, 1000)
})

console.log(1)
promiseTimeout.then(function() { console.log(3) })

// 1 -> 2 -> 3

Promiseとアロー演算子を用いた場合

const promiseTimeout = new Promise(resolve =>
    setTimeout(() => {
        console.log(2)
        resolve()
    }, 1000)
)

console.log(1)
promiseTimeout.then(() => console.log(3))

// 1 -> 2 -> 3

alt

async関数内で、Promise関数にawaitを用いると、同期的に処理できる

function promiseTimeout() {
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log(2)
            resolve()
        }, 1000)
    }) 
}

async function asyncFunc() {
    console.log(1)
    await promiseTimeout()
    console.log(3)
}

asyncFunc()

// 1 -> 2 -> 3

async/await とアロー演算子を用いた場合

const promiseTimeout = () => 
    new Promise(resolve => 
        setTimeout(() => {
            console.log(2)
            resolve()
        }, 1000)
    )

const asyncFunc = async () => {
    console.log(1)
    await promiseTimeout()
    console.log(3)
}

asyncFunc()

// 1 -> 2 -> 3

alt

まとめ

非同期処理の処理順をまとめました。
async/await を用いることで、とても簡潔に書けます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】setTimeoutを用いた、非同期処理入門

概要

setTimeoutを用いて、非同期処理の処理順をまとめました。
コールバック -> Promise -> async/await の順に説明する。

setTimeout

基本構文

setTimeout('コールバック関数', 'タイムアウト時間')

1秒後にhogeを出力する。

function callback(){
    console.log('hoge')
}

setTimeout(callback, 1000)

alt

無名関数を用いて書く。

setTimeout(function(){ console.log('hoge') }, 1000)

アロー演算子を用いて書く。

setTimeout(() => { console.log('hoge') }, 1000)

アロー演算子を用いて書く。(中括弧を省略)

setTimeout(() => console.log('hoge'), 1000)

処理順

非同期処理の処理順を確認する。

console.log(1)
setTimeout(() => console.log(2), 1000)
console.log(3)

// 1 -> 3 -> 2

alt

timeout時間 0秒 でも、処理順は変わらない。

console.log(1)
setTimeout(() => console.log(2), 0)
console.log(3)

// 1 -> 3 -> 2

alt

コールバック地獄

非同期処理が複数重なると、コールバック地獄に陥る。

setTimeout(() => {
    console.log(1)
    setTimeout(() => {
        console.log(2)
        setTimeout(() => {
            console.log(3)
        }, 1000)
    }, 1000)
}, 1000)

// 1 -> 2 -> 3

alt

コールバック地獄の回避方法

Promise化すると、thenを用いたメソッドチェーンで処理を繋げられる。

new Promise(function(resolve) {
    resolve('Hello')
}).then(function(value) {
    console.log(value)    // Hello
    return 'World'
}).then(function(value) {
    console.log(value)    // World
})

// Hello
// World

resolve、returnの値が、コールバック関数の引数になる。
アロー演算子を用いて書き換えてみる。

new Promise(resolve => resolve('Hello'))
.then(value => {
    console.log(value)    // Hello
    return 'World'
}).then(value => console.log(value))    // World

// Hello
// World

コールバック地獄を、Promiseで書き直してみる。

function sleep(ms) {
    return new Promise(function(resolve) { 
        setTimeout(function() { 
            resolve() 
        }, ms)
    })
} 

sleep(1000)
.then(function() {
    console.log(1)
    return sleep(1000)
}).then(function() {
    console.log(2)
    return sleep(1000)
}).then(function() {
    console.log(3)
})

// 1 -> 2 -> 3

Promiseとアロー演算子を用いて書き換えてみる。

const sleep = ms => new Promise(resolve => setTimeout(() => resolve(), ms))

sleep(1000)
.then(() => {
    console.log(1)
    return sleep(1000)
}).then(() => {
    console.log(2)
    return sleep(1000)
}).then(() => {
    console.log(3)
})

// 1 -> 2 -> 3

alt

async関数内で、Promise関数にawaitを用いると、同期的に処理できる。

function sleep(ms) {
    return new Promise(function(resolve) {
        setTimeout(resolve, ms)
    })
}

async function asyncFunc() {
    await sleep(1000)
    console.log(1)
    await sleep(1000)
    console.log(2)
    await sleep(1000)
    console.log(3)
}

asyncFunc()

// 1 -> 2 -> 3

async/await とアロー演算子を用いた場合

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

const asyncFunc = async () => {
    await sleep(1000)
    console.log(1)
    await sleep(1000)
    console.log(2)
    await sleep(1000)
    console.log(3)
}

asyncFunc()

// 1 -> 2 -> 3

更に、即時関数を用いて書くと、簡潔に書ける。

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

!(async () => {
    await sleep(1000)
    console.log(1)
    await sleep(1000)
    console.log(2)
    await sleep(1000)
    console.log(3)
})()

// 1 -> 2 -> 3

alt

まとめ

非同期処理の処理順をまとめました。
async/await を用いることで、とても簡潔に書けます。
間違い・指摘等があればコメントお願いします。

参考文献

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

アクセス解析タグ実装時、HTMLソース内からパラメータ情報を取得する(CSSセレクタがダメだった場合)

お困りごと

アクセス解析のタグを実装する際、解析エンジニア泣かせのこんな経験はないですか?

ページ遷移するとURLから必要なパラメータが欠落してしまい、GAの目標到達プロセスが設定できない:relaxed::relaxed:

本来このような場合は、お客さん・ディレクター・フロントエンジニアと調整を図り、ページのソースを変更することが理想です。
ただ、大抵の場合はサイトローンチ後でもうニッチもサッチも変更できないのがよくあるパターンと思います。(特にCMSからページを生成している場合)

そういうときは、解析エンジニアが唯一イジイジできるアクセス解析ツールたちを駆使して状況を打破しましょう!

注:CSSセレクタでパラメータを取得する方法の方が簡単ですが、それすらも困難なケースに当記事をお役立てください

当記事で解決可能な環境(条件面)

  1. 取得したいパラメータがHTMLソース内にsetされていること
  2. Google Tag Manager(GTM)タグが実装されており、公開権限、少なくとも編集権限が付与されていること
  3. Google Analytics(GA)タグがGTM内に実装されており、GAの編集権限が付与されていること

2と3の権限について、自身でタグ実装時の切り分け不可なロールであった場合、パワポとかに仕様を落として編集権限のあるレイヤーの人にやってもらいましょう。

必要な道具たち

XPathとは

XML Path Language (XPath(エックスパス)) はXML文書中から必要な要素群(サブセット)を取り出す、などといった用途に使うもの。- Wikipedia

要は、HTMLのソース構造から必要な情報を抜き出す言語です。厳密には違いますが、HTML版SQLのようなもの。

実装までの大きな流れ

  1. 取得したいパラメータ情報がHTMLソース内に記載されていることを確認する
  2. 1をXPathで表現(=記述)する
  3. GTM変数画面のカスタムJavaScriptに記述する
  4. カスタム変数としてGTMタグに設定する

実装方法

1. 取得したいパラメータ情報がHTMLソース内に記載されていることを確認する

Chrome利用想定ですが、ページにパラメータ情報が表示されている/表示されていないによって方法が異なります。

  • 表示されている:表示されているパラメータを右クリック→「検証」
  • 表示されていない:F12キー押下→「Elements」画面から探す

2. 1をXPathで表現(=記述)する

上記1のパラメータ情報の状態によって、以下2つのXPath記述方法があります。
1つ目は簡単だけど記述的に汚くなりがち、2つ目はきれい&応用がきくけどやや難易度高めです。

  1. Chromeディベロッパーツールから
    右クリック→「Copy」→「Copy XPath」
    ※かゆいとこに手が届かないのであまりおすすめしません

  2. XPathのお作法を調べて記述する
    上述した「必要な道具たち」のチートシートをもとに、XPathを自作します。
    (拡張機能を使うと検証が捗ります。)

3. GTM変数画面のカスタムJavaScriptに記述する

GTMのカスタムJavaScript画面で、GAへ渡す変数を作成します。
自分はmozillaのリファレンスを参考にしました。

ページURLの整形.js
function(){
  // ドメイン名を取得
  var getDomain = document.location.origin;
  // XPathでHTML内からパラメータ情報を取得
  var getXpath = document.evaluate([ここに上記2で調べたXPathを記述], document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
  // URLにパラメータを付加
  var concatURL = location.href.replace(getDomain + '/', '');
  var concatURL = concatURL + getXpath.singleNodeValue.nodeValue;

  return concatURL;
}

注:GTMへ実装する前に、ChromeディベロッパーツールのConsole画面で、コードを実行&期待値が出力されることを検証してください

4. カスタム変数としてGTMタグに設定する

3の変数をGAへURLとして送信すべく、以下のようにGTMタグを設定します。
GTMタグ.png

終わりに

GTMはいじり倒す領域もままあるためエンジニアリング的にも面白いですし、色々なデータが取得できるので分析観点からも有用と思います。
ただ、冒頭でも書いたように、ページを作るときに解析サイドの観点をもう少し反映してくれればこのようなちょっとムリ目なデータ取得はしなくてもよくなるわけで、前工程の現場の方々ともっとコミュニケーションが取れるようになれればいいなと願っています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptでの「配列のようなオブジェクト」とは

JavaScriptには「配列のようなオブジェクト」があるらしいので、そのメモ。

配列のようなオブジェクトとは

ArrayLikeObject や FakeArrayObject と呼ぶらしい。
Array.prototypeを継承していないため、
・Array.prototype.forEach()
・Array.prototype.map()
とかが使えない。

lengthプロパティを持ち、数字の添字でアクセスすることができる。

ArrayLike Objectをシンプルに表現すると

const arrayLikeObject = { 0: "a", 1: "b", 2: "c", 3: "d", length: 4 };

のようになる。

配列のようなオブジェクトに当てはまるのは

document.querySelectorAllが返すNodeList
document.getElementByTagNameが返すHTMLCollection
・関数呼び出し時に渡された引数のリストarguments
など、他にもあるのかな、、

※ NodeListとHTMLCollecitonの違いについて、以下を参照
「NodeList」と「HTMLCollection」の違いには気をつけよう

配列のようなオブジェクトを配列として使いたい時

Array.prototype.slice.callで配列に変換可能。
ただIEの古いバージョンでは、エラーになるみたい。その場合は、Array.apply(null, ...)を使うと変換可能。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む