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

Javascript 初めてのGSAPアニメーションの使い方 その2

前回の記事はこちら
Javascript 初めてのGSAPアニメーションの使い方 その1

Tween.from

Tween.fromでは指定したプロパティを起点に要素をアニメーションすることができます。

htmlとcssをを以下に追記します。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="styles/style.min.css" />
    <title>Document</title>
  </head>
  <body>
      <div class="circle"></div>
      <div class="square"></div>
      <div id="rectangle"></div> //追加

    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.6/gsap.min.js"></script>
    <script src="scripts/main.js"></script>
  </body>
</html>

body{
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
}

.circle{
  background-color: orangered;
  height: 150px;
  width: 150px;
  border-radius: 50%;
}

.square{
  width: 150px;
  height: 150px;
  background-color: green;
}

#rectangle{ //追記
  width: 150px;
  height: 50px;
  background-color: skyblue;
}

jsシート配下のように追記します。

TweenMax.to($('.circle'), 1, { x:150, y:150, backgroundColor: 'blue' });
TweenMax.to($('.square'), 3, { x: -150, y:-150, scale: 2, delay: 1, ease:Back.easeOut });

TweenMax.from($('#rectangle'), 2, { y: 200, rotation: 180 , scale:1.5 }); //追記

実行結果

Image from Gyazo

水色の長方形は起点として

y軸方向200pxから、180度回転しながら、1.5倍の大きさから

アニメーションしてきます。

Jqueryを合わせて使う

Jqueryのセレクター指定と合わせて使うこともできます。

Jquery
Jquery selectors

htmlに以下を追記します(CSSは変更なしです)
JqueryもCDNで読み込んで使います。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="styles/style.min.css" />
    <title>Document</title>
  </head>
  <body>
      <div class="circle"></div>
      <div class="square"></div>
      <div id="rectangle"></div>

      <ul>  //追記
        <li>Bacon</li>
        <li>Cheese</li>
        <li>Jam</li>
        <li>Bread</li>
        <li>Eggs</li>
      </ul>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> //追記
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.6/gsap.min.js"></script>
    <script src="scripts/main.js"></script>
  </body>
</html>

要素の取得をJqueryの$()に書き換えてみます。

TweenMax.to($('.circle'), 1, { x:150, y:150, backgroundColor: 'blue' });
TweenMax.to($('.square'), 3, { x: -150, y:-150, scale: 2, delay: 1, ease:Back.easeOut });

TweenMax.from($('#rectangle'), 2, { y: 200, rotation: 180 , scale:1.5 });

TweenMax.to($('li'),1,{ x:50 });

実行結果
Image from Gyazo

jqueryの記述によりli全体を取得してアニメーションすることができています。

以下のような記述も動作します。

TweenMax.to($('li:first-child'),1,{ x:50 }); //liの最初の要素を動かす(Bacon)
TweenMax.to($('li:last-child'),1,{ x:50 }); //liの最後の要素を動かす(Eggs)
TweenMax.to($('li:nth-child(3)'),1,{ x:50 }); //liの3番めの要素を動かす(Jam)
TweenMax.to($('li:nth-child(odd)'),1,{ x:50 }); //liの奇数の要素を動かす(Cheese,Bread)
TweenMax.to($('li:nth-child(even)'),1,{ x:50 }); //liの偶数の要素を動かす(Bacon,Jam,Eggs)

次回はTimelinemaxです
Javascript 初めてのGSAPアニメーションの使い方 その3

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

Javascript 初めてのGSAPアニメーションの使い方 その2 Tween.from/Jqueryとの併用

前回の記事はこちら
Javascript 初めてのGSAPアニメーションの使い方 その1 Tweenmax.toの使い方

Tween.from

Tween.fromでは指定したプロパティを起点に要素をアニメーションすることができます。

htmlとcssをを以下に追記します。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="styles/style.min.css" />
    <title>Document</title>
  </head>
  <body>
      <div class="circle"></div>
      <div class="square"></div>
      <div id="rectangle"></div> //追加

    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.6/gsap.min.js"></script>
    <script src="scripts/main.js"></script>
  </body>
</html>

body{
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
}

.circle{
  background-color: orangered;
  height: 150px;
  width: 150px;
  border-radius: 50%;
}

.square{
  width: 150px;
  height: 150px;
  background-color: green;
}

#rectangle{ //追記
  width: 150px;
  height: 50px;
  background-color: skyblue;
}

jsシート配下のように追記します。

TweenMax.to($('.circle'), 1, { x:150, y:150, backgroundColor: 'blue' });
TweenMax.to($('.square'), 3, { x: -150, y:-150, scale: 2, delay: 1, ease:Back.easeOut });

TweenMax.from($('#rectangle'), 2, { y: 200, rotation: 180 , scale:1.5 }); //追記

実行結果

Image from Gyazo

水色の長方形は起点として

y軸方向200pxから、180度回転しながら、1.5倍の大きさから

アニメーションしてきます。

Jqueryを合わせて使う

Jqueryのセレクター指定と合わせて使うこともできます。

Jquery
Jquery selectors

htmlに以下を追記します(CSSは変更なしです)
JqueryもCDNで読み込んで使います。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="styles/style.min.css" />
    <title>Document</title>
  </head>
  <body>
      <div class="circle"></div>
      <div class="square"></div>
      <div id="rectangle"></div>

      <ul>  //追記
        <li>Bacon</li>
        <li>Cheese</li>
        <li>Jam</li>
        <li>Bread</li>
        <li>Eggs</li>
      </ul>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> //追記
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.6/gsap.min.js"></script>
    <script src="scripts/main.js"></script>
  </body>
</html>

要素の取得をJqueryの$()に書き換えてみます。

TweenMax.to($('.circle'), 1, { x:150, y:150, backgroundColor: 'blue' });
TweenMax.to($('.square'), 3, { x: -150, y:-150, scale: 2, delay: 1, ease:Back.easeOut });

TweenMax.from($('#rectangle'), 2, { y: 200, rotation: 180 , scale:1.5 });

TweenMax.to($('li'),1,{ x:50 });

実行結果
Image from Gyazo

jqueryの記述によりli全体を取得してアニメーションすることができています。

以下のような記述も動作します。

TweenMax.to($('li:first-child'),1,{ x:50 }); //liの最初の要素を動かす(Bacon)
TweenMax.to($('li:last-child'),1,{ x:50 }); //liの最後の要素を動かす(Eggs)
TweenMax.to($('li:nth-child(3)'),1,{ x:50 }); //liの3番めの要素を動かす(Jam)
TweenMax.to($('li:nth-child(odd)'),1,{ x:50 }); //liの奇数の要素を動かす(Cheese,Bread)
TweenMax.to($('li:nth-child(even)'),1,{ x:50 }); //liの偶数の要素を動かす(Bacon,Jam,Eggs)

次回はTimelinemaxです
Javascript 初めてのGSAPアニメーションの使い方 その3 Timelinemax

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

iTunesAPIを使ってiTunesメディアからリンクを見つけ出す

大まかな処理内容

ショートカット側

1.テキストもしくはURLが渡される
→StoreIDが含んでいるかチェック、含んでいたらScriptableに渡す

{
   "how2": "lookup",
   "id": "< StoreID >"
}

2.iTunesメディアが渡される
→タイトル名、アルバム名、アーティスト名、メディアタイプを取得してScriptableに渡す

{
   "how2": "search",
   "title": "< タイトル >",
   "album": "< アルバム >",
   "artist": "< アーティスト >",
   "mediatype": "< メディアタイプ >"
}

3.何も渡されない
→エラー通知

Scriptable側

how2 == lookup

https://itunes.apple.com/lookup?id=<StoreID>&country=jp&lang=ja_jp
にGETリクエスト。
json貰ったらスクリプトを終了して出力。終わり!

how2 == search

https://iTunes.apple.com/search?term=<タイトル> <アルバム> <アーティスト>&media=<メディアタイプ>&country=jp&lang=ja_jp
にGETリクエスト。
jsonを貰ったら、findメゾットでtrackName,collectoonName,artistName<タイトル>,<アルバム>,<アーティスト>と一致する要素を見つける。
見つかった要素を(さぞlookupしたからのように)フォーマットして、スクリプトを終了して出力。おしまい!

ショートカット

6E17D475-EC1C-45E3-B41C-E391136437F2.jpeg

スクリプト

Musica.js
const INPUT = JSON.parse(args.plainTexts[0]);
const HOWTO= INPUT.how2;
const LIMIT = String(30);
const COUNTRY = 'jp';
const LANG = 'ja_jp';
const BASEURL = 'https://itunes.apple.com/' + HOWTO;

if (HOWTO == 'lookup') {
  let id = INPUT.id;
  let url = BASEURL +
           '?id=' + id +
           '&country=' + COUNTRY +
           '&lang=' + LANG;
  Pasteboard.copy(url) // debug
  let req = new Request(url);
  let response = await req.loadJSON();
  Script.setShortcutOutput(response);
  Script.complete();

} else if (HOWTO == 'search') {
  let title = INPUT.title;
  let album = INPUT.album;
  let artist = INPUT.artist;
  let mediatype = INPUT.mediatype;
  let term = [
               title,
               album,
               artist
             ].join(' ')
  let query = '?term=' +
              encodeURIComponent(term) +
              '&media=' + mediatype +
              '&country=' + COUNTRY +
              '&lang=' + LANG +
              '&limit=' + LIMIT;
  let url = BASEURL + query;
  Pasteboard.copy(url) // debug
  let req = new Request(url);
  let response = await req.loadJSON();
  let rslts = response.results
  let found = rslts.find(function(rslts) {
    return rslts.artistName == artist && rslts.trackCensoredName
  });

  if (found != null) {
    let formatted = {
                      "resultCount": 1,
                      "results": [
                        found
                      ]
                    };
// await QuickLook.present(found)
    Script.setShortcutOutput(formatted);
    Script.complete();

  } else {
    Script.setShortcutOutput('formatted is null')
    Script.complete();
  };

} else {
  console.error('HOWTO isn’t lookup or search');
  Script.complete();

};

半年間ずっとこいつと格闘した証です。とりあえず足跡が残せてよかった…

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

stateを直接参照しないシンプルなVuexサイクル

Vuexとは?

Vue.jsアプリケーションにおける状態管理パターンライブラリです。

コンポーネント間のデータや関数の受け渡しには、propsやemitを使いますが、Vuexは全てのコンポーネントのための集中型のStoreになります。
propsやemitだけでデータの受け渡しをするのは、バケツリレーに似ていて、vueファイルに何度も同じコードを書く必要があったり、無駄にロジックが発生します。

Vueコンポーネント層とVuexはどう繋がるのか?

今回の記事のタイトルは「stateを直接参照しないシンプルなVuexサイクル」です。
Vueコンポーネント層からStore内のstateにアクセスするためには

  • store.statemapStateを使ってstateを直接する方法
  • store.gettersmapGettersを用いて、取得する方法

があります。

ただ、Stateの変更をどこからでも直接、自由に変更、取得可能だと、全コンポーネント共通で参照可能なStateの秩序を保つことが出来ません。
そのため、公式ドキュメントにも掲載されている以下の図の通り、VuexのライフサイクルをルールとしてStateを変更、取得します。

vuex.png

stateの変更はMutationsが行い、vueコンポーネント層からはgetterを使用してStateの値を取得します。
そのMutaitionsはVueコンポーネント層から呼ばれるActionsによって実行されます

話がややこしくなってきたのでもう少し詳細にしてみます。
Vue.jsやNuxt.jsとVuexを使ってStateを管理したい。
そんなときに登場してくるのは Actions Mutaitions State Getters この4つです。

Vuex それぞれの役割

今回は、Firestoreに保存されているItemsを取得し、それをVuexのStateに格納して、コンポーネント層で使用する例を挙げます。
store/items以下に
- state.js
- action.js
- mutaition.js
- getter.js

を準備します。

State

Stateでは初期値を格納します。
基本的には配列が入る想定であれば[]、文字列を想定すれば''を初期値にします。
state.jsを用意して以下のように記述すると

export const state = () => {
  allItems: []
} 

stateの初期値はから配列になります。

Getters

GettersではStateの値をコンポーネント層で取得する際に使います。
例えば、Stateに格納されているallItemsを取得するなら以下のようにして

export const getters = () => {
  getAllItems() {
    state => state.allItems,
  }
}

vueコンポーネントからは

computed: {
  allItems() {
    return this.$store.getters['items/getAllItems']
  }
}

のようにして取得できます。

ただこのままだと、allItemsは空配列なのでMutaitionsからStateを更新します。

Mutations

Actionsを定義する前に、Mutationsを用意します。

export const mutations = () => {
  setAllItems(state, items) {
    state.allItems = items
  }
}

setAllItemsという関数を作ります。
第1引数は値を変更するstateで、第2引数はActionsからもらうitemsとなります。
そのitemsをStateのallItemsの代入することでStateを更新できます。

Actions

次にVueコンポーネント層からActionsを呼ぶ必要があるので定義します。

export const actions = () => {
  async fetch({ commit }) {
    const docs = await Firestore.collection('items').get()
    const items = docs.map(doc => doc.data())
    commit('setAllItems', items)
  },
}

commit関数の第1引数にMutaitionsの実行したい関数を文字列で指定し、第2引数にFirestoreから取得したitemsを指定します。

コンポーネント層でからは以下のようにActionsを呼ぶことが出来ます。

fetch({store}) {
  store.dispatch('items/fetchAllItems')
}

所感

今回はState Getters Mutations Actionsの順で実装しましたが、どこから実装するのかに問題はありません。
名称からどんな役割があってどこからどんなふうに呼び出せば良いのか覚えるまでに時間がかかるかもしれませんが、使っていくうちに分かってきます。

ちなみにmentaというコーチングサービスでVue.jsを中心にメンターをしています。
もう少し詳しい内容を聞きたい方や、Vue.jsの勉強に行き詰まっている方など、ぜひmentawatsuyo_2にメッセージをしてください!

参考

Vuex とは何か?

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

【JavaScript】forEachを用いてDOM操作で作成した要素たちにイベントハンドラーをかける

やりたいこと

①配列の中身である値をforEachで取り出し、値を反映させた<li>要素を作成する。
②<li>要素をクリックすると、値を反映した処理を行う

詰まったポイント

①単純にli要素にaddEventListenerをかけても、それぞれの値を反映した処理をしてくれない

②親要素(<ul>)にイベントハンドラをかけても、それぞれの値を反映した処理をしてくれない

実装

<ポイント>
forEachで要素を作成する時に、それぞれのaddEventListenerの処理も記述する!

例)
配列の中身をリストとして表示させる。
リストをクリックすると、表示されている値に応じて奇数か偶数か判定する。

const numbers = [1, 2, 3, 4, 5];

numbers.forEach(number => {
  const li = document.createElement('li');
  li.appendChild(number);

  li.addEventListener('click', () => {
     if(number % 2 === 0){
        console.log('偶数')
  }else{
        console.log('奇数')
  }
 })
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascriptの復習(3)

この記事について

これはJavascript初学者(現在progateで学習中)がアウトプットの場として書いている記事なので、間違いがあったらご指摘ください。また、Rubyを学習した後なのでRubyと比較して書いているかもしれないです。

アロー関数

関数の名前と言うよりは書き方といったほうがしっくりくると思います。

script.js
//これが従来の書き方
const greet = function(){
  console.log("こんにちは")
};

//"function()"を"()=>"と省略した書き方をアロー関数と言います。
const greet = ()=>{
  console.log("こんにちは")
};

引数

Rubyと同様に関数を呼び出した時に関数に渡す値のことです。同じ関数でも引数によって処理の結果が変わります。

script.js
//()の中に引数名を入れます。定数(値)の値が引数名に代入されます。
const greet = (name)=>{
  console.log(`こんにちは${name}`)
};
//関数の呼び出し(定数名(値))
greet("赤ちゃん");
//コンソール上に"こんにちは赤ちゃん"と出力
greet("さようなら");
//コンソール上に"こんにちはさようなら"と出力

戻り値

呼び出した関数の処理の結果を"戻り値"と言います。

script.js
const add = (a,b)=> {
  return a+b;
  //"return"を書くことで、aとbを足した値が戻り値として返される
  console.log("足し算しました");
  //これは実行されない。
};
const sum = add(1,3);
console.log(sum);

if文で使用するような条件式を関数に書くと真偽値が戻り値として返されます。

script.js
const search = (a)=> {
  return a%2 === 0;
  //returnの値がtrueかfalseかで返される。
};
console.log(search(6))
//trueと表示される。

スコープ

関数の引数や関数内の変数及び定数は、その関数内でしか使用できません。関数の外で定義した変数及び定数は関数内でも使用する事ができます。関数のみならず、条件式(if文)や繰り返し文(while文)の{}内の構文にもスコープがあり、構文中で定義した変数及び定数は構文外では使用できない。

script.js
const greet = ()=> {
  const name = "太郎";
  //この中では定数nameが使用できる。
};
//関数の外では定数nameを使用できない。

const math = "数学";
const subject = ()=> {
  console.log(math)
  //関数外で定義しているので定数mathを内外問わず使用できる。
};

const number = 2;
if(number%2 === 0) {
  const value = "偶数";
//この構文中でのみ定数valueを使用できる。
}

感想

引数や戻り値、スコープはRubyでも扱われているのですんなり理解できました。アロー関数はプログラミングスクールでも学習していない新規の内容でした。引き続きJavascriptの勉強を進めていきたいです。

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

【JavaScript】ブラウザでTwitterのタイムラインに自動いいね!

TwitterのTLに表示されているツイートに対して自動いいね

TwitterAPIを使用せずにブラウザ版でJavaScriptを使って自動いいね!します。
※google chromeブラウザを例にします。

!!注意!!
実行するとアカウントがBANされる可能性があります。実行は自己責任でお願いします。

自動いいね!処理

// 3秒毎に処理を実行する。
setInterval(
    function () {

        // いいね!の要素を取得
        var elems = document.body.querySelectorAll('div[data-testid="like"]');

        // 取得できたいいね!の要素の回数分実行
        for (var i = 0; i < elems.length; i++) {

            // いいね!ボタンをクリック
            elems[i].click();
        };

        // 現在位置+3000pxに縦スクロール
        window.scroll(0,window.scrollY + 3000);

    }, 3000);

実行手順

  1. Twitterホーム画面を表示する。
  2. Developer ToolsのConsoleタブを表示する。(windows:Ctrl+Shift+J,Mac:Cmd+Opt+J)
  3. Consoleに[自動いいね!処理]を入力する。
  4. 実行すると3秒に9個ずつくらいいいね!されていきます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

fetchを用いてAPIからJSONデータを取得しDOM操作に利用する

fetch()とは

非同期のネットワーク通信を簡単にわかりやすく記述できるメソッド。クライアント側のJavaScritから、HTTPと通信しデータを取得することができるようになる。
(参考:MDN web docs 『Fetchを使う』 https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch

fetchで取得したデータからJSONを取得しDOM操作に利用する

例)以下のデータ構造を持つJSONデータから、nameとageを取得し画面に表示させる
{
"results":[
{ "name": "太郎",
"age": 20},
{ "name": "次郎",
"age": 25},
{ "name": "三郎",
"age": 30},
]
}

const url = //任意のAPIのhttpアドレス

//操作したいHTML領域を取得
const name = document.getElementById('name');
const age = document.getElementById('age');

//APIからJSONデータを取得する
fetch(url)
   .then((response) => {
       return response.json() //ここでBodyからJSONを返す
   })
   .then((result) => {
       Example(result);  //取得したJSONデータを関数に渡す 
   })
   .catch((e) => {
       console.log(e)  //エラーをキャッチし表示     
   })
})

//JSONデータを引数に受け取ってDOM操作を行う関数を作成
function Example(jsonObj){
 const data = jsonObj.results[0]
 name.textContent = data.name;
 age.textContent = data.age;
}

ポイント1

then()メソッドを利用してfetch()の非同期処理を記述することにより、ネストが深くなるのを避けることができる。

エラーの処理も、例外処理であるcatch()を利用することで、チェーンとしてメソッドを繋ぐことができる。

(参考:MDN web docs 『Promise.prototype.then()』
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/then)

ポイント2

fetchで取得してくるデータそのものはPromiseオブジェクトである。

const data = fetch()
   .then((response) => {
       return response.json() //ここでBodyからJSONを返す
   })
   .then((result) => {
       Example(result);  //取得したJSONデータを関数に渡す 
   })
   .catch((e) => {
       console.log(e)  //エラーをキャッチし表示     
   })
})

console.log(data); //コンソールに"Promise"と表示される

fetchで持ってくる値そのものはPromiseオブジェクト。DOM操作に利用したいのは、取得したJSONオブジェクトそのもの。
そのため、fetch()メソッドの中に、JSONデータを渡す関数を記述し、引数としてJSONデータを渡す必要がある。

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

【JavaScript】fetchを用いてAPIからJSONデータを取得しDOM操作に利用する

fetch()とは

非同期のネットワーク通信を簡単にわかりやすく記述できるメソッド。クライアント側のJavaScritから、HTTPと通信しデータを取得することができるようになる。
(参考:MDN web docs 『Fetchを使う』 https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch

fetchで取得したデータからJSONを取得しDOM操作に利用する

例)以下のデータ構造を持つJSONデータから、nameとageを取得し画面に表示させる
{
"results":[
{ "name": "太郎",
"age": 20},
{ "name": "次郎",
"age": 25},
{ "name": "三郎",
"age": 30},
]
}

const url = //任意のAPIのhttpアドレス

//操作したいHTML領域を取得
const name = document.getElementById('name');
const age = document.getElementById('age');

//APIからJSONデータを取得する
fetch(url)
   .then((response) => {
       return response.json() //ここでBodyからJSONを返す
   })
   .then((result) => {
       Example(result);  //取得したJSONデータを関数に渡す 
   })
   .catch((e) => {
       console.log(e)  //エラーをキャッチし表示     
   })
})

//JSONデータを引数に受け取ってDOM操作を行う関数を作成
function Example(jsonObj){
 const data = jsonObj.results[0]
 name.textContent = data.name;
 age.textContent = data.age;
}

ポイント1

then()メソッドを利用してfetch()の非同期処理を記述することにより、ネストが深くなるのを避けることができる。

エラーの処理も、例外処理であるcatch()を利用することで、チェーンとしてメソッドを繋ぐことができる。

(参考:MDN web docs 『Promise.prototype.then()』
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/then)

ポイント2

fetchで取得してくるデータそのものはPromiseオブジェクトである。

const data = fetch()
   .then((response) => {
       return response.json() //ここでBodyからJSONを返す
   })
   .then((result) => {
       Example(result);  //取得したJSONデータを関数に渡す 
   })
   .catch((e) => {
       console.log(e)  //エラーをキャッチし表示     
   })
})

console.log(data); //コンソールに"Promise"と表示される

fetchで持ってくる値そのものはPromiseオブジェクト。DOM操作に利用したいのは、取得したJSONオブジェクトそのもの。
そのため、fetch()メソッドの中に、JSONデータを渡す関数を記述し、引数としてJSONデータを渡す必要がある。

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

JavaScriptオブジェクトのプロパティへのアクセス方法

概要

JavaScriptオブジェクトについてのまとめ
参考:https://jsprimer.net/basic/object/

オブジェクトとは

オブジェクトはプロパティの集合です。プロパティとは名前(キー)と値(バリュー)が対になったものです。 プロパティのキーには文字列またはSymbolが利用でき、値には任意のデータを指定できます。 また、1つのオブジェクトは複数のプロパティを持てるため、1つのオブジェクトで多種多様な値を表現できます。

こういうやつ

const obj = {
  hoge:'huga',
  foo:'bar'
};

プロパティへのアクセス方法

オブジェクトはプロパティの集合なので、プロパティにアクセスしましょう。
アクセス方法は以下の2通りです

ドット記法

オブジェクトに対してドットで繋げる記法。
ドット記法では、変数名と同じく識別子の命名規則を満たす必要がある。
具体的には以下の通り。

const obj = {
  hoge:'huga',
  123:456,
 'my-house':'house'
};

console.log(obj.hoge);       // OK 
console.log(obj.123);        // NG (数字から始まっているため)
console.log(obj.my-house);   // NG (ハイフンを含んでいるため)

ブラケット記法

[] の間に任意の式を書く。
プロパティ名は文字列へと暗黙的に変換される。
具体的には以下の通り

const obj = {
  hoge:'huga',
  123:456,
 'my-house':'house'
};
const is_hoge = 'hoge';

console.log(obj['hoge']);       // 'huga'
console.log(obj[123]);          // 456
console.log(obj['my-house']);   // 'house'
console.log(obj[is_hoge]);      // 'huga'

オブジェクトと分割代入

何度もプロパティを指定するのはめんどくさいので、プロパティを変数として定義できる。

const obj = {
  hoge:'huga',
  number:456,
  house:'house'
};
const a = obj.hoge;
const b = obj.number;
const c = obj.house;

console.log(a)    // 'huga' 
console.log(b)  // 456
console.log(c)   // 'house'

上記のように変数にプロパティを代入する場合は、分割代入を使用すると短く書ける。

const obj = {
  hoge:'huga',
  number:456,
  house:'house'
};
const {hoge, number, house} = obj;

console.log(hoge)    // 'huga' 
console.log(number)  // 456
console.log(house)   // 'house'

プロパティ名と変数名が一致していないとできないので注意です。

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

React[基礎知識編]

Reactとは

世界的に有名なJavaScriptのライブラリです。
Reactはサイトの見た目の部分を作ることができ、
Facebook、Airbnb、Dropboxなどの有名企業も多く使用しています。

雛形

// Reactをimport
import React from 'react';

// React.Compornentを継承するクラスの定義
class アプリ名 extends React.Compornent {
 constructor(props) {
  super(props);
  this.state = {変数: }
 }

 イベント名() {

 }

// JSXを戻り値とするrenderメソッドを定義
  render() {
  return (
     この中でJSXを記述する。
   )
  }
}

// クラスをexport
export default アプリ名;

JSXとは

Reactで使うHTMLのことを言います。ほぼHTMLと同じですが呼び方が違います。

複数の要素がある場合には < div >タグを使う。

render() {
 return (
  <h1>見出し1</h1>
  <h2>見出し2</h2>
  // <img>タグには閉じタグ(/)が必要
  <img src = '----'/>
 )
}

イベントの書き方

イベント名 = {() => { 処理 }} とすることでイベントを設定できます。

<button onClick = {() => { 処理 }}

stateとは

ユーザーの動きに合わせて変わる値のことをいいます。
stateは、constructorの中で、オブジェクトとして定義します。
ここで定義したオブジェクトがstateの初期値となります。
その他の部分の、定型文として覚えておけば大丈夫です。

constructor(props) {
  super(props);
  this.state = {変数: }
}

stateの表示

this.state.プロパティ名とすることで、指定したstateのプロパティ名に対応する値を取得できます。

constructor(props) {
  super(props);
  this.state = {name: '田中'}
 }
  render() {
  return (
     <h1>こんにちは、{this.state.name}さん!</h1>
   )
  }

stateの変更

this.setState({プロパティ名: 変更する値})とすることで、指定されたプロパティに対応するstateの値が変更される。
Reactでは、「stateの値に直接代入することで値を変更してはいけない」という決まりがあります。
値を変更したい場合は、setStateを使いましょう。

<button onClick = {() => {this.setState({name: '佐藤'})}}>佐藤</button>

コンポーネントとは

コンポーネントは「部品」や「パーツ」という意味です。
Reactでは、見た目を機能ごとにコンポーネント化して、コンポーネントを組み合わせることでWebサイトの見た目を作ります。
クラス名がコンポーネント名となり、下記の場合はLanguageがコンポーネント名となる。

Language.js
import React from 'react';

class Language extends React.Component {
  render() {
   return(
     JSXの記述
   );
}

コンポーネントの表示

1.作成したコンポーネントをexportする

Language.js
export default Language;

2.App.jsで、コンポーネントをインポートし、JSX内に記述する。

App.js
import React from 'react';

// コンポーネントをインポート
import Language from './Language.js';

class Language extends React.Component {
  render() {
   return(
     // JSX内に<コンポーネント名 />を記述する
     <Language />
   );
}

propsとは

propsは、「props名=値」という形で、コンポーネントを呼び出す箇所で渡します。
タグの中身は、改行してあげることで見やすくなります。
渡されたpropsは、this.propsで取得できます。
this.propsは{ props名: 値}というオブジェクトになります。

<Language
  prop = 
/>

propsの取得

this.propsと書くことで{props名: 値}というオブジェクトを取得できるので、「this.props.props名」とすることでpropsの値を取得できます。

      <div className='language-item'>
        <div className='language-name'>{this.props.name}</div>
        <img 
          className='language-image' 
          src={this.props.image} 
        />
      </div>

mapメソッドを利用する

mapメソッドで配列fruitListの各要素に対して順に処理を行い、各要素を < p >タグで囲んで表示しています。mapメソッドの戻り値はJSXなので、引数であるfruitItemは中括弧{}で囲むことに注意しましょう。

{languageList.map((languageItem) => {
  return (
    <Language
      name = {languageItem.name}
      image = {languageItem.image}
    />
  )
})}

コンポーネントが表示される流れ

コンポーネント

↓jsx

App.js

↓jsx

index.js

↓html

index.html

index.jsの中身は以下の記述を記載する。
index.js
import App from './compornents/App';
ReactDOM.render(<App />,document.getElementById('root');
index.htmlの中身は以下の記述を記載する。
<div id = 'root'>
 // 指定したid名の要素の中に挿入される
</div>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails5】[ Turbolinks ] ページ遷移やブラウザバックでJSが動かないときの対処法

実装した機能

開発環境

ruby > 2.6.5
rails > 5.2.4.2

実装したJS

$(document).ready(function(){
   $("#menu").on("click", function() {
      $(this).next().slideToggle();
   });
});

jqueryを使って、なんの変哲もない開閉式ハンバーガーメニューをつけました。
JSのコード自体の良し悪しはおいておいて、動作としては問題ないはずです。

状況

初期ロード時には問題なく動作する。
ページ遷移、ブラウザバックのときに挙動がおかしい。
クリックで発火はしているが、開閉をループしたり、不安定。
リロードすると通常動作する。

考察と対策

動いてはいるので、おそらく読み込みのタイミングが間違っている?
ready onload ajaxStop など一通り試してみてもダメ。

グーグル先生に相談したら、こんな記述を発見

$(document).on('turbolinks:load', function () {
...
});

turbolinks:load なにこれ見たことない…
どうやらRails独自の記述らしいです。

Turbolinksの扱い

こちらの記事を参考にさせていただきました。
turbolinksチートシート

Turbolinksとは?

  • Ajaxによるページ遷移の高速化のためのライブラリ(Gem)
  • ユーザ側から見て、通常のページ遷移と同じように表示される/動作する
  • Rails4からデフォルトでインストールされている

つまり、この機能が今回のJSに影響してしまっているようです。

turbolinksをどう扱うか

Gemなので、消してしまえば解消はできますが解決にはならないので
どう扱うべきかリサーチしてみました。

主にこのような扱いがあります。

  1. <a>タグごとにturbolinksを無効にする
  2. turbolinks自体を無効化(削除)する
  3. JSの読み込み時にturbolinksを適応しない、タイミングを変える

1.<a>タグごとにturbolinksを無効にする

リンク自体に{"turbolinks" => false}を指定すると
そのリンクはturbolinksが無効になります。

<%= link_to "HOGE", root_path, data: {"turbolinks" => false} %>
<%# => <a data-turbolinks="false" href="/">HOGE</a> %>

これを記述すれば、間違いなくturbolinksを外すことができます。
特定のスクリプトのみ制御する場合は良さそうですが、さすがに全部に記述するのは厳しそうですね…

2.turbolinks自体を無効化(削除)する

Gemを削除

この一行を削除

Gemfile
#gem 'turbolinks', '~> 5'
ターミナル
$ bundle update

application.jsを編集

app/assets/javascripts/application.js
//= require turbolinks #この行を削除

application.html.erbを編集

'data-turbolinks-track': 'reload' を削除します。

app/views/layouts/application.html.erb
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

これで無効化されました。
JSなどを多用するサイトでなければ無効化してしまうのが確実かもしれません。

3.JSの読み込みにturbolinksを適応しない

ready onload などと同じように、
この記述でturbolinksを適応せずにロードできます。

$(document).on('turbolinks:load', function () {
...
});

他にも、turbolinksを適応するタイミングも変更ができます。
詳しく知りたい方はこちらを参照ください。
その他のライフサイクルイベントをとる

スクリプトごとに微調整が効くので、今回はこれが最適解だと思います。

まとめ

果たして、Turbolinksは優れた機能なのか、おせっかい機能なのか…
今の所どちらとも言えません笑
デフォルトでインストールされているということは、きっとあったほうが良いのだろうと思いますが…
もっと効果的な使用法をご存じの方はぜひコメントを下さい!

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

Javascriptでクリップボード書き込み

※CodePenでは失敗します。

See the Pen Clipboard Write by 匠君 (@takumikunn15) on CodePen.

ブラウザ対応表

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

定番の顔メッシュ貼り替えを、ブラウザで動くfacemeshで作ってみた

顔メッシュ貼り替えといえば、iPhoneのFace Trackingですね。
今年の3月に登場したGoogleのfacemeshでもそこそこ似たようなことができます。

iPhoneのFace Trackingから早3年。顔メッシュ貼り替えは廃れたネタではありますが、今回はThree.jsで顔メッシュ貼り替えを試しました。

昨夜作り始めて割とさくっとできました。

Three.jsでの顔メッシュ生成

実装のコア部分となるThree.jsの顔メッシュですが、facemeshの推論の戻り値と、TRIANGULATIONのindexを混ぜ合わせると顔のメッシュは作れます。

// facemeshの戻り値
var keypoints = prediction.scaledMesh;

var texture = new THREE.CanvasTexture(canvas);
texture.flipY = false;

mesh.geometry.vertices = new Array(keypoints.length);
for (let i = 0; i < keypoints.length; i++) {
    const [x,y,z] = keypoints[i];
    mesh.geometry.vertices[i] = new THREE.Vector3(x,y,z);
}

mesh.geometry.faces = new Array(TRIANGULATION.length / 3);
for (let i = 0; i < TRIANGULATION.length / 3; i++) {

    let id0 = TRIANGULATION[i*3+0];
    let id1 = TRIANGULATION[i*3+1];
    let id2 = TRIANGULATION[i*3+2];
    mesh.geometry.faces[i] = new THREE.Face3(id0,id1,id2);

    // uvはcanvasのサイズに合わせて正規化
    let uv = [
    new THREE.Vector2(keypoints[id0][0] / videoWidth, keypoints[id0][1] / videoHeight),
    new THREE.Vector2(keypoints[id1][0] / videoWidth, keypoints[id1][1] / videoHeight),
    new THREE.Vector2(keypoints[id2][0] / videoWidth, keypoints[id2][1] / videoHeight),
    ];
    mesh.geometry.faceVertexUvs[0][i] = uv;
}
mesh.material = new THREE.MeshBasicMaterial({ map: texture , 
                                                side: THREE.DoubleSide });

実装ではcanvasの画像を変更すると他の顔のメッシュも自動生成できますのでお試しください。

オンラインデモ

https://mbotsu.github.io/facemask/

コード

https://github.com/mbotsu/facemask

その他

よろしければこちらも合わせてご覧ください。
TensorFlow.jsのfacemeshで顔向き推定を試してみた その2

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

イベントデータの設定

document.getElementById('form').select.onchange = function(){

:point_up:onchangeイベント:フォーム内容が変わった時に発生する

document.getElementById('form').select

部品を設定するにはname属性で取得をする

❸valueをつくる

document.getElementById('form').select.value

:point_up: プルダウンメニューの場合は親要素のvalueを調べに行ってくれる

❹location.hrefに代入する

location.href = document.getElementById('form').select.value;
location.href = 新しいURL
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

axios-hooksで楽々非同期処理!reduxにthunkもsagaもいらない?

はじめに

最近暇な時でGraphQLを触っていて、フロントエンドでApollo-clientをはじめて使ったんですが、
すごく使い勝手がいいhooksがあって(多分GraphQL分からなくても伝わるかなぁと):

// Apollo公式より抜粋
import gql from 'graphql-tag';
import { useQuery } from '@apollo/react-hooks';

const GET_DOGS = gql`
  {
    dogs {
      id
      breed
    }
  }
`;

function Dogs({ onDogSelected }) {
  const { loading, error, data } = useQuery(GET_DOGS); // これです

  if (loading) return 'Loading...';
  if (error) return `Error! ${error.message}`;

  return (
    <select name="dog" onChange={onDogSelected}>
      {data.dogs.map(dog => (
        <option key={dog.id} value={dog.breed}>
          {dog.breed}
        </option>
      ))}
    </select>
  );
}

これ凄くないか?
非同期処理を一行で終わらせてるぞ。

そこで考えたんですが、reduxのプロジェクトで使えたらいいなと。
…ん?待てよ。非同期関連のaxiosを使う人が山ほどいて、
同じことを考える人絶対いるんでしょ!?

調べたらいました、めちゃくちゃいました、山ほどいました(※そこまではいないです)。
2020-06-01 12-07-00.png
やったぜ。

axios-hooks

さて、今回紹介するのはその一つ:axios-hooksです
なぜこれを選んだのは単純に見比べてほしい機能が一番揃ってるからです。(自分なりに)
以下の例は大体公式のものです。

一番シンプルな例:

import useAxios from 'axios-hooks'

function App() {
  const [{ data, loading, error }, refetch] = useAxios(
    'https://api.myjson.com/bins/820fc'
  )

  if (loading) return <p>Loading...</p>
  if (error) return <p>Error!</p>

  return (
    <div>
      <button onClick={refetch}>refetch</button>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  )
}

公式のCodePenです

useAxios(url|config, options)

引数

  • url | config - configはaxiosのconfig objectです、なので基本的にaxiosで設定できるものであれば使えます。
  • options - 手動実行とキャッシュの設定です
    • manual - デフォルトはfalseです。trueに設定すればdidMountの時は自動実行しない。大体GETはfalseでCUD(Create, Update, Delete)ではtrueで使うはず。
    • useCache ( true ) - デフォルトはtrueです。

Return object

[{ data, loading, error, response }, execute]

  • data - axiosのresponse.dataです。
  • loading - 名前の通りpending中ではtrueです.
  • error - axios error object
  • response - axios response object
  • execute([config[, options]]) - 手動実行用のfunctionです、引数は基本的に前のconfig、optionsと同じです。

もっと完全な例

自分で書いたものですが、注釈は英語になってます、時間ができたら日本語に直したいと思います。
テスト用APIはwww.mocky.io使わせていただきました。
試したいであれば公式のCodePenにコピペすればできると思います。
(ReactDOMのインポートと下のrender部分は残ってください。)

import React from 'react';
import Axios from 'axios';
import useAxios, {configure} from 'axios-hooks'

// Define axios instance, you could use env to split up production & development
// The useAxios provides directly by axios-hooks use the same instance
// If you need more than one axios instance, explain later
const axiosInstance = Axios.create({baseURL: 'https://www.mocky.io/v2'})
configure({axios: axiosInstance})

// You should define your api url somewhere in your project
const api = {
  getMail: { url: () => '/5ed0a6ea3500005d00ff9d7b?mocky-delay=2000ms', method: 'GET'}, 
  putMail: { url: (id) => `/5ed0a49e3500009300ff9d6b/${id}`, method: 'POST'}
}

function App() {
  // The example to get some data
  // Execute once the component did mount
  const [{data = {iam: 'default data'}, loading, error}] = useAxios({
    url: api.getMail.url(),
    method: api.getMail.method, 
    data: {haha: "yes"}
  })
  // The example to CUD data
  // Pass { manual: true } into useAxios, then it won't execute when component did mount
  const [{data: updatedData}, updateData] = useAxios({method: api.putMail.method}, {manual: true})
  return (
    <div >
      {error && <div>error</div>}
      {data && <div>{`${JSON.stringify(data)} ${loading?'loading':''}`}</div>}
      {updateData && <div>{JSON.stringify(updatedData)}</div>}
      <button onClick={() => {
          // You can set the common authorization header like this way
          axiosInstance.defaults.headers.authorization='test101'
          // Example to update data
          updateData({url: api.putMail.url('myid'), data: {thedata: 'you want to put'}, headers: {'another-header': 'test202'}})
            .then((data) => {console.log(data)}) // Use it by the way you want, even with redux store
      }}>
        test
      </button>
    </div>
  );
}

// If you need more than one axios instance, set up with makeUseAxios & export it
// const anotherInstance = Axios.create({baseURL: 'http://some.another.api.url'})
// export const useAnotherAxios = makeUseAxios({axios: anotherInstance})

export default App;

終わりに

正直redux-sagaを使ったことないので、タイトルではクエスチョンマークを付けてます、ごめんなさい。
余談ですが公式では: axios-hooks is heavily inspired by graphql-hooksと記載しています。
同じgraphqlからの発想で作ったみたいで嬉しいです。

CodePenと注釈は時間ができたら直します。

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

[LINE BOT]FizzBuzz体験BOT

目次

  • はじめに
  • サンプルコード
  • サンプル画像と使用例
  • おわりに
  • 参考にしたサイト

はじめに

今回は、LINE BOTでFizzBuzzをやってみた。
なお、当LINE BOTを作成する際に、この記事をベースにした。

サンプルコード

'use strict';

// 使用パッケージ群
const express = require('express'); // Node.jsで利用できるWebアプリケーションフレームワーク
const line = require('@line/bot-sdk'); // ボットサーバへのリクエストが当LineBOTからきたものかどうかを検証してくれる(要は署名検証)
const PORT = process.env.PORT || 3000;  //Node.jsアプリケーションを使用するポートを設定
// LineBOT用定数群
const config = {
    channelSecret: 'YourChannelSecret',
    channelAccessToken: 'YourChannelAccessToken'
};

const app = express();

app.post('/webhook', line.middleware(config), (req, res) =>
{
    console.log(req.body.events);
    Promise
        .all(req.body.events.map(handleEvent))
        .then((result) => res.json(result));
});

const client = new line.Client(config);

async function handleEvent(event)
{
    if (event.type !== 'message' || event.message.type !== 'text')
    {
        return client.replyMessage(event.replyToken, {
            type: 'text',
            text: "数字を入力してください。"
        });
    } else
    {
        if ((event.message.text % 3 === 0) && (event.message.text % 5 === 0))
        {
            // LineBOTに返信されるメッセージを設定
            return client.replyMessage(event.replyToken, {
                type: 'text',
                text: 'FizzBuzz' // LineBOTに返信されるメッセージ
            });
        } else if (event.message.text % 3 === 0)
        {
            return client.replyMessage(event.replyToken, {
                type: 'text',
                text: 'Fizz'
            });
        } else if (event.message.text % 5 === 0)
        {
            return client.replyMessage(event.replyToken, {
                type: 'text',
                text: 'Buzz'
            });
        } else
        {
            return client.replyMessage(event.replyToken, {
                type: 'text',
                text: event.message.text
            });
        }
    }
};

app.listen(PORT);
console.log(`Server running at ${PORT}`);

サンプル画像と使用例

こんな感じになります。
コメント 2020-06-01 131340.png

おわりに

次は、VercelでLINE BOTを動かす 2020年5月版を参考に、Vercelを介してLINE BOTを動かしてみようと思う。

参考にしたサイト

  • 使用したパッケージ群について

Node.jsのフレームワーク「Express」とは【初心者向け】
LINE BOTをHeroku + Node.jsでやるまで

  • webhook について

Webhookって何?を子どもでもわかるように描いてみた

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

クリップボードに貼り付けた画像を取得する javascript

初投稿です。とりあえずメモ変わりに。
間違ってる所、改善点などあればどんどん突っ込んで下さい。

目的

画像ファイルの読み込み方法に、クリップボードから取得できれば便利だと思ったので調べてみた。

コード

div contentditable要素にコピーした画像を貼り付けてimg要素に表示する処理。

html
 <div id="box" contenteditable="true">画像ペーストエリア、</div>
 <img src="" alt="" id="image">
js
 var blob2=null;
$("#box").on("paste",function(e) {
  var items = (event.clipboardData || event.originalEvent.clipboardData).items;
for (var i = 0; i < items.length; i++) {//クリップボードに画像があるか探してる
  if (items[i].type.indexOf("image") === 0) {
    blob2 = items[i].getAsFile();
  }
}//for
if (blob2 !== null) {//方法1
//blob2に画像がある場合の処理画像url作成処理
    var blobURL = URL.createObjectURL(blob2);
    $("#image").attr("src",blobURL);
}else{//方法2 
//boxへ貼り付けられた画像をdomで取得する方法 
  setTimeout(fn, 1000);//1秒待ってから実行する。
}
});
var fn = function() {
var p = document.querySelector("#box");
var pa = p.querySelector("img");
if (pa) {
test(pa.src);
}else{
//この方法でも取得できなければ失敗
alert("画像を取得できませんでした。");
} 
}

function test(pasrc) {//urlの形式判別、base64形式とhttp形式に別れる
if (pasrc.match(/^data:image/)) {
$("#image").attr("src",pasrc);
} else if (pasrc.match(/^http:\/\/|^https:\/\//)) {
  $("#image").attr("src",pasrc);
} 
}

説明

方法1はクリップボードから取り出すのでblobとして存在している。画像形式は全てPNGとなっている。(mac)の場合、他は未検証

方法2は、div contenteditable="true"を使いdiv要素に貼り付けられた画像を取得している。元のurlの状態で取得できる、「http」か「base64画像」に分別されると思う。

ほとんどの場合方法1で取得できた。ウェブ上でコピーできる画像ならなんでも対応できた。

検証したブラウザは safari chrome firefox 環境はmacです。

参考サイト

https://qiita.com/tatesuke/items/00de1c6be89bad2a6a72
https://www.vrtmrz.net/javascript/contenteditable-paste-image

デモ

一応デモ、ごちゃごちゃしてるかも
取り敢えず一応動くというのを確認する用
https://jsbin.com/cadajireli/1/edit?html,js,output

右上の auto run jsにチェック入れて動かして下さい。

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

非同期通信の手順書

今回の前提条件

コメント機能を非同期通信にするという前提で書きます

ファイルを用意しましょう

JS内にcomment.jsを用意しましょう。
ここにajaxの記述などを書いていくのでした。
ajaxって何?

comment.jsに記述していこう

まずは以下のようにします。

$(function(){
  $('クラス名').on('submit', function(e){
    e.preventDefault();
    var formData = new FormData(this);
  })
})

今回はコメント機能なので、form要素のクラス名を記入します。そしてonメソッドでイベントセッティングをしましょう。
そしてpreventDefaultで元々のイベントを止めることができます。
今回、ここで新しくFormDataが出てきました。

FormData

簡単にいうとformタグ内の要素のデータをJSのオブジェクトにすることです。
あ、JSにするために FormDataがあるんだ!って思っても大丈夫です。
今回引数にthisを入れてるのはinput要素の内容を取得するためです。

ajaxを書いていこう

var url = $(this).attr('action')
    $.ajax({
      url: url,
      type: "POST",
      data: formData,
      dataType: 'json',
      processData: false,
      contentType: false

attrメソッド

要素が持つ指定属性の値を返します。

contentType

簡単にいうと、 FormDataを使用している時は必ずfalseにします。
この認識で問題ないです。

次はcreateアクションをいじっていきます。

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

Javascript 初めてのGSAPアニメーションの使い方 その1

フロントの技術強化のためのGSAPアニメーションを基礎から紹介します。

GSAPとは

GSAP(GreenSock Animation Platform)

GSAP(GreenSock Animation Platform)は、高速・軽量のHTML5アニメーションライブラリで、アニメーションライブラリのデファクトスタンダードといえる程多くのサイトで利用されています。

主なモジュールとして、トゥイーンを実現するTweenMaxと、タイムラインを制御すTimelineMaxの2種があります。また、TweenMaxと、TimelineMaxのそれぞれの簡易版の2種があります。

準備

今回は以下のディレクトリ構成で進めます。
GSAPの読み込みはCDNを使用します。
記事作成時点のバージョンは3.2.6です。

・index.html
・/styles/style.css
・/scripts/main.js

各シートを読み込んで準備しましょう。

今回の制作物

まずは基本的なアニメーションを作成します。
Image from Gyazo

Tweenmax.to

Tweenmax.toは指定したプロパティーに向かって要素がアニメーションします。

赤い丸→青に変わりながら1秒かけて右と下に150px移動
緑の四角→丸のアニメーション終了後(1秒後)左と上に150px移動しながら2倍のサイズになりバウンスする

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="styles/style.css" />  
    <title>Document</title>
  </head>
  <body>
      <div class="circle"></div>
      <div class="square"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.6/gsap.min.js"></script>//GSAPの読み込み
    <script src="scripts/main.js"></script> //JSシートの読み込み
  </body>
</html>
body{  //要素を画面中心に配置
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
}

.circle{  //赤い丸
  background-color: orangered;
  height: 150px;
  width: 150px;
  border-radius: 50%;
}

.square{  //緑の四角
  width: 150px;
  height: 150px;
  background-color: green;
}

実行結果
Image from Gyazo

ここからjsを記述します。

TweenMax.to('.circle', 1, { x:150, y:150, backgroundColor: 'blue' });
//赤丸(class=".circle")をx軸,y軸へ150px移動と色を青に指定

TweenMax.to('.square', 3, { x: -150, y:-150, scale: 2, delay: 1, ease:
Back.easeOut });
//四角(class=".square")をx軸y軸へ150px移動と1秒後に2倍に拡大してeaseOutでバウンスする

Image from Gyazo

基本の記述は

tweenmax.to( '取得したい要素' , { アニメーションのプロパティ });

です。プロパティは改行しても動きますのでお好みで選んでください。

個人的には1ライナーで書いたほうが見やすいかなと思います。

次回はTween.fromとJqueryとの連携です。

Javascript 初めてのGSAPアニメーションの使い方 その2

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

Laravel MixでVue.js+Bootstrapの簡単なログインフォームを作る。

FIRST PLAN株式会社のフロントエンドエンジニアtakeです。

sampleText

さて、今回はテストでログインフォームを作ったので良ければご覧ください。

こんな事をやりました

Image from Gyazo

環境設定

まずはVue.js。
今回はLaravel内で使っているので、LaravelでVue.jsを使えるようにするには以下のコマンドを実行します。

$ composer require laravel/ui
$ php artisan ui vue --auth

すぐにvueを始めたい場合は公式ドキュメントにある通りCDNでも使えますし、初めからwebpackの環境構築がされている「Vue CLI」もあるので活用してみてください。
ただ今回は環境設定の記事ではないのでそこら辺は割愛します。

そして次にBootstrapですが、BootstrapVueを使用します。
機能は同じでBootstrapの記法もそのまま使えるのですが、jQuery依存だったコンポーネントがVueのディレクティブが使えるように拡張されています。

$ npm install bootstrap-vue bootstrap

でインストールし、
以下のコードをメインのjsファイルに追加します。

import BootstrapVue from 'bootstrap-vue';
Vue.use(BootstrapVue);

後は好みの問題なのですが、FontAwesomevue-sweetalert2をインストールします。

vue-sweetalert2もBootstrapVueと同じで機能は元のsweetalert2と変わりませんが、Vueでそちらを使うとアイコン部分で表示崩れが起きる事があるのでそれを修正したものになります。
Bootstrapと同様にこちらもインストールします。

FontAwesome

$ npm install --save @fortawesome/fontawesome-free
import '@fortawesome/fontawesome-free/css/all.css';

vue-sweetalert2

$ import VueSweetalert2 from 'vue-sweetalert2';
import 'sweetalert2/dist/sweetalert2.min.css';
Vue.use(VueSweetalert2);

コード

HTML

今回は以下のような感じで、cardinput-groupを使って実装しました。

HTML
<template>
  <section class="login-form d-flex flex-solumn justify-content-center align-items-center w-100 text-center">
    <div class="login-form__field card rounded-0 pt-5 pb-3">
      <i class="login-form__budge fas fa-user-circle text-primary bg-white rounded-circle"></i>
      <div class="card-body">
        <h1 class="login-form__heading card-title text-primary font-weight-bold mb-0">Sign in</h1>

        <hr class="bg-primary mt-0 mb-4">

        <p class="card-text text-muted mb-4">ユーザー名とパスワードを入力してください。</p>

        <div class="input-group mx-auto mb-4">
          <div class="input-group-prepend">
            <span class="input-group-text bg-primary text-white" id="basic-addon1">
              <i class="fas fa-user"></i>
            </span>
          </div>
          <input class="form-control" type="text" placeholder="Username" v-model="login.user_name">
        </div>

        <div class="input-group mx-auto mb-4">
          <div class="input-group-prepend">
            <span class="input-group-text bg-primary text-white" id="basic-addon1">
              <i class="fas fa-key"></i>
            </span>
          </div>
          <input class="form-control rouded-0" type="password" placeholder="Password" v-model="login.password">
        </div>

        <button class="login-form__button btn btn-primary w-75 mb-4" type="button" :disabled="checkInput" @click="signIn">Login </button>

        <div class="login-form__attention d-flex justify-content-between my-o mx-auto">
          <div class="custom-control custom-checkbox">
            <input type="checkbox" class="custom-control-input" id="remember-me" v-model="login.remember">
            <label class="custom-control-label" for="remember-me">情報を記憶する</label>
          </div>
          <a class="card-link" href="#">
            <u>パスワードを忘れた方はこちら</u>
          </a>
        </div>
      </div>
    </div>
  </section>
</template>

CSS

飽くまでBootstrapベースなので、scssでBEMを採用して影のエフェクト以外は軽く整える程度にしました。

scss
.login-form {
  &__field {
    position: relative;
    max-width: 30rem;
    //影エフェクト
    &:after {
      content: '';
      position: absolute;
      bottom: 20px;
      right: 55px;
      z-index: -1;
      width: 50%;
      height: 100px;
      box-shadow: 100px 0 10px 15px rgba(0, 0, 0, .3);
      transform: skew(-40deg);
    }
  } //__field
  &__budge {
    position: absolute;
    top: -2rem;
    left: 50%;
    font-size: 6rem;
    transform: translateX(-50%);
  } //__budge
  &__attention {
    font-size: 0.6rem;
  } //__attention
  &__button {
    border-radius: 0!important;
  } //__button
} // .login-form

JavaScript

以下がデフォルトのVueの記述方法になります。

ざっくり説明するとdataは変数等を格納する場所・computedは変更があるとリアルタイムで値を再計算してくれる場所・methodsは文字通りメソッドを格納する場所です。
他にもwatchやライフサイクルフック等開発に便利な機能が色々とあるので、詳しくは公式ドキュメントをご参照ください。

さて、まずdata内のloginは「ユーザーネーム・パスワード・ユーザー情報を記憶するか否か」を保持しています。
html内にv-model="login.user_name"といった記述があると思いますが、このv-modelによって双方向データバインディングを実現しています。
どちらかが変更された段階でもう片方にも自動で変更が同期されるため、自分で逐一document.querySelector(element)~をする必要がなくなります。

次はcomputedですが、今回はlogin.user_namelogin.passwordが条件に組み込まれていますが、どちらかの値が変更された段階で自動で値を更新します。
メソッド内にif (!this.checkInput)と指定されている関数がありますが、このように使用する事で他の場所で毎回入力を確認する必要がなくなります。
ちなみにbuttonにある:disabled="checkInput"にも使用されていますが、まずこの:マークはv-bindといってhtml内の属性(classhref等)に付ける事で内部でJavaScriptのコードを直接使用可能にするものです。
今回はこれらを利用してdisabledの値を入力の有無によって動的に変更しています。

最後にmethodsは一番簡単で、関数を格納する場所になります。
とりあえず確認用にv-modelで同期した値をsweetalert2で表示しています。
イベントハンドリングはhtml側で@event="hoge"のように行います。(今回の場合は@click="signIn"
ちなみにこの@click以下はコンパイル時に削除されるため、JavaScriptのコードでhtmlを汚染したくない方にも安心です。

JavaScript
export default {
  data() {
    return {
      //ログイン情報
      login: {
        user_name: '',
        password: '',
        remember: false
      }
    }
  },
  computed: {
    //ユーザーネームとパスワードの両者が入力されている事を判定する値、自動で変更を検知し反映してくれる
    checkInput() {
      return (!this.login.user_name || !this.login.password) ? true : false;
    }
  },
  methods: {
    signIn() {
      //ユーザーネームとパスワードの両者が入力されていた時のみ発火する。
      if (!this.checkInput) {
        //v-modelで同期した値を出力する
        this.$swal({
          icon: 'info',
          title: 'Information',
          text: `
            ID: ${this.login.user_name},
            Password: ${this.login.password},
            Remember: ${this.login.remember}
          `
        })
      }
      return;
    } //signIn
  }
}

最後に

$ npm run dev

でコンパイルして確認すると...

Image from Gyazo

キチンと動いています。

よろしければ参考にしてみて下さい。

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

Lravel MixでVue.js+Bootstrapの簡単なログインフォームを作る。

FIRST PLAN株式会社のフロントエンドエンジニアtakeです。

sampleText

さて、今回はテストでログインフォームを作ったので良ければご覧ください。

こんな事をやりました

Image from Gyazo

環境設定

まずはVue.js。
今回はLaravel内で使っているので、LaravelでVue.jsを使えるようにするには以下のコマンドを実行します。

$ composer require laravel/ui
$ php artisan ui vue --auth

すぐにvueを始めたい場合は公式ドキュメントにある通りCDNでも使えますし、初めからwebpackの環境構築がされている「Vue CLI」もあるので活用してみてください。
ただ今回は環境設定の記事ではないのでそこら辺は割愛します。

そして次にBootstrapですが、BootstrapVueを使用します。
機能は同じでBootstrapの記法もそのまま使えるのですが、jQuery依存だったコンポーネントがVueのディレクティブが使えるように拡張されています。

$ npm install bootstrap-vue bootstrap

でインストールし、
以下のコードをメインのjsファイルに追加します。

import BootstrapVue from 'bootstrap-vue';
Vue.use(BootstrapVue);

後は好みの問題なのですが、FontAwesomevue-sweetalert2をインストールします。

vue-sweetalert2もBootstrapVueと同じで機能は元のsweetalert2と変わりませんが、Vueでそちらを使うとアイコン部分で表示崩れが起きる事があるのでそれを修正したものになります。
Bootstrapと同様にこちらもインストールします。

FontAwesome

$ npm install --save @fortawesome/fontawesome-free
import '@fortawesome/fontawesome-free/css/all.css';

vue-sweetalert2

$ import VueSweetalert2 from 'vue-sweetalert2';
import 'sweetalert2/dist/sweetalert2.min.css';
Vue.use(VueSweetalert2);

コード

html

今回は以下のような感じで、cardinput-groupを使って実装しました。

HTML
<template>
  <section class="login-form d-flex flex-solumn justify-content-center align-items-center w-100 text-center">
    <div class="login-form__field card rounded-0 pt-5 pb-3">
      <i class="login-form__budge fas fa-user-circle text-primary bg-white rounded-circle"></i>
      <div class="card-body">
        <h1 class="login-form__heading card-title text-primary font-weight-bold mb-0">Sign in</h1>

        <hr class="bg-primary mt-0 mb-4">

        <p class="card-text text-muted mb-4">ユーザー名とパスワードを入力してください。</p>

        <div class="input-group mx-auto mb-4">
          <div class="input-group-prepend">
            <span class="input-group-text bg-primary text-white" id="basic-addon1">
              <i class="fas fa-user"></i>
            </span>
          </div>
          <input class="form-control" type="text" placeholder="Username" v-model="login.user_name">
        </div>

        <div class="input-group mx-auto mb-4">
          <div class="input-group-prepend">
            <span class="input-group-text bg-primary text-white" id="basic-addon1">
              <i class="fas fa-key"></i>
            </span>
          </div>
          <input class="form-control rouded-0" type="password" placeholder="Password" v-model="login.password">
        </div>

        <button class="login-form__button btn btn-primary w-75 mb-4" type="button" :disabled="checkInput" @click="signIn">Login </button>

        <div class="login-form__attention d-flex justify-content-between my-o mx-auto">
          <div class="custom-control custom-checkbox">
            <input type="checkbox" class="custom-control-input" id="remember-me" v-model="login.remember">
            <label class="custom-control-label" for="remember-me">情報を記憶する</label>
          </div>
          <a class="card-link" href="#">
            <u>パスワードを忘れた方はこちら</u>
          </a>
        </div>
      </div>
    </div>
  </section>
</template>

Scss

今回はBootstrapベースなので、BEMを採用して影のエフェクト以外は軽く整える程度にしました。

scss
.login-form {
  &__field {
    position: relative;
    max-width: 30rem;
    //影エフェクト
    &:after {
      content: '';
      position: absolute;
      bottom: 20px;
      right: 55px;
      z-index: -1;
      width: 50%;
      height: 100px;
      box-shadow: 100px 0 10px 15px rgba(0, 0, 0, .3);
      transform: skew(-40deg);
    }
  } //__field
  &__budge {
    position: absolute;
    top: -2rem;
    left: 50%;
    font-size: 6rem;
    transform: translateX(-50%);
  } //__budge
  &__attention {
    font-size: 0.6rem;
  } //__attention
  &__button {
    border-radius: 0!important;
  } //__button
} // .login-form

JavaScript

これがデフォルトのVueの記述方法になります。
ざっくり説明するとdataは変数等を格納する場所・computedは変更があるとリアルタイムで値を再計算してくれる場所・methodsは文字通りメソッドを格納する場所です。
他にもwatchやライフサイクルフック等開発に便利な機能が色々とあるので、詳しくは公式ドキュメントをご参照ください。

まずdata内のloginは「ユーザーネーム・パスワード・ユーザー情報を記憶するか否か」を保持しています。
html内にv-model="login.user_name"といった記述があると思いますが、このv-modelによって双方向データバインディングを実現しています。
これによりデータと表示の同期・DOM操作を自力でやる必要がなくなります。

次はcomputedですが、今回はlogin.user_namelogin.passwordが条件に組み込まれていますが、どちらかの値が変更された段階で自動で値を更新します。
メソッド内にif (!this.checkInput)と指定されている関数がありますが、このように使用する事で、他の場所で毎回入力を確認する必要がなくなります。

最後にmethodsは一番簡単で、関数を格納する場所になります。
とりあえず確認用にv-modelで同期した値をsweetalert2で表示しています。

JavaScript
export default {
  data() {
    return {
      //ログイン情報
      login: {
        user_name: '',
        password: '',
        remember: false
      }
    }
  },
  computed: {
    //ユザーネームとパスワードの両者が入力されている事を判定する値、自動で変更を検知し反映してくれる
    checkInput() {
      return (!this.login.user_name || !this.login.password) ? true : false;
    }
  },
  methods: {
    signIn() {
      //ユーザーネームとパスワードの両者が入力されていた時のみ発火する。
      if (!this.checkInput) {
        //v-modelで同期した値を出力する
        this.$swal({
          icon: 'info',
          title: 'Information',
          text: `
            ID: ${this.login.user_name},
            Password: ${this.login.password},
            Remember: ${this.login.remember}
          `
        })
      }
      return;
    } //signIn
  }
}

最後に

$ npm run dev

でコンパイルして確認すると...

Image from Gyazo

キチンと動いています。

よろしければ参考にしてみて下さい。

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

Amazon Chime SDK新機能を使ってホワイトボードを実装した件

この記事はこちらでも紹介しています。
https://cloud.flect.co.jp/entry/2020/06/01/115652

前回は、Amazon Chime SDKのVirtual背景の作り方についてご紹介しました。

https://qiita.com/wok/items/962929e63bc98e4033b9

今回も引き続きAmazon Chime SDKのお話をしたいと思います。

さて、先日Amazon がAmazon Chime SDKの新機能追加を発表したのをご存知でしょうか。

https://aws.amazon.com/jp/about-aws/whats-new/2020/05/amazon-chime-sdk-data-messages-real-time-signaling/

本機能は、Amazon Chimeで使われているデータ通信路を間借りすることで、会議の参加者間でデータメッセージのやり取りを可能にする機能です。発表にも書かれているとおり、これにより例えば会議室参加者間でホワイトボードを共有したり、絵文字のやり取りを簡単に行えたりします。また、活用の仕方によっては、参加者のミュートを強制するなど会議室の状態制御を行うことも可能になります。

ということで今回は、早速この機能を使ってホワイトボードを作ってみましたのでご紹介したいと思います。

今回作ったホワイトボードの挙動はこんな感じです。

20200529102321.gif

Amazon Chimeとシグナリング

今回のAmazon Chime SDKの追加機能は、もともとAmazon Chimeで使われているシグナリング通信を間借りして実現されているようです。Amazon Chimeのビデオ会議はWebRTCという技術を用いて実現されていますが、この制御を行う際に用いられている通信がシグナリング通信です。具体的には、WebRTCではブラウザ間でP2Pの通信を行うのですが、この通信を開始するために相手の宛先を特定したり、暗号通信の鍵交換をしたりするときに用いられます。また、P2P通信と言ってもFirewallを越えた通信を行う場合にはTURNという中継サーバを経由する必要がありますが、こういった経路に関する情報交換もシグナリング通信で行われます。

WebRTCの全体像とシグナリングの関係はこちらのページが詳しいので、詳しく知りたい方は参照ください。

Amazon Chimeは、様々なネットワーク環境下においてもビデオ会議を簡単に開始できるように、マネージドな中継サーバやシグナリング通信の通信路を提供しています。今回追加された機能はこのシグナリングを行うマネージドな通信路を活用して、任意のデータメッセージをやり取りできるようにしています。なので、開発者はメッセージング用サーバを用意する必要がなく、簡単にビデオ会議システムに共有ホワイトボードなどを追加することができます。

API概観

新たに提供されたメソッドは下記の3つです。

本機能では、Topicというタグをつけてデータメッセージに送受信します。

まず、各クライアントではTopic毎に処理を定義したコールバック関数を登録しておきます。そして、送信側がTopicと共にデータメッセージを送信すると、そのデータメッセージを受け取ったクライアントはTopicに応じたコールバック関数を呼び出し処理を行います。内部処理、特にデータの流れの詳細は不明ですが、おそらく一般的なpublish/subscribeモデルで動いていると思われます。

今回使用してみて、とても使いやすい機能だと感じました。

なお、本機能では、データメッセージのPublisherが、そのデータメッセージのTopicをsubscribeしていても、そのデータメッセージを受信できないので、注意が必要かもしれません。
私は、PublisherとSubscriberがお互いの関係性を全く無視できるようにするのが、publish/subscribeモデルの利点だと思っているので、PublsherとSubscriberが同じソフトウェア(セッション)だとデータを受信できないというのは、ちょっとだけ違和感がありました。(個人的には、未だに自分のプログラムのバグかも?とも思ってます。)今回のホワイトボードのようなリアルタイムにPublisherのUIに情報を反映する必要があるものについては、受信時のフィルタリングをしなくてよく、ありがたいとも言えますので、個人の感じ方次第だとは思いますが。

新たに提供されたメソッド

https://aws.github.io/amazon-chime-sdk-js/interfaces/audiovideofacade.html#realtimesubscribetoreceivedatamessage

  • データ購読解除 image.png

https://aws.github.io/amazon-chime-sdk-js/interfaces/audiovideofacade.html#realtimeunsubscribefromreceivedatamessage

共有ホワイトボードの処理の概要

今回作成した共有ホワイトボードの大まかな処理の流れは次のとおりです。

  1. Publisherのブラウザのキャンバス(HTMLCanvasElemnt)上でマウスイベント/タッチイベントを検出し、座標を特定
  2. Publisherのキャンバス上に描画
  3. 座標をデータメッセージとしてBroker(Chime)に送信 (realtimeSendDataMessage)
  4. Brokerから各Subscribersに座標を送信
  5. 各Subscribersのキャンバスに描画

前述の通りPublisherは、自分の送信するデータメッセージを受信することができないので、自分のキャンバスに描画してからデータメッセージを送信する必要があります。
ホワイトボードのようにユーザ操作を遅延なくUIに反映させたいアプリケーションを作る場合はデータメッセージ送信前に自身のUIに反映させる方がユーザ体験上良いので、Publisherがデータメッセージを受信できる、できないにかかわらず同じような作りにはなるとは思いますが。
むしろ、Publisherは、自分の送信するデータメッセージを受信することができないので、Publisherで受信データを破棄しなくて良いので実装が楽かもしれません。

image.png

実装

サブスクライブ設定

realtimeSubscribeToReceiveDataMessageでsubscribeするtopicと対応するコールバック関数を登録するラッパ関数の例です。ここでは、データメッセージの受領時に、app.app.receivedDataMessageを呼び出すコールバック関数を定義して引数として使っています。なお、app.app.receivedDataMessage自体は別の場所で任意の処理を定義しておいてください。

export const setRealtimeSubscribeToReceiveDataMessage = (app:App, audioVideo:AudioVideoFacade, topic:string) =>{
    const receiveDataMessageHandler = (dataMessage: DataMessage): void => {
        app.receivedDataMessage(dataMessage)
    }
    audioVideo.realtimeSubscribeToReceiveDataMessage(topic, receiveDataMessageHandler)
}

データメッセージ送信

realtimeSendDataMessageを用いてデータメッセージを送る処理例です。
ホワイトボードに描画を行うために、始点と終点の座標、ストローク情報、線の太さなどをjosn化して送信しています。

    sendDrawsingBySignal = (targetId: string, mode:string, startXR:number, startYR:number, endXR:number, endYR:number, stroke:string, lineWidth:number)=>{
        const gs = this.props as GlobalState
        const message={
            action: 'sendmessage',
            data: JSON.stringify({ 
                cmd         : MessageType.Drawing,
                targetId    : targetId, 
                startTime   : Date.now(),
                mode        : mode,
                startXR     : startXR,
                startYR     : startYR,
                endXR       : endXR,
                endYR       : endYR,
                stroke      : stroke,
                lineWidth   : lineWidth
            })
        }
        gs.meetingSession?.audioVideo.realtimeSendDataMessage(MessageType.Drawing.toString(), JSON.stringify(message))
    }

動作デモ

ホワイトボード

作成したホワイトボード機能の動きはこのような感じになります。このデモは授業のホワイトボードを模擬したものとなります。右側で描画した内容が左側に反映されているのが分かるでしょうか?

20200530170337.gif

画面共有上での描画

また、このホワイトボードをオーバレイの形で作ってあげると、Amazon Chime SDKの画面共有機能と併用しながらプレゼンをすることができます。
20200530173726.gif

FLECT Amazon Chime Meeting

今回説明した機能は、現在FLECT研究開発室で、ビデオ会議を使った新機能のテストベッドに組み込まれています。
下記のリポジトリに公開していますので、ご興味を持たれましたらアクセスしてみてください。

https://github.com/FLECT-DEV-TEAM/FLECT_Amazon_Chime_Meeting

最後に

今回は、Amazon Chime SDKの最新機能を用いてホワイトボードを作成してみました。
日本では先日、緊急事態宣言の解除が発表されました。しかし、まだまだ教室に多くの人を集めて授業をするのは難しそうです。
また、同様に接客を行うにも、なかなかFace-to-Faceで接客というのもリスクがあり、難しいかもしれません。
ビデオ会議と共有ホワイトボードで、こういった課題に対応するというのも選択肢の一つではないかと思います。

次回は、またAmazon Chimeで遊ぶか、以前紹介したマルチバーコードリーダの技術的な内容をご紹介するかをしようと思ってます。
では。

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

フレームワークなしでSPAを作るために、WebComponentsを用いてFluxを実装する

SPAを作るために必要となる設計思想Flux。今回はWebComponentsを用いてVanillaJSでFluxを実装してみたいと思います。

なお今回のコードを書くにあたって10分で実装するFluxを参考にさせていただいています。

まず、Fluxのデータの流れをおさらいしましょう。かなり簡単に書くと以下のような感じだと思います。
View -> Action -> Dispatcher -> Store -> View -> ...
まずユーザーがWebサイトにアクセスすると何らかのViewが表示されます。そしてユーザーが何らかの行動を起こしActionが発火します。Actionはアクション名と必要なデータをDispatcherを通してStoreに伝えます。StoreはActionに応じて状態(state)を更新します。そして、更新したことをViewに伝え、Viewは新しい状態に基づいて再描画します。

ActionはDispatcherを使ってStoreへ、StoreはDispatcherを使って任意のActionを監視します。そのため、DispatcherはEventEmitterで実装できそうです。なお、ブラウザにEventEmitterは標準で実装されていないので、今回は代わりにEventTargetを使います。
Storeも状態を更新したことをViewに伝え、Viewは任意のStoreを監視します。つまり、StoreもEventTargetを使って実装します。
Viewは今回は、WebComponentsのCustomElementsを用いて実装してみたいと思います。

なお今回実装するのは、ボタンを押したらカウントが増えるアプリです。
ren.gif

まず、Dispatcherを見てましょう。DispatcherはEventTargetそのものです。

dispatcher.mjs
export default new EventTarget();

では次にActionを見てましょう。

actions.mjs
import dispatcher from 'dispatcher.mjs';

export default {
    countUp() {
        dispatcher.dispatchEvent(new CustomEvent('countUp'));
    }
}

Dispatcherを利用してActionをStoreに伝えるにはこのようにnew CustomEvent()を使います(new Event()でもよい)。CustomEventを使うとデータも渡せます。データは
eventTarget.dispatchEvent(new CustomEvent('eventName', { detail: data }));
と書くことによって、受け取り側は、
eventTarget.addEventListener((e) => { const data = e.detail });
と書くことによってデータを受け取れます。
では次にStoreを見てみましょう。

store.mjs
import dispatcher from 'dispatcher.mjs';

const initialState = {
    count: 0
};

class Store extends EventTarget {
    constructor() {
        super();

        Object.assign(this, initialState);

        dispatcher.addEventListener('countUp', () => {
            this.count++;
            this.dispatchEvent(new Event('CHANGE'));
            //stateを変更したことをcomponentに伝える
        });
    }
}

export default new Store();

'countUp'アクションをリッスンして、内部のthis.countというstateを更新していますね。そして、'CHANGE'イベントを発火させています。こうすることによって、Viewにstateが更新したことを伝えることができます。
では次に、View(Component)を見てみましょう。

Component.mjs
import actions from 'actions.mjs';
import store from 'store.mjs';

const html = `
<p>Count: <span>${store.count}</span></p>
<button type="button">Count Up</button>
`;

export default class Component extends HTMLElement {
    constructor() {
        super();

        const shadowRoot = this.attachShadow({mode: 'open'});
        shadowRoot.innerHTML = html;

        this.span = shadowRoot.querySelector('span');
        shadowRoot.querySelector('button').onclick = () => {
            actions.countUp();
        }

        this.handleStoreChange = this.handleStoreChange.bind(this);
    }

    connectedCallback() {
        //storeのstateの変更を監視する;
        store.addEventListener('CHANGE', this.handleStoreChange);
        //最初の描画;
        this.handleStoreChange();
    }

    disconnectedCallback() {
        store.removeEventListener('CHANGE', this.handleStoreChange);
    }

    handleStoreChange() {
        //storeのstateが変更されたらcomponentのstateへ渡す;
        this.count = store.count;
    }

    set count(value) {
        //componentのstateが新しい値に置き換わった時のみ描画する
        if(value === this._count) return;
        this._count = value;

        this.span.innerHTML = value;
    }
}

少し長いですね、順を追って見ていきましょう。

まず、CustomElementsを作るときはHTMLElementを継承したクラスを作ります。そして、constructor内でshadowRootをCustomElementsに追加し、そのshadowRootのinnerHTMLにベースとなるhtmlを代入します。これはお決まりのパターンでshadowRootを追加しないでconstructor内でhtmlを直接CustomElementsに代入すると、document.createElement()時にエラーになるので気を付けましょう。

また、constructor内でbutton要素のonclickを登録しています。ボタンがクリックされたらActionが呼ばれるようにしています。

constructorの下にconnectedCallbackとdisconnectedCallbackというメソッドがあります。これはCustomElementsのライフサイクルの一つで、このCustomElementsがdocumentに追加された時connectedCallback()が、切断された時はdisconnectedCallback()が呼ばれます。

ここでは、connectedCallbackでStoreの監視を始めています。監視しているStore内で'CHANGE'イベントが発火されると、handleStoreChangeメソッドが呼ばれます。なお、Viewがdocumentに追加されたタイミングで一回handleStoreChangeメソッドを呼び、Storeからstateを取得しています。
そして、disconnectedCallbackではStoreの監視を止めています。メモリリーク防止のためです。

では、handleStoreChangeメソッドでは何を行っているのでしょう。Componentのsetterであるcountにstore.countを代入しています。実はComponentは内部にstateを持つようにしています。stateを持つことによって、stateが変更された部分のみが差分で描画されるようになっています。

set count()では、Storeから渡されたstateが新しい値に置き換わっているか調べていて、新しい値が渡された時のみ、DOMを更新します。
このように、ViewにCustomElementsを使った場合、setter内部で描画を更新する処理を書きます。

以上Viewを見てきました。では、このComponentをdocumentに追加しましょう。以下のように書きます。

index.mjs
import Component from 'Component.mjs';

customElements.define('x-component', Component);

document.body.innerHTML = '<x-component></x-component>';

まず作ったCustomElementsを定義します。customElements.define(要素名, Component)で定義できます。要素名には必ず一つの-(ハイフン)が必要です。

これを実行した結果が以下です。以下のコードをブラウザのコンソールに直接入力してもカウントアップアプリができます。(SafariはEventTargetのpolyfillが必要です。)


See the Pen
CustomElements-Flux-sample
by shigure (@webkatu)
on CodePen.


以上、FluxをVanillaJSで実装してみました。以下のブログ記事にこのFlux実装を基にSPAを作る方法を簡単なサンプルを交えながら説明しています。
フレームワークなしで作るSPA

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

A-Frame: aframe-multisrc-componentの調査

困った

A-Frameでサイコロを作りたいのですが、標準のマテリアルコンポーネントだと立方体の6面とも同じ画像が表示されてしまいます。
cheat_dice.gif
demo
これでは困る。

いい感じになった

立方体の6面とも異なる画像を表示できるコンポーネントを調べてみると、良さそうなのがありました。
aframe-multisrc-component
dice.gif
demo
いい感じになりました。
個々の面に対して色を指定したり、メタリックにしたりもできるようです。

バグっぽい

使ってみて気づいたのですが、

  • multisrcコンポーネントをエンティティから削除する
  • multisrcコンポーネントを持つエンティティを削除する

のどちらかを実行すると、multisrcコンポーネントのremoveイベントが発火しますが、その中の処理にバグがあるっぽいです。

以下のようなエラーが発生します。

Cannot read property 'addEventListener' of undefined

ループの中からthisが参照できなくなっているようです。

remove: function(){
    var defaultMaterial = this.el.components.material.material 
    this.el.getObject3D('mesh').material = defaultMaterial
    this.el.removeEventListener('componentchanged', this.compChange);
    var animationEventsArray = ['animationbegin', 'animationstart', 'animationcomplete', 'animationend']
    animationEventsArray.forEach(function(event) {
      this.el.addEventListener(event, this.materialChangedByAnimation) // <- ここでthisがundefinedになる
    });
    this.reduceMaterialChangedThrottle(200);
}

このコードを追記してremove関数を上書きしてあげると、とりあえずうごきました。

AFRAME.components.multisrc.Component.prototype.remove = function () {
    var defaultMaterial = this.el.components.material.material;
    this.el.getObject3D('mesh').material = defaultMaterial;
    this.el.removeEventListener('componentchanged', this.compChange);
    var animationEventsArray = ['animationbegin', 'animationstart', 'animationcomplete', 'animationend'];
    var self = this; // <- thisをselfで保持してループの中からも参照できるようにした
    animationEventsArray.forEach(function (event) {
        self.el.addEventListener(event, self.materialChangedByAnimation); <- thisをselfに置き換えた
    });
    this.reduceMaterialChangedThrottle(200);
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者向け】【Javascrript】FizzBuzz問題

今回はFizzBuzz問題をHTMLのscriptタグ内に書きました。

FizzBuzz問題とは?

1から30まで順に数えて出力して行き、3で割り切れる数は「Fizz」5で割り切れる数は「Buzz」両方で割り切れる数は「FizzBuzz」と出力する、ようにプログラムを書く問題です。

これが書けるか書けないかでプログラマー志願者を仕分けられるようになったようです。実際今でも使われていますから押さえておいて損はないと思います。FizzBuzz詳細

私が書いたコード

sample.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset=UTF-8>
        <title>loop</title>
    </head>
    <body>
        <script>

                var i = 0;
                for (i = 1; i <=100; i++) {
                if (i % 15 === 0 ) {
                        document.write("<p>" + "FizzBuzz" + "</p>");

                    } else if(i % 3 === 0) {
                        document.write("<p>" + "Fizz" + "</p>");

                    } else   if(i % 5 === 0 ) {
                        document.write("<p>" + "Buzz" + "</p>");
                    } else 
                    document.write("<p>" + i + "</p>");
            }
        </script>
    </body>
</html>

書く順番

ifは条件が厳しいものから順番に書いていった方が良いです。今回の問題だと"Fizz"や"Buzz"を表示させるifを先に書いてしまうと3と5の倍数の数字がif文を通過するとき、3の倍数をFizzに置き換えるところで条件を満たしてしまうのでその先の処理は行わないからです。

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

ノーバンドルな開発ツール「Vite」を試してみる【no bundle dev enviroment for Vue 3.0】

最近猛烈にスターを集めている開発ツール、Viteを触ってみたので簡単に紹介します。
この記事はVite v0.19.1時点での情報です。

Viteとは?

ViteはVue.jsの作者のEvan You氏が開発中のノーバンドルな開発ツールです。
ネイティブのESモジュールのインポートを利用しバンドル不要で高速に動作するdevサーバーと、Rollup.jsをベースとしたプロダクションビルド機能を提供します。
設定不要で.vueのSFCをコンパイルできて、さらにデフォルトで今開発中のVue3.0が使えます。
しかも、vue-cliのようにVue.js限定ではなく、React、Preactにも対応しています。

スクリーンショット 2020-06-01 5.59.11.png

:warning: 注意

Still experimental, but we intend to make it suitable for production.

とある通り、まだ絶賛開発中です。プロダクションで使うのは控えた方が良さそうです。

何が嬉しいの?

VueのSFC(Single File Components)での開発ならVue CLIで良いのでは?と思うかもしれません。
Viteを使う利点は以下の通りです。

  • 開発時はバンドル不要で動作するので、大規模プロジェクトでも初回起動が非常に早い
  • HMR(画面の再描画無しにファイル変更をブラウザに適用してくれる機能)が、モジュールの総数と切り離されているため一貫して高速に動作する

ざっくり言うと総じて動作が早いです。開発体験が良い。
他詳細な説明はREADMEにも記載されているのでそちらもどうぞ。

https://github.com/vitejs/vite#how-and-why

インストールとプロジェクト作成

VueとReactのプロジェクト作成を簡単に説明します。

Vue

以下コマンドでプロジェクトを作成します。

$ npm init vite-app example-vue

これだけでOKです。example-vueにVueのプロジェクトが作成されます。
あとは依存モジュールをinstallして起動するだけです。

$ cd example-vue
$ npm i
$ npm run dev

スクリーンショット 2020-05-31 20.42.05.png

React

--template reactでReactのテンプレートを指定したうえでプロジェクトを作成します。

$ npm init vite-app --template react example-react

example-reactにReactのプロジェクトが作成されます。
あとはvueと同じく起動するだけです。

$ cd example-react
$ npm i
$ npm run dev

スクリーンショット 2020-05-31 20.47.13.png

ビルド

ViteではRollup.jsを内部的に使ってリソースをバンドルしプロダクションビルドが可能です。
buildコマンドでビルドが実行されます。

$ npm run build

dist配下に成果物が生成されるので、あとはそれを公開するだけです。

細かいビルドオプションを設定したい場合は、コマンド引数で指定するか、設定ファイルを作り記述できます。
以下成果物のディレクトリをoutディレクトリに変更する例です。

プロジェクトルートにvite.config.jsを追加して以下を記述します。

vite.config.js
module.exports = {
  outDir: "out"
}

あとはそのままビルドすればoutディレクトリに成果物が生成されます。

他設定可能なオプションはソースコードをみるのが良さそうです。
https://github.com/vitejs/vite/blob/master/src/node/config.ts

TypeScriptへの対応

TypeScriptへの対応も設定なしで可能です。Vueの場合は.vue<script lang="ts">を設定するだけ。
Reactの場合は、.jsx.tsxに変更するだけです。
.tsファイルもそのままimportできます。

App.vue
<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <HelloWorld msg="Hello Vue + Vite" />
</template>

<script lang="ts">
import HelloWorld from './components/HelloWorld.vue'
import { defineComponent } from 'vue'

// Composition APIなので defineComponent()を利用
export default defineComponent({
  name: 'App',
  components: {
    HelloWorld
  },
  setup() {
    return {}
  }
})
</script>

:warning: 注意点
2020/05/31現在、TypeScriptはJSへのトランスパイルのみ対応していて、型チェックは行えません。
基本的にエディタ上での型エラーの確認と、tsconfig.jsonを追加しビルド前にtsc --noEmitを利用して型チェックを行ってください。

PostCSS、Scssへの対応

Viteは、.vueファイルとインポートされた.cssファイルの全てにPostCSSを自動的に適用します。
必要なPostCSSのプラグインをnpmでインストールして、あとはpostcss.config.jsをプロジェクトルートに追加するだけです。

Autoprefixerの利用例。

$ npm i autoprefixer --save-dev
postcss.config.js
module.exports = {
  plugins: [
    require('autoprefixer')()
  ]
}

CSS Pre-Processorsのscssも、sassをnpmインストールして、.vueファイルで<style lang="scss">を指定するだけで利用できます。

$ npm i sass --save-dev
<style lang="scss">
/* scss */
</style>

終わりに

とても高速に動作するのでViteかなり良いですね。あと、Vue 3.0のsandbox環境としても最高です。
今回紹介した以外にも機能盛り沢山なので、是非READMEを読んで欲しいです。
https://github.com/vitejs/vite

今後もさらに機能が拡張されるようなので引き続き動向を見ていきたいです。

最後に。Evan You氏のVite作成時の投稿がカッコ良すぎる。
こんなこといつか言ってみたいです。

参考

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

ノーバンドルな開発ツール「Vite」を触ってみる 【no bundle dev enviroment for Vue 3.0】

最近猛烈にスターを集めている開発ツール、Viteを触ってみたので簡単に紹介します。
この記事はVite v0.19.1時点での情報です。

Viteとは?

ViteはVue.jsの作者のEvan You氏が開発中のノーバンドルな開発ツールです。
ネイティブのESモジュールのインポートを利用しバンドル不要で高速に動作するdevサーバーと、Rollup.jsをベースとしたプロダクションビルド機能を提供します。
設定不要で.vueのSFCをコンパイルできて、さらにデフォルトで今開発中のVue3.0が使えます。
しかも、vue-cliのようにVue.js限定ではなく、React、Preactにも対応しています。

スクリーンショット 2020-06-01 5.59.11.png

:warning: 注意

Still experimental, but we intend to make it suitable for production.

とある通り、まだ絶賛開発中です。プロダクションで使うのは控えた方が良さそうです。

何が嬉しいの?

VueのSFC(Single File Components)での開発ならVue CLIで良いのでは?と思うかもしれません。
Viteを使う利点は以下の通りです。

  • 開発時はバンドル不要で動作するので、大規模プロジェクトでも初回起動が非常に早い
  • HMR(画面の再描画無しにファイル変更をブラウザに適用してくれる機能)が、モジュールの総数と切り離されているため一貫して高速に動作する

ざっくり言うと総じて動作が早いです。開発体験が良い。
他詳細な説明はREADMEにも記載されているのでそちらもどうぞ。

https://github.com/vitejs/vite#how-and-why

インストールとプロジェクト作成

VueとReactのプロジェクト作成を簡単に説明します。

Vue

以下コマンドでプロジェクトを作成します。

$ npm init vite-app example-vue

これだけでOKです。example-vueにVueのプロジェクトが作成されます。
あとは依存モジュールをinstallして起動するだけです。

$ cd example-vue
$ npm i
$ npm run dev

スクリーンショット 2020-05-31 20.42.05.png

React

--template reactでReactのテンプレートを指定したうえでプロジェクトを作成します。

$ npm init vite-app --template react example-react

example-reactにReactのプロジェクトが作成されます。
あとはvueと同じく起動するだけです。

$ cd example-react
$ npm i
$ npm run dev

スクリーンショット 2020-05-31 20.47.13.png

ビルド

ViteではRollup.jsを内部的に使ってリソースをバンドルしプロダクションビルドが可能です。
buildコマンドでビルドが実行されます。

$ npm run build

dist配下に成果物が生成されるので、あとはそれを公開するだけです。

細かいビルドオプションを設定したい場合は、コマンド引数で指定するか、設定ファイルを作り記述できます。
以下成果物のディレクトリをoutディレクトリに変更する例です。

プロジェクトルートにvite.config.jsを追加して以下を記述します。

vite.config.js
module.exports = {
  outDir: "out"
}

あとはそのままビルドすればoutディレクトリに成果物が生成されます。

他設定可能なオプションはソースコードをみるのが良さそうです。
https://github.com/vitejs/vite/blob/master/src/node/config.ts

TypeScriptへの対応

TypeScriptへの対応も設定なしで可能です。Vueの場合は.vue<script lang="ts">を設定するだけ。
Reactの場合は、.jsx.tsxに変更するだけです。
.tsファイルもそのままimportできます。

App.vue
<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <HelloWorld msg="Hello Vue + Vite" />
</template>

<script lang="ts">
import HelloWorld from './components/HelloWorld.vue'
import { defineComponent } from 'vue'

// Composition APIなので defineComponent()を利用
export default defineComponent({
  name: 'App',
  components: {
    HelloWorld
  },
  setup() {
    return {}
  }
})
</script>

:warning: 注意点
2020/05/31現在、TypeScriptはJSへのトランスパイルのみ対応していて、型チェックは行えません。
基本的にエディタ上での型エラーの確認と、tsconfig.jsonを追加しビルド前にtsc --noEmitを利用して型チェックを行ってください。

PostCSS、Scssへの対応

Viteは、.vueファイルとインポートされた.cssファイルの全てにPostCSSを自動的に適用します。
必要なPostCSSのプラグインをnpmでインストールして、あとはpostcss.config.jsをプロジェクトルートに追加するだけです。

Autoprefixerの利用例。

$ npm i autoprefixer --save-dev
postcss.config.js
module.exports = {
  plugins: [
    require('autoprefixer')()
  ]
}

CSS Pre-Processorsのscssも、sassをnpmインストールして、.vueファイルで<style lang="scss">を指定するだけで利用できます。

$ npm i sass --save-dev
<style lang="scss">
/* scss */
</style>

終わりに

とても高速に動作するのでViteかなり良いですね。あと、Vue 3.0のsandbox環境としても最高です。
今回紹介した以外にも機能盛り沢山なので、是非READMEを読んで欲しいです。
https://github.com/vitejs/vite

今後もさらに機能が拡張されるようなので引き続き動向を見ていきたいです。

最後に。Evan You氏のVite作成時の投稿がカッコ良すぎる。
こんなこといつか言ってみたいです。

参考

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

TypeScriptで学ぶデザインパターン〜Interpreter編〜

対象読者

  • デザインパターンを学習あるいは復習したい方
  • TypeScriptが既に読めるあるいは気合いで読める方
    • いずれかのオブジェクト指向言語を知っている方は気合いで読めると思います
  • UMLが既に読めるあるいは気合いで読める方

環境

  • OS: macOS Mojave
  • Node.js: v12.7.0
  • npm: 6.14.3
  • TypeScript: Version 3.8.3

本シリーズ記事一覧

Interpreterパターンとは

解決したい問題を簡単なミニ言語で表現するためのパターンです。

サンプルコード

Interpreterパターンで作られたクラス群がどんなものになるのか確認していきましょう。

今回は、題材として"足し算をする機能"を想定します。GitHubにも公開しています。

具体的には2 1 3 + +という文字列を渡すと2 + 1 + 3の結果(6)を出力するプログラムを考えます。

modules/Expression.ts

構文木のノードを示すインターフェースです。

Expression.ts
export default interface Expression {
  interpret(numbers: number[]): void;
}

interpretで構文解析を行います。

modules/ExpressionNumber.ts

数値ノードを示すクラスです。

ExpressionNumber.ts
import Expression from "./Expression";

export default class ExpressionNumber implements Expression {
  private number: number;
  constructor(number: number) {
    this.number = number;
  }
  interpret(numbers: number[]): void {
    numbers.push(this.number);
  }
}

interpretでは数値をnumbersに追加していきます。

modules/ExpressionPlus.ts

足し算ノードを示すクラスです。

ExpressionPlus.ts
import Expression from "./Expression";

export default class ExpressionPlus implements Expression {
  interpret(numbers: number[]): void {
    numbers.push(numbers.pop() + numbers.pop());
  }
}

interpretでは数値をnumbersから2つ取り出してそれを足したものをnumbersに格納しています。

modules/Parser.ts

構文解析を行うためのクラスです。

Parser.ts
import Expression from "./Expression";
import ExpressionPlus from "./ExpressionPlus";
import ExpressionNumber from "./ExpressionNumber";

export default class Parser {
  private parseTree: Expression[] = [];

  constructor(string: string) {
    for (const part of string.split(' ')) {
      if (part === '+') {
        this.parseTree.push(new ExpressionPlus);
      } else {
        this.parseTree.push(new ExpressionNumber(parseInt(part)));
      }
    }
  }

  evaluate(): number {
    const context: number[] = [];
    for (const node of this.parseTree) {
      node.interpret(context);
    }
    return context.pop();
  }
}

evaluateでは設定したノードを一つずつ取り出して構文解析しています。

Main.ts

本パターンで作成されたクラス群を実際に使用する処理が書かれています。

Main.ts
import Parser from "./modules/Parser";

const expression: string = '5 2 19 + +';
const parser: Parser = new Parser(expression);
console.log(parser.evaluate());

クラス図

ここまでInterpreterパターンで作られたクラス群を1つずつ確認してきました。次にクラス図を示します。Interpreterパターンの全体像を整理するのにお役立てください。

Interpreter.png

  • AbstractExpression: サンプルコードではExpressionインターフェースが対応
  • TerminalExpression: サンプルコードでは終端となる表現がないため該当なし(start 2 2 + endのような構文解析をさせる場合endに該当する表現が終端になる)
  • NonTerminalExpression: サンプルコードではExpressionNumberクラスExpressionPlusクラスが対応
  • Context: サンプルコードではParserクラスが対応
  • Client: サンプルコードではMainが対応

LucidChartを使用して作成

解説

最後に、このデザインパターンの存在意義を考えます。

普通はなんらかの処理をさせる場合はTypeScriptのプログラミング言語しか登場しないと思います(当然ですが)。ところが今回はミニ言語とミニ言語を解析するTypeScriptのプログラムといういわば2階層になっています。ということはミニ言語を修正したい場合は当然TypeScriptのプログラムを修正する必要はありません。このように、修正をミニ言語に止めることができるのです。

補足

サンプルコードの実行方法はこちらと同様です。

参考

あとがたり

終端をクラスで表現するようサンプルコードをいつか書き換えたい。BNFは基本情報で少し触れてから全く関わりがないため個人的に難しい。

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

React hooksを基礎から理解する (useEffect編)

React hooksとは

React 16.8 で追加された新機能です。
クラスを書かなくても、stateなどのReactの機能を、関数コンポーネントでシンプルに扱えるようになりました。

  • React hooksを基礎から理解する (useState編)
  • React hooksを基礎から理解する (useEffect編) :point_left_tone2: 今ここ
  • React hooksを基礎から理解する (useContext編)
  • React hooksを基礎から理解する (useReducer編)
  • React hooksを基礎から理解する (useCallback編)
  • React hooksを基礎から理解する (useMemo編)
  • React hooksを基礎から理解する (useRef編)

useEffectとは

useEffectを使うと、useEffectに渡された関数はレンダーの結果が画面に反映された後に動作します。
つまりuseEffectとは、「関数の実行タイミングをReactのレンダリング後まで遅らせるhook」になります。

副作用の処理(DOMの書き換え、変数代入、API通信など)を関数コンポーネントで扱えます。
クラスコンポーネントでのライフサイクルメソッドに当たります。

  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

参考:React公式サイト 副作用フックの利用法

create-react-appでReactのコードを書く

create-react-appをひさしぶりに npm installしようとしたらテンプレートが出来ない?
困っていて見つけた記事。解決出来ました。ありがとうございます。

参考:ひさしぶりにcreate-react-appしたらテンプレートができなかった時の対処法

Material-UIをinstall

Material-UIをinstallしたら、使いたいコンポーネントをすぐ見つけられるし、勝手にスタイリングしてくれるのでテンションあがります?

$ npm install @material-ui/core

参考:MATERIAL-UI

クリックしたらタイトルも同時に変更されるコンポーネントを作る

クラスコンポーネント

react.js
import React, { Component } from 'react'
import ButtonGroup from '@material-ui/core/ButtonGroup';
import Button from '@material-ui/core/Button';

class EffectClass extends Component {
  constructor(props){
    super(props);
    this.state = {
      count: 0,
    }
  }

  componentDidMount(){
    document.title =`${this.state.count}回クリックされました`
  }

  componentDidUpdate(){
    document.title =`${this.state.count}回クリックされました`
  }

  render() {
    return (
      <>
        <p>{`${this.state.count}回クリックされました`}</p>
        <ButtonGroup color="primary" aria-label="outlined primary button group">
          <Button onClick={()=> this.setState({count: this.state.count + 1})} >
            ボタン
          </Button>
          <Button onClick={()=> this.setState({count: 0})}>
            リセット
          </Button>
        </ButtonGroup>
      </>
    )
  }
}

export default EffectClass

関数コンポーネント

react.js
import React, {useState, useEffect} from 'react'
import ButtonGroup from '@material-ui/core/ButtonGroup'
import Button from '@material-ui/core/Button'

const EffectFunc = () => {
  const [count, setCount] = useState(0)
  useEffect(() => {
    document.title =`${count}回クリックされました`
  })

  return (
    <>
      <p>{`${count}回クリックされました`}</p>
      <ButtonGroup color="primary" aria-label="outlined primary button group">
        <Button onClick={()=>setCount((prev) => prev + 1)}>
          ボタン
        </Button>
        <Button onClick={()=>setCount(0)}>
          リセット
        </Button>
      </ButtonGroup>
    </>
  )
}

export default EffectFunc

クラスコンポーネントの場合

副作用は ReactがDOMを更新したあとに起こすようにしたいので、componentDidMountcomponentDidUpdateに記載します。するとReactDOMに変更を加えた後に、document.titleを更新しています。

react.js
  componentDidMount(){
    document.title =`${this.state.count}回クリックされました`
  }

  componentDidUpdate(){
    document.title =`${this.state.count}回クリックされました`
  }

関数コンポーネントでuseEffectを使った場合

デフォルトでは、useEffectは毎回のレンダー後に呼ばれます。

react.js
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title =`${count}回クリックされました`
  })

React公式サイトのstate とライフサイクルをもう一度読むと理解が進みました。
参考:React公式サイト state とライフサイクル

useEffectの第2引数に配列を渡して特定の値が変わったときだけ再レンダーさせる

useEffect()の第2引数に[count]を渡すと、countに変化があったときだけ再レンダーします。

react.js
import React, {useState, useEffect} from 'react'
import { makeStyles } from '@material-ui/core/styles';
import ButtonGroup from '@material-ui/core/ButtonGroup'
import Button from '@material-ui/core/Button'
import Input from '@material-ui/core/Input';

const useStyles = makeStyles((theme) => ({
  root: {
    '& > *': {
      margin: theme.spacing(1),
    },
  },
}));

const EffectFunc = () => {
  const classes = useStyles();
  const [count, setCount] = useState(0)
  const [name, setName] = useState({
    lastName: '',
    firstName: ''
  })
  useEffect(() => {
    document.title =`${count}回クリックされました`
  },[count])

  return (
    <>
      <p>{`${count}回クリックされました`}</p>
      <ButtonGroup color="primary" aria-label="outlined primary button group">
        <Button onClick={()=>setCount((prev) => prev + 1)}>
          ボタン
        </Button>
        <Button onClick={()=>setCount(0)}>
          リセット
        </Button>
      </ButtonGroup>
      <p>{`私の名前は${name.lastName} ${name.firstName}です`}</p>
      <form className={classes.root} noValidate autoComplete="off">
        <Input
          placeholder=""
          value={name.lastName}
          onChange={(e)=>{setName({...name,lastName: e.target.value})}}/>
        <Input
          placeholder=""
          value={name.firstName}
          onChange={(e)=>{setName({...name,firstName: e.target.value})}}/>
      </form>
    </>
  )
}

export default EffectFunc
useEffectの第2引数に[count]を取るとき
react.js
  useEffect(() => {
    document.title =`${count}回クリックされました`
    console.log(`再レンダーされました`)
  },[count])

nameが更新されても再レンダーされず、countが更新された場合だけ、document.titleが再レンダーされていることがわかります。

第2引数に[count]を取らないとき

useEffectはデフォルトで、副作用関数は初回のレンダー時および毎回の更新時に呼び出されます。

react.js
  useEffect(() => {
    document.title =`${count}回クリックされました`
    console.log(`再レンダーされました`)
  })

countが更新された場合だけだけでなく、nameが更新された場合にも、document.titleが再レンダーされていることがわかります。

第2引数にからの配列[]を取ったとき

レンダリングは初回のみで再レンダーされないので、document.titleは更新されません。

react.js
  useEffect(() => {
    document.title =`${count}回クリックされました`
    console.log(`再レンダーされました`)
  },[])

クリーンアップについて

クリーンアップとはイベントリスナの削除、タイマーのキャンセルなどのことです。
クリーンアップ関数をreturnすると、2度目以降のレンダリング時に前回の副作用を消してしまうことができます。

クラスコンポーネントの場合

componentWillUnmountは、クリーンアップ(addEventLitenerの削除、タイマーのキャンセルなど)に使用されます。componentDidMountに副作用を追加し、componentWillUnmountで副作用を削除します。

react.js
componentDidMount() {
  elm.addEventListener('click', () => {})
}

componentWillUnmount() {
  elm.removeEventListener('click', () => {})
}
関数コンポーネントの場合

上記に相当するhookは以下。「クリーンアップ関数」をreturnすることで、2度目以降のレンダリング時に前回の副作用を消してしまうことができます。

react.js
useEffect(() => {
   elm.addEventListener('click', () => {})

  // returned function will be called on component unmount 
  return () => {
     elm.addEventListener('click', () => {})
  }
}, [])

最後に

次回は useContext について書きたいと思います。

参考にさせていただいたサイト

https://reactjs.org/

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