揚げログ

karaageの技術開発関連のブログです

store.stateの読み込みは遅延する - Nuxt.js

概要

Nuxt.jsでvuex storeでデータのCRUDを行ってる状況を想定する。

すでにstate.userには他のページか親コンポーネントでの操作(dispatch, commit)で、undefinedではない何らかのObjectが入っている前提とします。

ダメなコード

このコードは動いたり動かなかったりします。(stateの読み込み速度によって結果が変わってしまう)

$store.getters を使っても、同様の症状になると思います。

<template>
    <v-container>
         <p>{{user}}</p>
    </v-container>
<template>

<script>
export default {
  beforeCreated() {
    this.user = this.$store.state.user;
  },
  data(){
    return {
      user: Object
    }
}
</script>

→ Error! user is undefined

ちゃんと動くコード

stateのLoad状況によって、表示を切り替え、undefinedエラーを防ぎます。

<template>
    <v-container>
         <p>{{user}}</p>
    </v-container>
<template>

<script>
export default {
  computed: {
    user() {
      const user = this.$store.state.user
        if (!!user) {
         return user;
      }
    else {
      return {};
    }
  }
}
</script>

SSRで使えないライブラリをSSRで使う - Nuxt.js

状況

Nuxt.jsでwebサービス開発をしていると、SSRで使えないライブラリをSSRで使いたい時がある。例えば、

  • DOMにアクセスするライブラリ

(SSRで使うと毎回orページリロード時にdocument is not defined, window is not definedのようなエラーが発生する)

回避方法

ここでは、vue-grid-layoutというライブラリを例にする。

エラーが出る例

<template>
  <v-container>
    <grid-layout>
     ...
    </grid-layout>
  </v-container>
</template>

<script>
import VueGridLayout from "vue-grid-layout"
...
export default {
...
}
</script>

成功例

<template>
  <v-container>
    <client-only>
        <grid-layout>
     ...
        </grid-layout>
    </client-only>
  </v-container>
</template>

<script>
// import VueGridLayout from "vue-grid-layout" ここでインポートしない
export default {
    ...
    created(){
           if (process.client) { //clientでの実行ならば
              require("vue-grid-layout");
          }
    }
}

まとめ

  1. ライブラリを使うコンポーネントのhtmlタグ部分を、 <client-only> で囲う

参考: https://ja.nuxtjs.org/api/components-client-only/

  1. ライブラリのインポートを created() で行う

createdはVueインスタンスが生成された直後に実行される。これをasyncDataとかでやると、まだDOMがないのでエラー(document is not defined)になる。

参考: http://lab.astamuse.co.jp/entry/2019/05/29/114500

  1. if (process.client) {} で囲む

created()はサーバー側・クライアント側両方で実行されるが、このif文を入れておくことで、確実にサーバー側での実行をブロックできる。

CSSアニメーション、使っていきたい

Webページを作るとき、画像でなくてもCSSで描ける記号やマークは、軽量化のためにCSSで書きたいと思っている。ブラウザのCSSレンダリングエンジンを有効活用でき、画像をダウンロードする無駄な通信を減らせる。

Googleのサイト評価サービスである「PageSpeed Insights」では、初期ロードの速さがスコアに大きく影響する。

PageSpeed Insights

サイト作成側としても、無駄なアセットが減り管理コストが低下する。当然コードベースなのでGitでデザイン変更の管理が出来ることも、メリットとして特筆すべきであろう。

予てより、github.ioにhexoを使って構築していた頃の私のブログは、CSSによるヘッダーデザインを採用していた。

f:id:krgpi:20200120235805p:plain
CSSで描画したタイル状の背景(jpg)

これにより、仮にYouTubeを見過ぎて通信制限になっていても、ヘッダー画像のロードを待機することなく、ページ全体が描画される。

このように、私のサイトでは積極的にCSSによるデザインを使ってきた。また、今後もCSSによる更に高度なデザインを作っていきたいのと同時に、動きのあるCSSにも触れていきたいと考えている。


CSSアニメーションとは

  • あるCSSのスタイル設定を別の設定へ遷移させる
  • アニメーションは2種類の要素で構成される
    • アニメーションについての記述するスタイル
    • アニメーションの始点と終点のスタイルを示すキーフレーム(通過点のスタイルも示すことができる)

JSのアニメーションに比べた利点

  • 単純なアニメーションには使いやすい
  • レンダリングエンジンのパフォーマンスを可能な限り滑らかに保てる(自動でフレームが省略されたり)
  • ブラウザはアニメーションの流れを制御してパフォーマンスと効率を最適化する

出典: animation - CSS: カスケーディングスタイルシート | MDN https://developer.mozilla.org/ja/docs/Web/CSS/animation


バイト先のエンジニアが作ってたCSSアニメーションは、もうもはや動画作品の域に達していて、凄すぎてもはや感動してしまった。

私としては、今後、自身の手がけた様々なことについて扱う、ポートフォリオのようなwebページを作りたいと考えており、そこでのデザインの一部として効果的にCSSを使っていきたいと思った。

Nuxt.jsで作ったSSRなwebサービスをfirebaseにデプロイする

環境

  • node v10.16.3
  • npm v6.9.0
  • nuxt v2.10.2

Nuxt.jsで作ったSSRなwebアプリをfirebase Hosting, functionsにデプロイするために行った設定の一覧です。すでにSSRモードでwebアプリができている前提で話を進めます。

1 firebase CLIの開発環境へのインストール

Firebase CLI リファレンス  |  Firebase

ここを参考に導入していきます。

2 プロジェクトの初期化を行う

$ firebase init

をプロジェクトルートで叩き、

  1. 自分のGoogleアカウントと紐付け
  2. 自分のFirebase プロジェクトと紐付け

を行います。それ以外の設定は、あとで変更するので、適当に進めます。

3 [プロジェクトルート]/firebase.json を編集する

プロジェクトルート直下に、 firebase.json というファイルが生成されているはずです。後ほど使うfirebase serve, firebase deploy のコマンドは、このファイルで設定を読み込んで実行されます。これを編集します。

{
  "functions": {
    "functions": {
      "source": "functions"
    }
  },
  "hosting": {
    "public": "dist",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "function": "ssr"
      }
    ]
  }
}

functionsで、Firebase Functionsで動作するSSRの実行関数(js)部分の場所を指定しています。

hostingで、Firebase Hostingで静的配信するhtmlがある場所を指定しています。

rewritesで、静的ルートへのアクセスをSSRのルートへ飛ばします。sourceでどのページを飛ばすか、functionでどの関数に飛ばすかを指定しています。ここでは、"**"(全て)を"ssr"(あとで定義する関数)へ飛ばすことにしています。

4 [プロジェクトルート]/functions/package.jsonを編集する

プロジェクトルート直下に functionsというディレクトリが生成されいるはずです。firebase Functionsには、ここにあるファイル群がデプロイされます。

さらに、/functions/package.json も生成されていると思います。

{
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "scripts": {
    "serve": "firebase serve --only functions",
    "shell": "firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
+  "engines": {
+    "node": "10"
+  },
  "dependencies": {
    "firebase-admin": "^8.6.0",
    "firebase-functions": "^3.3.0",
 +   "nuxt": "^2.10.2"
  },
  "devDependencies": {
    "firebase-functions-test": "^0.1.6"
  },
  "private": true
}
  1. firebase Functionsで実行するnodeのバージョンを指定します。ここでは、 v10を指定します。ちなみに、Firebaseの制約で8か10しか指定できません(2019.11.25現在)
  2. また、firebase Functionsで実行する際に必要なNuxtを依存に追加します。
  3. 追加したあとで、npm install を実行してnuxtをインストールします。

5 [プロジェクトルート]/functions/index.jsを編集or作成する

functionsディレクトリの中に index.js というjsファイルを作成します。4で編集したpackage.jsonと同じ階層です。

このファイルは、Firebase Functionsで一番初めに実行されるファイルになります。3のrewritesで指定した"ssr"はここに作成する exports.ssr です。

const functions = require('firebase-functions');
const { Nuxt } = require('nuxt');
const nuxt = new Nuxt({
    buildDir: 'ssr',
    dev: false
});

exports.ssr = functions.https.onRequest(async (req, res) => {
    await nuxt.ready()
    return nuxt.render(req, res)
})

buildDir で指定する 'ssr' は、[プロジェクトルート]/functions/ssr ですが、この次の5でこの場所に必要なファイルをコピーする設定をします。

6 [プロジェクトルート]/package.jsonを編集する

[プロジェクトルート]/package.json には、nuxtが使う依存関係や npm run dev でプレビューが起動するようなスクリプトが記載してあります。

{
  "name": "MyProject",
  "version": "1.0.0",
  "description": "My shining Nuxt.js project",
  "author": "krgpi",
  "private": true,
  "scripts": {
    "dev": "nuxt",
    "build": "nuxt build && npm run build:copy:ssr",
    "build:copy:ssr": "rimraf functions/ssr && mkdirp functions/ssr && cp -R .nuxt/dist functions/ssr/dist",
    "start": "nuxt start",
    "generate": "nuxt generate"
  },
  "dependencies": {
   ...(省略)
    "nuxt": "^2.10.2",
  },
  "devDependencies": {}
}

ここで、scripts→ build:copy:ssrを追加し、build で追加したbuild:copy:ssr も実行するように修正します。

build:copy:ssr に書いてあるコマンドで、nuxt buildで生成された出力ファイルを、先ほど3で指定したパスにコピーしています。

7 ローカルでプレビューする

プロジェクトルートで $ firebase serve を実行してみましょう。 $ firebase serve -p 3000 とかするとポート番号を指定することができます。

ここでは、3000にポート番号を指定したとします。コマンドを叩くと、以下のようなログが出てくると思います。

✔  functions: Using node@11 from host.
✔  functions: Emulator started at http://localhost:3001
i  functions: Watching "/[プロジェクトルート]/functions" for Cloud Functions...
i  hosting: Serving hosting files from: dist
✔  hosting: Local server: http://localhost:3000

8 Firebaseにデプロイする

ここまでできれば、プロジェクトルートで $ firebase deploy を叩くだけです。

参考

nuxt.js + firebase (cloud functions) で最小構成SSR | 丸ノ内テックブログ

SSR モードの Nuxt.js を Firebase に Deploy する Tutorial - Qiita

ローカルでテストしてサイトにデプロイする  |  Firebase

最後に

エビデンスベースの議論が出来るディスカッションサイト TopicNote をNuxt, Go, Firebaseを使って作りました。まだまだベータ版ですが、ぜひ今後の情報などをフォローしていただけると嬉しいです。技術発信などもしていきたいと思ってます!

TopicNote | ディスカッションサイト

TopicNote - ディスカッションサイト(@topicnote_jp)さん / Twitter

AR kit ImageTracking - トラッキング画像が画面外に出たことを検出

デリゲートメソッド didUpdate に以下のように書く。

didUpdateはARAnchorが更新される(TrackingImageの座標が変化する)間、呼ばれ続けるデリゲートメソッドである。

TrackingImageが画面外から出ると当時に、ARAnchorは更新されなくなり、このデリゲートメソッドは呼ばれなくなる。

が、画面外に出る瞬間までは、ARanchorがアップデートされるので、Trackingが終わる瞬間が最終アップデートになる

つまり、以下のコードでは、TrackingImageが画面外に出た時に一度だけPrintされる。

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
     if let imageAnchor = anchor as? ARImageAnchor{
          if !imageAnchor.isTracked {
             print(imageAnchor.referenceImage.name) //画面外に出たTrackingImageの名前をPrint 
            } 
        } 
    }

AR kit で顔面キャスアプリを作る (その4 transformから目の回転角を求める)

だいぶ間が空いてしまいました。

この間は、AR kit FaceTracking Configurationで、表情の値(blendshapes)を取得しましたが、今回は目の動きを取ってみたいと思います。

全体手順

  1. AR kitのFaceTrackingで、目の動きを数値データで取り出す
  2. ソケット通信でサーバーに送信
  3. クライアント側で数値を受信
  4. three.js VRMloaderでVRMデータを読み込む
  5. VRMデータに数値を反映させる

2, 3は、前回までで動作確認まで終わっています。

4, 5は次回の記事に多分書きます。

コード

下記コードは、1,2を行う処理で、func rendererは顔情報がアップデートされるたび(didUpdate)に呼ばれる関数です。

コードの全体は、

facecast_ios/eyeGaze.swift at master · krgpi/facecast_ios · GitHub

です。

class eyeGaze: NSObject, VirtualContentController { 
    var emitArray:[String: Float] = ["eyeL": 0.00, "eyeR": 0.00, "faceDir": 0.00]
//中略
    func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
        guard #available(iOS 12.0, *), let faceAnchor = anchor as? ARFaceAnchor
            else { return }
        
        emitArray["eyeL"] = asin(faceAnchor.leftEyeTransform.columns.2.x)
        emitArray["eyeR"] = asin(faceAnchor.rightEyeTransform.columns.2.x)
        emitArray["faceDir"] = asin(faceAnchor.transform.columns.2.x)

        do {
            let e = try JSONSerialization.data(withJSONObject: emitArray, options: .prettyPrinted)
            let str = String(bytes: e, encoding: .utf8)
            socket.socketIOClient.emit("chat message", str!)
        } catch  {
            print("err")
        }

    }
//後略
}

AR kit の Transformについて

ここで出てくる、leftEyeTransform, rightEyeTransform, transformは、いずれも

transform - ARAnchor | Apple Developer Documentation

にあるように、基準ベクトル(正面向き・まっすぐ前向き)から現在のベクトルへの変換行列です。3次元の変換行列なので、以下のようにパラメータを定めた時、

params 内容
d_{x} x方向への移動距離
d_{y} y方向への移動距離
d_{z} z方向への移動距離
\theta_{x} x軸周りの回転角
\theta_{y} y軸周りの回転角
\theta_{z} z軸周りの回転角

ただし、EyeTransformに関しては、

^\forall \theta_{z} = 0

AR kitでは、右手系(親指から順にx,y,z軸)を用いていて、目の奥から先に向かう軸をZ軸としています。

目はX, Y軸中心にしか回転しないので、Z軸回りの回転角が常に0です。

今回の計算

今回は、目の水平方向の回転を取り出したいので、 \theta_{y} を求めたいと思います。

^\forall \theta_{z} = 0 の時の3次元変換行列は、
\left(\begin{array}{rrrr}\cos{\theta_{y}} & 0 & -\sin{\theta_{y}} & d_{x}\\ \sin{\theta_{x}}sin{\theta_{y}} & cos{\theta_{x}} & \sin{\theta_{x}}cos{\theta_{y}}  & d_{y} \\ \cos{\theta_{x}}sin{\theta_{y}}  & -\sin{\theta_{x}} & \cos{\theta_{x}}cos{\theta_{y}}  & d_{z} \\0 & 0 & 0 & 1\\\end{array}\right)
です。

transformのデータ型は simd_float4x4 という型で、transform.colums.0, 1, 2, 3 という形で、まず列が取り出せます。

さらに、transform.colums.0.x, y, x, wという形で、行数を選択することができます。

ここで、 \theta_{y}=\arcsin({transform.colums.2.x}) となります。

まとめの図

f:id:en0qi:20190319111231j:plain
transform(simd_float4x4)

iPad Pro、便利ですね!!

次回はthree.jsを使ったウェブビューワーの作成の記事を投げます!

AR kit で顔面キャスアプリを作る (その3 顔面情報の送受信)

今回やること

今回は、その2で確認した、顔面各パーツの数値データを、アプリとサーバーの間で送受信します。

送信

こんな感じでサーバーに数値をリアルタイムに送信することに成功しました。

FaceTracking中にfaceGeometryがアップデートされた時に呼ばれる関数に、Socketに値を送信するコードを挿入しました。

受信

サーバーから受信した値を元に顔の3Dオブジェクトを動かします。

続きを読む