- 投稿日:2020-06-03T23:42:08+09:00
Vue.jsの基礎を学ぶ(2日目)
bind()メソッドについて
const module = { x: 42, getX: function() { return this.x; } }; const unboundGetX = module.getX; console.log(unboundGetX()); // The function gets invoked at the global scope // expected output: undefined const boundGetX = unboundGetX.bind(module); console.log(boundGetX()); // expected output: 42thisの意味するものがその時々で異なるので、bind()メソッドを使って指定してあげるというイメージで合ってるかな?
*以下参考サイト
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
ビットコインの価格表示プログラム
<div id="app"> <h2> Bitcoin Price </h2> <section v-if="hasError"> Error, </section> <section v-else> <div v-if="loading"> Loading... </div> <div v-else> <ul v-cloak> <li v-for="(rate, currency) in bpi"> {{ currency }} : {{ rate.rate_float | currencyDecimal }} </li> </ul> </div> </section> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>注意すべきは、v-ifディレクティブとv-elseディレクティブ。
rubyのif elseと同じように、v-ifがfalseの時にv-else以下のコードが表示される。var app = new Vue({ //options el: '#app', data: { bpi: null, hasError: false, loading: true }, //マウントされた後に呼ばれる mounted: function() { axios.get('https://api.coindesk.com/v1/bpi/currentprice.json') //responseにAPIからの戻り値が入る .then(function(response){ //console.log(response.data.bpi) //console.log(response.data.bpi.USD.rate_float) this.bpi = response.data.bpi }.bind(this)) //コンソールにエラーを出力 .catch(function(error){ console.log(error) this.hasError = true }.bind(this)) //finallyは処理の最後に行うことができる //これでLoading...マークが消えて価格が表示される .finally(function(){ this.loading = false }.bind(this)) }, filters: { currencyDecimal(value) { //toFixedメソッドは小数点の桁数を引数に指定することができる。 return value.toFixed(2) } } })様々なテンプレート制御ディレクティブ
v-once
初回だけテキストバインディングを行いたい時に使う。
つまり、初回だけテンプレートを評価する
<div id="app"> <p v-once> {{ message }} </p> <button v-on:click="clickHandler"> Click! </button> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16"></script>var app = new Vue({ //options el: '#app', data: { message: 'Hello Vue.js!' }, methods: { clickHandler: function(event) { this.message = this.message.split('').reverse().join('') } } })上記ではボタンがクリックされるとmessageプロパティの文字を反転させるプログラムを組んでいるが、テンプレート側にv-onディレクティブが記載されているため、初回の
Hello Vue.js!は表示されるが、ボタンを押して文字が反転することはない。これはv-onceが初回だけバインディングする機能によるもの。
v-pre
要素と全ての子要素のコンパイルをスキップしたい時に使う。
生のマスタッシュタグを表示したい
XSS対策
コンパイルのスピードを上げたいそんな時に使うらしい。
<div id="app"> <p v-pre> {{ message }} </p> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16"></script>var app = new Vue({ //options el: '#app', data: { message: 'Hello Vue.js!' } })本来は{{ message }}によってブラウザにHello Vue.js!と表示される。
しかし、pタグにv-preディレクティブを追加すると、コンパイルが行われずに
{{ message }} とブラウザに表示されるようになる。
v-html
プレーンなhtmlを挿入したい時に使う
本来はXSS対策のため、ユーザーが
アイウエオ
と文字列を入力しても<p>アイウエオ</p>と表示される。
ただ、v-html="プロパティ名"を追加すると、それがhtmlタグとして評価される
<div id="app"> <p> {{ message }} </p> <p v-html="message"> </p> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16"></script>var app = new Vue({ //options el: '#app', data: { message: 'Hello <span style="color:red">Vue.js!</span>' } })htmlファイルにpタグが2つあるが、
一つ目は
Hello <span style="color:red">Vue.js!</span>と表示され、
v-htmlがついている2つ目は
Hello Vue.js!と表示される。
v-cloak
cloakは日本語で覆い隠すという意味。
ページを表示開始してからインスタンスの作成が終わるまでの間に、
マスタッシュタグやコンパイル前のテンプレートが表示されてしまうのを防ぎたい。v-cloakディレクティブを使ったチラつき防止のやり方
1.インスタンスのコンパイルが終了するまで、非表示にしたい要素に、v-cloakディレクティブを追加。
2.cssでv-cloakがついている要素を非表示にする
3.v-cloakディレクティブは、インスタンスの準備が終わると、自動的に取り除かれる。
<div id="app"> <p v-cloak> {{ message }} </p> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16"></script>pタグにv-cloakをつける
var app = new Vue({ //options el: '#app', data: { message: 'Hello Vue.js' } })[v-cloak] { display: none; }v-cloakをセレクタとしてcssを書けば、インスタンスの準備中だけマスタッシュタグを非表示にすることができる。
v-text
マスタッシュの代わりに、ディレクティブを使いたい時。
v-text="プロパティ名"とすることで、マスタッシュ構文を使わずにプロパティを表示することができる。
ただ、基本的にはマスタッシュ構文に統一するのがおすすめ。
<div id="app"> <p> {{ message }} </p> <p v-text="message"> </p> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16"></script>var app = new Vue({ //options el: '#app', data: { message: 'Hello Vue.js' } })バインディング式
マスタッシュ構文の中では、プロパティにメソッドを呼び出したり、三項演算子で場合分けをしたりすることができる。
var app = new Vue({ //options el: '#app', data: { message: 'Hello Vue.js', number: 100, ok: true } })<div id="app"> <p> {{ message }} </p> <p> {{ number + 1 }} </p> <p> {{ message.split('').reverse().join('')}} </p> <p> {{ ok? 'Yes' : 'No' }} </p> <p> {{ ok? message : '' }} </p> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16"></script>このようにマスタッシュ構文内で計算をしたり、メソッドを呼び出したりできる。
ただ、var app = ~~ のような代入はすることができないので注意。=の右側に書ける処理ならマスタッシュ構文内でも書けると覚えておこう。
フィルタ(ローカルフィルタ)
数字を千円区切りのカンマ区切りで表示したい。
ポイント!!!
Vue.jsでは、式の終わりに任意のフィルタを追加することができる。
{{ 式 | フィルタの名前 }}上記のようにフィルタを呼び出す。
<div id="app"> <p> {{ price | numberFormat }} </p> <input type="text" v-bind:value="price | numberFormat"> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16"></script>var app = new Vue({ //options el: '#app', data: { price: 29800000 }, filters: { numberFormat: function(value) { return value.toLocaleString() } } })このように、filtersの中にフィルター名を決めて処理を書いていく。
フィルターはマスタッシュ構文内だけではなく、
<input type="text" v-bind:value="price | numberFormat">このようにプロパティを書けるところなら| フィルター名 で呼び出すことができる。
フィルタ(グローバルフィルタ)
グローバルフィルターを定義するには
Vue.filter(フィルタ名, フィルタの動作)とする
<div id="app"> <p> {{ price | numberFormat }} </p> <input type="text" v-bind:value="price | numberFormat"> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16"></script>Vue.filter('numberFormat', function(value) { return value.toLocaleString() }) var app = new Vue({ //options el: '#app', data: { price: 29800000 } })このように定義する。
グローバルフィルターを定義したことで、#appの要素以外でもフィルターを呼び出すことができるってこと。フィルターの連結
日本円をUSDに変換して、さらに3桁ずつで区切りたい。
ポイントはフィルタは複数連結することが可能ということ。
//日本円をドルに変換 Vue.filter('toUSD', function(jpy) { return jpy / 100 }) //変換したドルを3桁ずつ区切る Vue.filter('numberFormat', function(value) { return value.toLocaleString() }) var app = new Vue({ //options el: '#app', data: { jpyPrice: 29800000000 } })ここでは、複数のグローバルフィルタを用意する。
<div id="app"> <p> {{ jpyPrice | toUSD | numberFormat }} </p> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16"></script>上記のように|を複数使って、フィルターを連結することができる。
フィルタの引数
長い文字列を、20文字...のように変換したい。
ポイントはフィルタをview側で呼び出す時に引数を指定できるということ。
//lengthは何文字に省略するか、suffixは省略後の...のような文字列をどうするか Vue.filter('readMore', function(text, length, suffix) { return text.substring(0, length) + suffix }) var app = new Vue({ //options el: '#app', data: { text: 'In publishing and graphic design, Lorem ipsum is a placeholder text commonly used to demonstrate the visual form of a document or a typeface without relying on meaningful content. Lorem ipsum may be used before final copy is available, but it may also be used to temporarily replace copy in a process called greeking, which allows designers to consider form without the meaning of the text influencing the design.' } })<div id="app"> <p> {{ text | readMore(30, '...') }} </p> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16"></script>readMoreフィルターでは引数が3つあるが、view側で呼び出す時は2つになっている点に注意。
この場合は、length, suffixをview側のフィルターを呼び出す引数で指定してあげれば良い。
v-bind省略記法
完全な構文 <a v-bind:href="url">LINK</a> 省略記法 <a :href="url">LINK</a>v-bindを省略して:だけにすることができる。
var app = new Vue({ //options el: '#app', data: { url: 'google.com' } })<div id="app"> <p> <a v-bind:href="url" target="_blank">Link1</a> <a :href="url" target="_blank">Link2</a> </p> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16"></script>二つのリンクがあるが、どちらも同じ。
算出プロパティ
算出プロパティ computedとは、
関数によって算出したデータを返すことができるプロパティ算出プロパティを使って処理をまとめてみよう
<div id="app"> <p> {{ message.split('').reverse().join('') }} </p> <p> {{ reversedMessage }} </p> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16"></script>var app = new Vue({ //options el: '#app', data: { message: 'Hello Vue.js!' }, computed: { reversedMessage: function() { return this.message.split('').reverse().join('') } } })算出プロパティに関しては、computed:以下に処理を書いていく。
Rubyでいうヘルパーみたいなものだと思っている。今回はreversedMessageという算出プロパティを定義した。
これを<p>{{ reversedMessage }}</p>このようにview側に書くだけで、文字を反転させて表示できる。
しかもこれを何回も使い回すことができる。メソッドと算出プロパティの違い
<div id="app"> <p> {{ message.split('').reverse().join('') }} </p> <p> {{ reversedMessage }} </p> <p> {{ reversedMessageMethod() }} </p> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16"></script>var app = new Vue({ //options el: '#app', data: { message: 'Hello Vue.js!' }, //算出プロパティ。プロパティなので呼び出す時にカッコがいらない。 computed: { reversedMessage: function() { return this.message.split('').reverse().join('') } }, //メソッド。メソッドなので呼び出す時はカッコがいる。 methods: { reversedMessageMethod: function() { return this.message.split('').reverse().join('') } } })一つ目の違いは、プロパティは呼び出す時に()が必要ない。対して、メソッドは呼び出す時に()が必要になる。
2つ目は、getterとsetter。これは以降で解説。
3つ目は、キャッシュがあるかどうか。プロパティはキャッシュがあるが、メソッドにはない。
ということで、使えるならなるべくプロパティを使った方が良いってこと?
算出プロパティのgetter,setter
プロパティはgetterだけではなく、setterも定義できるのがmethodとの違い。
税抜き価格を入れると、税込価格を表示するプログラムを見ていこう
<div id="app"> <p> base price: <input type="text" v-model="basePrice"> </p> <p> tax included price: <input type="text" v-model="taxIncludedPrice"> </p> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16"></script>var app = new Vue({ //options el: '#app', data: { basePrice: 100 }, computed: { taxIncludedPrice: { get: function() { //parseIntは整数値を返す return parseInt(this.basePrice * 1.08) }, set: function(taxIncludedPrice) { //Math.ceilは引数として与えた数以上の最小の整数を返します。 this.basePrice = Math.ceil(taxIncludedPrice / 1.08) } } } })getterとsetterは文字通り、値を取得するのか、それとも値をセットするのか、というもの。
前提として、v-modelなのでviewからtaxIncludedPriceの値をセットすることが可能。
set:以下では、viewからセットされた税込価格をさらに1.08で割って税抜き価格をbasePriceに代入している。
これにより、税込価格を入れても税抜き価格が算出されるような形になる。
算出プロパティのキャッシュ
comuputed キャッシュされる
methods キャッシュされないランダムな数字を取得する処理を算出プロパティとメソッドの両方でやってみる。
<div id="app"> <h2>Computed</h2> <ol> <li>{{ computedNumber }}</li> <li>{{ computedNumber }}</li> <li>{{ computedNumber }}</li> </ol> <h2>Methods</h2> <ol> <li>{{ methodsNumber() }}</li> <li>{{ methodsNumber() }}</li> <li>{{ methodsNumber() }}</li> </ol> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16"></script>var app = new Vue({ //options el: '#app', computed: { computedNumber: function() { return Math.random() } }, methods: { methodsNumber: function() { return Math.random() } } })この場合、算出プロパティは3つのランダムな数字が全て同じ値になる。
なぜなら、キャッシュされるから。対して、メソッドの場合は毎回異なる数字になる。
なぜなら、キャッシュされないから。
- 投稿日:2020-06-03T21:18:21+09:00
DjangoでVue.js(webpack)を扱うための備忘録
前書き
この記事はPythonをバックエンド(Djangoをフレームワークとして使用)、Node.jsでWebpackを用いてVue.jsをフロントエンドとして使用できるようにするための備忘録記事です。
node.jsの知識が浅いので、使い方を間違っていることもあります。ご了承ください。
また、この記事は
pipenv
やyarn
を使用しています。導入していない場合は導入してからこの記事を読むことをお勧めします。パッケージのインストール
Djangoのstartprojectで自動作成されたフォルダを基にします。
Python
pip install django django-admin startproject django_vuejs cd django_vuejs pipenv --python 3 pipenv install django pipenv install django-webpack-loader==0.7.0node.js
yarn init -yp # package.jsonを作る(対話なし) yarn add --dev @babel/core @babel/preset-env yarn add --dev babel-loader css-loader sass-loader style-loader vue-loader vue-template-compiler yarn add --dev webpack webpack-cli yarn add --dev webpack-bundle-tracker@0.4.3 yarn add vue vuex vue-router yarn add --dev clean-webpack-plugin # これは任意ですが、これを入れておくとビルドしたjsファイルがかさばらなくなるので導入推奨。気をつけてほしいのは
webpack-bundle-tracker
のバージョン指定です。バージョンが新しすぎてPython側のdjango-webpack-loader
が上手く動作しなかったため、バージョン指定しています1。念の為Python側のdjango-webpack-loader
のバージョンも指定してます。設定
webpack
webpack.config.jsvar path = require("path"); var webpack = require('webpack'); var BundleTracker = require('webpack-bundle-tracker'); var VueLoaderPlugin = require('vue-loader/lib/plugin'); var { CleanWebpackPlugin } = require('clean-webpack-plugin'); // clean-webpack-pluginを導入した場合は追記 module.exports = { context: __dirname, mode: process.env.NODE_ENV, entry: { main: './src-front/main.js' }, output: { filename: "[name]-[hash].js", path: path.resolve('./static/build/') }, plugins: [ new BundleTracker({ path: '.', filename: 'webpack-stats.json' }), // clean-webpack-pluginを導入した場合は追記(ここから) new CleanWebpackPlugin({ verbose: true }), // (ここまで) new VueLoaderPlugin() ], module: { rules: [ { test: /\.js$/, loader: 'babel-loader' }, { test: /\.vue$/, loader: 'vue-loader' }, { test: /\.scss$/, use: [ 'style-loader', 'css-loader', 'sass-loader' ] }, { test: /\.css$/, use: [ 'vue-style-loader', 'style-loader', { loader: 'css-loader', options: { url: false, sourceMap: true } } ] } ] }, resolve: { extensions: ['.js', '.vue'], modules: [ 'node_modules' ], alias: { 'vue': path.resolve('./node_modules/vue/dist/vue.js') } } }Django
django_vuejs/settings.py# (省略) INSTALLED_APPS = [ # ... # 以下の文を追記 'webpack_loader', ] # 追記 WEBPACK_LOADER = { 'DEFAULT': { 'CACHE': not DEBUG, 'BUNDLE_DIR_NAME': 'build/', 'TIMEOUT': None, 'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'), 'IGNORE': [r'.+\.hot-update.js', r'.+\.map'] } } STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static') ]package.json
package.json
内に以下の内容を追記します。
--progress
オプションはwebpackの進捗状況を可視化できるオプションのため、不要であれば付ける必要はありません。package.json"scripts": { "dev": "webpack --mode development --progress", "build": "webpack --mode production --progress", "watch": "webpack --watch -d --progress" },メインページ
これがフロントエンドがベースとなるファイルです。ここにwebpackでビルドしたCSSやjavascriptのリンクが埋め込まれます。
template/index.html{% load render_bundle from webpack_loader %} <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>django_vuejs</title> {% render_bundle 'main' 'css' %} </head> <body> <noscript> <p>Your browser is disabled JavaScript. To use this web application, please enable JavaScript.</p> </noscript> <div id="app"></div> {% render_bundle 'main' 'js' %} </body> </head>Django URL
※今回は1ファイルにまとめていますが、適宜場所を移してください。
django_vuejsfrom django.contrib import admin from django.urls import path from django.shortcuts import render def index(request): return render(request, "index.html") urlpatterns = [ path('admin/', admin.site.urls), path('', index, name="index"), ]フロントエンド
src-front/main.jsimport Vue from 'vue' import App from './App' import router from './router.js' import store from './store' Vue.config.productionTip = false const app = new Vue({ el: '#app', router, store, template: "<App/>", components: { App } })src-front/App.vue<template> <div> <p>Hello World!</p> <router-view></router-view> </div> </template> <script> export default { name: 'App' } </script> <style> </style>
./router.js
や./store
はVue.jsのコマンドで自動生成されるものともろ一緒なので、申し訳ないのですが割愛します。gitignore (gitで管理する場合は設定)
.gitignore# 追記 /static/build/ /webpack-stats.jsonビルド
本番用でビルドする場合は
yarn run build
、
開発用としてビルドする場合はyarn run dev
またはyarn run watch
(フロントエンド系のソースを集中的に弄る場合は推奨)
のコマンドを実行します。ビルドが完了したら
python manage.py runserver
でDjangoの開発サーバーを起動し、http://localhost:8000/
にアクセスすると、表示されるはずです。参考文献
- 投稿日:2020-06-03T20:02:25+09:00
A-FrameをつかってWebVRをためしてみた!
概要
この記事はVue.jsで作ったサイトに3Dをいれてみたいというところで手軽にWebVRを実装できるというA-Frameをさわってみました
できたもの
3Dさわってみました!表面がついてきてくれなくてホラー作品になってしまった・・・;;https://t.co/352AEXcKVe#protoout pic.twitter.com/HnRVh6VigU
— 3yaka (@3yaka4) June 3, 2020色々足りず、ホラーなものができました。
2匹猫がいて、猫をクリクすると、猫が回ったり、雪が降ったりします。
猫はこちらからシャム猫3Dモデルとアメリカンショートヘアの3Dモデルをお借りしたのですが、なぜか表面がついてきませんでした。。。
環境
macOS Catalina Visual Studio Code 1.45.1 Node.js: v13.11.0 npm:6.14.5 Vue:@vue/cli 4.3.1大まかな流れ
- Vue.jsとFirebaseでログイン認証付きのページをつくる
- A-Frameをいれる
- 3Dモデルに動きをつける
- ドメインをとって公開
という流れです
1. Vue.js くみたてる
Vue.jsを勉強中のため、ちょっとむやみにセキュアなページにします。
こちらを参考に
Vue.jsでつくったサイトにfirebaseでユーザ認証してこっそり人の顔年齢を試して遊ぶ - Qiita
Firebaseでログイン処理をつくってVue.jsで3Pほどのページをつくります。2. A-Frameをいれる
npmでいれたいのでaframe - npmこちらからインストールしていきましょう。
サンプルをベースに猫の3Dオブジェクトを2体配置します。
猫にたいしてクリックイベントをつけます。
A-Frameのパーティクルコンポーネントを使って雪をふらしましょう
Entity-Component-System – A-Frame
こちらもnpmでいれちゃいますaframe-particle-system-component - npm3.3Dモデルに動きをつける
猫をクリックすると猫がグルングルン回るものと
猫をクリックしたら雪が降るというものをつくります<template> <a-text font="kelsonsans" value="Click Me!" width="6" position="1.5 1 -1.5" rotation="0 0 0"></a-text> <a-assets> <img id="sample_img" src="/static/cat.png" /> <a-asset-item id="sample_obj" src="/static/cat-print.obj"></a-asset-item> <a-asset-item id="sample_mtl" src="/static/cat-print.mtl"></a-asset-item> </a-assets> <a-obj-model scale="0.3 0.3 0.3" position="2.5 0 -1.5" src="#sample_obj" mtl="#sample_mtl" animation__star_rotation="property: rotation; startEvents:click; from: 0 0 0; to: 0 360 0; loop:5;"></a-obj-model> <a-entity id="snow" visible="false"> <a-entity particle-system="preset:snow;" position="0 0 -4"></a-entity> </a-entity> <a-assets> <img id="neko_img" src="static/CatTexture.png" /> <a-asset-item id="neko_obj" src="/static/neko.obj"></a-asset-item> <a-asset-item id="neko_stl" src="/static/neko.stl"></a-asset-item> </a-assets> <a-obj-model scale="0.2 0.2 0.2" position="-4.5 -1 -1.5" src="#neko_obj" mtl="#neko_stl" @click="handlerMouseEnter"></a-obj-model> </template> <script> import firebase from 'firebase' import 'aframe' export default { name: 'Vue3d', el: '#aframeApp', data () { return { msg: 'Welcome to Your Vue.js App', name: firebase.auth().currentUser.email, skyboxSrc: 'https://images.unsplash.com/photo-1505252772853-08ed4d526ceb?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1600&q=80' } }, methods: { handlerClick: function (event) { let boxid = event.target.id if (boxid === 'areabox2') { this.skyboxSrc = 'https://images.unsplash.com/photo-1557971370-e7298ee473fb?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1600&q=80' } }, handlerMouseEnter: function (event) { document.getElementById('snow').setAttribute('visible', 'true') } } } </script>全体のコードはこちら
<template> <div class="hello"> <div class="inf"> <h2>猫を探してください</h2> <p>ここには猫が2匹います。<br>真ん中にある<span class="maru">◯</span>を動かして、猫を探してクリックしてあげてください </p> </div> <a-scene> <a-entity id="aframeApps"> <!-- 背景設定 --> <a-sky ref="skybox" v-bind:src="skyboxSrc" rotation="0 -130 0"></a-sky> <a-box id="areabox2" position="2.5 0 1.5" rotation="45 45 45" @click="handlerClick"></a-box> </a-entity> <!-- ぐるぐる回る猫 --> <a-text font="kelsonsans" value="Click Me!" width="6" position="1.5 1 -1.5" rotation="0 0 0"></a-text> <a-assets> <img id="sample_img" src="/static/cat.png" /> <a-asset-item id="sample_obj" src="/static/cat-print.obj"></a-asset-item> <a-asset-item id="sample_mtl" src="/static/cat-print.mtl"></a-asset-item> </a-assets> <a-obj-model scale="0.3 0.3 0.3" position="2.5 0 -1.5" src="#sample_obj" mtl="#sample_mtl" animation__star_rotation="property: rotation; startEvents:click; from: 0 0 0; to: 0 360 0; loop:5;"></a-obj-model> <!-- 雪を最初非表示 --> <a-entity id="snow" visible="false"> <a-entity particle-system="preset:snow;" position="0 0 -4"></a-entity> </a-entity> <!-- 雪を降らす猫 --> <a-assets> <img id="neko_img" src="static/CatTexture.png" /> <a-asset-item id="neko_obj" src="/static/neko.obj"></a-asset-item> <a-asset-item id="neko_stl" src="/static/neko.stl"></a-asset-item> </a-assets> <a-obj-model scale="0.2 0.2 0.2" position="-4.5 -1 -1.5" src="#neko_obj" mtl="#neko_stl" @click="handlerMouseEnter"></a-obj-model> <a-entity> <a-camera> <a-cursor></a-cursor> </a-camera> </a-entity> </a-scene> </div> </template> <script> import firebase from 'firebase' import 'aframe' export default { name: 'Vue3d', el: '#aframeApp', data () { return { msg: 'Welcome to Your Vue.js App', name: firebase.auth().currentUser.email, skyboxSrc: 'https://images.unsplash.com/photo-1505252772853-08ed4d526ceb?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1600&q=80' } }, methods: { handlerClick: function (event) { console.log('handlerClick') console.log(event.target.id) let boxid = event.target.id if (boxid === 'areabox2') { this.skyboxSrc = 'https://images.unsplash.com/photo-1557971370-e7298ee473fb?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1600&q=80' } }, handlerMouseEnter: function (event) { console.log('handlerMouseEnter') console.log(event) document.getElementById('snow').setAttribute('visible', 'true') } } } </script> <style scoped> h1, h2 { font-weight: normal; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } .inf{ position: absolute; left: calc(50% - 16rem); top: 20%; z-index: 99999; color: #fff; font-size: 1rem; padding: 0.5rem; background: hsla(0,0%,100%,.18); width: 32rem; } .maru{ font-size:80%; color:#000; font-weight:bold; } </style>ドメインを取るNetlifyにDeploy
こちらを参考にfreenomでドメインをとってNetlifyにDeployします
Vue.jsでつくったサイトにfirebaseでユーザ認証してこっそり人の顔年齢を試して遊ぶ - Qiitaできなかったところ
最初のロード時にA-Frameのデフォルト?の水色とか出てきちゃうのをとめたかった。。。あれはなんだろう。
猫の表面が読み込めず、石膏像になってしまった。
Singin画面に画像を置いたのですがうまく反映されず。publicにおいたのに。。。<img v-bind:src="img"> ---略--- data: function () { return { img: '/public/img/nko3d.png' } }参考サイト
AframeをVue.jsで使うよ! : 新人SEの気まぐれ日記
新しいWebVRフレームワークA-Frame入門 - Qiita
A-Frame v1.0 で クリスマスアニメーション - Qiita感想
簡単に3Dが実装できた!でもちょっとやり込もうとするとやはり難しかった・・・。
- 投稿日:2020-06-03T17:27:58+09:00
難読化されたJavaScriptを解読するツールを作った
Obfuscator.ioというJavaScriptコードを難読化できるツールがあります。
例えば以下のような感じ
正直難読化されてても慣れればコードを読むのはそんなに苦じゃないのですが、自動ですべて解読するツールを作ったらおもしろそうだと思ってやってみました。
難読化されたJavaScriptを半自動で解読するツール
Vue.js(サイト側)とCloud Functions(API側)で作ってみました。
公開URL: https://sigr.io/deobfuscator/
残念ながら完全自動化までは至りませんでしたが、最初にターゲットとなる関数名を入力すれば後は自動で解析してくれます。
ツールの使い方
- 解読したいコードをInputに貼り付ける。
- 貼り付けたコードの中に以下のような部分があるので関数名をコピーする(3つのうちどれかに似てるはず)
_0x439c[103]
_0x439c('0x4')
_0x439c('0x2','f]Xg')
これの場合は_0x439cが対象(関数名)。Target function nameの欄にコピーしたのを貼り付ける
3.「Deobfuscate」をクリック
4.ちゃんと解読されればResultに結果が表示されるちゃんと解読されると難読化された部分が読めるようになるはずです。
さいごに
GitHubにフロント側のソースコードをうpしています。
https://github.com/LostMyCode/javascript-deobfuscatorCloudFunctionsで動いているAPI側のコードもいつか公開するかもしれません。
- 投稿日:2020-06-03T17:20:27+09:00
【VueコピぺOK】なにこの動き?100倍センスを感じるCSSアニメーション実装
Vueバージョン確認
npm list vueまずは上記コマンドでバージョンの確認
twinzlabo@0.1.0 /Users/twinzlabo ── vue@2.6.11なにこの動き?100倍センスを感じるアニメーション実装
上の方で確認してもらったかと思いますが、
すでに虹色の背景を付けたりとかなり洗練されている画像一覧に更にスタイル修正を行うことで
一方の画像をhoverするとスーッと画像全体が現れる不思議すぎてクールなアニメーション実装をしていきましょう
デフォルトの上の画像をhoverしたら下の画像のように一方の画像のタイトルが消えて全体像が現れる
アニメーションにカスタマイズしていきますこれやばくないですか?
上の画像を見てるだけだと実際の動きを想像しづらいかもしれませんが、
実装してみて初めてわかる快感を味わえます
これは損得抜きでまじでおすすめのアニメーションです
兎にも角にも、実装してみたら感動すること間違いなしです!
では早速コードをコピペしていきましょう
<template> <div class="images"> <div class="image"> <img src="https://images.unsplash.com/photo-1513543806865-85e29a7c0352?ixlib=rb-1.2.1&auto=format&fit=crop&w=2775&q=80"> <span>Day</span> </div> <div class="image"> <img src="https://images.unsplash.com/photo-1551607117-21fa129a211d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1720&q=80"> <span>Evening</span> </div> </div> </template><style> .images { display: flex; width: 100%; padding: 4% 2%; box-sizing: border-box; height: 60vh; } .image { flex: 1; overflow: hidden; transition: .5s; margin: 0 2%; box-shadow: 0 20px 30px rgba(0,0,0,.1); line-height: 0; } .image img { width: 200%; height: calc(100% - 10vh); object-fit: cover; transition: .5s; } .image span { background : linear-gradient(41deg, purple 25%, orange 50%, rgba(255, 107, 0, 0.48)) fixed; font-weight: bold; color: #fff; font-size: 3.8vh; display: block; text-align: center; height: 10vh; line-height: 2.6; border-top: 3px solid #fff; } .image:hover > img { width: 100%; height: 100%; } </style>いかがでしたでしょうか?
画像をhoverするとタイトルが消えて画像の全体像が現れるアニメーションが実行されましたか?
実際に自分のローカルブラウザで反映できると動きも確認できてめっちゃ興奮しますよね
こちらに他にも面白いアニメーション実装記事があるので参考までに
以上です
参考記事(他の魅力的なアニメーション)
現役フロントエンドエンジニアがおすすめするコピペで使えるアニメーション記事まとめ12選
- 投稿日:2020-06-03T16:15:44+09:00
vue.jsでビルドしたら何も表示されなかった時の対処法
vue.jsでビルドしたらindex.htmlに何も表示されなかった時の対処法
vue.jsでビルドした時にできるdistフォルダの中のindex.htmlを開いてみたらブラウザに
なにも表示されていなかった。...せっかくwebアプリを作ったにも関わらず
今回はその時の自分が行った対処法を載せていきます。1 ターミナルを開き vue ui と
入力2 左下の家のマークをクリック
3 対象プロジェクトを選択
4 左ペインの上から4つ目の歯車のマークをクリック
5 右側にでてくる公開パスの/を決して更新する
この手順でもう一度ビルド後のindex.htmlファイルを更新すると内容が反映されていると思います。
最後まで見ていただきありがとうございました。
つっかえた技術とかあったらその都度書き込んでいきたいと思います
- 投稿日:2020-06-03T16:10:57+09:00
【Javascript】 2つの配列に格納されているオブジェクト内部要素を比較し、新しいオブジェクト要素を追加
背景
vueでaxios使ってAPI情報取得したときに、使うことが多いかもなーってことで、もっと良い方法がありそうだが、ひとまず、自分用にメモで取っておきます。
vueでAPI叩いたときに、配列内部にオブジェクトを抱えたものが返ってくる前提です。
もっとこのほうがいいなど、あればアドバイスいただければ幸いです。
内容
結論
内容// axios叩いたときに返って来るdataの文字列 var obj = "data" var test02 = [ { "site_no": "00002", "term_id": "00002", }, { "site_no": "00001", "term_id": "00001", }, { "site_no": "00003", "term_id": "800003", }, { "site_no": "00004", "term_id": "00004", } ] // axios叩いたときに返ってくる配列(配列に格納されたオブジェクト一覧が返ってくる前提) var test08 = [ { "config":{ "test01": "hoge1" }, "data": { "image_url": "http://test01", "term_id": "800001", }, "header": { "aafa": "http://test01", "gdsa": "800001", }, "status": 200 }, { "config":{ "test01": "hoge1" }, "data": { "image_url": "http://test03", "term_id": "800003", }, "header": { "aafa": "http://test01", "gdsa": "800001", }, "status": 200 }, { "config":{ "test01": "hoge1" }, "data": { "image_url": "http://test02", "term_id": "800002", }, "header": { "aafa": "http://test01", "gdsa": "800001", }, "status": 200 } ] // 比較する配列内部のオブジェクトに一致する文字列があるかを確認し、ある・ないで処理を分ける // (2つ配列の内部どちらにもオブジェクトがあり、オブジェクト内部の特定文字列が存在することを確認する) // ある:比較対象側オブジェクト(端末一覧情報オブジェクト)に、要素を追加 // ない:特に処理をせず終了 test08.some((val01)=>{ test02.some((val02) => { console.log("val01: ", val01) if(obj !== ""){ if( ( "term_id" in val01[obj] && "term_id" in val02 ) && ( val01[obj]["term_id"] === val02["term_id"] ) ){ // console.log("存在します。") val02.image_url = val01[obj].image_url }else{ // console.log("存在しません。") } }else{ // オブジェクト内部に、検査したい要素があること確認 && それぞれのオブジェクトで要素が一致すること確認 if( ( "term_id" in val01 && "term_id" in val02 ) && ( val01["term_id"] === val02["term_id"] ) ){ // console.log("存在します。") // 条件に合致した場合に、片方の配列のオブジェクト要素に代入 val02.image_url = val01.image_url // 合致したタイミングで処理を抜ける return true; }else{ // console.log("存在しません。") } } }) }) console.log("result: ", test02)結果result: [ { site_no: '00002', term_id: '00002' }, { site_no: '00001', term_id: '00001' }, { site_no: '00003', term_id: '800003', image_url: 'http://test03' }, { site_no: '00004', term_id: '00004' } ]
- 投稿日:2020-06-03T16:10:20+09:00
Vue.js、AWS Amplifyおよびboto3でサンプルアプリを作ってみる(第三回:axiosでAPIコール編)
第二回で、Amplify CLIを使ってバックエンドリソースを作成し、CICDを回してVueアプリを外部公開するところまで辿り着いた。
今回は、さらにAPIとLambda関数を用意し、Vue.jsからaxiosで呼び出すところまでをトライ。これができれば、「Vue.jsでフロントエンドを作ってAWSサービスを叩いてみる」という当初の目的を達成できたことになる。前回の内容はこちら。
Vue.js、AWS Amplifyおよびboto3でサンプルアプリを作ってみる(第二回:Amplifyとバックエンドリソース編)(今回)やりたいこと
- axiosをセットアップする
- APIとLambda関数を作る
- Vue.jsからaxios経由でAPIを呼び出す
あえてやらないこと
AmplifyはライブラリやUIコンポーネントを備えていて、その気になればVue.js内から直接AWSサービスを操作できる様子(例:学習/デプロイ済みの機械学習モデルエンドポイントに推論リクエストを投げる、S3にオブジェクトをアップロードするなど)。
ただ、それをやるとなるとVue.jsのディレクティブやJavascript SDKにもう少し深入りする必要がありそう。あっちもこっちも手を出したくないので、今回は初志貫徹でコードはPython、SDKはboto3に留めておく。ということで、上図の通りフロントエンドからはAPIを叩くだけにして、ロジックやSDKの使用はバックエンドで行う役割分担にする。
axiosについて
簡単に言うとHTTPクライアントで、Vue.jsのコード内からPostman的にREST APIを呼び出す方法がないかと探すうちに発見。
これなら簡単にAPIの呼び出しと結果の受領ができそうだ。
以下を参考にさせていただきました。感謝。
axios を利用した API の使用
axiosを乗りこなす機能についての知見集
Vue.js+axiosでDynamo DBにAjax通信する
[axios] axios の導入と簡単な使い方Step by Step
1. Amplifyライブラリのインストール
やらないとは言いつつも、後学のために、セットアップして使えるようにするところまでは試しておく。
% npm install aws-amplify % npm install aws-amplify-vue2. src/main.jsへの取り込み
src/main.jsを編集して、Amplifyライブラリの取り込みを指定する。
src/main.js// Amplify import Amplify, * as AmplifyModules from 'aws-amplify' import { AmplifyPlugin } from 'aws-amplify-vue' import awsconfig from './aws-exports' Amplify.configure(awsconfig) Vue.use(AmplifyPlugin, AmplifyModules)aws-exportsが見つからない、というエラーが出る場合は、
.gitignore
でGitのトラッキング対象外になってしまっていないかを確認する。
自分の環境では、これをコメントアウトすると動いた。
.gitignore
への追加はAmplify自身が行っているようなので若干謎だが、とりあえず動いたので、気にせず先に進む。.gitignore.DS_Store node_modules /dist # local env files .env.local .env.*.local # Log files npm-debug.log* yarn-debug.log* yarn-error.log* # Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln *.sw? #amplify amplify/\#current-cloud-backend amplify/.config/local-* amplify/mock-data amplify/backend/amplify-meta.json amplify/backend/awscloudformation build/ dist/ node_modules/ # aws-exports.js <-- ここ awsconfiguration.json amplifyconfiguration.json amplify-build-config.json amplify-gradle-config.json amplifyxc.config3. axiosのインストール
Amplifyライブラリと同じ手順。
% npm install axios4. src/main.jsへの取り込み
ここもAmplifyライブラリと同様だが、axoisの仕様に若干のクセがありハマった。
axiosは厳密にはプラグインでないので、main.jsでVue.use()
に書いてあっても、this.axios
がUndefined
となってしまう。
代わりに以下のようにprototype.$axios
として定義し、読み込み元では$axios.get()
として呼ぶ必要がある。// ダメな例 // Axios import axios from 'axios' import VueAxios from 'vue-axios' Vue.use(AmplifyPlugin, AmplifyModules, VueAxios, axios)// 動く例 // Axios import axios from 'axios' import VueAxios from 'vue-axios' Vue.use(AmplifyPlugin, AmplifyModules, VueAxios) Vue.prototype.$axios = axios
main.js
は最終的にこのようになる。src/main.jsimport Vue from 'vue' import App from './App.vue' import router from './router' // Element UI import './plugins/element.js' // Axios import axios from 'axios' import VueAxios from 'vue-axios' // Amplify import Amplify, * as AmplifyModules from 'aws-amplify' import { AmplifyPlugin } from 'aws-amplify-vue' import awsconfig from './aws-exports' Amplify.configure(awsconfig) Vue.config.productionTip = false Vue.use(AmplifyPlugin, AmplifyModules, VueAxios) Vue.prototype.$axios = axios new Vue({ router, render: h => h(App) }).$mount('#app')これでようやくaxiosの事前準備が完了。
5. Lambda関数の準備
バックエンド側を作る。
そろそろAmplifyに全部やらせるのも飽きてきたので、練習も兼ねて、amplify add function
ではなくスクラッチで用意する。
こんな感じのLambda関数を書く。lambda_function.pyimport boto3 import json import os import datetime print('Loading function') glue = boto3.client('glue') gluedb = os.environ['GLUEDBNAME'] # dict内のdatetime型データをJSON対応のISOフォーマット(文字列)に変換する def convert_datetime2iso(object): if isinstance(object, type(datetime)): return object.isoformat() # 本体 def lambda_handler(event, context): operation = 'GET' if operation == 'GET': tables = glue.get_tables( DatabaseName=gluedb )["TableList"] response = json.dumps(tables, default=convert_datetime2iso) return { 'isBase64Encoded': False, 'statusCode': 200, 'headers': { # CORSの許可 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, 'body': response } else: response = ('Unsupported method:' + format(operation)) return responseファイル名が
lambda_function.py
、関数名がlambda_handler()
なので、Lambdaからはlambda_function.lambda_handler
として呼び出すことになる。AWS Glueを呼び出し、DB名を渡してテーブル一覧を取得する簡単な関数で、
GLUEDBNAME
をLambdaの環境変数として渡す形を取っている。今回はsh10_external
をDB名とした。よーく見る(見なくても)とメソッドがGETしか定義されてないが、お試しなのでご容赦。。実際にGlueに渡す命令はglue.get_tables()のみで、特に複雑な処理はない。
6. APIの準備
次に、axiosから呼び出すREST APIを作成する。このAPIのバックエンドとして、先程のLambda関数を実行する形。
これもamplify add api
ではなくスクラッチでAPIを作成してみる。
マネジメントコンソールでAPI Gatewayの画面に移動し、以下の仕様でAPIを作成。
- Lambdaプロキシー統合
- /tablesリソース
- ANYメソッド
API作成まではすんなりいったものの、Lambda関数と統合して動かすまでに色々ハマった。
まず、
not JSON seriarizable
エラー(クライアントから見ると500エラー)が出まくる。Lambda単体では動くようになっても、APIから呼ぶとまた出る。ここで大分時間を使った。
詳細はまた項を改めるが、結論としてはGlueからのdict型のレスポンスをjson.dump()
した上で、API GatewayがLambdaプロキシ統合で要求する形式に成形して返すことで、無事API様に受け取って貰えた。ようやく関門突破かと思いきや、今度は
CORS header 'Access-Control-Allow-Origin' missing'といったエラーが出る。何か見覚えある単語が。
OPTIONS`メソッドでこれを設定するメニューがあったので設定してみたが、どうやら効いてなさそうだ。
これは、axiosというか今回のSPAが当該APIを別のオリジンから呼ぶ形になるので、CORSを許可する必要があるということだ。
CORSは一般にサーバー側で設定し、API Gatewayも色々調べたところ、今回使用したLambdaプロキシー統合の場合はどうもバックエンドのLambdaの方で明示的にCORSを許可するヘッダを記述してやる必要がある模様。
上記の返値の中ほど、'headers':{}
の中にCORS設定を書いてやると、ようやく動いた。やれやれ。return { 'isBase64Encoded': False, 'statusCode': 200, 'headers': { # CORSの許可 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, 'body': response }7. API呼び出しとテーブルへの取り込み設定
最後は再びフロントエンド側に戻り、axiosでAPIを呼び出してデータを取得する箇所と、取得したデータをElement UIのtablesの中に成形して取り込む受け皿部分を作る。
今回のSPAではsrc/components/
配下に全てのコンポーネントを配置してVue Routerからルーティングしているが、この一つとしてCatalogueTable.vue
というページを用意する。ここでのポイントは以下の三つ。
- getDummyData()内のthis.$axios.get
でaxiosを呼び出し、REST APIを叩く
-tables
という配列でデータを受け取る
- Element UIのel-table
コンポーネントでtablesのデータ(:data="tables"
)を成形するaxiosの引数となるREST APIのURIには、先程API Gatewayで作成したAPIのエンドポイントを指定する。
成形は<el-table></el-table>
や<el-table-column></el-table-column>
の中で、Element UIの書式を使ってわりと自由に行うことができる。
ここでは最低限の属性だけを設定してみた。
- テーブル
属性 内容 :data データソース stripe ストライプ表示にする style="width: 100%" 横幅の長さ align="center" 中央揃え
- 列
属性 内容 prop 列名 label 列の表示名 width 列の長さ sortable ソート可能な列として指定 src/components/CatalogueTable.vue<template> <div class="cataloguetable"> <p></p> <h2>テーブル一覧</h2> <el-button type="primary" @click="getDummyData" :loading="false">実行</el-button> <el-table :data="tables" stripe style="width: 100%" align="center"> <el-table-column prop="Name" label="Table" width="250" sortable> </el-table-column> <el-table-column prop="DatabaseName" label="Database" width="200" sortable> </el-table-column> <el-table-column prop= "TableType" label="Type" width="200" sortable> </el-table-column> <el-table-column prop="PartitionKeys[0][Name]" label="Partition Key 1" width="150" sortable> </el-table-column> <el-table-column prop="PartitionKeys[1][Name]" label="Partition Key 2" width="150" sortable> </el-table-column> </el-table> </div> </template> <script> export default { data() { return { input: '', tables: [] } }, methods: { getDummyData() { let uri = 'https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/prod/tables'; this.$axios.get(uri) .then((response) => { this.tables = response.data }) .catch((e) => { alert(e); }); } } } </script>これを呼び出す側の
App.vue
はこんな感じで記述する。
(最終的には色々やりたいので、Element UIを使ったコンポーネントのガラだけはたくさん作ってあるが、CatalogueTable.vue
以外の中身はまだ空に近い)。src/App.vue<template> <div id="app"> <div> <img alt="Vue Logo" src="@/assets/gluelogo.png/" width="60" height="60"> <h1>Data Catalogue Explorer</h1> <el-menu :default-active="activeIndex" mode="horizontal" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b" router> <el-menu-item index="home" :route="{ name:'Home' }">ホーム</el-menu-item> <el-menu-item index="dataset" :route="{ name:'Dataset' }">データセットの一覧を見る</el-menu-item> <el-menu-item index="finder" :route="{ name:'Finder' }">データセットを探す</el-menu-item> <el-menu-item index="adhoc" :route="{ name:'Adhoc' }">アドホック検索</el-menu-item> <el-submenu index="catalogue"> <template slot="title">システムカタログ</template> <el-menu-item index="catalogue-database" :route="{ name:'CatalogueDatabase' }">データベース</el-menu-item> <el-menu-item index="catalogue-table" :route="{ name:'CatalogueTable' }">テーブル</el-menu-item> <el-menu-item index="catalogue-crawler">クローラー</el-menu-item> <el-menu-item index="catalogue-job">ジョブ</el-menu-item> <el-menu-item index="catalogue-jdbc">JDBC接続</el-menu-item> </el-submenu> <el-menu-item index="api" :route="{ name:'API' }">API実行</el-menu-item> <el-menu-item index="element" :route="{ name:'Element' }">Element UI</el-menu-item> <el-menu-item index="about" :route="{ name:'About' }">Vue.js</el-menu-item> <el-menu-item index="resources" :route="{ name:'Resources' }">リソース</el-menu-item> </el-menu> <router-view /> </div> </div> </template> <script> export default { name: 'app', data () { return { activeIndex: this.$route.name } } } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>Vue Routerにもこのコンポーネントへのルートを忘れずに追加してやる必要がある。
router/index.jsimport Vue from 'vue' import VueRouter from 'vue-router' ...(略)... # ここと import CatalogueTable from '@/components/CatalogueTable.vue' ...(略)... Vue.use(VueRouter) const routes = [ { path: '/', name: 'Home', component: Home }, ...(略)... # ここ { path: '/cataloguetable', name: 'CatalogueTable', component: CatalogueTable }, ...(略)...最後にここまでの内容をコミットし、レポジトリにプッシュ(このままAmplifyの方で自動ビルドが回り、ホスティングされている内容が更新される。詳しくは前回の記事を参照)。
% git add . % git commit -m "axios defined" % git push -u origin master9. 動作確認
Amplifyがデプロイを回してサイトの更新を完了するまで、待つこと数分。
ブラウザ、スマホでアクセスしてみると、どうやら無事動作している模様。10. 落ち穂拾い
- Amplifyマネージドのビルドで、なぜか
console.log
が失敗する。
- 構文チェックツールの
ESlint
設定が何かおかしいのかも、と疑って、このあたりを参考にあれこれ検証してみるも解決に至らず。console.log
を出さなければいいだけの話なので、ここではいったん忘れることにした。いずれ解決したい。ポスト三個分の長丁場になってしまったが、ようやくタイトル通りの自習が完了。
追々、APIを追加したりUIをいじってみたりと、色々遊んでみたい。
- 投稿日:2020-06-03T13:42:27+09:00
NuxtJS(+Vuetify)にFirebaseを入れたらビルドエラーが出る
バージョン
"firebase": "^7.14.6" "nuxt": "^2.0.0" "@nuxtjs/vuetify": "^1.0.0"エラー
core-js
関連でエラーが出ていることが分かります。ERROR in ./node_modules/vuetify/lib/util/helpers.js Module not found: Error: Can't resolve 'core-js/modules/es6.array.fill' in '/Users/shinozaki/src/github.com/shinoshu/google-analytics-for-firebase/node_modules/vuetify/lib/util' @ ./node_modules/vuetify/lib/util/helpers.js 3:0-40 @ ./node_modules/vuetify/lib/components/VList/index.js @ ./layouts/default.vue @ ./.nuxt/App.js @ ./.nuxt/index.js @ ./.nuxt/client.js @ multi ./.nuxt/client.js # 省略 ERROR in ./node_modules/vuetify/lib/util/colorUtils.js Module not found: Error: Can't resolve 'core-js/modules/web.dom.iterable' in '/Users/shinozaki/src/github.com/shinoshu/google-analytics-for-firebase/node_modules/vuetify/lib/util' @ ./node_modules/vuetify/lib/util/colorUtils.js 3:0-42 @ ./node_modules/vuetify/lib/services/theme/utils.js @ ./node_modules/vuetify/lib/services/theme/index.js @ ./node_modules/vuetify/lib/services/index.js @ ./node_modules/vuetify/lib/framework.js @ ./.nuxt/vuetify/plugin.js @ ./.nuxt/index.js @ ./.nuxt/client.js @ multi ./.nuxt/client.js原因
The problem is nuxt/@nuxt/babel-preset-app is not able to locate the right version of core-js when there are multiple versions of core-js in node_modules, which is a common situation in the npm ecosystem. If anything, it is something nuxt should address.
問題はnuxt / @ nuxt / babel-preset-appが、node_modulesに複数のバージョンのcore-jsがある場合に正しいバージョンのcore-jsを見つけられないことです。これは、npmエコシステムの一般的な状況です。どちらかと言えば、それはnuxtが対処すべきものです。
https://github.com/firebase/firebase-js-sdk/issues/2968#issuecomment-617975070
解決方法
https://nuxtjs.org/guide/release-notes#v2.6.0
方法1
core-js ver.2 を使う方法
$ npm i -D core-js@2
$ yarn add -D core-js@2
方法2
core-js ver.3 を使う方法
こちらは公式サイトに詳しくやり方が記載されていますので、ご参照ください。
- 投稿日:2020-06-03T13:42:10+09:00
ゼロから始めるVue.js生活
はじめに
プログラミングの「プ」の字もわからなかった入社2年目が新たにVue.jsを学習していき、分かったことをまとめる日記です。
Vue.jsを始めてみよう!
まずは手軽にVue.jsを始めてみよう
↓こちらで試し撃ちができるようです。
CodePen Home Vue.js Playgroundまた、CDNとしても提供されているので、HTML中にこちらを挿入することでローカル環境でも使えるようになリマス。
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>ゼロから始めるVue.js生活
Vueインスタンスを使いこなす
まずは以下のコードを打ち込んだhtmlを表示して、画面に「ほげぇぇぇぇぇぇぇぇ」を出力してみよう
<!-- HTML --> <div>--------------</div> <div id="hoge"> {{ message }} </div> <div>--------------</div> <!-- HTML --> <!-- Vue --> <script> var app = new Vue({ el: '#hoge', data: { message: 'ほげぇぇぇぇぇぇぇぇ' } }) </script> <!-- Vue -->..するとこのような表示が確認できる
-------------- ほげぇぇぇぇぇぇぇぇ --------------このとき、
Vueコード側では
new Vue()
でVueインスタンスを作成el:
で適用する範囲を指定data:
でプロパティを設定(app.message
として参照可)一方、HTML側では、
{{ message }}
でdata: {message:
を参照し、展開しているdataに階層構造を作る
dataプロパティはこのように子プロパティをネストすることが可能
<!-- HTML --> <div>--------------</div> <div id="hoge"> {{ oya_hoge.kodomo_hoge }} <!-- hogeeeee --> </div> <div>--------------</div> <!-- HTML --> <!-- Vue --> <script> var app = new Vue({ el: '#hoge', data: { oya_hoge: { kodomo_hoge: 'hogeeeee' } } }) </script> <!-- Vue -->mustache {{ }} について
{{ }}
では、vueのプロパティだけではなく、JavaScriptコードを展開可能<!-- HTML --> <div class="hoge"> {{ message + '?????' }} <!--ほげぇぇぇぇぇぇぇぇ?????--> {{ Math.abs(-999999)}} <!--999999--> </div> <!-- HTML -->el: について
el:
で指定したタグ以外では、message
は展開できない<!-- HTML --> <div class="not_hoge"> {{ message }} <!--{{ message }}--> </div> <div class="hoge"> {{ message }} <!--ほげぇぇぇぇぇぇぇぇ--> </div> <!-- HTML --> <!-- Vue --> <script> var app = new Vue({ el: ".hoge", data: { message: "ほげぇぇぇぇぇぇぇぇ", }, }); </script> <!-- Vue -->ただし、指定したタグ配下であれば、どこであっても展開できる
<!-- HTML --> <div class="hoge"> {{ message }} <!--ほげぇぇぇぇぇぇぇぇ--> <div class="in_hoge"> {{ message }} <!--ほげぇぇぇぇぇぇぇぇ--> </div> </div> <!-- HTML --> <!-- Vue --> <script> var app = new Vue({ el: ".hoge", data: { message: "ほげぇぇぇぇぇぇぇぇ", }, }); </script> <!-- Vue -->複数のタグを対象とすることはできない
el:
が適用されるタグは必ず1つ。
したがって、同じクラス名をもつタグを複数作っても、先頭のタグ内のみmessage
が展開される。<!-- HTML --> <div class="hoge"> {{ message }} <!--ほげぇぇぇぇぇぇぇぇ--> </div> <div class="hoge"> {{ message }} <!--{{ message }}--> </div> <!-- HTML --> <!-- Vue --> <script> var app = new Vue({ el: ".hoge", data: { message: "ほげぇぇぇぇぇぇぇぇ", }, }); </script> <!-- Vue -->同一の親ノード内で、既にあるプロパティは定義できない
今回は、
app.message
を先に定義しているのでmessage: "ほげぇぇぇぇぇぇぇぇ"
が優先されて適用されている。
ノードの親子関係は関係なく、先に書いてある方が優先して適用されるので注意されたし。<!-- HTML --> <div class="hoge"> <div class="under_hoge"> {{ message }} <!-- ほげぇぇぇぇぇぇぇぇ --> </div> </div> <!-- HTML --> <!-- Vue --> <script> var app = new Vue({ el: ".hoge", data: { message: "ほげぇぇぇぇぇぇぇぇ", }, }); var app2 = new Vue({ el: ".under_hoge", data: { message: "Hogeeee", }, }); </script> <!-- Vue -->ディレクティブを使いこなす
ディレクティブというのは
v-bind
やv-html
など、v-
で始まる命令文のこと。
v-
の後に続く命令によってVueは処理を切り替えている。v-bindを使いこなす
v-bind
では、htmlタグの属性値(href,class,id...)を動的に変化させることができる。<!-- HTML --> <div class='hoge' v-bind:href="vue_href"></div> <!--<div href="http://hogehoge.com/" class="hoge"></div>--> <!-- HTML --> <!-- Vue --> <script> var app = new Vue({ el: ".hoge", data: { vue_href: "http://hogehoge.com/", }, }); </script> <!-- Vue -->
v-bind:href
で、href属性を変更することを宣言v-bind:href="vue_href"
で、data: {vue_href:
を参照し、展開classを変更する時は、既存classに加えて、新たにclassを追加する。
<!-- HTML --> <div class='hoge' v-bind:class="vue_class"></div> <!--<div class="hoge hogeeeee"></div>--> <!-- HTML --> <!-- Vue --> <script> var app = new Vue({ el: ".hoge", data: { vue_class: "hogeeeee", }, }); </script> <!-- Vue -->idを変更する場合は、既存idを上書きしてしまうので注意
v-bindの省略記法
また、
v-bind
はよく使われるため、このように省略されることがままある。<div class='hoge' v-bind:class="vue_class"></div>これを、
v-bind:
を省略して:
と書くことが可能<div class='hoge' :class="vue_class"></div>v-htmlを使いこなす
v-html
を使うことで、エスケープ処理が行われなくなる。
任意のHTML要素を動的に出したい場合に役に立つ<!-- HTML --> <div>-----------</div> <div class='hoge' v-html="html_message"></div> <div>-----------</div> <!-- HTML --> <!-- Vue --> <script> var app = new Vue({ el: ".hoge", data: { html_message: "<h1>ほげええええええ</h1>", }, }); </script> <!-- Vue -->v-ifを使いこなす
visible = true
であれば<button>
タグが生成される<!-- HTML --> <div id="hoge"> <button v-if="visible">ほげボタン</button> <!-- ほげボタン --> </div> <!-- HTML --> <!-- Vue --> <script> var app = new Vue({ el: '#hoge', data: { visible: true } }) </script> <!-- Vue -->else文もこのように使うことができる
<!-- HTML --> <div id="hoge"> <div>{{now}}</div> <button v-if="visible">ほげボタン</button> <button v-else="visible">ホゲってないぞ</button> <!-- ホゲってないぞ --> </div> <!-- HTML --> <!-- Vue --> <script> var app = new Vue({ el: '#hoge', data: { visible: false } }) </script> <!-- Vue -->v-showを使いこなす
visible = true
であれば<button>
タグが生成される<!-- HTML --> <div id="hoge"> <button v-show="visible">ほげボタン</button> <!-- ほげボタン --> </div> <!-- HTML --> <!-- Vue --> <script> var app = new Vue({ el: '#hoge', data: { visible: true } }) </script> <!-- Vue -->v-ifとの違いについて
v-if
では、条件がfalse
の場合、タグ自体が生成されない
のに対して、
v-show
ではタグそのものは存在するが見えない(display: none;)
となるしたがって、表示切り替えを頻繁に行うなら
v-show
、
たまに行う程度ならv-if
という使い分けになると考えられる。メソッドを使いこなす
<!-- HTML --> <div class='hoge'> {{hoge_func()}} <!-- ほげえええええええ --> </div> <!-- HTML --> <!-- Vue --> <script> var app = new Vue({ el: ".hoge", methods: { hoge_func: function () { return "ほげえええええええ" } }, }); </script> <!-- Vue -->メソッドに引数をとる
<!-- HTML --> <div class='hoge'> {{hoge_func('ほゲゲゲゲゲゲ')}} <!-- ほゲゲゲゲゲゲ?????? --> </div> <!-- HTML --> <!-- Vue --> <script> var app = new Vue({ el: ".hoge", methods: { hoge_func: function (argument) { return argument+'??????' } }, }); </script> <!-- Vue -->算出プロパティ(computed)を使いこなす
<!-- HTML --> <div id="hoge"> {{ hogeReverse }} <!-- ぇぇぇぇぇぇぇぇげほ --> </div> <!-- HTML --> <!-- Vue --> <script> var app = new Vue({ el: '#hoge', data: { message: 'ほげぇぇぇぇぇぇぇぇ' }, computed: { hogeReverse: function () { return this.message.split("").reverse().join(""); //文字列を反転させる } } }) </script> <!-- Vue -->ここで、
-this
はappインスタンスを表している(this.hogeReverse = app.hogeReverseと同等)
- このthisは省略できない(message is not defined
)また、
あくまでcomputedで定義されるのはプロパティ
であって、メソッド
ではないので引数を取ることはできないhogeReverse() ❌ hogeReverse(arg) ❌
- 投稿日:2020-06-03T12:16:12+09:00
props,emit備忘録
簡単なpropsはなんとなく分かっていましたが、親コンポーネント側の配列を子コンポーネント内でfor文で回した時の挙動が難しかったので、
【Vue.js】子から親コンポーネントのデータを更新する方法|たのしいWeb開発
を参考にさせていただき、少しまとめてみました。1.親コンポーネント側で、配列fluitsを宣言。中には、2つのオブジェクトが入っている。
2.親コンポーネント側で子コンポーネントを表示した際に、for文で回して親コンポーネントの配列fluitsの中身を1つ1つ表示させている。
fluitの中のオブジェクトの要素数に応じて、表示する数は変動する。今回の表示は下記のようになる。{{fluit.name}}={{subtotalCount}} <button @click = "increment()">増やす</button> <button @click = "reduce()">減らす</button> {{fluit.name}}={{subtotalCount}} <button @click = "increment()">増やす</button> <button @click = "reduce()">減らす</button>3.for文で親コンポーネントのオブジェクトを子コンポーネントに渡すときは、v-bind:【propsで渡す変数名】="配列データのキー"
4.子コンポーネント側で、clicked-increment-button,clicked-reduce-buttonが発火されたら、親コンポーネント側のincrementTotalCount,decrementTotalCount
を呼び出す、ということ。
この流れをemitと言い、子コンポーネントから親コンポーネントに値を渡すときに使用される。
子コンポーネント側でsubtotalCountが増減するのに合わせて、親コンポーネント側のtotalCountも増減する。
- 投稿日:2020-06-03T09:50:10+09:00
Notification API + Laravel + Vue.js、 PWAで メッセージ機能を作る 作例編
概要
以前の 製作事例公開内容となりますが
会員メンバー間で、メッセージを送受信できる機能を
Laravel+ Vue でPWA対応、実装しました。
新着の自動更新は、JSタイマで起動し、
Notification API での通知等の仕組みとなります。参考のコード / GitHub
https://github.com/kuc-arc-f/lara58a_7message
構成
Progressive Web Apps / PWA
Notification API
Laravel 5.8
Vue.js
javascript
nginx
mysqlmigrations
画像
・受信一覧、
メールのような、受信、送信タブで切替表示としました実装など
・JSタイマー
定期実行、自分宛の送信メッセージを監視し、
新着があれば、トリガー発火して。新着通知等の処理を実行します。function set_time_text(){ var data = { 'user_id': USER_ID, 'type': 1, }; axios.post('/api-1234' , data).then(res => { var item = res.data if(item.id != null){ $("input#time_text").val( item.id ); $("input#message_title").val( item.title ); }else{ $("input#time_text").val( 0 ); } console.log( item ); }); } set_time_text(); var timer_func = function(){ set_time_text(); }; var TIMER_SEC = 1000 * 600; setInterval(timer_func, TIMER_SEC );・新着の通知
function display_notification(title, body ){ if (!('Notification' in window)) {//対応してない場合 alert('未対応のブラウザです'); } else { // 許可を求める Notification.requestPermission() .then((permission) => { if (permission === 'granted') {// 許可 var options ={ body: body, icon: 'https://hoge.net/icon.png', tag: '' }; var n = new Notification(title,options); console.log(n); setTimeout(n.close.bind(n), 5000); } else if (permission == 'denied') {// 拒否 } else if (permission == 'default') {// 無視 } }); } }参考のページ
https://knaka0209.hatenablog.com/entry/lara58_26message
.
- 投稿日:2020-06-03T09:18:29+09:00
Rails と Vue (Single File Components) 共存
Rails と Vue の共存をしたい。t
できれば以前のコードを活かしたい。
いきなりSPAにするにはサンクコストが大きくて、踏ん切りがつかない。(編集中)
基本は置き換えていく方向で検討中。
参考
2018年くらいに書かれたものが多い。Railsの遺産を残しながらVueの単一コンポーントの利点を使いたい、しかしSPAには振り切りきれない。
- Vue.jsとRailsの最適な融合を考える
- 実際のサービスをRails+Vue.js(Single File Components)を用いてSPAへリファクタリングした話
- SPAじゃないVue.js〜Railsとともに〜
webpack
- Webpackってどんなもの?
- RailsでVue.jsのSFC(単一ファイルコンポーネント)を使うためにWebpackを入れてみた
- Vue.jsのコンポーネント化の方法まとめ(rails+webpacker+vue.js)
- Webpackerを導入してから外すまでをふりかえる
- Rails に Webpack と Vue を導入しました!
おススメ派
要は、今後の主流になるのは間違いないので、やっておけ。最初は大変だがあとでいろんなところで役に立つよ。
- Rails: webpack(er)に乗り換える25の理由(翻訳)
Sprockets
未検討
検討中
- Webpacker を使わずに webpack
- あとから外すもありみたいなので、最初は学習コストを下げるために使う [決定]
- Sprockets 検討中(よくわかっていない)
- [使わない] わからない=評価できない=なしで進む
- Vue をRailsとは別でBuildしてマウントする方式が良さそう
まずは
- 別のサービスをRails6 Webpacker Vue で実装する
- SPA にしなくてもいい気はする
- 投稿日:2020-06-03T09:18:29+09:00
Rails と Vue の共存
Rails と Vue の共存をしたい。
できれば以前のコードを活かしたい。
いきなりSPAにするにはサンクコストが大きくて、踏ん切りがつかない。(編集中)
参考
2018年くらいに書かれたものが多い。Railsの遺産を残しながらVueの単一コンポーントの利点を使いたい、しかしSPAには振り切りきれない。
- Vue.jsとRailsの最適な融合を考える
- 実際のサービスをRails+Vue.js(Single File Components)を用いてSPAへリファクタリングした話
- SPAじゃないVue.js〜Railsとともに〜
webpack
- Webpackってどんなもの?
- RailsでVue.jsのSFC(単一ファイルコンポーネント)を使うためにWebpackを入れてみた
- Vue.jsのコンポーネント化の方法まとめ(rails+webpacker+vue.js)
- Webpackerを導入してから外すまでをふりかえる
- Rails に Webpack と Vue を導入しました!
おススメ派
- Rails: webpack(er)に乗り換える25の理由(翻訳)
Sprockets
未検討
検討中
- Webpacker を使わずに webpack
- あとから外すもありみたいなので、最初は学習コストを下げるために使う [決定]
- Sprockets 検討中(よくわかっていない)
- [使わない] わからない=評価できない=なしで進む
- Vue をRailsとは別でBuildしてマウントする方式が良さそう
まずは
- 別のサービスをRails6 Webpacker Vue で実装する
- SPA にしなくてもいい気はする
- 投稿日:2020-06-03T08:56:01+09:00
【Vue.js】半日でできる、ビデオチャット上で動くオリジナルゲームの開発から公開まで。
Vue.jsを使って簡単なゲーム開発し、完成したものをwh.imというゲームのプラットフォームに公開するまでをまとめてみました。
この記事を読めば、Vue.js未経験の方であっても数時間あればオリジナルゲームを開発・公開し、友達と遊べるようになります!
できる限りわかりやすく書いたので長くなりましたが、ぜひ最後まで読んでみてください(記事の完成に3日かかりました? 服装の変化を楽しんでください)。ご質問はコメントまで!対象読者
- html / css はある程度わかる方
- Vue.jsに入門したい人
- ビデオチャット上でゲームを作ってみたい方
はじめに
wh.im というサービスの開発している@aitaroです。Vue.jsを使い始めてかれこれ1年半になります。今でこそ wh.im を開発できるぐらいの知識がつきましたが(wh.imはVue.jsとそのフレームワークであるNuxt.jsを使っています)、Vue.jsを始めたときは右も左もわからない状態でした。そこで、同じようにVue.js初心者の方に対して、ゲームの作成といった一つのプロダクトの完成とデプロイを目指した入門記事を今回書こうと思いました。wh.im要素が多めはなりますが、Vue.jsの基礎も理解できるようになっているので、是非最後まで走りきってみてください。
また、Vue.js経験者の方にも、wh.imの始め方から公開の仕方までが一通りまとまっているので、ビデオチャットで遊べるでゲームを作ってみたいときは是非参考にしてください。
構成
このエントリは以下の6つのパートと21の章から成ります。
Ⅰ. wh.imとは?
Ⅱ. Vue.jsの環境構築
- 1. node.jsをインストール
- 2. npmでvue-cliをインストール
- 3. Vue.jsのプロジェクトを作る
- 4. ソースコードを変更してみる。
Ⅲ. wh.imでの開発のための環境設定
- 5. wh.im上でvueを動かしてみる
- 6. wh.im上で開発の準備
Ⅳ. ゲームの実装
- 7. main.jsのでライブラリ読み込み
- 8. vue.jsのコンポーネントの追加
- 9. じゃんけん画像の設置
- 10. グーを選択するメソッドを追加しよう!
- 11. 選んだ手のstateの登録
- 12. チョキやパーを選択できるようにしよう!
- 13. 関数を抽象化しよう!
- 14. 選択後の画面を作ろう
- 15. 結果の画面を作ろう
- 16. 結果の画面に全員の選んだ手を表示してみよう!
- 17. 最後に見た目を整えよう!
Ⅴ デプロイして公開する
- 18. githubにコードを上げる
- 19. netlifyの設定をする
- 20. wh.imのdevelop画面でゲームの登録をする
- 21. 遊んで見る!
Ⅵ. まとめ
wh.imとは?
wh.imとは、ビデオチャットしながら遊べるゲームのプラットフォームです。wh.imにアクセスすると、じゃんけんやワードウルフなどのゲームが友達と遊べます。さらに、wh.imの特徴はなんと言ってもオリジナルゲームを投稿できること!自分で作ったゲームを登録すれば、今日中にでもそのゲームで友達と遊ぶことができます。今回はこの wh.im を題材に使って、Vue.jsを用いたゲームを作っていこうと思います!
wh.imについて詳しくしりたい方は、新型コロナの自宅待機中に、ビデオチャットしながらゲームで遊べるサービスを作った話を是非読んでみてください。
Vue.jsの環境構築
1. node.jsをインストール
まずはMacにnode.jsをインストールしてください。インストールの方法はこちらの記事に簡潔にまとまっています。
Macにnode.jsをインストール2. npmでvue-cliをインストール
node.jsをインストールしたあなたは、npm使えるようになっているはずです。npmとは、node package managerの略で、JavaScript系のパッケージを管理するためのツールです。
以下の方法で、npmを使ってvue-cliをインストールしましょう。
-gをつけることで、グローバルにインストールされるので、どこからでもvueコマンドを呼び出せるようになります。$ npm install -g @vue/cli3. Vue.jsのプロジェクトを作る
ここから、簡単なじゃんけんアプリの開発を通じてVue.jsを使ったゲーム開発を学んでいきましょう。
まずはVue.jsのプロジェクトを作成してみます。今回はじゃんけんゲームを作っていくので、jankenとしますが、好きな名前で大丈夫です。
$ vue create janken
すると、次のような選択画面になります。
? Please pick a preset: (Use arrow keys) ❯ default (babel, eslint) Manually select featuresこれはそのままEnterを押してしまいましょう。
最後にこのような画面ができたら成功です!? Successfully created project janken. ? Get started with the following commands: $ cd jaknen $ npm run serve作成されたら、画面の指示に従ってそのjankenというディレクトリに移動してみましょう。
この段階で試しに起動してみましょう。$ cd janken $ npm run serve起動は成功しているので、Chrome等ブラウザのアドレスバーに
http://localhost:8080
と入力してみてください。
このような画面が出てくると思います。
これで環境構築はひとまず終了です!お疲れ様でした?4. ソースコードを変更してみる。
これで、Vue.jsの起動は完了しました。
Vue.jsを初めて触る人向けに、コードの雰囲気を掴んでもらいます。
まず、vue create で作成されたサンプル画面を見ていきましょう。
このサンプル画面のコードは src > components > HelloWorld.vue にあります。
ではそのうちこの文章を変えてみましょう。src/components/HelloWorld.vue<p> For a guide and recipes on how to configure / customize this project,<br> check out the <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>. </p>これを適当に変えてみます。
src/components/HelloWorld.vue<p> 100日目にVue.jsをマスターする俺<br> check out the <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>. </p>変えたら、command + S(winはctrl + S)で変更を保存してみましょう。
すると、ブラウザでみると自動で反映されていると思います!
これはVue.jsのうちのWebpackというライブラリで、HotReloadという機能です!変更が保存されると、自動で反映されます!ただ時々バグるので、もしバグったなというときはおとなしくブラウザの画面をリロードしましょう。wh.imでの開発のための環境設定
5. wh.im上でvueを動かしてみる
wh.im上でVueを動かしてみましょう。
wh.imから遊び場を作成します。
次に、wh.imを開発モードにします。
URLの最後に&develop=true
を追加して、リロードすると、wh.imが開発モードに変わります。
4開発モードになると、右上のボタンからアプリを選択するときに、開発用(port:8080)が出てきます。
この開発用(port:8080)を選択し、プレイすると先程の画面になります。
これで、開発の準備は整いました。
6. wh.im上で開発の準備
では、実際にwh.imを使ったゲームを作っていきましょう。wh.imはVueで開発しやすいように、ライブラリを用意しています。(ライブラリとは拡張機能みたいなもので、追加することでVueでやれることが広がります)
一旦、ターミナルにいき、 command + C (windows の場合は ctrl + C)でサーバーを止めましょう。
その後、次のコマンドを打ち込んでライブラリを追加します。$ npm install whim-client-vueこれで、ライブラリが入りました!
package.jsonを開いてみると、追加されているのがわかります。(バージョンは多少ことなる場合があります。)package.json{ "name": "janken", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint" }, "dependencies": { "core-js": "^3.6.5", "vue": "^2.6.11", "whim-client-vue": "^1.1.4" // ←ここ }, "devDependencies": { "@vue/cli-plugin-babel": "~4.4.0", "@vue/cli-plugin-eslint": "~4.4.0", "@vue/cli-plugin-vuex": "~4.4.0", "@vue/cli-service": "~4.4.0", "babel-eslint": "^10.1.0", "eslint": "^6.7.2", "eslint-plugin-vue": "^6.2.2", "vue-template-compiler": "^2.6.11" } }そして次に、Vueの設定を変更します。janken(ルートディレクトリ)の直下にvue.config.jsを作り、以下のコードを加えましょう。
これはクロスドメインでアプリを呼び出すときに、host名を明示的にlocalhostにするためです。vue.config.jsmodule.exports = { devServer: { host: "localhost" }, };これでもう一度、Vueを起動します。
$ npm run serve
vueを再起動したときは、wh.imの方でも一回、ゲームを終了して再度選択する必要があります。
ゲームの実装
7. main.jsのでライブラリ読み込み
ここからは実際にコードを書いていきます。
まず、src/main.jsを開き、whim-client-vueを使うように設定します。
3,4行目を増やしました。src/main.jsimport Vue from 'vue' import App from './App.vue' import whimClientVue from "whim-client-vue"; Vue.use(whimClientVue); Vue.config.productionTip = false new Vue({ render: h => h(App), }).$mount('#app')これは、whim-client-vueのライブラリを呼び出すコードです。
src/main.jsimport whimClientVue from "whim-client-vue";
Vue.use
を使うことで、さきほど呼び出した、whim-client-vueのライブラリを、Vueに登録します。src/main.jsVue.use(whimClientVue);8. vue.jsのコンポーネントの追加
次に、App.vueを編集します。これは、ページにアクセスすると最初に表示される画面です。
これをデフォルト画面から変更してみます。変更点は、divタグの中身を消したこと、HelloWorldコンポーネントは今回使わないので、componentsを消したことです。src/App.vue<template> <div id="app"> Player: {{ $whim.accessUser.name }} </div> </template> <script> export default { name: "App", }; </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>すると、自分の名前が表示されていると思います。
template内の {{ }} は、そこの部分を式評価するという意味です。
今回は、そこに $whim.accessUser.name と記述することで、アクセスしてるユーザーの名前がとれました。
ここで言う、アクセスしているユーザーとはそのゲームをプレイしている人のことです。wh.imはビデオチャットサービスなので、複数人でゲームをプレイします。そのとき、このブラウザからアクセスしている人はだれかということをゲーム側で把握しなければなりません。
実際に確認してみましょう。プライベートブラウザや、違うブラウザで、wh.imの同じルームにアクセスします。すると今度は、 Player:モナリザ と表示されました。
このようにアクセスする人によって、表示を変えたい場合は、accessUserを使うことで可能になります。
9. じゃんけん画像の設置
次に、ゲーム上にじゃんけんの選択肢を配置しましょう。
まず、じゃんけんの画像を用意します。
私は素材ライブラリーさんの画像を使わせていただきました。ダウンロードした画像を、 src/assetsの中に配置します。
ファイル構成は以下のようになると思います。janken └── assets ├── paper.png ├── rock.png └── scissors.pngそしてこれを先程のApp.vue画面に配置します。
先程の、Player: {{ $whim.accessPlayer.name }}
を下のように書き換えてください。src/App.vue<div> <h2> 選択してください! </h2> <div> <img src="@/assets/rock.png" width="150" height="150" /> <img src="@/assets/scissors.png" width="150" height="150" /> <img src="@/assets/paper.png" width="150" height="150" /> </div> </div>
App.vue全体
src/App.vue<template> <div id="app"> <div> <h2> 選択してください! </h2> <div> <img src="@/assets/rock.png" width="150" height="150" /> <img src="@/assets/scissors.png" width="150" height="150" /> <img src="@/assets/paper.png" width="150" height="150" /> </div> </div> </div> </template> <script> export default { name: 'App' } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>そして、wh.imの画面を確認します。すると、以下のような選択画面が現れていると思います!
10. グーを選択するメソッドを追加しよう!
次に、グーチョキパーを選択できるようにします。
目標は、画像をクリックすると文字が選択済み!に変わって、データベースに選択した手が書き込まれることです。
ではまず、データベースに書き込む処理をしましょう。グーから行きます。
グーの画像に@click="selectRock"
を付け足します。以下のような感じです。これはclickしたときにselectRockという関数を実行するという意味です。src/App.vue<img src="@/assets/rock.png" width="150" height="150" @click="selectRock" />そしてselectRock関数をvueに登録します。
を以下のように編集します。src/App.vue<script> export default { name: 'App', methods: { selectRock() { // ここに処理を書いていく console.log('selectされた!') } } } </script>
App.vue全体
src/App.vue<template> <div id="app"> <div> <h2> 選択してください! </h2> <div> <img src="@/assets/rock.png" width="150" height="150" @click="selectRock" /> <img src="@/assets/scissors.png" width="150" height="150" /> <img src="@/assets/paper.png" width="150" height="150" /> </div> </div> </div> </template> <script> export default { name: 'App', methods: { select(hand) { // ここに処理を書いていく console.log('selectされた!') }, }, } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>ここで出てきた
console.log
はブラウザのconsole画面にログを表示する命令です。
これで、グーを押したときの挙動を確認しましょう。
wh.imの画面に戻って、Chromeのデベロッパーコンソールを開きます。macの方は command + option + i で開きます。 そして、その状態でグーをクリックします。するとコンソール画面にselectされた!
と表示されるはずです。(以下の画像を参照)このように@clickでselectRock関数が実行されたのがわかると思います。
11. 選んだ手のstateの登録
次にデータベースに登録しましょう。selectメソッドを変更していきます。
データーベースはwh.im上ではstateと呼ばれます。また、このstateはデーターベースといいつつ、いわゆるRDBではなくJSON型で保存されます。(そしてこのデーターベースはこのアプリを立ち上げているブラウザ間で同期されます。)
stateを変更するときは、$whim.assignState
関数を使います。
今回はアクセスしてるユーザーがグーを出したことを保存したいので、stateに[this.$whim.accessUser.id]: "rock"
を書き込みます。src/App.vueselectRock() { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: "rock" }) }動作確認をしてみましょう。グーをクリックしてみます。するとstateにグーが登録されます。wh.imの開発モードにはこのstateを確認する機能があります。右上のメニューボタンから、SHOW APP STATE を選んでください。
すると次にような画面が出てきます。
この
Object
がstateです。
今回はstateにoNyPTFZuMbOOZruCR4UMSsP9zRu2:"rock"
が登録されていることがわかりました。この、oNyPTFZuMbOOZruCR4UMSsP9zRu2
はユーザーidです。
App.vue全体
src/App.vue<template> <div id="app"> <div> <h2> 選択してください! </h2> <div> <img src="@/assets/rock.png" width="150" height="150" @click="selectRock" /> <img src="@/assets/scissors.png" width="150" height="150" /> <img src="@/assets/paper.png" width="150" height="150" /> </div> </div> </div> </template> <script> export default { name: 'App', methods: { selectRock() { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: "rock" }) }, }, } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>12. チョキやパーを選択できるようにしよう!
今のところまだ、グーしか選択できません。チョキやパーも選択できるようにしましょう。これは今までの要領でいくと簡単です。
まずselect関数を2こ増やします。src/App.vue<script> export default { name: 'App', methods: { selectRock() { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: "rock" }) }, selectScissors() { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: "scissors" }) }, selectPaper() { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: "paper" }) } } } </script>これで、関数が3つに増えました。これをそれぞれの画像のclickイベントに追加していきます。
src/App.vue<div> <img src="@/assets/rock.png" width="150" height="150" @click="selectRock" /> <img src="@/assets/scissors.png" width="150" height="150" @click="selectScissors" /> <img src="@/assets/paper.png" width="150" height="150" @click="selectPaper" /> </div>これで、3つともクリックしたらstateに反映されるようになりました!
(注意事項:動作確認は必ずSTATE確認画面を閉じてから行ってください。)
App.vue全体
src/App.vue<template> <div id="app"> <div> <h2> 選択してください! </h2> <div> <img src="@/assets/rock.png" width="150" height="150" @click="selectRock" /> <img src="@/assets/scissors.png" width="150" height="150" @click="selectScissors" /> <img src="@/assets/paper.png" width="150" height="150" @click="selectPaper" /> </div> </div> </div> </template> <script> export default { name: 'App', methods: { selectRock() { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: "rock" }) }, selectScissors() { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: "scissors" }) }, selectPaper() { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: "paper" }) } }, } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>13. 関数を抽象化しよう!
先程のようにグー・チョキ・パーselect関数を3つ作ってもいいですが、これらの関数には似たような処理が多いです。こういうときは一つの関数にまとめます。これはプログラミングにおいて重要な抽象化の概念です。処理の内容を日本語化するとわかりやすいと思います。
まとめる前
- selectRock関数: グーをstateに登録する。
- selectScissors関数: チョキをstateに登録する。
- selectPaper関数: パーをstateに登録する。
まとめた後
- select関数: 出した手をstateに登録する。
グー、チョキ、パーが出した手に変わったこと以外は同じです。けどこのままだと、出した手ってなんやねん!ってなると思います。ここで関数の引数機能を使います。引数は
関数名(引数名)
みたいな感じで使います。
では早速コードを書いてみましょう。src/App.vue<script> export default { name: 'App', methods: { select(hand) { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: hand }) } } } </script>そして、この関数の呼び出し側も変更します。
src/App.vue<div> <img src="@/assets/rock.png" width="150" height="150" @click="select('rock')" /> <img src="@/assets/scissors.png" width="150" height="150" @click="select('scissors')" /> <img src="@/assets/paper.png" width="150" height="150" @click="select('paper')" /> </div>これで、先程と同じ用に動くはずです。確認してみましょう。
お気づきの人もいるかも知れませんが、このステップでは機能としてなにも進んでいません。コードの書き方を変えただけです。この機能を変えずによりよいコードにすることをリファクタリングといいます。「機能が増えないなら時間の無駄やんw」という人もいるかも知れませんが、どんどん大きなプロジェクトになっていくと、それをメンテナンスするためにはコードの品質が重要になっていきます。そして、「良いコード」を書くのは一朝一夕でできることでもないので、普段から意識的に「良いコード」を書くことを意識することがおすすめです。
App.vue全体
src/App.vue<template> <div id="app"> <div> <h2> 選択してください! </h2> <div> <img src="@/assets/rock.png" width="150" height="150" @click="select('rock')" /> <img src="@/assets/scissors.png" width="150" height="150" @click="select('scissors')" /> <img src="@/assets/paper.png" width="150" height="150" @click="select('paper')" /> </div> </div> </div> </template> <script> export default { name: 'App', methods: { select(hand) { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: hand }) }, }, } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>14. 選択後の画面を作ろう
今のままですと、何回もじゃんけんが選択できてしまいます。選択が終わったら、選択済みにかえましょう。
選択が終わったかどうかは、stateを見たらわかります。stateが空なら未選択ですし、stateに自分のaccessUserIdがあれば、選択済みです。これは$whim.state[$whim.accessUser.id]
で確認できます。また、vue.jsのテンプレートではv-if, v-else を使って条件分岐ができます。確認してみましょう。src/App.vue<template> <div id="app"> <div v-if="$whim.state[$whim.accessUser.id]"> <h2> {{ $whim.state[$whim.accessUser.id] }}を選択済みです。 </h2> </div> <div v-else> <h2> 選択してください! </h2> (以下略)
$whim.state
というのは以下のようなObject型です。(SHOW APP STATE で確認できると思います。){ jibunNoID: "paper" }ここでいう
jibunNoID
は$whim.accessUser.id
で取れる値です。(これはscriptの方で書いたthis.$whim.accessUser.id
と同じですが、vueのtemplateではthisは省略します。)
なので、すでにpaperを選んでいる場合は$whim.state[$whim.accessUser.id]
の値はpaperになりますし、何も選んでいない場合はundefined
になります。これを利用して、v-ifで条件分岐をしています。
App.vue全体
src/App.vue<template> <div id="app"> <div v-if="$whim.state[$whim.accessUser.id]"> <h2> {{ $whim.state[$whim.accessUser.id] }}を選択済みです。 </h2> </div> <div v-else> <h2> 選択してください! </h2> <div> <img src="@/assets/rock.png" width="150" height="150" @click="select('rock')" /> <img src="@/assets/scissors.png" width="150" height="150" @click="select('scissors')" /> <img src="@/assets/paper.png" width="150" height="150" @click="select('paper')" /> </div> </div> </div> </template> <script> export default { name: 'App', methods: { select(hand) { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: hand }) }, }, } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>15. 結果の画面を作ろう
最後に結果の画面です。まずは、二人プレイでどのようにstateが登録されているか確認します。
一人目はpaperを選んでいて、二人目はrockを選んでいます。これを画面に表示します。
まずはじめに、全員がじゃんけんの手をすでに選んだかどうかを確認します。javascriptではfor文でループを表すことができます。usersを一人ずつ、stateに手が登録されているかを確認します。for文の詳しい説明はこちらなどを参考にしてください。
src/App.vuelet result = true for (let i = 0; i < this.$whim.users.length; i++ ) { if(!this.$whim.state[this.$whim.users[i].id]){ result = false } } return resultここで、
this.$whim.users
は今部屋にいるuser全員です。
まず、resultにtrueを設定しておきます。そして、users一人ずつ、手がセットしているかをthis.$whim.state[this.$whim.users[i].id]
で確認します。
ここでセットされていなかったら、resultをfalseに変更します。こうすることで、ひとりでもじゃんけんの手を選んでなかったらresultがfalseになります。これを関数として実装し、またtemplate側でv-ifを使います。(もともと、v-ifだったところはv-else-ifに変えました)この処理をisEveryoneSelectとして追加したコードは次のようになります。
src/App.vue<template> <div id="app"> <div v-if="isEveryoneSelect"> <h2> 全員が選択を終わりました。 </h2> </div> <div v-else-if="$whim.state[$whim.accessUser.id]"> <h2> {{ $whim.state[$whim.accessUser.id] }}を選択済みです。 </h2> </div> <div v-else> <h2> 選択してください! </h2> <div> <img src="@/assets/rock.png" width="150" height="150" @click="select('rock')" /> <img src="@/assets/scissors.png" width="150" height="150" @click="select('scissors')" /> <img src="@/assets/paper.png" width="150" height="150" @click="select('paper')" /> </div> </div> </div> </template> <script> export default { name: 'App', methods: { select(hand) { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: hand }) }, }, computed: { isEveryoneSelect() { let result = true for (let i = 0; i < this.$whim.users.length; i++ ) { if(!this.$whim.state[this.$whim.users[i].id]){ result = false } } return result } } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>ここで使用したcomputedとは、値を動的に算出するときに使うものです。今回ですと、usersが変わりうるので、methodsよりもcomputedのほうが適切です。詳しくはこの記事を読むといいでしょう。
では実際に確認しましょう。一人プレイにときはじゃんけんの手を選ぶと、すぐに全員が選択を終わりましたと出ると思いますが、2人プレイのときは、選択画面→グーを選択済みです。→全員が選択を終わりました。と順番に変更すると思います。16. 結果の画面に全員の選んだ手を表示してみよう!
全員の選択が終わったら結果を表示します。ここではVue.jsのv-forという機能を使います。v-forはtemplate内でループをするという機能です。今回の場合、全員分の結果を表示させないと行けないので、usersに対してv-forでループさせます。
src/App.vue<div v-if="isEveryoneSelect"> <h2 v-for="user in $whim.users" :key="user.id"> {{user.name}}の出した手は、{{$whim.state[user.id]}}です。 </h2> </div>
App.vue全体
src/App.vue<template> <div id="app"> <div v-if="isEveryoneSelect"> <h2 v-for="user in $whim.users" :key="user.id"> {{user.name}}の出した手は、{{$whim.state[user.id]}}です。 </h2> </div> <div v-else-if="$whim.state[$whim.accessUser.id]"> <h2> {{ $whim.state[$whim.accessUser.id] }}を選択済みです。 </h2> </div> <div v-else> <h2> 選択してください! </h2> <div> <img src="@/assets/rock.png" width="150" height="150" @click="select('rock')" /> <img src="@/assets/scissors.png" width="150" height="150" @click="select('scissors')" /> <img src="@/assets/paper.png" width="150" height="150" @click="select('paper')" /> </div> </div> </div> </template> <script> export default { name: 'App', methods: { select(hand) { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: hand }) }, }, computed: { isEveryoneSelect() { let result = true for (let i = 0; i < this.$whim.users.length; i++ ) { if(!this.$whim.state[this.$whim.users[i].id]){ result = false } } return result } } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>17. 最後に見た目を整えよう!
最後に画像やcssを使って、見た目を整えます。Vue.jsでは srcの前でコロンをつけることで、動的に値を設定します。
全体のコードはこちら
src/App.vue<template> <div id="app"> <!-- class="result"を追記します。 --> <div v-if="isEveryoneSelect" class="result"> <div v-for="user in $whim.users" :key="user.id"> <!-- じゃんけんの画像(rock.pngなど)を出した手に応じて表示します。 --> <img :src="require('@/assets/' + $whim.state[user.id] + '.png')" width="150" height="150" /> <h2>{{user.name}}</h2> </div> </div> <div v-else-if="$whim.state[$whim.accessUser.id]"> <h2> {{ $whim.state[$whim.accessUser.id] }}を選択済みです。 </h2> </div> <div v-else> <h2> 選択してください! </h2> <div> <img src="@/assets/rock.png" width="150" height="150" @click="select('rock')" /> <img src="@/assets/scissors.png" width="150" height="150" @click="select('scissors')" /> <img src="@/assets/paper.png" width="150" height="150" @click="select('paper')" /> </div> </div> </div> </template> <script> export default { name: 'App', methods: { select(hand) { // ここに処理を書いていく console.log('selectされた!') this.$whim.assignState({ [this.$whim.accessUser.id]: hand }) }, }, computed: { isEveryoneSelect() { let result = true for (let i = 0; i < this.$whim.users.length; i++ ) { if(!this.$whim.state[this.$whim.users[i].id]){ result = false } } return result } } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } /* resultクラスに対応するcssを追記します。 */ .result { display: flex; justify-content: center; } </style>画像は、この行の
$whim.state[user.id]
('rock', 'cissors', 'paper'のいずれか)が動的に表示されます。
:src="require('@/assets/' + $whim.state[user.id] + '.png')"
ここまでで、実装は終わりです。お疲れ様でした?
デプロイして公開する
18. githubにコードを上げる
ではここから、このゲームの公開方法に移ろうと思います。今のところ、このゲームはローカルで動いてるだけなので、なんらかのサーバにホスティングする必要があります。まず1ステップ目として、今作ったゲームをgithubに上げましょう。具体的は方法はここらへんの記事が参考になると思います。
その後、codeをgithubに上げます。
以下のようなコマンドになるともいます。$ git add . $ git commit -m "janken完成" $ git remote add origin git@github.com:username/janken.git $ git push -u origin master最終的にこのような感じで上げれたら完成です。
19. netlifyの設定をする
次にnetlifyの設定をします。こちらの記事が参考になりますが、この記事でも解説しようと思います。Netlifyは静的なサイトを無料でホスティングしてくれるサービスです。Netlifyのサイトよりアカウントを作成します。githubのアカウントに関連付けて作成します。
この画面に来たら、 New site from Git を選択します。
GitHubを選択します
自分のレポジトリ一覧が出てくるので、先程作ったレポジトリを選択します。
最後にデプロイ設定を以下のようにします。Build commandは
npm run build
Publish directoryはdist
に設定します。設定が終われば Deploy site を押しましょう!
この画面になれば成功です!
表示されているURLをコピーしておきましょう。
20. wh.imのdevelop画面でゲームの登録をする
wh.imの開発者用画面でアプリを登録します。
アクセスすると、googleでのログイン画面になると思います。(これは、gmailで開発者の認証のためです。)
ここで自分のgoogleアカウントで登録します。
すると次の画面に遷移します。これが開発者アプリ登録画面です。右上のボタンから、新規アプリを登録します。
その後、必要事項を記入していきます。
ゲームのhostのURLは先程コピーしたURLを記入します。
最後のチェックボックスは色々な人が遊べるように公開したい場合は、チェックをします。(個人的に遊ぶだけの場合はチェックを入れなくて大丈夫です。)saveを押すと公開完了です!
21. 遊んで見る!
早速遊んでみましょう。まず、IDをコピーします。そして、wh.imに移動します。(今回は開発モードにする必要はありません!)
アプリ選択画面を開き、コピーしたIDを入力しましょう。まとめ
長い記事になりましたが、これでVue.js未経験者の方でもwh.imを使ったゲーム公開までの流れは一通り理解できたかと思います。Vue.jsをもっと学んで行くと、ワードウルフといった複雑なゲームも作れるようになるので、是非挑戦して見てください!
最後に、これらの情報が体系的にまとめた(つもりの)、開発者用ドキュメントも用意しているので、参考にしてみてください。
- 投稿日:2020-06-03T04:11:36+09:00
サイトに3Dメニューページを作ってみる(A-frame + HTML Shader + html2canvas)
この記事でやっていること・できていないこと
- できたこと
- A-Frameを使ってサイトのメニューを3D化した。
- A-Frame内で2次元の普通の画像要素を3次元で使うタイミングでできないことがあったので、HTML Shader + html2canvasを使った。
- できてないこと
- 犬の画像をAPIで取得して、それをA-Frame空間内でも表示しようとしたがクロスドメインの関係でできなかった。(https://dog.ceo/api/breeds/image/random)
サンプル
このように3次元空間内にboxを置き、その上にhtmlで作った画像付き要素をのせています。
またクリックをすると、遷移対象のページに飛びます。https://simasima.work/3d-menu.html
↑ こちらから試すことができます。
A-Frame
https://aframe.io/
ほんの数行のhtmlの記述だけで3D画像と空間を表現でき、とても便利です。
例えば下記サンプルコードですぐに複数の図形と3次元空間を表現できます。aframe-sample.html<html> <head> <script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script> </head> <body> <a-scene> <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box> <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere> <a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder> <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane> <a-sky color="#ECECEC"></a-sky> </a-scene> </body> </html>HTML Shader + html2canvas について
A-Frameの3次元空間内に2次元のDOM要素を加える場合に、どうしてもうまくいかないケースがありました。
本記事では、下記が大変参考になりました。
A-FrameとHTML Shaderで美しい日本語テキストを表示する方法この記事によると、
HTML Shaderは、その名の通り2次元のDOM要素をマテリアルとして、3次元のA-Frameオブジェクト上に貼り付けることができるコンポーネントで、それは html2canvas というライブラリ上に成り立っているということです。以降で説明していきますが、そのため上記2つ(HTML Shader + html2canvas)を使えるようにする必要があります。
まず、HTML Shader と html2canvasを読み込む
HTML Shader
https://github.com/mayognaise/aframe-html-shader
内にかけばOKです。
このページによると、npmでのインストールに加えて、ブラウザファイルも用意されているようなので手ごろに使う場合は下記を<script src="https://aframe.io/releases/0.5.0/aframe.min.js"></script> <script src="https://unpkg.com/aframe-html-shader@0.2.0/dist/aframe-html-shader.min.js"></script>html2canvas
私が少し調べた限りでは、ブラウザファイルは用意されていないようなので、
http://html2canvas.hertzen.com/
このページにいき、html2canvas.min.js
を押しその中身をコピーして、html2canvas.min.js というファイル名で保存します。
今回のフォルダ構成
images └── .... js └── html2canvas.min.js style └── style.css 3d-menu.htmlソースコード
3d-menu.html<!DOCTYPE html> <html lang="ja"> <head> <title>Hello!!!!</title> <meta name="description" content="Hello, WebVR! • A-Frame"> <script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script> <script src="https://unpkg.com/aframe-html-shader@0.2.0/dist/aframe-html-shader.min.js"></script> <script src="js/html2canvas.min.js"></script> <link rel="stylesheet" href="style/style.css"> </head> <body> <div id="loader"> <p>loading...</p> </div> <div id="target1" class="target"> <img src="imgs/face.png" alt="A-Frame"> <div class="cf"><h3>感情分析</h3></div> <p class="detail">クリックでページに遷移 </p> </div> <div id="target2" class="target"> <img v-if="url" v-bind:src="url" alt="A-Frame"> <div class="cf"><h3>犬の画像を表示</h3><p>立方体クリックで犬の画像を表示します(動かず)</p></div> <!-- <p class="detail">640px × 400px</p> --> </div> <div id="target3" class="target"> <img src="imgs/gotop.png" alt="A-Frame"> <div class="cf"><h3>TOPへ</h3></div> <p class="detail">クリックでページに遷移 </p> </div> <div id="target4" class="target"> <img src="imgs/orc.png" alt="A-Frame"> <div class="cf"><h3>OCR分析</h3></div> <p class="detail">クリックでページに遷移</p> </div> <div id="target5" class="target"> <img src="imgs/dog.jpg" alt="A-Frame"> <div class="cf"><h3>犬画像ページ</h3></div> <p class="detail">クリックでページに遷移</p> </div> <div id="target6" class="target"> <img src="imgs/jaga.png" alt="A-Frame"> <div class="cf"><h3>予備</h3></div> </div> <div> <a-scene> <a-entity id="aframeApp" > <a-box id="areabox1" position="-10 6 -15" width="16" height="10" rotation="0 30 0" material="shader:html;target: #target1;" @click="handlerClick"></a-box> <a-box id="areabox2" position=" 0 6 30" width="16" height="10" rotation="0 15 0" material="shader:html;target: #target2;"></a-box> <a-box id="areabox3" position=" 18 6 0" width="16" height="10" rotation="0 75 0" material="shader:html;target: #target3;" @click="handlerClick"></a-box> <a-box id="areabox4" position="-10 -6 -15" width="16" height="10" rotation="0 30 0" material="shader:html;target: #target4;" @click="handlerClick"></a-box> <a-box id="areabox5" position=" 0 -6 30" width="16" height="10" rotation="0 15 0" material="shader:html;target: #target5;" @click="handlerClick"></a-box> <a-box id="areabox6" position=" 18 -6 0" width="16" height="10" rotation="0 75 0" material="shader:html;target: #target6;" @click="handlerClick"></a-box> <a-box position="-2 0 5" rotation="0 45 0" color="#4CC3D9" @click="getdog"></a-box> <a-plane position=" 18 -6 0" width="16" height="10" @click="getdog"></a-plane> <a-sky src="https://images.unsplash.com/photo-1557971370-e7298ee473fb?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1600&q=80" rotation="0 45 0"></a-sky> </a-entity> <a-entity camera wasd-controls look-controls position="-8 0 8"></a-entity> <a-entity> <a-camera> <a-cursor></a-cursor> </a-camera> </a-entity> </a-scene> </div> <script src="https://unpkg.com/vue@latest/dist/vue.min.js"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script> // vue.jsの記載 const app = new Vue({ el: '#aframeApp', data: { url:"imgs/logo.png" }, methods: { getdog:async function(){ const URL = 'https://dog.ceo/api/breeds/image/random'; const response = await axios.get(URL); this.message = response.data; this.url = response.data.message; console.log(this.url); } ,handlerClick: function (event) { console.log('handlerClick'); console.log(event.target.id); const boxid = event.target.id; if( boxid == 'areabox1' ){ location.href = 'https://simasima.work/contents/face-emotion.html'; } else if( boxid == 'areabox4' ){ this.skyboxSrc = 'https://simasima.work/contents/ocr-read.html'; } else if( boxid == 'areabox3' ){ this.skyboxSrc = 'https://simasima.work/contents/dog.html'; }else if( boxid == 'areabox5' ){ this.skyboxSrc = 'https://simasima.work/'; } } , handlerMouseEnter: function (event) { console.log('handlerMouseEnter'); console.log(event); event.target.setAttribute('color', 'blue'); } , handlerMouseLeave: function (event) { console.log('handlerMouseLeave'); console.log(event); event.target.setAttribute('color', 'red'); } } , mounted() { console.log('mounted'); } }) </script> </body> <script> var scene = document.querySelector('a-scene'); var run = function () { document.getElementById("loader").classList.add("hidden"); } if (scene.hasLoaded) { run(); } else { scene.addEventListener('loaded', run); } </script> </html>スタイルシート
style.css* { margin: 0; padding: 0; } #loader { width: 100%; height: 100%; position: fixed; z-index: 100000000000; background-color: #333; color: #fff; } #loader>p { position: absolute; top: 50%; margin-top: -0.5em; width: 100%; text-align: center; font-size: 200%; font-weight: bold; } #loader.hidden { display: none; } .target { position: absolute; width: 1600px; height: 1000px; font-size: 500%; background-color: #FFF; display: hidden; /*z-index: 1;*/ } .target>img { float: left; display: block; width: 32%; padding: 4% 0 4% 4%; } .target>div { margin-left: 36%; width: 52%; padding: 4%; } .target>.detail { padding: 3% 0; text-align: center; width: 92%; color: #fff; background-color: rgba(1, 1, 1, 0.6); margin: 4%; }ちょこっと解説・ポイント
今回はこのページを大変参考にさせて頂きました!
http://vr-lab.voyagegroup.com/entry/2016/11/16/122115その中で、開発に慣れてなくてつまずいたことを記載しておきます。(初心者な内容もあり)
1. エディターのLive server的な機能でみると画像が崩れる時がある
「コメント部分消しただけなのに全体的になぜか崩れたぞ??」
→ これキャッシュが残ってるからでした。スーパーリロードしましょう。// Mac版 Cmd + Shift + R // Windows版 Shift + F52. 3次元空間ないのカーソルが出てこない
これを加えましょう。
<a-entity> <a-camera> <a-cursor></a-cursor> </a-camera> </a-entity>3. カーソルを追加したら達が出なくなった
要素は生き延びていたのですが、要素だけ軒並み非表示になってしまいました。。
おそらく奥行きの問題かと考え、をに変更しました。4. 角度や場所の指定が難しい
ここは触りながら慣れるしかないですね。。
私もかなり苦労しました。おわりに
A-Frameはすごいです。
3D空間を作ることに関しては、本当に何も考えることなくできました。
いろんなものが身近になってきていますね。