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

急にaxiosでURLリクエストできなくなった!?

何が起きたのか

スクリーンショット 2019-08-28 19.46.36.jpg

今まで普通に運用できていたシステムでaxiosを利用してURLリクエストを行なっている箇所から上記のようなエラーが。。


from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.


あ〜CORSのエラーね、
ん〜
CORS周辺なんか最近いじったかなぁ?
まぁ調べてみるか

const functions = require('firebase-functions');
const corsLib = require('cors');
const cors = corsLib();

exports.say = functions.https.onRequest((request, response) => {
    return cors(request, response, () => {
        response.send("Hello from Firebase!");
    })
});

もともとこんな感じで書かれていました。
これの関数の中身がしっかり書かれている感じ

ちゃんとCORS許可してるぞ〜??

気がつけば3時間ハマってた。

CORSの問題ではなかった

corsのバージョンを変えてみたり、
firebaseのcloud functionsが悪いのかとデプロイし直したり、
しかし原因がわからない。
簡単なログを吐くfunctionを作成して呼んでみると、
動いた…

ますますわからん、cors全然問題ないじゃん…

色々調べてやっとわかった。
今回のバグ、もともとはメールを送るcloud functionだったのだが、
メールの本文をaxiosのgetでクエリパラメータとして渡していた。
今回ユーザーが長文を送ったことでバグが発生。
どうやらaxiosのget関数は渡せるクエリパラメータの長さに限界があるみたいだ。

axiosの関数をgetからpostに変更すると解決!

ちゃんとデータを来るときはpost使おうねというお話。

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

Vue.js:Vuexのエラー:「[vuex] Do not mutate vuex store state outside mutation handlers」の原因と対応例

原因

  • コンポーネントの「v-model」に、stateの値を設定していたため。
  • v-modelは参照と設定を同時に行うため、タイトルのエラーが発生する。
issue.vue
<template>
 <!-- v-modelにstateの値を設定 ->
 <v-test-comp v-model="theValue"></v-test-comp>
</template>

<script>
import { mapState } from "vuex";

export default {
  computed: {
    //stateの値にアクセスできるよう設定
    ...mapState(["theValue"])
  }
}
</script>

対応

  • v-modelに設定する値を算出プロパティに置き換える。
  • 算出プロパティのgetでstateの値を返却し、set内で、storeのactionを呼び出す。
resolve.vue
<template>
 <!-- v-modelにstateの値を設定 ->
 <v-test-comp v-model="theValue"></v-test-comp>
</template>

<script>
import { mapState } from "vuex";

export default {
  computed: {
    theValue: {
      get() {
        return this.$store.theValue
      },
      set(value) {
        this.$store.dispatch("setTheValue", value)
      }
    }
  }
}
</script>

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

個人的にwebスクレイピングでよく使うメソッド(Javascript)

備忘録としてNode.jsでWebスクレイピングをする際に個人的によく使うメソッドをまとめておく。

前処理編

filter

配列にフィルターを施す。
任意の条件に対して返り値がtrueであった値のみ出力される。
外部ファイルからデータを読み込むようにしていたため、不正なURLやパラメータが記載されていた場合は排除するようにしていた

example.js
var urlList= ["https://example.co.jp/item", "https://example.co.jp/list", "https://exmple.co.jp/user"];
var filteredUrlList = urlList.filter(url => url.includes("https://example.co.jp"));
console.log(filteredUrlList ) // Array ["https://example.co.jp/item", "https://example.co.jp/list"]

splice

配列から指定した値を削除したり追加したりする。
filterと似たような使い方をしていたため、どちらかというと配列内のデータを削除したいときに使う。

toLowerCase & toUpperCase

対象の文字を大文字もしくは小文字に変換する。
外部ファイルから読み込んだデータをURLのパラメーターとして使う際によく置換していた

データ加工編

replace

対象の文字を指定した文字に変更できる。
正規表現と組み合わせて余分な空白を削除したり日付を任意のフォーマットに変更するときによく使う。

example.js
var date = "2018/02/21";
var modifiedDate = date.replace(/\/,-g/, "-");
console.log(modifiedDate) // "2018-02-21"

substring & substr

対象の文字列から指定したインデックスの文字を取得する。
substringは 第二引数は終了地点であるのに対し、substrは取得文字数を意味している。
**

example.js
var date = "2018/02/21 12:32";
var modifiedDate = date.substring(0, 10);
console.log(modifiedDate) // "2018/02/21"

【追記】
確認すると、どうやらsubstrは非推奨とまではいかないものの、できるだけ使用は避けるべきと公式に記載がされている。

出力編

push & contat

指定したデータを配列の一番後ろに追加することができる。
push を使うとpushされる前のデータはなくなる(破壊的結合)が、contat を使うとその前のデータは保持される(非破壊的結合)という違いがある。
Webスクレイピングで取得したデータを出力対象に追加する際に使う。

example.js
var items = ["a", "b", "c", "d"]; // So Many Data
var newItem= "e";
items.push(newItem);
console.log(items); // Array ["a", "b", "c", "d", "e"]

slice

配列もしくは文字列から指定した範囲を抜き取る。
spliceとは異なり、以前のデータは破壊されない。
取得したデータを分割して出力する際に使う

example.js
var items = ["a", "b", "c", "d", "more"]; // So Many Data
for (i=0, i*50 >item.length, i++) {
    var list= date.slice(i*50, (i+1)*50);
    console.log(list) // "50件ずつ出力"
};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactのComponentの中での条件分岐

子要素を分岐させたい時のメモ。
基本的に、子要素はReact.createElementの第3引数になるので、
JSXのタグの間に挟まるものは式ではなくてはならない。
が、javascriptのifは式ではなくて文なので少し面倒。

対応策1:三項演算子を使う

三項演算子を使えば文ではなく式になるのでOK

<div>
 {someCondition ? <Hoge/> :<PIYO/>}
</div>

対応策2

無名関数中でif+returnを書き即実行する。

<div>
  {(() => {
      if(someCondition){
         return <HOGE/>;
      }
      return <PIYO/>;
    })()}
</div>

対応策3

対応策2の応用で関数部分を関数コンポーネント化する

const HogePiyo = (props) => {
      if(props.someCondition){
         return <HOGE/>;
      }
      return <PIYO/>;
    };


//中略
render () {
  return ( <div>
             <HogePiyo someCondition={someCondition}/>
           </div>);
}


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

github からユーザを検索する javascript と、それをテストする mocha (コンソール版)

github からユーザを検索する

元ネタは現代の JavaScript チュートリアル

rethrow.js
'use strict';

const fetch = require('node-fetch');

const rl =
  require('readline')
    .createInterface({ input: process.stdin, output: process.stdout });

class HttpError extends Error {
  constructor(response) {
    super(`${response.status} for ${response.url}`);
    this.name = 'HttpError';
    this.response = response;
  }
}

function loadJson(url) {
  return fetch(url)
    .then(response => {
      if (response.status == 200) return response.json();
      else                        throw new HttpError(response);
    });
}

function demoGithubUser() {
  rl.question('Enter a name? : ', name => {
    return loadJson(`https://api.github.com/users/${name}`)
      .then(user => {
        console.log(`Full name: ${user.name}.`);
        rl.close();
        return user;
      })
      .catch(err => {
        if (err instanceof HttpError && err.response.status == 404) {
          console.log('No such user, please reenter.');
          return demoGithubUser();
        } else {
          throw err;
        }
      });
  });
}

demoGithubUser();

実行結果

rethrow.jpg

mocha でテスト

3つテストがあります。それぞれ node を立ち上げます。

test.js
'use strict';

const assert = require('assert');
const child_process = require('child_process');

let proc;

describe("rethrow", () => {
  beforeEach(() =>
    proc = child_process.spawn('node', ['./rethrow.js'], { stdio: 'pipe' })
  )

  it('should output a prompt.', done => {
    proc.stdout.once('data', output => {
      assert.equal(output, 'Enter a name? : ');
      proc.stdin.end();
      done();
    });
  });

  it('should accept a name', done => {
    proc.stdout.once('data', output => {
      proc.stdin.write('matz\n');
      proc.stdout.once('data', output => {
        assert.equal(output, 'Full name: Yukihiro "Matz" Matsumoto.\n');
        done();
      });
    });
  })

  it('should answer for user who is none', done => {
    proc.stdout.once('data', output => {
      proc.stdin.write('no-such-user\n');
      proc.stdout.once('data', function (output) {
        assert.equal(output, 'No such user, please reenter.\n');
        proc.stdin.end();
        done();
      });
    });
  })
});

実行結果

test.jpg

参考にしたページ

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

JS-Interpreterの勉強メモ

JS-Interpreterとは、JavaScriptで記述されたサンドボックス化されたJavaScriptインタープリターです。Javascriptコードを実行する時には、任意のJavaScriptコードを1行ずつ実行に行きます。JavaScript環境から完全に分離されています。JS-Interpreterのインスタンスが複数に作られた場合は、Web稼働環境(ブラウザーなんとか)を使用せずに複数のJavaScriptコード同時にマルチスレッドで実行することが可能です。

環境準備

以下の2つのJavaScriptソースファイルをインポートします。

<script src="acorn.js"></script> 
<script src="interpreter.js"></script>

または、圧縮バンドルを使用します。

<script src = "acorn_interpreter.js"> </ script>

使い方1:外部API無し

外部APIが使わない場合は、JS-Interpreterの初期化関数を使用せずに、インスタンスを作ります。
Javascriptコードをステップごとに実行するには、step関数をfalseを返すまでに繰り返し呼び出します。
コードがが無限ループではなく安全なコードだって言うことが分かれば、run関数を1回のみ呼び出してコードを最後までに実行することが構わないです。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Blockly Demo: Step Execution with JS Interpreter</title>
  <script src="acorn_interpreter.js"></script>
</head>
<body>
    <button onclick="runCode()" id="runButton">Run JavaScript</button>
    <button onclick="stepCode()" id="stepButton">Step JavaScript</button>
 <script>
    var runButton = document.getElementById('runButton');
    var stepButton = document.getElementById('stepButton');
    var myInterpreter = null;
    var myCode = 'var a=1; for(var i=0; i<4; i++ ){a=a*i;};';
    myInterpreter = new Interpreter(myCode);
    function stepCode() {
        if(this.myInterpreter.step()){
            window.setTimeout(stepCode,0);
        }
    }
    function runCode() {
        myInterpreter.run();
    }
</script>
</body>
</html>

使い方2:外部APIあり(標準Javascriptコードの場合)

外部APIが使った場合は、JS-Interpreterの初期化関数を利用してインスタンスを作ります。
ただし、例えば、外部APIがalertようなJavascriptの標準コードであれば、JS-Interpreterの初期化関数を作成する時には、Javascriptのコードを定義することが必要だけです

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Blockly Demo: Step Execution with JS Interpreter</title>
  <script src="acorn_interpreter.js"></script>
</head>
<body>
    <button onclick="runCode()" id="runButton">Run JavaScript</button>
    <button onclick="stepCode()" id="stepButton">Step JavaScript</button>
 <script>
    var runButton = document.getElementById('runButton');
    var stepButton = document.getElementById('stepButton');
    var myInterpreter = null;
    var myCode = 'var a=1; for(var i=0; i<4; i++ ){a=a*i;alert(i);};';
    //初期化関数
    var initFunc = function(interpreter, scope) {
        var wrapper = function(text) {
            return alert(text);
        };
        interpreter.setProperty(scope, 'alert', interpreter.createNativeFunction(wrapper));     
    };
    myInterpreter = new Interpreter(myCode, initFunc);
    function stepCode() {
        if(this.myInterpreter.step()){
            window.setTimeout(stepCode,0);
        }
    }
    function runCode() {
        myInterpreter.run();
    }
</script>
</body>
</html>

使い方2:外部APIあり(関数が自分定義した場合)

外部APIがJavascriptの標準コードではなく、自分で定義した関数を実行する場合には、JS-Interpreterの初期化関数を作成する時に、自分の関数を定義することが必要で、さらに、この関数の処理の詳細も記述する関数も定義しないと行けません

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Blockly Demo: Step Execution with JS Interpreter</title>
  <script src="acorn_interpreter.js"></script>
</head>
<body>
    <button onclick="runCode()" id="runButton">Run JavaScript</button>
    <button onclick="stepCode()" id="stepButton">Step JavaScript</button>
 <script>
    var runButton = document.getElementById('runButton');
    var stepButton = document.getElementById('stepButton');
    var myInterpreter = null;

    var myCode = 'var a=1; for(var i=0; i<4; i++ ){a=a*i;console_log(i);};';

    var initFunc = function(interpreter, scope) {
        var wrapper = function(text) {
            return console_log(text);
        };
        interpreter.setProperty(scope, 'console_log', interpreter.createNativeFunction(wrapper));
    };
    myInterpreter = new Interpreter(myCode, initFunc);
    function stepCode() {
        if(this.myInterpreter.step()){
            window.setTimeout(stepCode,0);
        }
    }
    function runCode() {
        myInterpreter.run();
    }
    function console_log(value){
        console.log(value.data);
    }
</script>
</body>
</html>

実行結果は下記です。
タイトルなし.png

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

AmazonConnectでオペレータ側の音声をリアルタイムに取得してみた

概要

今回はamazon-connect-streamsconnect-rtc-jsを使うことでAmazonConnectのオペレータの音声をリアルタイムに取得してみました。音声をリアルタイムに取得することができたことで、COTOHA等の音声認識システムに入れるなどの幅が広がります。

なぜそんなことをしたの?

 AmazonConnectにはリアルタイムに音声を取得する方法として「リアルタイム顧客音声ストリーム機能」が存在します。
 しかしこの機能では、お客様側(カスタマ側)の音声しかリアルタイムに取得することができません。
 そこで今回はAmazonConnectのオペレータ側の音声をリアルタイムに取得してみました。

イメージ図

image.png
※今回はCCPを動かすサーバ(取得した音声を操作するサーバ)をAWSのS3上に作っています。

作成方法

前提

  • 実行する環境にNode.jsがインストールされていること
  • 実際に動作をさせるにはAmazon Connectに1度ログインをしていること

ライブラリの取得とビルド

  1. ソフトフォンを使用するためのソースを右のリンクから取得します。amazon-connect-streams
  2. 通話しているストリームを取得するためのソースを右のリンクから取得します。connect-rtc-js
  3. 1で取得したソースをビルドします。amazon-connect-streamsをおいたディレクトリにてmakeを実施してください。実施するとamazon-connect-${version}.jsといったファイルが作られます。(versionの部分は変化します)。

HTML側の作成

  1. 自作のhtmlを作成し、以下のようにコードを書きます。
    amazon-connect-1.3-12-xxxxxxxx.jsはビルドしてできたjavascriptのファイル名に置き換えてください。
    URLでxxxx.awsapps.comとしている個所はAmazon Connectのインスタンスに置き換えてください。
    divのidに指定しているcontainerDivとremote-audioは変更しないでください。
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Custom CCP</title>
    <script type="text/javascript" src="amazon-connect-1.3-12-xxxxxxxx.js"></script>
    <script type="text/javascript" src="connect-rtc.js"></script>
</head>

<body>
    <center>
        <div id="containerDiv" style="width: 320px; height: 465px"></div>
        <div><audio id="remote-audio" controls><p>音声を再生するには、audioタグをサポートしたブラウザが必要です。</p></audio></div>
        <div><audio id="user-audio" controls><p>音声を再生するには、audioタグをサポートしたブラウザが必要です。</p></audio></div>
    </center>
</body>
<script type="text/javascript">
    const ccpUrl = "https://xxxx.awsapps.com/connect/ccp#/";

    connect.core.initCCP(containerDiv, {
        ccpUrl:        ccpUrl,
        loginPopup:    true,
        softphone: {
            allowFramedSoftphone: false
        },
    });
    connect.core.initSoftphoneManager({allowFramedSoftphone: true});

    // コンタクトイベントの取得
    connect.contact(function(contact) {
        var softphoneMediaInfo = contact.getAgentConnection().getSoftphoneMediaInfo();

        var remoteAudio = document.getElementById("remote-audio");
        var mediaInfo = JSON.parse(softphoneMediaInfo);
        var rtcConfig = mediaInfo.webcallConfig || JSON.parse(mediaInfo.callConfigJson);//mediaInfo.webcallConfig is used internally by Amazon Connect team only
        var session = new connect.RTCSession(rtcConfig.signalingEndpoint,
            rtcConfig.iceServers,
            mediaInfo.callContextToken,
            console);
        session.echoCancellation = true;
        session.remoteAudioElement = remoteAudio;
        session.forceAudioCodec = 'OPUS';
        session.onSessionConnected = () => {
            statsCollector = setInterval(() => {
                var collectTime = new Date();
                Promise.all([session.getUserAudioStats(), session.getRemoteAudioStats()]).then((streamStats) => {
                    console.log(collectTime + " Audio statistics : " + JSON.stringify(streamStats));
                });
            }, 2000);
        };
    });
 </script>
</html>

amazon-connect-1.3-12-xxxxxxxx.jsの修正

1.このままではマイクから音声を取得するためのコードがないので、マイクから音声を取得するためのコードを書きます。

今回は通話の開始時にマイクの音声を取得するために、ビルドしてできたamazon-connect-1.3-12-xxxxxxxx.jsの
変数onRefreshContactに定義されている関数の末尾に以下のコードを追加します。

「amazon-connect-streams」内にあるsoftphone.jsのソースコードを変更して、再度ビルドしても同じ結果となります。

var handleSuccess = function(stream) {
    document.getElementById('user-audio').srcObject = stream;
};
navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(handleSuccess);

2.通話が終了したときにマイクの音声の再生が終了するように変数deleteLocalMediaStreamに定義されている関数の末尾に以下のコードを追加します。

こちらも「amazon-connect-streams」内にあるsoftphone.jsのソースコードを変更して、再度ビルドしても同じ結果となります。

document.getElementById('user-audio').srcObject = null;

HTMLとjavascriptの配備

今回はcloudfrontを使用して、S3に置いたファイルを使用して自作のHTMLファイルを表示させ動作確認を行います。
S3にバケットを作成して、作成したhtmlファイル、amazon-connect-1.3-12-xxxxxxxx.js、connect-rtc.jsを配備します。
S3に配置したhtmlファイルをwebページとして表示するための手順はこちらの記事を参考にしました。
特定バケットに特定ディストリビューションのみからアクセスできるよう設定する
cloudfrontで公開するURLが発行されたら、Amazon Connectのアプリケーション統合画面でURLを追加します。その際のURLはxxxxxxxxx.cloudfront.netまでにしてください。

image.png

動作確認

アプリケーション統合画面で追加した画面にアクセスを行うと以下のような画面が表示されます。
初めてアクセスする場合はマイクの使用許可のポップアップが表示されるので許可をしてください。
CCPカスタム初期画面.jpg

通話中画面(電話番号部分は隠してあります)
ソフトフォンの方が電話をかけ始めてすぐにストリームが作成されますが、マイク側のstreamは経過時間と同じ時間であることが確認できます。
CCPカスタム通話中画面.jpg

今後の利用

これを応用すれば、マイクから取得した音声をCOTOHA 音声認識APIに投げる、Google Speech to Textに投げるなどの応用が考えられます。
機会があればそちらも記事にしたいと思います。

参考資料

特定バケットに特定ディストリビューションのみからアクセスできるよう設定する
https://dev.classmethod.jp/cloud/aws/cloudfront-s3-origin-access-identity/

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

ES6のジェネレーター

ジェネレーターとは

for...ofループの動作を簡単にカスタマイズできる仕組み。

サンプルコード

簡単な例

function* colors() {
  yield 'red';
  yield 'blud';
  yield 'green';
}

const myColors = [];
for (let color of colors()) {
  myColors.push(color)
}

iteratorを別に実装する例

const testingTeam = {
  lead: '由紀子',
  tester: ''
}

const engineeringTeam = {
  testingTeam,
  size: 3,
  department: '開発部',
  lead: '太郎',
  manager: '花子',
  engineer: '次郎'
};

function* TestingTeamIterator(team) {
  yield team.lead;
  yield team.tester;
}

function* TeamIterator(team) {
  yield team.lead;
  yield team.manager;
  yield team.engineer;
  const testingTeamGenerator = TestingTeamIterator(team.testingTeam);
  yield* testingTeamGenerator;
}

const names = [];
for(let value of TeamIterator(engineeringTeam)) {
  names.push(value);
}

iteratorを組み込んだ例

const testingTeam = {
  lead: '由紀子',
  tester: '',
  [Symbol.iterator]: function* () {
    yield this.lead;
    yield this.tester;
  }
}

const engineeringTeam = {
  testingTeam,
  size: 3,
  department: '開発部',
  lead: '太郎',
  manager: '花子',
  engineer: '次郎',
  [Symbol.iterator]: function* () {
    yield this.lead;
    yield this.manager;
    yield this.engineer;
    yield* this.testingTeam;
  }
};

const names = [];
for(let value of engineeringTeam) {
  names.push(value);
}

ツリー構造を再帰で呼び出す例

class Comment {
  constructor(content, children) {
    this.content = content;
    this.children = children;
  }
  *[Symbol.iterator]() {
    yield this.content;
    for (let child of this.children) {
      yield* child;
    }
  }
}

const children = [
  new Comment("賛成", []),
  new Comment("反対", []),
  new Comment("うーん", [])
    ]

const tree = new Comment("いい記事ですね!", children);

const nodes = [];
for (let node of tree) {
  nodes.push(node);
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LINEBotのカルーセル作成でハマった話

作ろうとしてたもの

写真をとって送信すると、受け取った画像に対して画像検索をして
その画像を同じであろう商品をリストアップして買取価格をカルーセル表示してくれるLinebot

詰まった点

linesdkが画像を送ってくる仕組みが
メッセージの中にmessage.typeみたいなパラメータを持っていて、
それがimageとかデータが入ってるものだと

client.getMessageContent(message.id)

みたいな感じで自力でデータを取りに行かないといけない仕組みなんだけど

await client.getMessageContent(message.id)
.then(async(stream) => {
  stream.on('data', chunk =>{
     //ここにデータの受け取りの処理を書こうとした。
  });
});

こんな感じでデータを取ろうとしてここのデータの引数のchunkに画像データのバイナリが入ってるのかな〜みたいな予測で画像をFirebaseに保存してみた

すると
画像の上の部分だけ保存された変なデータが・・・

色々調べて、非同期の処理が途中で終了してしまってるのかとか色々考えるんだけど、答えなし。

ふとソースを見てみるとstreamとか書いてる。
そういえば昔C#で見たことあるような言葉だなぁと思い検索すると案の定
要はStreamはデータを小分けで送ってくれるような仕組みらしい
便利に使えれば処理のストップがなくて素晴らしいんだけど、
LineSDKのドキュメント、バイナリで返すって書いてるやん、
バイナリをStreamで返すって言ってよ・・・

修正しました

こんな感じ

tempfilepath = "/tmp/example.jpg"
const dest = fs.createWriteStream(tempfilepath, 'binary')
await client.getMessageContent(message.id)
.then(async(stream) => {
  stream.on('data', chunk => dest.write(chunk));
  stream.on('end', async() => {
    dest.end()
    //ここでさっき保存したデータをリードして使う
  });
}); 

こんな感じで修正しました。

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

PlayCanvasでJavasScriptのクラス構文を使って開発をする

PlayCanvasとは?

PlayCanvasは、インタラクティブなウェブコンテンツ用のビジュアル開発プラットフォームです。作成するツールとウェブアプリは、どちらもHTML5を使用しています。 プラットフォームはウェブでホストされているため、インストールするものは何もなく、対応されているウェブブラウザを実行する任意のデバイスからアクセスできます。

PlayCanvas ユーザーマニュアルより

PlayCanvasのpcオブジェクトの拡張について

PlayCanvasで拡張機能を作る場合にはよくpc.extendを使用して拡張を行います。
Spineのプラグインなどもその方法で作られています。

PlayCanvasのGUIからスクリプトを作ると初期状態ではこのような状態でスクリプトが生成され、スクリプトが適用されます。

スクリプト作成をした初期状態

rotate.js
var Rotate = pc.createScript('rotate');

Rotate.prototype.initialize = function() {
};
Rotate.prototype.update = function(dt) {
};

回転をアップデート毎に行うスクリプト

rotate.js
var Rotate = pc.createScript('rotate');

Rotate.prototype.initialize = function() {
};
Rotate.prototype.update = function(dt) {
    this.entity.rotate(0,1,0)
};

今回作ったスクリプトを事前に読み込んでおくとおくと以下の流れでScriptを記述することができるようになります。

rotate.js
const registerScript = (App, attributeses) => {
  const name = App.name.toLowerCase();
  const app = pc.createScript(name);
  if (attributeses !== undefined) {
    const attributes = Object.values(attributeses);
    for (let attr of attributes) {
      Object.entries(attr).forEach(item => {
        app.attributes.add(item[0], item[1]);
      });
    }
  }
  Object.setPrototypeOf(app.prototype, App.prototype);
};

class Rotate {
  initialize() {}

  update() {
    this.entity.rotate(0, 1, 0);
  }
}

registerScript(Rotate);

//PlayCanvasエディター上にregisterScriptのコードをコピーした場合は pc.registerScript(Rotate)で登録ができます。
// External Scriptsから読み込んだ場合は registerScriptとして呼び出します。

とすると回らせることができます。

補足

属性(アトリビュート)を追加することでGUIから色々値を変更できるようになります。

const registerScript = (App, attributeses) => {
  const name = App.name.toLowerCase();
  const app = pc.createScript(name);
  if (attributeses !== undefined) {
    const attributes = Object.values(attributeses);
    for (let attr of attributes) {
      Object.entries(attr).forEach(item => {
        app.attributes.add(item[0], item[1]);
      });
    }
  }
  Object.setPrototypeOf(app.prototype, App.prototype);
};

class Rotate {
  initialize() {}

  update() {
    this.entity.rotate(this.x, this.y, this.z);
  }
}
const attributeses = [
  {
    x: {
      type: "number",
      default: 0
    },
    y: {
      type: "number",
      default: 0
    },
    z: {
      type: "number",
      default: 0
    }
  }
];

registerScript(Rotate, attributeses);

s3l9i-9w0o6

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

JavaScriptで連想配列入りの2次元配列を作るサンプル

やりたいこと

JavaScriptで2次元配列を作って、2次元の内1方向を連想配列にする。

サンプルコード

以下のようにすると、2次元配列の中に連想配列が入るっぽい。

test.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>さんぷる</title>
  </head>
  <body>
    <button type="button" id="testButton">てすと</button>
    <script src="test.js"></script>
  </body>
</html>
test.js
let twoDimArray = [];
document.getElementById('testButton').addEventListener('click', function() {
  let infoSet = {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6};
  twoDimArray.push(infoSet);
});

結果

以下のコードで内容を確認したのが下の画像なので、なんとなく上手くいってそう。

test.js
let twoDimArray = [];

document.getElementById('testButton').addEventListener('click', function() {
  let infoSet = {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6};
  twoDimArray.push(infoSet);
  console.log(infoSet);
  console.log(twoDimArray);
  console.log(twoDimArray[0]);
  console.log(twoDimArray[0].a);
});

WS_000026.JPG

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

無料ゲーム(フリーゲーム)の新作公開【ボンバーマン風の爆弾アクションゲーム 全60ステージ】

BOMBER60(ボンバー60)

sns.png

今回の自作ゲームは「爆弾アクション」。
スマホ、タブレット、パソコンのメジャーなブラウザに動作対応している、無料ブラウザゲームです。

プログラマーの皆様、作業中の気分転換(現実逃避?)に、プレイして頂ければ幸いです。

BOMBER60のプレイ動画

bomber.gif

ご覧の通り、一人プレイ用ボンバーマン のような雰囲気のゲームです。

BOMBER60はコチラからどうぞ↓
https://hotnews8.net/game/act/bomber60/

この他にも自作ゲームを公開し、Qiitaでご紹介しております↓
https://qiita.com/hotnews/items/47e2bb7af47678d34514

もし少しでも面白いと思ったら、シェアしてください
m(_ _)m

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

プログラマのための1次変換

はじめに

なんか1次変換を教えることが続いたので、記事にしました。
教えた時に「なんでこれで、回転とか平行移動ができるんだ?」って疑問を持つ人が多かったので、
変換行列導出について、丁寧に記載しました。
数学的に厳密でないところが多々あると思います。
予めご了承ください。:muscle:

なお、アフィン変換について知っている人は応用編のところからお読みください。

前提

・中学レベルの数学を理解していること
・三角関数の加法定理を知っていること
・行列の掛け算ができること

変換するとは

diagram-20190711 (14).png

xとyが以下の、①、②式でx'とy'になるとする。

x'=2x-y…①\\
y'=x+y…②

①、②式のx,yに具体的な値を入れてみましょう。

例えば、①、②式に\\
x=3,y=1を代入してみる。\\
すると、\\
x'=2 \times 3 - 1 = 5\\
y'=3 + 1 = 4\\
になる。

これが変換です。
xとyの次数が1次なので、「1次変換」って言います。

座標の変換に使われるのが「行列」

①、②式は行列を使うと、以下のように書ける。

\begin{pmatrix}
x'\\
y'\\
\end{pmatrix}
= 
\begin{pmatrix}
2 & -1\\
1 & 1\\
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
\end{pmatrix}…③\\

行列を使って表示すると、すっきりして見える。
実際、③式を計算すると、

\begin{pmatrix}
x'\\
y'\\
\end{pmatrix}
= 
\begin{pmatrix}
2x-y\\
x+y\\
\end{pmatrix}…④

となり、④式は①、②式をまとめて書いたものだということがわかる。

以下で、よく使われるを3つ(拡大行列、回転行列、平行移動行列)求めてみる。
ちなみに、拡大、回転、平行移動、せん断による変換を「アフィン変換」と呼びます。
※せん断については本記事では取り上げません。

拡大行列を求めてみよう

diagram-20190711 (15).png

xがs_{x}倍、yがs_{y}倍する2行2列の行列を求める。\\
 \\
求めたい拡大行列を\\
\begin{pmatrix}
a & b\\
c & d\\
\end{pmatrix}\\
とおく。\\
 \\
xがs_{x}倍、yがs_{y}倍とされるとき\\
\begin{pmatrix}
a & b\\
c & d\\
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
\end{pmatrix}
=
\begin{pmatrix}
s_{x}x\\
s_{y}y\\
\end{pmatrix}…⑤となる。\\
 \\
⑤式の左辺を計算すると、\\
\begin{pmatrix}
ax+by\\
cx+dy\\
\end{pmatrix}…⑥\\
となる。\\
⑤、⑥式の係数を比較して\\
a=s_{x}\\
b=0\\
c=0\\
d=s_{y}\\
 \\
a,b,c,dが求まったので、拡大行列は、\\
\begin{pmatrix}
s_{x} & 0\\
0 & s_{y}\\
\end{pmatrix}…⑦\\
となる。

確認

さて、求めた拡大行列で実際に座標を変換してみましょう。
diagram-20190711 (12).png

\begin{pmatrix}
2\\
2\\
\end{pmatrix}をxに2倍、yに3倍する行列を掛けて、
\begin{pmatrix}
4\\
6\\
\end{pmatrix}となるか検証します。\\
 \\
xに2倍、yに3倍する拡大行列は⑦より\\
\begin{pmatrix}
2 & 0\\
0 & 3\\
\end{pmatrix}\\
 \\
この拡大行列に
\begin{pmatrix}
2\\
2\\
\end{pmatrix}を掛けると\\
\begin{pmatrix}
2 & 0\\
0 & 3\\
\end{pmatrix}
\begin{pmatrix}
2\\
2\\
\end{pmatrix}
=
\begin{pmatrix}
2 \times 2 + 0 \times 2\\
0 \times 2 + 3 \times 2\\
\end{pmatrix}
=
\begin{pmatrix}
4\\
6\\
\end{pmatrix}

となり、思った通りの結果が得られました。

回転行列を求めてみよう

diagram-20190711 (16).png

{\theta}回転する2行2列の行列を求める。\\
 \\
求めたい回転行列を\\
\begin{pmatrix}
a & b\\
c & d\\
\end{pmatrix}\\
とおく。\\
 \\
まず\\
\begin{pmatrix}
1\\
0\\
\end{pmatrix}\\
が{\theta}回転する場合を考える。\\
 \\
\begin{pmatrix}
a & b\\
c & d\\
\end{pmatrix}
\begin{pmatrix}
1\\
0\\
\end{pmatrix}
=
\begin{pmatrix}
\cos{\theta} \\
\sin{\theta}\\
\end{pmatrix}…⑧となる。\\
 \\
⑧式の左辺を計算すると、\\
\begin{pmatrix}
a \times 1 + b \times 0\\
c \times 1 + d \times 0\\
\end{pmatrix}
=
\begin{pmatrix}
a\\
c\\
\end{pmatrix}…⑨\\
となる。\\
⑧、⑨式より、\\
a=\cos{\theta}\\
c=\sin{\theta}\\
 \\
次に\\
\begin{pmatrix}
0\\
1\\
\end{pmatrix}\\
が{\theta}回転する場合を考える。\\
 \\
\begin{pmatrix}
a & b\\
c & d\\
\end{pmatrix}
\begin{pmatrix}
0\\
1\\
\end{pmatrix}
=
\begin{pmatrix}
-\sin{\theta} \\
\cos{\theta}\\
\end{pmatrix}…⑩となる。\\
 \\
⑩式の左辺を計算すると、\\
\begin{pmatrix}
a \times 0 + b \times 1\\
c \times 0 + d \times 1\\
\end{pmatrix}
=
\begin{pmatrix}
b\\
d\\
\end{pmatrix}…⑪\\
となる。\\
⑩、⑪式より、\\
b=-\sin{\theta}\\
d=\cos{\theta}\\

 \\
a,b,c,dが求まったので、回転行列は\\
\begin{pmatrix}
\cos{\theta} & -\sin{\theta}\\
\sin{\theta} & \cos{\theta}\\
\end{pmatrix}…⑫\\
となる。

確認

さて、求めた回転行列で実際に座標を変換してみましょう。
diagram-20190711 (6).png

\begin{pmatrix}
\frac{\sqrt{3}}{2}\\
\frac{1}{2}\\
\end{pmatrix}を\frac{\pi}{6}(30度)回転する行列を掛けて、
\begin{pmatrix}
\frac{1}{2}\\
\frac{\sqrt{3}}{2}\\
\end{pmatrix}となるか検証します。\\
 \\
\frac{\pi}{6}回転する行列は⑫より\\
\begin{pmatrix}
\cos\frac{\pi}{6} & -\sin\frac{\pi}{6}\\
\sin\frac{\pi}{6} & \cos\frac{\pi}{6}\\
\end{pmatrix}
=
\begin{pmatrix}
\frac{\sqrt{3}}{2} & -\frac{1}{2}\\
\frac{1}{2} & \frac{\sqrt{3}}{2}\\
\end{pmatrix}\\

 \\
この回転行列に
\begin{pmatrix}
\frac{\sqrt{3}}{2}\\
\frac{1}{2}\\
\end{pmatrix}を掛けると\\
\begin{pmatrix}
\frac{\sqrt{3}}{2} & -\frac{1}{2}\\
\frac{1}{2} & \frac{\sqrt{3}}{2}\\
\end{pmatrix}
\begin{pmatrix}
\frac{\sqrt{3}}{2}\\
\frac{1}{2}\\
\end{pmatrix}
=
\begin{pmatrix}
\frac{\sqrt{3}}{2} \times \frac{\sqrt{3}}{2} + (-\frac{1}{2}) \times \frac{1}{2}\\
\frac{1}{2} \times \frac{\sqrt{3}}{2} + \frac{\sqrt{3}}{2} \times \frac{1}{2}\\
\end{pmatrix}\\
=
\begin{pmatrix}
\frac{3}{4}-\frac{1}{4}  \\
\frac{\sqrt{3}}{4} + \frac{\sqrt{3}}{4}\\
\end{pmatrix}
=
\begin{pmatrix}
\frac{2}{4}  \\
\frac{2\sqrt{3}}{4}\\
\end{pmatrix}
=
\begin{pmatrix}
\frac{1}{2}  \\
\frac{\sqrt{3}}{2}\\
\end{pmatrix}


となり、思った通りの結果が得られました。

「同次座標系」の導入

2次元の点の平行移動は2行2列の行列では表現できません。
そこで、行列は3行3列にします。
座標も2次元から3次元にして、3次元目を1にします。
3次元目が1なので、実質2次元です。
ちょっと気持ち悪いかもしれませんが、「便宜的に」そうします。
これを「同次座標系」といいます。

\begin{pmatrix}
x\\
y\\
\end{pmatrix}
を同次座標系で表すと
\begin{pmatrix}
x\\
y\\
1\\
\end{pmatrix}
です。

ところで、先ほど求めた2行2列の拡大行列、回転行列を
同次座標系の3行3列の行列表す場合、どうすればいいのでしょうか?
実は、
無題.png
こんな感じで3行目と3列目に赤色で囲った数字を埋めます。
こんなことしていいのでしょうか?
以下で、そうしてよいことを証明します。:ok_hand:

まず、2行2列の行列と座標を掛けてみます。\\
\begin{pmatrix}
a & b\\
c & d\\
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
\end{pmatrix}
=
\begin{pmatrix}
ax+by  \\
cx+dy\\
\end{pmatrix}…⑬\\
 \\
次に「同次座標系」の3行3列の行列と座標を掛けてみます。\\
\begin{pmatrix}
a & b & 0\\
c & d & 0\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
1\\
\end{pmatrix}
=
\begin{pmatrix}
ax+by+ 0\times 1\\
cx+dy+ 0\times 1\\
0\times x + 0\times y + 1\\
\end{pmatrix}
=
\begin{pmatrix}
ax+by\\
cx+dy\\
1\\
\end{pmatrix}…⑭\\
 \\
⑬と⑭を比較すると、x値とy値が等しいことがわかります。

これで2行2列の変換行列は、同次座標系の3行3列の行列で書き直すことができます。
ここからは、同次座標系のみついてのみ考えていきます。
早速、同次座標系の拡大行列と回転行列を求めておきましょう。

同次座標系の拡大行列と回転行列

⑦式より、xをs_{x}倍、yをs_{y}倍する同次座標系の拡大行列は\\
\begin{pmatrix}
s_{x} & 0 & 0\\
0 & s_{y} & 0\\
0 & 0 & 1\\
\end{pmatrix}…⑮\\
となります。\\
 \\
⑫式より、\theta回転する同次座標系の回転行列は\\
\begin{pmatrix}
\cos{\theta} & -\sin{\theta} & 0\\
\sin{\theta} & \cos{\theta} & 0\\
0 & 0 & 1\\
\end{pmatrix}…⑯\\
となります。\\

同次座標系で平行移動行列を求めてみよう

diagram-20190711 (11).png

xがt_{x}、yがt_{y}平行移動する3行3列の行列を求める。\\
 \\
求めたい平行移動行列を\\
\begin{pmatrix}
a & b & c\\
d & e & f\\
g & h & i\\
\end{pmatrix}\\
とおく。\\
 \\
xがt_{x}、yがt_{y}平行移動するとき\\
\begin{pmatrix}
a & b & c\\
d & e & f\\
g & h & i\\
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
1\\
\end{pmatrix}
=
\begin{pmatrix}
x+t_{x}\\
y+t_{y}\\
1\\
\end{pmatrix}…⑰となる。\\
 \\
⑰式の左辺を計算すると、\\
\begin{pmatrix}
ax+by+c\times 1\\
dx+ey+f\times 1\\
gx+hy+i\times 1\\
\end{pmatrix}
=
\begin{pmatrix}
ax+by+c\\
dx+ey+f\\
gx+hy+i\\
\end{pmatrix}…⑱\\
となる。\\
⑰、⑱式の係数を比較して\\
a=1\\
b=0\\
c=t_{x}\\
d=0\\
e=1\\
f=t_{y}\\
g=0\\
h=0\\
i=1\\
 \\
a,b,c,d,e,f,g,h,iが求まったので、平行移動行列は、\\
\begin{pmatrix}
1 & 0 & t_{x}\\
0 & 1 & t_{y}\\
0 & 0 & 1\\
\end{pmatrix}…⑲\\
となる。

確認

さて、求めた平行移動行列で実際に座標を変換してみましょう。
diagram-20190711 (10).png

\begin{pmatrix}
2\\
5\\
\end{pmatrix}をxに3、yに-4平行移動する行列を掛けて、
\begin{pmatrix}
5\\
1\\
\end{pmatrix}となるか検証します。\\
 \\
xに3、yに-4平行移動する行列は⑲式より\\
\begin{pmatrix}
1 & 0 & 3\\
0 & 1 & -4\\
0 & 0 & 1
\end{pmatrix}\\
 \\
この平行移動行列に
\begin{pmatrix}
2\\
5\\
1\\
\end{pmatrix}を掛けると\\
\begin{pmatrix}
1 & 0 & 3\\
0 & 1 & -4\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
2\\
5\\
1\\
\end{pmatrix}
=
\begin{pmatrix}
1 \times 2 + 0 \times 5 + 3 \times 1\\
0 \times 2 + 1 \times 5 + (-4) \times 1\\
0 \times 2 + 0 \times 5 + 1 \times 1\\
\end{pmatrix}
=
\begin{pmatrix}
5\\
1\\
1\\
\end{pmatrix}

となり、思った通りの結果が得られました。

行列クラス作成

同次座標系での変換の行列が求まったので、プログラムを書いてみます。

// 行列クラス
class Matrix {

  // 行列の積
  static multiply(m1, m2) {
    return [
      m1[0] * m2[0] + m1[1] * m2[3] + m1[2] * m2[6],
      m1[0] * m2[1] + m1[1] * m2[4] + m1[2] * m2[7],
      m1[0] * m2[2] + m1[1] * m2[5] + m1[2] * m2[8],
      m1[3] * m2[0] + m1[4] * m2[3] + m1[5] * m2[6],
      m1[3] * m2[1] + m1[4] * m2[4] + m1[5] * m2[7],
      m1[3] * m2[2] + m1[4] * m2[5] + m1[5] * m2[8],
      m1[6] * m2[0] + m1[7] * m2[3] + m1[8] * m2[6],
      m1[6] * m2[1] + m1[7] * m2[4] + m1[8] * m2[7],
      m1[6] * m2[2] + m1[7] * m2[5] + m1[8] * m2[8]            
    ];
  }

    // 行列とベクトルの積(戻り値はベクトル)
  static multiplyVec(m, v) {
    return {
      x:  m[0] * v.x + m[1] * v.y + m[2],
      y:  m[3] * v.x + m[4] * v.y + m[5] 
    };
  }

  // 平行移動行列を作成する
  static translate(x, y) {
    return [1, 0, x, 0, 1, y, 0, 0, 1];
  }

  // 拡大行列を作成する
  static scale(x, y) {
    return [x, 0, 0, 0, y, 0, 0, 0, 1];
  }

  // 回転行列を作成する(angleはラジアンで指定すること)
  static rotate(angle) {
    return [Math.cos(angle), -Math.sin(angle), 0, Math.sin(angle), Math.cos(angle), 0, 0, 0, 1];
  }
}

行列とベクトルの演算では3次元目は省略しています。
気になる人は修正して使ってください。

アフィン変換の行列の性質

性質その1 - 3行目が 0 0 1

ここまでで、拡大行列、回転行列、平行移動行列を求めてきました。
これらの行列をよーく見てみると、3行目が 0 0 1 となっています。
3行目が0 0 1 である行列同士を掛け算すると、面白いことがわかります。

\begin{pmatrix}
a & b & c\\
d & e & f\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
g & h & i\\
j & k & l\\
0 & 0 & 1\\
\end{pmatrix}\\
=
\begin{pmatrix}
ag + bj + c\times 0 & ah + bk + c\times 0 & ai + bl + c\times 1\\
dg + ej + f\times 0 & dh + ek + f\times 0 & di + el + f\times 1\\
0\times g + 0\times j + 1\times 0 & 0\times h + 0\times k + 1\times 0 & 0\times i + 0\times l + 1\times 1\\
\end{pmatrix}\\
=
\begin{pmatrix}
ag + bj & ah + bk & ai + bl + c\\
dg + ej & dh + ek & di + el + f\\
0 & 0 & 1\\
\end{pmatrix}

となり、掛け算した行列の3行目も 0 0 1 であることが分かります。
JavaScriptのcanvasのsetTransformという座標変換用のAPI関数の引数は6つなのですが、
3行3列の行列のうち、1行目と2行目の成分の数値が6つであることが理由です。

性質その2 - 逆行列を簡単に求められる

まず逆行列の表記ですが、逆行列は行列の右上に-1付けることで表します。

\begin{pmatrix}
a & b & c\\
d & e & f\\
0 & 0 & 1\\
\end{pmatrix}の逆行列は
\begin{pmatrix}
a & b & c\\
d & e & f\\
0 & 0 & 1\\
\end{pmatrix}^{-1}と表示します。

では逆行列について考えてみます。
アフィン変換の逆行列は変換の逆を幾何的に考えればよいです。
x方向に4倍する変換の逆は「x方向に1/4倍する」ことですよね。
同様に、
Θ回転する変換の逆は「-Θ回転する」ことで、
xに4平行移動する変換の逆は「xに-4平行移動する」ことです。
で、その変換の逆がアフィン変換における「逆行列」です。
しかも、既に導いたアフィン変換の行列をそのまま使えるので、逆行列を計算することなく求めることができます。

xがs_{x}倍、yがs_{y}倍する行列の逆行列は、\\
xが\frac{1}{s_{x}}倍、yが\frac{1}{s_{y}}倍する行列なので、⑮式より\\
\begin{pmatrix}
s_{x} & 0 & 0\\
0 & s_{y} & 0\\
0 & 0 & 1\\
\end{pmatrix}^{-1}
=
\begin{pmatrix}
\frac{1}{s_{x}} & 0 & 0\\
0 & \frac{1}{s_{y}} & 0\\
0 & 0 & 1\\
\end{pmatrix}\\
となります。\\
 \\
\theta回転する行列の逆行列は\\
-\theta回転する行列なので、⑯式より、\\
\begin{pmatrix}
\cos{\theta} & -\sin{\theta} & 0\\
\sin{\theta} & \cos{\theta} & 0\\
0 & 0 & 1\\
\end{pmatrix}^{-1}
=
\begin{pmatrix}
\cos{(-\theta)} & -\sin{(-\theta)} & 0\\
\sin{(-\theta)} & \cos{(-\theta)} & 0\\
0 & 0 & 1\\
\end{pmatrix}
=
\begin{pmatrix}
\cos{\theta} & \sin{\theta} & 0\\
-\sin{\theta} & \cos{\theta} & 0\\
0 & 0 & 1\\
\end{pmatrix}\\
となります。\\
 \\
xがt_{x}、yがt_{y}平行移動する行列の逆行列は、\\
xが-t_{x}、yが-t_{y}平行移動する行列なので、⑲式より、\\
\begin{pmatrix}
1 & 0 & t_{x}\\
0 & 1 & t_{y}\\
0 & 0 & 1\\
\end{pmatrix}^{-1}
=
\begin{pmatrix}
1 & 0 & -t_{x}\\
0 & 1 & -t_{y}\\
0 & 0 & 1\\
\end{pmatrix}\\
となります。\\

性質その3 - 掛ける順番が重要

点Pを平行移動した後、回転した時と
回転してから平行移動した場合、変換後の座標が異なるってことです。
実際に計算してそれを示してみます。

変換前の座標を(1,0)\\
平行移動はyに+1\\
回転角度は\frac{\pi}{4}とします。\\
\\
まず平行移動してから、回転させてみます。\\
\begin{align}
&\begin{pmatrix}
\cos{\frac{\pi}{4}} & -\sin{\frac{\pi}{4}} & 0\\
\sin{\frac{\pi}{4}} & \cos{\frac{\pi}{4}} & 0\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
1 & 0 & 0\\
0 & 1 & 1\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
1\\
0\\
1\\
\end{pmatrix}\\
&=
\begin{pmatrix}
\cos{\frac{\pi}{4}} & -\sin{\frac{\pi}{4}} & 0\\
\sin{\frac{\pi}{4}} & \cos{\frac{\pi}{4}} & 0\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
1\\
1\\
1\\
\end{pmatrix}\\
&=
\begin{pmatrix}
\frac{1}{\sqrt{2}} & -\frac{1}{\sqrt{2}} & 0\\
\frac{1}{\sqrt{2}} & \frac{1}{\sqrt{2}} & 0\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
1\\
1\\
1\\
\end{pmatrix}\\
&=
\begin{pmatrix}
0\\
\sqrt{2}\\
1\\
\end{pmatrix}…⑳
\end{align}\\
\\
次に回転した後に平行移動させてみます。\\
\begin{align}
&\begin{pmatrix}
1 & 0 & 0\\
0 & 1 & 1\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
\cos{\frac{\pi}{4}} & -\sin{\frac{\pi}{4}} & 0\\
\sin{\frac{\pi}{4}} & \cos{\frac{\pi}{4}} & 0\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
1\\
0\\
1\\
\end{pmatrix}\\
&=
\begin{pmatrix}
1 & 0 & 0\\
0 & 1 & 1\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
\frac{1}{\sqrt{2}} & -\frac{1}{\sqrt{2}} & 0\\
\frac{1}{\sqrt{2}} & \frac{1}{\sqrt{2}} & 0\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
1\\
0\\
1\\
\end{pmatrix}\\
&=
\begin{pmatrix}
1 & 0 & 0\\
0 & 1 & 1\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
\frac{1}{\sqrt{2}}\\
\frac{1}{\sqrt{2}}\\
1\\
\end{pmatrix}\\
&=
\begin{pmatrix}
\frac{1}{\sqrt{2}}\\
\frac{1}{\sqrt{2}} + 1\\
1\\
\end{pmatrix}…㉑\\
\end{align}\\
\\
⑳と㉑が一致しないことが確認できました。

基本的な内容はここで終わりです。
以下で、実務的なというかまあ普通こういう変換を求められるよね、って事について書いていきます。

任意の座標で回転 - 応用その1

応用として、任意の座標で回転をする行列を求めてみたいと思います。
イメージは下図です。点Pが点Cを中心にθ回転して点P'へ変換されます。
diagram-20190711 (9).png
これについては、以下のように考えます。

\begin{align}
&[考え方]\\
& \\
&1: 回転中心を原点に移動するような平行移動をする \\
&(xを-c_{x},yを-c_{y}平行移動する)\\
& \\
&2: \theta回転する\\
& \\
&3: 回転中心をもとの位置に戻すような平行移動をする\\
&(xをc_{x},yをc_{y}平行移動する)\\
\end{align}\\

では求めてみます。
diagram-20190711 (2).png

点P(x, y)が点C(c_{x},c_{y})を中心に\theta回転させた点をP(x',y')とすると、\\
\begin{pmatrix}
1 & 0 & c_{x}\\
0 & 1 & c_{y}\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
\cos{\theta} & -\sin{\theta} & 0\\
\sin{\theta} & \cos{\theta} & 0\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
1 & 0 & -c_{x}\\
0 & 1 & -c_{y}\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
1\\
\end{pmatrix}
=
\begin{pmatrix}
x'\\
y'\\
1\\
\end{pmatrix}\\
 \\
よって求める行列は\\
\begin{pmatrix}
1 & 0 & c_{x}\\
0 & 1 & c_{y}\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
\cos{\theta} & -\sin{\theta} & 0\\
\sin{\theta} & \cos{\theta} & 0\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
1 & 0 & -c_{x}\\
0 & 1 & -c_{y}\\
0 & 0 & 1\\
\end{pmatrix}\\
となります。\\
考え方を示せたので、これ以上計算はせずにこれを結果とします。

これをプログラムで書くと、こんな感じです。

/**
 * ある座標を中心に回転する
 * @param {Object} center 回転中心座標
 * @param {Number} angle 回転角度(単位はラジアン)
 * @param {Object} pos 変換前の座標
 * @return {Object} 変換後の座標
 */
function rotate(center, angle, pos) {

    // 原点へ回転中心を平行移動
    const transMat = Matrix.translate(-center.x, -center.y);

    // 回転する
    const rotateMat = Matrix.rotate(angle);

    // 回転中心をもとの位置へ戻す
    const revTransMat = Matrix.translate(center.x, center.y);

    // 3つの行列を掛ける
    let matrix = Matrix.multiply(rotateMat, transMat);
    matrix = Matrix.multiply(revTransMat, matrix);

    // 行列に座標(ベクトル)を掛ける
    const ret = Matrix.multipleVec(matrix, pos);

    return ret;
}

任意の座標で拡大 - 応用その2

応用第2弾として、任意の座標における拡大をする行列を求めてみたいと思います。
イメージは下図です。点Pが点Cを中心にxをSx倍、yをSy倍して点Pへ変換されます。
diagram-20190712 (2).png
これについては、先ほどの「任意の点を中心に回転」と同じように考えます。

\begin{align}
&[考え方]\\
& \\
&1: 拡大中心を原点に移動するような平行移動をする \\
&(xを-c_{x},yを-c_{y}平行移動する)\\
& \\
&2: xをs_{x}倍、yをs_{y}倍する\\
& \\
&3: 拡大中心をもとの位置に戻すような平行移動をする\\
&(xをc_{x},yをc_{y}平行移動する)\\
\end{align}\\
点P(x, y)が点C(c_{x},c_{y})を中心にxをs_{x}倍、yをs_{y}倍させた点をP(x',y')とすると、\\
\begin{pmatrix}
1 & 0 & c_{x}\\
0 & 1 & c_{y}\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
s_{x} & 0 & 0\\
0 & s_{y} & 0\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
1 & 0 & -c_{x}\\
0 & 1 & -c_{y}\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
1\\
\end{pmatrix}
=
\begin{pmatrix}
x'\\
y'\\
1\\
\end{pmatrix}\\
 \\
よって求める行列は\\
\begin{pmatrix}
1 & 0 & c_{x}\\
0 & 1 & c_{y}\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
s_{x} & 0 & 0\\
0 & s_{y} & 0\\
0 & 0 & 1\\
\end{pmatrix}
\begin{pmatrix}
1 & 0 & -c_{x}\\
0 & 1 & -c_{y}\\
0 & 0 & 1\\
\end{pmatrix}\\
となります。
応用その1同様、これ以上計算しないでおきます。

これをプログラムで書くと、こんな感じです。

/**
 * ある座標を中心に拡大する
 * @param {Object} center 拡大中心座標
 * @param {Object} scale 拡大率
 * @param {Object} pos 変換前の座標
 * @return {Object} 変換後の座標
 */
function scale(center, scale , pos) {

    // 原点へ拡大中心を平行移動
    const transMat = Matrix.translate(-center.x, -center.y);

    // 拡大する
    const scaleMat = Matrix.scale(scale.x, scale.y);

    // 拡大中心をもとの位置へ戻す
    const revTransMat = Matrix.translate(center.x, center.y);

    // 3つの行列を掛ける
    let matrix = Matrix.multiply(scaleMat , transMat);
    matrix = Matrix.multiply(revTransMat, matrix);

    // 行列に座標(ベクトル)を掛ける
    const ret = Matrix.multipleVec(matrix, pos);

    return ret;
}

原点を通る直線(y=mx)に折り返し - 応用その3

応用第3弾として、原点を通る直線に対する折り返しをする行列を求めてみたいと思います。
イメージは下図です。PがP'へy=mxに対称に変換されます。
図11.png

今まで通り、アフィン変換を組み合わせて求めます。
考え方としては、

\begin{align}
&[考え方]\\
& \\
&y=mxとx軸のなす角を\thetaとする。\\
& \\
&1: y=mxがx軸と重なるような回転をする \\
&(-\theta回転する)\\
& \\
&2: x軸に折り返す\\
&(xを1倍、yを-1倍する)\\
& \\
&3: x軸が元のy=mxに重なるように回転をする\\
&(\theta回転する)\\
\end{align}\\

といった感じです。
今まで解き方と似ていますね。
勝手にΘって置いていいのかって思うかもしれませんが、まあ最終的に消えていればOKです。
そういえば今回は平行移動行列が出てきませんね、ってことで途中まで「同次座標系」ではなく2次元でやってみます。

y=mxとx軸のなす角を\thetaとする。\\
点P(x, y)がy=mxに対称に移動した点をP(x',y')とすると、\\
\begin{pmatrix}
\cos{\theta} & -\sin{\theta}\\
\sin{\theta} & \cos{\theta}\\
\end{pmatrix}
\begin{pmatrix}
1 & 0\\
0 & -1\\
\end{pmatrix}
\begin{pmatrix}
\cos{(-\theta)} & -\sin{(-\theta)}\\
\sin{(-\theta)} & \cos{(-\theta)}\\
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
\end{pmatrix}
=
\begin{pmatrix}
x'\\
y'\\
\end{pmatrix}
\\
 \\
よって求める行列は\\
\begin{align}
&\begin{pmatrix}
\cos{\theta} & -\sin{\theta}\\
\sin{\theta} & \cos{\theta}\\
\end{pmatrix}
\begin{pmatrix}
1 & 0\\
0 & -1\\
\end{pmatrix}
\begin{pmatrix}
\cos{(-\theta)} & -\sin{(-\theta)}\\
\sin{(-\theta)} & \cos{(-\theta)}\\
\end{pmatrix}\\
&=
\begin{pmatrix}
\cos{\theta} & -\sin{\theta}\\
\sin{\theta} & \cos{\theta}\\
\end{pmatrix}
\begin{pmatrix}
1 & 0\\
0 & -1\\
\end{pmatrix}
\begin{pmatrix}
\cos{\theta} & \sin{\theta}\\
-\sin{\theta} & \cos{\theta}\\
\end{pmatrix}\\
&=
\begin{pmatrix}
\cos{\theta} & -\sin{\theta}\\
\sin{\theta} & \cos{\theta}\\
\end{pmatrix}
\begin{pmatrix}
\cos{\theta} & \sin{\theta}\\
\sin{\theta} & -\cos{\theta}\\
\end{pmatrix}\\
&=
\begin{pmatrix}
\cos^{2}{\theta} - \sin^{2}{\theta} & \sin{\theta}\cos{\theta} + \cos{\theta}\sin{\theta}\\
\sin{\theta}\cos{\theta} + \cos{\theta}\sin{\theta} & -(\cos^{2}{\theta} - \sin^{2}{\theta})\\
\end{pmatrix}…㉒
\end{align}\\
また、\\
\cos{\theta}=\frac{1}{\sqrt{m^2+1}},
\sin{\theta}=\frac{m}{\sqrt{m^2+1}}\\
なので、㉒式に代入して、
=
\begin{pmatrix}
\frac{1-m^{2}}{m^{2}+1} & \frac{2m}{m^{2}+1}\\
\frac{2m}{m^{2}+1} & \frac{m^{2}-1}{m^{2}+1}
\end{pmatrix} \\
これを同次座標系で表すと\\
\begin{pmatrix}
\frac{1-m^{2}}{m^{2}+1} & \frac{2m}{m^{2}+1} & 0\\
\frac{2m}{m^{2}+1} & \frac{m^{2}-1}{m^{2}+1} & 0\\
0 & 0 & 1\\
\end{pmatrix}\\
となります。\\
 \\
また、㉒に三角関数の加法定理(2倍角の公式)を用いると\\
\begin{pmatrix}
\cos{2\theta} & \sin{2\theta}\\
\sin{2\theta} & -\cos{2\theta}\\
\end{pmatrix}\\
となります。\\
\cos{2\theta} と \sin{2\theta}が事前にわかっている場合、こちらを使ってもよいでしょう。\\

次の応用その4で、今回の結果を使うので、応用その1、その2とは違い計算しました。
ついでに、この結果をMatrixクラスのメソッドに追加しましょう。

  // 原点を通る直線(y=mx)に折り返す
  static symmmetic(m) {
    return [(1 - m * m) / (m * m + 1), 2 * m / (m * m + 1), 0,  2 * m / (m * m + 1), (m * m - 1) / (m * m + 1), 0, 0, 0, 1];   
  }

直線(y=mx+n)に折り返し - 応用その4

応用第4弾として、普通の直線に対する折り返しをする行列を求めてみたいと思います。
イメージは下図です。PがP'へy=mx+nに対称に変換されます。
図12.png

今度は直線が原点を通るとは限りません。
先ほど求めた原点を通る直線の折り返しの変換を使いましょう。

\begin{align}
&[考え方]\\
& \\
&1: 直線のY切片を原点に移動\\
& \\
&2: 直線(原点を通る)で折り返す\\
& \\
&3: 直線のY切片をもとの位置へ戻す\\
\end{align}\\

考え方は、応用その1、応用その2と似ています。
こういう考え方にもずいぶん慣れてきたんじゃないでしょうか。
応用その3で、原点を通る直線での折り返しを我々は求めていたので、このような考え方をしましたが、
それを知らない場合でも、

\begin{align}
&["原点を通る直線での折り返し"を知らない場合の考え方]\\
& \\
&y=mx+nとx軸のなす角を\thetaとする。\\
& \\
&1: 直線のY切片を原点に平行移動(y方向に-n平行移動)\\
& \\
&2: 直線がx軸と重なるような回転をする \\
&(-\theta回転する)\\
& \\
&3: x軸に折り返す\\
&(xを1倍、yを-1倍する)\\
& \\
&4: x軸が元のy=mxに重なるように回転をする\\
&(\theta回転する)\\
& \\
&5: 直線のY切片をもとの位置へ戻す\\
\end{align}\\

と考えればOKです。

これをプログラムで書くと、こんな感じです。

/**
 * ある直線(y=mx+n)で折り返す
 * @param {Number} m y=mx+nのm
 * @param {Number} n y = mx+nのn
 * @param {Object} pos 変換前の座標
 * @return {Object} 変換後の座標
 */
function symmetricLine2(m, n, pos) {

     // 切片を原点へ平行移動
    const transMat = Matrix.translate(0, -n);

    // 直線で折り返す
    const symmmeticMat = Matrix.symmmetic(m);

    // 切片をもとの位置へ戻す
    const revTransMat = Matrix.translate(0, n);

    // 3つの行列を掛ける
    let matrix = Matrix.multiply(symmmeticMat, transMat);
    matrix = Matrix.multiply(revTransMat, matrix);

    // 行列に座標(ベクトル)を掛ける
    const ret = Matrix.multipleVec(matrix, pos);

    return ret;
}

JavaScriptによるアフィン変換のサンプルプログラムの使い方

サンプルプログラムはこちらです。

無題.png

①変換前の座標を入力し、「座標確定」ボタンを押す。
②行いたい変換のパラメータを入力した後、変換する。
③変換した座標が計算され、図示(目盛りはないけど)される。
図示ですが、canvasを使って描画しております。
:zap:注意!!:zap:
canvasのサイズは200x200で、X+方向は右、Y+方向は下です。
ですので、原点はcanvasの左上隅で、+回転は時計周りとなります。

最後に

行列には、今回紹介した以外にも興味深い性質が多数ありますので、是非調べてみてください。
なお、本記事の内容は数学的にかなりあいまいであると思うので、一度ちゃんとした書籍などで勉強することを強く強くお勧めします。

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

【Vue・React・Elm】ビューの書き方を比べてみた

Vue.jsの場合


Vueのビューはこんな感じ

<template>
  <div>
    <button v-on:click="decrement">-1</button>
    <div>{{ count }}</div>
    <button v-on:click="increment">+1</button>
  </div>
</template>

ほぼHTMLですね。見やすいです。


Vueでの条件分岐や繰り返し

v-ifv-forなどのディレクティブというものが用意されていて、それをHTMLの属性みたいに書きます。

【例】記事タイトル一覧

記事があれば、その分だけ繰り返し表示するし、1件もなければ「記事がありません。」と表示する例です。

<template>
  <ul v-if="items.length">
    <li v-for="item in items">
      {{ item.title }}
    </li>
  </ul>
  <p v-else>記事がありません。</p>
</template>

v-ifとかv-forとか色々覚えないといけませんが、個人的には割と分かりやすいと思います。


Reactの場合


Reactのビューはこんな感じ

render() {
  return (
    <div>
      <button onClick={decrement}>-1</button>
      <div>{ count }</div>
      <button onClick={increment}>+1</button>
    </div>
  )
}

Babelというトランスパイラを使うことで、ReactのビューもHTMLみたいに書けます。
これはJSXという書き方なんですが、これが一部の方からは「何か気持ち悪い」と不評みたいです。。。
個人的には、ほぼHTMLそのままで読みやすいと思うのですが。。。


Reactでの条件分岐や繰り返し

JSXの中では、if文やfor文は使えません。
そのため三項演算子や配列のmapメソッドを使って記述します。

【例】記事タイトル一覧

render() {
  return (
    <div>
      { items.length > 0 ? (
        <ul>
          { items.map((item) => {
            return <li>{ item.title }</li>;
          })}
        </ul>
      ) : (
        <p>記事がありません。</p>
      )}
    </div>
  )
}

JSの構文と混じると、少し読みづらいかもしれません。。。


Elmの場合


賢い人は考えた

「HTML要素って、要はタグ名属性中に入ってる子要素たち・・・」
「この3つから構成されているやん?」

<div class="container" id="hoge">
  <span>子要素</span>
</div>

「じゃあ、divとかspanっていう関数を作って・・・」
「第一引数には[ class "containor", id "hoge" ]みたいに属性のリストを渡して」
「第二引数には子要素のリストを渡せばええやん」

div [ class "container", id "hoge" ] [
    span [] [ text "子要素" ]
]

「↑こう書いたらHTML要素を返してくれるような・・・」
「各タグ名に対応したHTML関数たちを作ればええんや!」
「ただの関数やからv-ifとかv-forとか要らんで」
「ElmのifList.map関数とかと、シームレスに組み合わせられるんや!」


Elmではビューすらも関数

Elmでは、HTML要素も関数引数で表現します。
ビュー部分プログラム部分、という風に分かれていない感じです。
そのため、ビュー部分でもElmの関数を普通に使えます。

これによって、かなり柔軟にビューを書けます。
「配列の分だけループして表示させるための特別な構文」もありませんし、
「属性値を動的に生成するための特別な書き方」もありません。

HTMLに相当する部分もElmのコードそのものなので、Elmの関数をそのまま使えます。

もちろん、各HTMLタグに対応した関数(header, section, p 等々・・・)は初めから用意されています!


ということで、Elmのビューはこんな感じ

view model =
    div []
        [ button [ onClick Increment ] [ text "+1" ]
        , div [] [ text <| String.fromInt model.count ]
        , button [ onClick Decrement ] [ text "-1" ]
        ]

Elmでの条件分岐や繰り返し

条件分岐や繰り返しに関して、ビュー専用の構文はありません。
普通にElmのList.map関数とかifなどを使用します。

【例】記事タイトル一覧

view model =
    div []
        [ if List.length model.items > 0 then
            ul [] (model.items |> List.map liComponent)
            -- 記事(items)が1件以上あれば、
            -- その分だけliComponentを呼び出して表示する

          else
            p [] [ text "記事がありません。" ]
        ]


-- ただ関数を作ればコンポーネントのように利用できる
liComponent title =
    li [] [ text title ]


シンプルでいい感じ!


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

ES6の分割代入

問題

二次元配列をオブジェクトの配列に変換する。

問題
const classes = [
  [ '化学', '1時限目', '鈴木先生' ],
  [ '物理', '2時限目', '佐藤先生'],
  [ '数学', '3時限目', '木村先生' ]
];

//これをこうしたい。
const classesAsObject = [
    { subject:'化学', time: '1時限目', teacher: '鈴木先生'},
    { subject:'物理', time: '2時限目', teacher: '佐藤先生'},
    { subject:'数学', time: '3時限目', teacher: '木村先生'}
]

ES5で書く

器となる配列を先に宣言しておいて、forループの中で作成したオブジェクトをその配列に放り込んでいく。

var classesAsObject = [];
for (var i=0; i<classes.length; i++) {
  var element = { 
    subject: classes[i][0],
    time: classes[i][1],
    teacher: classes[i][2]
  }
  classesAsObject.push(element);
};

ES6で書く

引数を分割代入で受け取ってオブジェクトリテラルを返すmapを作る。

const classesAsObject = classes.map(([subject, time, teacher])=> {
    return {subject, time, teacher};
});

何を使っているか

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

【Vue.js】子から親コンポーネントのイベント実行・データ受け渡し

子から親コンポーネントのデータを更新

初心者用記事です。Vue.jsに少し慣れたので忘れないうちに記事にします。

今回、二つの方法に触れます。
$emitで子から親コンポーネントの処理を呼び出す
$emitで子から親コンポーネントへ値を渡す

以下の記事を参考に追加機能付きで作成しました。
https://dev83.com/vue-emit/
分かりやすい記事なのでお勧めです。

親から子の受け渡しを行うアプリで、ボタンを押すと数が増減する簡単なアプリ作っていきます。
↓ 例
image.png

実際に作ってもいいですが、JSFiddleで動作テストしてもいいと思います。

<div id="app">
</div>

new Vue({
        el: '#app',
        data: {
          // 下部に出てる合計個数
          totalCount: 0,
          items: [
            {
              name: 'りんご',
              price: 100,
              imgUrl: 'https://4.bp.blogspot.com/-uY6ko43-ABE/VD3RiIglszI/AAAAAAAAoEA/kI39usefO44/s800/fruit_ringo.png'
            },
            {
              name: 'バナナ',
              price: 200,
             imgUrl: 'https://1.bp.blogspot.com/-JaRzIloEZw4/UgSMOL-UzYI/AAAAAAAAW_A/vOiX6Tz5oO4/s800/fruit_banana.png'
            },
          ],
        },
      });

style部分

  .item-wrap {
    list-style-type: none;
    display: flex;
    flex-wrap: wrap;
    margin: 0;
    padding: 0;
  }
  .item-wrap li {
    padding: 1em;
  }
  .item-wrap img {
    border-radius: 50%;
  }
  .total-count {
    padding: 1em;
    background-color: #222;
    color: #fff;
  }

現状では何も表示されません。

簡単アプリを追加

本体に追加

<div id="app">
  <ul class="item-wrap">
    <item v-for="item in items" :item="item"></item>
  </ul>
  <div class="total-count">合計:{{ totalCount }}個</div>
</div>

子コンポーネント追加。ファイル読み込んだ方がいいですが今回はjsFiddle使って確認したので一つにまとめて書いてます。

Vue.component('item', {
        // 親から値もらう
        props: ['item'],
        // 子コンポーネントのテンプレート部分
        template: `
          <li>
            <img :src="item.imgUrl" style="height: 200px;">
            <div>{{ item.name }} = {{ childTotal }}個</div>
            <button @click="addCart">増やす</button>
            <button @click="removeCart">減らす</button>
          </li>
        `,
        // 子コンポーネントデータ
        data: function () {
          return {
            childTotal: 0
          };
        },
        // ボタン押すと発火する処理
        methods: {
          addCart: function () {
            this.childTotal += 1;
          },
          removeCart: function () {
            if (this.childTotal > 0) {
              this.childTotal -= 1;
            }
          },
        },
      });

new Vue({
        // 省略。さっき書いたところ
      });

ここまでの状態だと子コンポーネントから親コンポーネントに値を渡していないので
「合計:個」には変化が起きません。

親コンポーネントのデータを更新

子コンポーネントのデータが更新されたら親コンポーネントのデータが更新されるようにします。

子コンポーネントのmethodsを修正。

methods: {
          addCart: function () {
            this.childTotal += 1;
            // ↓追加。親の合計個数増やす
            this.$emit('addbtn');
          },
          removeCart: function () {
            if (this.childTotal > 0) {
              this.childTotal -= 1;
              // ↓追加。親の合計個数減らす
              this.$emit('removebtn');
            }
          },
        },

親コンポーネントを修正
itemコンポーネント部分
methods追加

<div id="app">
  <ul class="item-wrap">
    <item
          v-for="item in items"
          :item="item"
          @addbtn="addParentTotal"
          @removebtn="removeParentTotal"
          >
    </item>
  </ul>
  <div class="total-count">合計:{{ totalCount }}個</div>
</div>
new Vue({
        el: '#app',
        data: {
          totalCount: 0,
          items: [
            {
              name: 'RedApple',
              imgUrl: 'https://4.bp.blogspot.com/-uY6ko43-ABE/VD3RiIglszI/AAAAAAAAoEA/kI39usefO44/s800/fruit_ringo.png'
            },
            {
              name: 'GreenApple',
              imgUrl: 'https://1.bp.blogspot.com/-JaRzIloEZw4/UgSMOL-UzYI/AAAAAAAAW_A/vOiX6Tz5oO4/s800/fruit_banana.png'
            },
          ],
        },
        methods: {
          addParentTotal: function () {
            this.totalCount += 1;
          },
          removeParentTotal: function () {
            this.totalCount -= 1;
          },
        },
      });

最終的な子コンポーネント

      Vue.component('item', {
        props: ['item'],
        template: `
          <li>
            <img :src="item.imgUrl" style="height: 200px;">
            <div>{{ item.name }} = {{ childTotal }}個</div>
            <button @click="addCart">増やす</button>
            <button @click="removeCart">減らす</button>
          </li>
        `,
        data: function () {
          return {
            childTotal: 0
          };
        },
        methods: {
          addCart: function () {
            this.childTotal += 1;
            this.$emit('addbtn')
          },
          removeCart: function () {
            if (this.childTotal > 0) {
              this.childTotal -= 1;
              this.$emit('removebtn');
            }
          },
        },
      });

ひとまず完成

vue-emit.gif

@addbtn=”addParentTotal”
@removebtn=”removeParentTotal”

親コンポーネントの<item>~~~</item>に追加したのはこのふたつ。

子コンポーネントのthis.$emit('addbtn')
もしくはthis.$emit('removebtn');が発火したら
親コンポーネントのmethodsにあるaddParentTotal removeParentTotal
を呼び出してくださいという処理です。

子から親に値を受け渡す

現状だと親コンポーネントで合計を計算していますが、子コンポーネントからの値を直接受け取り、計算したい場合もあると思います。

サンプル:値段を計算

親コンポーネントに値段を定義して、子コンポーネントから受け取った個数をもとに合計金額を計算する機能を追加します

受け渡しは引数を追加してやります。

this.$emit('addbtn', this.childTotal, name)

この場合だとthis.childTotalとnameを渡しています。
親の受け取る側

addParentTotal: function (total, name){
}

これで子から親へデータを渡すことができます。

サンプルを作ったのですが、 改造してたら意外とシンプルにならずに申し訳ないです…

<div id="app">
  <ul class="item-wrap">
    <item
          v-for="item in items"
          :item="item"
          @addbtn="addParentTotal"
          @removebtn="removeParentTotal"
          >
    </item>
  </ul>
  <div class="total-count">合計:{{ totalCount }}個</div>
  <p class="total-count" v-for="item in items">
    {{ item.name }}の合計金額:¥{{ item.totalYen }}
  </p>
</div>

子コンポーネント

Vue.component('item', {
  props: ['item'],
  template: `
    <li>
      <img :src="item.imgUrl" style="height: 200px;">
      <div>{{ item.name }} = {{ childTotal }}個</div>
      <button @click="addCart(item.name)">増やす</button>
      <button @click="removeCart(item.name)">減らす</button>
    </li>
  `,
  data: function () {
    return {
      childTotal: 0
    };
  },
  methods: {
    addCart: function (name) {
      this.childTotal += 1;
      this.$emit('addbtn', this.childTotal, name)
    },
    removeCart: function (name) {
      if (this.childTotal > 0) {
        this.childTotal -= 1;
        this.$emit('removebtn', this.childTotal, name);
      }
    },
  },
});

親コンポーネント

new Vue({
        el: '#app',
        data: {
          totalCount: 0,
          items: [
            {
              name: 'リンゴ',
              price: 100,
              imgUrl: 'https://4.bp.blogspot.com/-uY6ko43-ABE/VD3RiIglszI/AAAAAAAAoEA/kI39usefO44/s800/fruit_ringo.png',
              number: 0,
              totalYen: 0
            },
            {
              name: 'バナナ',
              price: 200,
              imgUrl: 'https://1.bp.blogspot.com/-JaRzIloEZw4/UgSMOL-UzYI/AAAAAAAAW_A/vOiX6Tz5oO4/s800/fruit_banana.png',
              number: 0,
              totalYen: 0
            },
          ],
        },
        methods: {
          addParentTotal: function (total, name) {
            this.totalCount += 1;
            // 名前一致する該当の要素をオブジェクトから入れる
            const item = this.items.find(ele => ele.name === name)
            item.number = total
            item.totalYen = total * item.price
          },
          removeParentTotal: function (total, name) {
            this.totalCount -= 1;
            // 名前一致する該当の要素をオブジェクトから入れる
            const item = this.items.find(ele => ele.name === name)
            item.number = total
            item.totalYen = total * item.price
          },
        },
      });
  .item-wrap {
    list-style-type: none;
    display: flex;
    flex-wrap: wrap;
    margin: 0;
    padding: 0;
  }
  .item-wrap li {
    padding: 1em;
  }
  .item-wrap img {
    border-radius: 50%;
  }
  .total-count {
    padding: 1em;
    background-color: #222;
    color: #fff;
  }

それぞれのオブジェクトに個数を操作。
また、1個当たりの値段から金額を計算するアプリ完成。
vue-emit2.gif

これなにに使えるの?

色々ありますけど、モーダルなどが分かりやすい例です。
モーダルを子コンポーネントにして、フォームの入力をしてもらうと親の値も変わるみたいな感じです。

それについては次の機会に

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

超初心者が受けるJS社内勉強会:第4回

実践

<script> タグを書く場所

  • <HTML> <Body>タグの一番下</body>の前
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="keywords" content="">
<meta name="description" content="">
<title></title>
</head>

<body>

<script type="text/javascript" src="js/jquery-3.1.0.min.js"></script>
<script type="text/javascript" src="js/randomBg.js"></script>

</body>
</html>

決まり事

$(function(){
});
  • DOMを全部読み込んでから処理するという意味。{ });内に処理を書く
var data
  • functionの中でのみ、変数を使えるようにする。ローカル変数。
    変数のスコープをファンクション内に設定する
  • varをつけないと グローバル変数 となり、function外でも使えてしまう。(上書きされる)
$(data.module).on('click', data.btn, function(){}
  • 処理実行を軽減するため、限定的な書き方をする。チェックボックスとか。
alert('Hello, world!');
  • (ブラウザの)ポップアップを出す
'use strict';
  • エラーを発見しやすくなる。

決まり事の部分は、jQueryでもともと用意されているので、そちらを参照(公式→https://api.jquery.com/ )し、バージョンに合わせたものを使用する。

暗記モノ嫌い :frowning2: :sob:

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

初心者によるJavaScriptでストップウォッチを作るための学習 その2

初心者によるJavaScriptでストップウォッチを作るための学習 その2です。

その1はこちら https://qiita.com/amanomunt/items/6329ab755dc30264ee0e

ストップウォッチを装飾する

目標とする形は以下のようになります。
No-00.png

現在のhtmlは

  <div id="timer">00:00.000</div>
  <button id="start">Start</button>
  <button id="stop">Stop</button>
  <button id="reset">Reset</button>

です。これに
1.装飾のためにbutton要素をdiv要素に変更
2.全体を整えるために大枠divを用意
3.ボタンdivをFlexboxへ
のようにしていきます。

  <div class="container"> <!--大枠 ここから-->
    <div id="timer">00:00.000</div>
    <div class="controls"> <!--Flexbox親要素 ここから-->
      <div class="btn" id="start">Start</div>
      <div class="btn" id="stop">Stop</div>
      <div class="btn" id="reset">Reset</div>
    </div> <!--Flexbox親要素 ここまで-->
  </div> <!--大枠 ここまで-->

それを整えるCSSは

body {
  font-family: 'Courier New', monospace;
  font-size: 14px;
  background: #eee;
}

.container { /* 大枠 */
  margin:20px auto;
  width:270px;
  background: #fff;
  padding:15px;
  text-align: center;
}

#timer { /* タイマー表示部 */
  background:#ddd;
  height: 120px;
  line-height: 120px;
  font-size: 40px;
  margin-bottom: 15px;
}

.btn { /* ボタン部 */
  width: 80px;
  height: 45px;
  line-height: 45px;
  background: #ddd;
  font-weight: bold;
  cursor: pointer;
  user-select: none; /* 連打による範囲選択を拒否 */
}

.controls { /* Flexbox */
  display: flex;
  justify-content: space-between; /* 均等配置 */
}

以上で装飾については完了です。

調整して完成させる

上記の装飾の過程で、button要素をdiv要素にしたので、その調整を行います。

まずbutton要素では使えた.disabled = true;での無効化がdivでは使えません。
そのため無効化に関してはdisabledに対応するスタイルを使って制御します。

まずはそのスタイルを用意します。

.inactive {
  opacity: 0.6;
}

次にJavaScriptのdisabledプロパティのところを以下のようにclassListを使うよう編集します。
disabled = true; → classList.add('inactive');
disabled = false; → classList.remove('inactive');

  function setButtonStateInitial() {
    start.classList.remove('inactive');
    stop.classList.add('inactive');
    reset.classList.add('inactive');
  }
  function setButtonStateRunning() {
    start.classList.add('inactive');
    stop.classList.remove('inactive');
    reset.classList.add('inactive');
  }
  function setButtonStateStopped() {
    start.classList.remove('inactive');
    stop.classList.add('inactive');
    reset.classList.remove('inactive');

次に、ボタンにinactiveクラスがついていたら、各ボタンを押しても実行されないようにします。
そのためには、if文を使って、もしinactiveクラスを保持していたら、returnするとします。

  start.addEventListener('click', () => {
    if (start.classList.contains('inactive') === true) {
      return;
    }
    setButtonStateRunning(); // Runningへ以降
    startTime = Date.now();
    countUp();
  });
  stop.addEventListener('click', () => {
    if (stop.classList.contains('inactive') === true) {
      return;
    }
    setButtonStateStopped(); // Stoppedへ以降
    clearTimeout(timeoutId);
    elapsedTime += Date.now() - startTime;
  });
  reset.addEventListener('click', () => {
    if (reset.classList.contains('inactive') === true) {
      return;
    }
    setButtonStateInitial(); // Initualへ以降
    timer.textContent = '00:00.000';
    elapsedTime = 0;
  });

以上でストップウォッチ制作を完了とします。

※ちなみにこれらはドットインストールの講義を参考にしたものです。

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

Firebase&JavaScriptでGoogleAPIを叩く

GoogleAPIのドキュメントにあるJavaScriptを”とりあえず動かす”ためのメモです。

全く初めてのGoogle Tasks API リスト取得でcURLまで実施する方法を書いたので、さらにJSで動かすにはどうするかをこちらに書いていきます。
なので引き続き使うAPIはGoogleTASKです。


スクリーンショット 2019-08-18 17.41.47.png

前回記事で動かしたcURLと同列に書かれているJAVASCRIPTのコードですが、意外とサクッと動かせません。

書かれているコードはこちら

index.html
<script src="https://apis.google.com/js/api.js"></script>
<script>
  /**
   * Sample JavaScript code for tasks.tasks.list
   * See instructions for running APIs Explorer code samples locally:
   * https://developers.google.com/explorer-help/guides/code_samples#javascript
   */

  function authenticate() {
    return gapi.auth2.getAuthInstance()
        .signIn({scope: "https://www.googleapis.com/auth/tasks.readonly"})
        .then(function() { console.log("Sign-in successful"); },
              function(err) { console.error("Error signing in", err); });
  }
  function loadClient() {
    gapi.client.setApiKey("YOUR_API_KEY");
    return gapi.client.load("https://content.googleapis.com/discovery/v1/apis/tasks/v1/rest")
        .then(function() { console.log("GAPI client loaded for API"); },
              function(err) { console.error("Error loading GAPI client for API", err); });
  }
  // Make sure the client is loaded and sign-in is complete before calling this method.
  function execute() {
    return gapi.client.tasks.tasks.list({
      "tasklist": "tasklistTASKLISTtasklist"
    })
        .then(function(response) {
                // Handle the results here (response.result has the parsed body).
                console.log("Response", response);
              },
              function(err) { console.error("Execute error", err); });
  }
  gapi.load("client:auth2", function() {
    gapi.auth2.init({client_id: "YOUR_CLIENT_ID"});
  });
</script>
<button onclick="authenticate().then(loadClient)">authorize and load</button>
<button onclick="execute()">execute</button>

こちらに、前回調べた
・TASKList_Id
・YOUR_API_KEY
・YOUR_CLIENT_ID
を入力して実行してみたのですが、うまくいきませんでした。
まず、ローカルに作ったindex.htmlで実行したのですが、

Uncaught uO
Uncaught TypeError: Cannot read property 'signIn' of null

というエラーが出ます。
どうやらGoogleのAPIはローカルのファイルからでは使えないという問題と、
前回の設定では「APIを呼びだす場所」がその他になっていたので、その点も問題です。

そのあたりを直し、firebaseにhtmlファイルをアップロードしてAPIを動かしてみたいと思います。

Firebase内にプロジェクトを作成する

FirebaseConsoleにアクセス
firebase.png
プロジェクトを追加を押下

このプロジェクトはGoogleAPIのプロジェクトと同様なので、あとでこちらのプロジェクトにAPIを有効にしていくことになります。

FirebaseでDeployする

ローカルでhtmlを動かしてはGoogleAPIが動かないので、
Firebaseを使います。

ターミナルにFirebaseをインストールします。

$ firebase -V
7.2.2

作成時は7.2.2

$ firebase login 

ターミナルへのインストールができたら、Googleにログイン

$ firebase list

作成したプロジェクトのIDを調べる

$ firebase init --project={ID}

調べたIDでfirebaseのプロジェクトとフォルダを紐づける。

$ ls
firebase.json   public

firebase側のfirebase.jsonpublicフォルダが作成されているので、
publicフォルダ内にindex.htmlを作成し、ドキュメントのJSを貼り付ける。

$ firebase deploy

デプロイすると、URLが表示され、アクセスすることができるようになる。

スクリーンショット 2019-08-18 18.42.57.png

しかしこの時点では、まだAPIの有効化ができていないので、
APIは正常には動かない。
次にAPIの設定をやっていく。

Google_TASK_APIを設定する。

Google API Consoleへアクセスする。
左上にある、「プロジェクトの選択」からfirebaseで作ったプロジェクトを選択しAPIとサービスの有効化をクリックする。

スクリーンショット 2019-08-28 10.46.31.png

次にAPIを選択する。ここではTASKAPIを使用する。

apI.png

TasKs APIを有効にする。

次に認証情報

スクリーンショット 2019-08-28 10.51.10.png

その際にAPIを使用するアプリケーションをJavaScriptにし、リダイレクトURIを先ほどのfirebaseDeploy時にアクセスしたURLにしておきます。

JavaScriptの設定を書き換える

サンプルのJSソースにIDやKEYを設定する必要がある。
・tasklist_Id
・API_KEY
・CLIENT_ID
前回記事を参考にこの3つの値を取得し設定。
その後firebaseで再度Deployしアクセスすると完成です。

結果はlogに出るのでブラウザのDebToolsでConnsoleを見て自分の登録したTASKが取得できていれば完成です。

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

ReactでuseChildrenを使って子ノードのpropsを書き換えるコンポーネントを作る

ReactでuseChildrenを使って子ノードのpropsを書き換える

1.子ノードを自動で書き換えるコンポーネントの作成

 1.1 基本のソースコード

 まずはこのコンポーネントの出力結果を見てください

function App() {
  return (
    <Parent>
      <input type="button" value="A" />
      <input type="button" value="B" />
      <input type="button" value="C" />
      <div><input type="button" value="E" /></div>
    </Parent>
  );
}

 1.2 表示結果

 Parentコンポーネントがinputタグのvalueを書き換えています。
 これによって、「のボタン」というテキストが付加されます。

image.png

 1.3 Parentコンポーネントのソースコード

 useChildrenを使って子ノード中のelementを列挙し、inputタグのpropsを書き換えています。
 valueに限らずpropsのあらゆるパラメータが書き換え可能です。

function Parent(props: {
  children?: ReactNode;
}) {
  let children = useChildren(props, element => {
    if (element.type === "input") {
      return { value: element.props.value+"のボタン" };
    }
    return null;
  });
  return <>{children}</>;
}

2.イベントを仕込む

 2.1 ソースコード

 onChildClickというイベントを定義して、ボタンが押されたらvalueの内容を返します。

function Parent(props: {
  children?: ReactNode;
  onChildClick: (value: string) => void;
}) {
  let children = useChildren(props, element => {
    if (element.type === "input") {
      return { onClick: () => props.onChildClick(element.props.value) };
    }
    return null;
  });
  return <>{children}</>;
}

 こちらはParentにのみイベントを設定し、各inputにはイベントの設定を行う必要はありません。
 ちなみにParentタグが見える範囲であれば、後から動的に追加した子ノードに対してもPropsの書き換えは行われます。

function App() {
  const [msg, setMsg] = React.useState("");
  return (
    <Parent onChildClick={setMsg}>
      <input type="button" value="A" />
      <input type="button" value="B" />
      <input type="button" value="C" />
      <div>
        <input type="button" value="E" />
      </div>
      <div>押されたボタン:{msg}</div>
    </Parent>
  );
}

 2.2 表示結果

ボタンを押すとメッセージが切り替わります
image.png

3.useChildrenのソースコード

 以下のモジュールをインポートすれば、useChildrenが使えるようになります。
 内容的には単純で、ReactElementをクローンしつつ列挙しているだけです。

UseChildren.ts
import React from "react";
import { ReactNode, ReactElement } from "react";

const useChildren = (
  props: { children?: ReactNode },
  proc: (element: ReactElement) => { [key: string]: unknown } | null
): ReactNode | null | undefined => {
  const hook = (
    node: ReactNode | null | undefined
  ): ReactNode | null | undefined => {
    if (typeof node !== "object" || node == null) return node;

    if (node instanceof Array) {
      return node.map(hook);
    } else {
      let element = node as ReactElement;
      let children = element.props ? element.props.children : undefined;
      if (children) {
        children = React.Children.map(element.props.children, hook);
      }
      const props = proc(element);
      if (props) {
        props.children = children;
        return React.cloneElement(element, props);
      }
      return React.cloneElement(element, { children });
    }
  };
  return hook(props.children);
};

export default useChildren;

4.まとめ

 props.childrenで子ノードのpropsの書き換えが出来るという記事はあるのですが、実際に活用している内容が見当たらなかったのでソースコードに落とし込んでみました。

 今回の内容を使って、現在作成中のReact用仮想ウインドウモジュールのイベント処理を行おうと目論んでいます。
 仮想ウインドウシステムではウインドウ同士の重ね合わせやアクティブなウインドウの調整など、コンポーネント間の調整が必要となり、それをまとめる管理コンポーネントがあるとべんりだと考えています。
 現在の書き方は、イベント処理を生DOMのメッセージで行っています。

 
 今後の予定としては、今回の内容につけ加えて自由なタイミングでPropsの再投入できるようにしたり(たぶん可能)、仮想ウインドウシステムの方の完成を目指したいと思います。

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

未経験からアメリカのプログラミング・ブートキャンプ�を経てフロントエンドエンジニアになるまで

初投稿です。
現在アメリカ、南カリフォルニアの企業でフロントエンドエンジニアをしています。
高校卒業後、音楽の夢を追いかけてアメリカに来てから10数年、33歳にして全くの未経験からフロントエンドエンジニアに転職するまでの経緯をシェアしていきたいと思います。
もし将来アメリカで働きたい人や、現在アメリカにいて転職を考えてる人に参考にしてもらえればと思います。

codeschool.png

バックグラウンド

高校は日本の普通の高校を卒業し、特に英語が喋れるわけでもないですが謎の使命感に押されて渡米。19歳から32歳までは音楽とアートに専念し、ギリギリ食べていける状態をキープしていました。

2016年にアメリカ人女性と結婚したことでビザの問題がクリアされ、新たなことにチャレンジしたくなり、ちょうど自分のウェブサイトを作りたいと思っていたので、友人がインストラクターをしているJavaScriptブートキャンプに参加することに決めました。

JavaScriptブートキャンプとは

ここ数年アメリカではプログラミングを習う人が急激に増えています。
その大きな理由がこういった短期のコーディングブートキャンプ。
ほとんどがフルスタックを教えて、更に卒業後は就職のサポートまでしてくれます(中には企業と提携して、優秀な生徒を在学中から送り込む学校もあります。)。学校によっては最初に授業料は発生せず、就職後に給料から毎月返済していくシステムのところもあります。期間は基本的に3ヶ月から6ヶ月なので、サクッとプログラミングを習えるシステムになっています。

ブートキャンプ体験

私が行ったブートキャンプはJavaScript(React)とNodejsというスタックでした。
2クラス、各6人の生徒にインストラクター1人ずつという非常にコンパクトな学校でした。授業料は15,000ドル、日本円だと約150万円ほどでした。週5回毎日10時から4時半までカッチリとコードを書いて、終わったらプログラミングのことは考えないようにするというのが学校のモットーだったので、他の学校と比べると割と易しかったのではと思います。ですが、12人で始まったクラスも終わるときには9人まで減っていました。

クラスの流れは最初の週にHTMLとCSSをざっとレビューした後、
2週目からJavaScriptが始まり4週間毎に自分で考えたプロジェクトを作るといった形です。
Reactは結局9周目からのみで、それまでは全てVanilla JavaScript(ES6)での作業でした。
プロジェクトは基本的に自由でどんな技術を使っても良いため、みなそれぞれ面白いものを作っていました。私はTone.jsを使ったシンセサイザーや、SPAのeCommerceサイトを作ったりしました。

英語はやはりしっかりとできるに越したことは無いのですが、クラスに一人台湾からの留学生がおり、彼はあまり英語が話せるわけではなかったですが、読む力は十分にあったため遅れることなく、というかむしろ優秀な成績で卒業していきました。なので読解力があれば問題ないかと思います。

ブートキャンプが終わってから就職まで

前述の通り、ほとんどのブートキャンプは卒業後の就職活動もサポートしてくれます。
私の行った学校では、履歴書の添削や、面接/テストの対策等色々してくれました。
中でも俗に言うWhiteboard Challenge(ホワイトボード上でJavaScriptの問題を解く)の練習と、
面接で聞かれるであろう質問集は重宝しました。

私は日本で就職したことが無いため、比べることができませんが、現時点のアメリカではブートキャンプ出身者が未経験のまますんなりと仕事に就くことは非常に難しいです。
私も3ヶ月間ひたすら応募しまくり(合計302社)やっとエントリーレベルの仕事に就くことができました。学校からのアドバイスも最低100社でうまく行けば就職できるとのことだったのである程度覚悟していたのですが大変でした。。中には卒業後1年経っても就職できず諦めていった人もいます。
アメリカでの就職活動に関してはまた別の記事にしたいと思います。

就職活動中、毎日ひたすらコードを書き、何社ものテストを受けるうちに自然とスキルアップもできたため、この期間は金銭的に大変でしたが非常に有意義なものでした。

まとめ

正直こういったブートキャンプは賛否両論あり、企業によっては採用を敬遠するところもあるそうです。しかし、私を含め、ブートキャンプのおかげで人生が変わった人も多いため私はブートキャンプおすすめします。ただ学校によっては生徒数が多すぎてちゃんと教えていなかったり、古いテクノロジーで教えるところもあったりと学校の選別が大事だと思います。最近ではカリフォルニア州立大学も同様のブートキャンプを催したりしているので今後更にブートキャンプ出身者は増えるのではと思います。

日本語で文章を書くなんて高校以来なので稚拙で恐縮ですが読んでくれてありがとうございます!

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

未経験からアメリカのプログラミング・ブートキャンプを経てフロントエンドエンジニアになるまで

初投稿です。
現在アメリカ、南カリフォルニアの企業でフロントエンドエンジニアをしています。
高校卒業後、音楽の夢を追いかけてアメリカに来てから10数年、33歳にして全くの未経験からフロントエンドエンジニアに転職するまでの経緯をシェアしていきたいと思います。
もし将来アメリカで働きたい人や、現在アメリカにいて転職を考えてる人に参考にしてもらえればと思います。

codeschool.png

バックグラウンド

高校は日本の普通の高校を卒業し、特に英語が喋れるわけでもないですが謎の使命感に押されて渡米。19歳から32歳までは音楽とアートに専念し、ギリギリ食べていける状態をキープしていました。

2016年にアメリカ人女性と結婚したことでビザの問題がクリアされ、新たなことにチャレンジしたくなり、ちょうど自分のウェブサイトを作りたいと思っていたので、友人がインストラクターをしているJavaScriptブートキャンプに参加することに決めました。

JavaScriptブートキャンプとは

ここ数年アメリカではプログラミングを習う人が急激に増えています。
その大きな理由がこういった短期のコーディングブートキャンプ。
ほとんどがフルスタックを教えて、更に卒業後は就職のサポートまでしてくれます(中には企業と提携して、優秀な生徒を在学中から送り込む学校もあります。)。学校によっては最初に授業料は発生せず、就職後に給料から毎月返済していくシステムのところもあります。期間は基本的に3ヶ月から6ヶ月なので、サクッとプログラミングを習えるシステムになっています。

ブートキャンプ体験

私が行ったブートキャンプはJavaScript(React)とNodejsというスタックでした。
2クラス、各6人の生徒にインストラクター1人ずつという非常にコンパクトな学校でした。授業料は15,000ドル、日本円だと約150万円ほどでした。週5回毎日10時から4時半までカッチリとコードを書いて、終わったらプログラミングのことは考えないようにするというのが学校のモットーだったので、他の学校と比べると割と易しかったのではと思います。ですが、12人で始まったクラスも終わるときには9人まで減っていました。

クラスの流れは最初の週にHTMLとCSSをざっとレビューした後、
2週目からJavaScriptが始まり4週間毎に自分で考えたプロジェクトを作るといった形です。
Reactは結局9周目からのみで、それまでは全てVanilla JavaScript(ES6)での作業でした。
プロジェクトは基本的に自由でどんな技術を使っても良いため、みなそれぞれ面白いものを作っていました。私はTone.jsを使ったシンセサイザーや、SPAのeCommerceサイトを作ったりしました。

英語はやはりしっかりとできるに越したことは無いのですが、クラスに一人台湾からの留学生がおり、彼はあまり英語が話せるわけではなかったですが、読む力は十分にあったため遅れることなく、というかむしろ優秀な成績で卒業していきました。なので読解力があれば問題ないかと思います。

ブートキャンプが終わってから就職まで

前述の通り、ほとんどのブートキャンプは卒業後の就職活動もサポートしてくれます。
私の行った学校では、履歴書の添削や、面接/テストの対策等色々してくれました。
中でも俗に言うWhiteboard Challenge(ホワイトボード上でJavaScriptの問題を解く)の練習と、
面接で聞かれるであろう質問集は重宝しました。

私は日本で就職したことが無いため、比べることができませんが、現時点のアメリカではブートキャンプ出身者が未経験のまますんなりと仕事に就くことは非常に難しいです。
私も3ヶ月間ひたすら応募しまくり(合計302社)やっとエントリーレベルの仕事に就くことができました。学校からのアドバイスも最低100社でうまく行けば就職できるとのことだったのである程度覚悟していたのですが大変でした。。中には卒業後1年経っても就職できず諦めていった人もいます。
アメリカでの就職活動に関してはまた別の記事にしたいと思います。

就職活動中、毎日ひたすらコードを書き、何社ものテストを受けるうちに自然とスキルアップもできたため、この期間は金銭的に大変でしたが非常に有意義なものでした。

まとめ

正直こういったブートキャンプは賛否両論あり、企業によっては採用を敬遠するところもあるそうです。しかし、私を含め、ブートキャンプのおかげで人生が変わった人も多いため私はブートキャンプおすすめします。ただ学校によっては生徒数が多すぎてちゃんと教えていなかったり、古いテクノロジーで教えるところもあったりと学校の選別が大事だと思います。最近ではカリフォルニア州立大学も同様のブートキャンプを催したりしているので今後更にブートキャンプ出身者は増えるのではと思います。

日本語で文章を書くなんて高校以来なので稚拙で恐縮ですが読んでくれてありがとうございます!

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

Win上でRails+Bootstrapを使用した時に出てくるExecJS エラーを直したい(直す)

はじめに

WindowsでRailsアプリケーションを作成し、
Bootstrapを導入したところ、ExecJSエラーが出て、起動しない。
- Windows 10 proもしくはhomeで確認
- Railsのバージョンは5.2.3
- Rubyのバージョンは2.6.3

最近Rails6にアップデートされたので、この記事は今後使えなくなる可能性があります。

詳細

こんなエラーがターミナル上で出てるのではないでしょうか。

ExecJS::ProgramError in Home#index
Showing C:/Source/Repos/graph-tutorials/ruby-test/app/views/layouts/application.html.erb where line #8 raised:

identifier '(function(opts, pluginOpts) {return eval(process' undefined
Rails.root: C:/Source/Repos/graph-tutorials/ruby-test

解決策

僕はこのissueをみて使えるようになりました。
https://github.com/twbs/bootstrap-rubygem/issues/157

やり方

1.Gemfileの修正

duktapeのgemを探してコメントアウト、もしくは削除します。

2.Node.jsのインストール(Node.jsがインストールされていない場合のみ)

Node.jsの公式ページからダウンロードします。
https://nodejs.org/ja/download/
推奨版と最新版があって、最新版をダウンロードしたくはなりますが、
ここはその気持ちを押し殺して推奨版をダウンロードします。その後インストールをしてください。
nodejs.org_ja_download_.png

その後Windowsで環境変数の設定を開き、PATHを通してください。
PATHの通し方はこちらを参考にするといいです。
いろんな方法がありますね。
https://qiita.com/yuki12/items/9723f60907508b11504b

最後にコマンドプロンプト or Gitbash or PowerShellを開いて
下記のコマンドを入力し、バージョンが表示されるか確認してください。

$ node -v

config/boot.rbに追記する。

下記のコードを貼り付けてください。
runtime時にnodeを使うように指示します。

config/boot.rb
ENV['EXECJS_RUNTIME'] = 'Node'

サーバーを再起動

サーバーを再起動して、エラーが出ないか確認してください。

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

Win上でRails+Bootstrapを構築の時に出てくるExecJS エラーを解決したい(解決する)

はじめに

WindowsでRailsアプリケーションを作成し、
Bootstrapを導入したところ、ExecJSエラーが出て、起動しない。
- Windows 10 proもしくはhomeで確認
- Railsのバージョンは5.2.3
- Rubyのバージョンは2.6.3

最近Rails6にアップデートされたので、この記事は今後使えなくなる可能性があります。

詳細

こんなエラーがターミナル上で出てるのではないでしょうか。

ExecJS::ProgramError in Home#index
Showing C:/Source/Repos/graph-tutorials/ruby-test/app/views/layouts/application.html.erb where line #8 raised:

identifier '(function(opts, pluginOpts) {return eval(process' undefined
Rails.root: C:/Source/Repos/graph-tutorials/ruby-test

解決策

僕はこのissueをみて使えるようになりました。
https://github.com/twbs/bootstrap-rubygem/issues/157

やり方

1.Gemfileの修正

duktapeのgemを探してコメントアウト、もしくは削除します。

2.Node.jsのインストール(Node.jsがインストールされていない場合のみ)

Node.jsの公式ページからダウンロードします。
https://nodejs.org/ja/download/
推奨版と最新版があって、最新版をダウンロードしたくはなりますが、
ここはその気持ちを押し殺して推奨版をダウンロードします。その後インストールをしてください。
nodejs.org_ja_download_.png

その後Windowsで環境変数の設定を開き、PATHを通してください。
PATHの通し方はこちらを参考にするといいです。
いろんな方法がありますね。
https://qiita.com/yuki12/items/9723f60907508b11504b

最後にコマンドプロンプト or Gitbash or PowerShellを開いて
下記のコマンドを入力し、バージョンが表示されるか確認してください。

$ node -v

config/boot.rbに追記する。

下記のコードを貼り付けてください。
runtime時にnodeを使うように指示します。

config/boot.rb
ENV['EXECJS_RUNTIME'] = 'Node'

サーバーを再起動

サーバーを再起動して、エラーが出ないか確認してください。

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

nodejsプロジェクトをgulpでDropboxにバックアップする

要約

nodejsを使った開発環境でバージョン管理じゃなくて単なるバックアップとしてソースコードを管理する方法を検討しました。

※この記事の対象読者:
nodejsのプロジェクトでタスク管理にgulpを利用している人で、バージョン管理以外でバックアップとしてソースコード管理をしたい人

問題点

  • node_modulesとか.gitとかdistとかを同期したくない
  • 主要なクラウドサービス(Dropbox, GoogleDrive, iCloud)は特定のファイル・ディレクトリを同期から除外するための設定が若干めんどくさい、または存在しない。

解決方法

  • gulpを利用してビルドのたびにバックアップディレクトリにソースファイルをコピーする。
  • 基本的にプロジェクト全体をバックアップするが、gulpの設定で特定のディレクトリはコピーの対象から外す。

背景

昨日SSDが逝きました。

リモートリポジトリにはまだpushしてなかったコード達がお亡くなりになりました。
バージョン管理とバックアップは別物なんだなぁ 
                   みつを
本当にありがとうございました。

(主にMacを使ってるので)昔はTimeMachineでバックアップを取ってたのですが、NASが壊れたのを機にそのまま放置してしました・・・。

自前でバックアップとるのはもう嫌なので、手軽にクラウドでバックアップを取る方法を調べて検討したのでメモします。

本当はもっと賢い方法があるのでしょうが、わからないので誰かご存じでしたら教えていただけると助かります・・・。

従来手法?

Dropbox

.gitignoreっ的なもので制御
dbignoreというOSSがあるけどMacしか対応してない(多分)。
(たまにWindowsも使うんじゃ・・・T^T)
http://konolige.com/dbignore/

公式も「たくさん要望あるけどまだ作れてござらん」っておっしゃってる?
https://www.dropboxforum.com/t5/Files-folders/Exclude-specific-folder-names-with-Smart-Sync/td-p/289837

GUIで制御
selective syncという設定をすることで、ディレクトリを一つ一つ同期するかしないかを選択できる?らしいけど、これやるとローカルからディレクトリが消されるので開発できないorz。重たいファイルを特定のデバイスにダウンロードさせないための機能っぽい。
https://help.dropbox.com/installs-integrations/sync-uploads/selective-sync-overview

GoogleDrive

GUIで制御
drop boxと同じでローカルから削除されるっぽい(未検証)
https://support.google.com/drive/forum/AAAAOxCWsTozRU34aolkdo/?hl=en&gpf=%23!topic%2Fdrive%2FzRU34aolkdo

iCloud

ファイル名で制御
ファイル末尾に.nosync または.tmpをつける。
これは仮にできたとしても、やりたくないので未検証・・・
https://discussions.apple.com/thread/8137146

OneDrive

ない

「OneDriveの思想はOneDriveの中身すべてを同期させることやぞ」
「特定のディレクトリを除外する機能を作るとしてもNot Right Nowだ」とのこと(2015年)

https://onedrive.uservoice.com/forums/262982-onedrive-archive/suggestions/6988070-use-a-file-to-ignore-exclude-files-or-folder

従来手法のまとめ
クロスプラットフォームで簡単に開発中のものをバックアップするような主要なクラウドサービスはない(多分。ほんとか?)。

提案手法

Gulp(v4)でビルドするたびにどっかにコピーする。

下準備
バックアップディレクトリの定義
(1人/1つの開発マシンしかないならこれをやらずにgulpfile.jsにパスをべた書きでOK。)
mac/linuxだったら単にterminalで、windowsだったらgitbashで下記を.bashrcとかに追記

※gulp
https://gulpjs.com/

EXPORT backup_directory="/backup/directory"

gulpfile.jsに設定を追加

var gulp = require('gulp');
var path = require("path");

gulp.task('backup:src', function () {
  return gulp.src([
    './**',                 //コピーしたいファイルのパス
    '!./node_modules/**',   //除外したいパス(先頭に!をつける)
    '!./dist/**',           //同上
    '!./.git'               //同上
  ])
 .pipe(gulp.dest(process.env.backup_directory+'/'+path.basename(__dirname)));
 //back_updirectory/projectname配下にバックアップ
});

gulp.task('default', gulp.series(
    //ほかのタスク:コンパイルとかデプロイとか
    'backup:src',
    //...
  )
);

タスクを実行

npx gulp

結果
結果
Mac死んでるからWindowsで検証してる・・・orz

結論

gulpを使えば、ビルドの度に手軽にソースコードをDropboxにバックアップでき、node_modulesなどは除外できました。多分他のクラウドサービスも同じように実行できると思います。

まとめと感想

初めてQiita記事を書いてみました。
間違いなどあれば、ご指摘いただけると助かります。

本当に簡単にソースコードバックアップできるサービスないのかな。。。

Javaとか他の開発環境には適用できないし、いっそすべての言語のプロジェクトをGulpでタスク管理するか・・・。と思ったけどそれは違う気がする。

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