20191126のJavaScriptに関する記事は29件です。

処理を分岐する-if文-

 条件分岐構文

JavaScriptには二つの中から片方を選択するするif文と複数の選択肢の中から、1つの選択肢を選ぶswitch文の二つが条件分岐構文として用意されています。

if命令

if文とは、もし○○だったら、△△せよ、さもなくば××を行えというように与えられた値を条件式で比較し『true(真)/false(偽)』を判断します。
JavaScriptの世界ではtrue(真)/false(偽)の事を真偽値もしくはBooleanとも呼ばれます。
if文の基本的な構文は下記となります。

if(条件式){
 //条件がtrueの時の処理
}else{
 //条件がfalseの時の処理
}

falseの時の処理がない場合はtureの時の処理のみ記述しfalse部分の処理を省略することが出来ます。

if(条件式){
 //条件がtrueの時の処理
}

次に具体的なサンプルコードを見てみます。

let num = 25;

if( num < 50 ){
    console.log('numは50以下です');
}else{
    console.log('numは50以上です')
}

変数numに格納されている値が50以下か50以上かを判断するための式となっています。
numの値は25なのでif文はtrueと判断され一つ目の処理が行われます。

複数の分岐処理ができるelse if

elseの代わりにelse ifと書くことで分岐条件を追記していくことが可能です。

let num = 25;

if( num > 100 ){
    console.log('numは100以上です');
}else if( num > 50 ){
    console.log('numは50以上です');
}else{
    console.log('numは50以下です');
}

else ifは必要に応じ増やすことが可能の様です。

今回記事を書くにあたり下記のページ、書籍を参考にさせて頂きました。
https://jsprimer.net/
https://ja.javascript.info/
https://gihyo.jp/book/2016/978-4-7741-8411-1

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

JavaScript記事のまとめ

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

※JavaScript記事のまとめ

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

はじめてのVue.js

概要

社内の技術共有会で使用したもの。
自分の勉強も兼ねつつ、Vueを触ったことない方向けに作成したものです。

What's Vue.js ?

  • jsのフレームワークのひとつ
    • 他だとAngular, Reactあたりが有名
  • プログレッシブフレームワークである。
    • モノリシックではないので、開発レベルに合わせて段階的に導入することが可能
  • ECMAScript5対応ブラウザで使用してね
    • 骨董品みたいなブラウザで使用するのは厳しいです
    • せめてIE9から IE8以下? 知らない子ですね

Hello World

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title> わくわくVue </title>
<!-- 公式公開されているVue.js を読み込む -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>

    <div id="app">
        <p>{{ message }}</p>
        <button v-on:click="displayMsg">Click!!</button>
      </div>

<script>
const app = new Vue({
  el: '#app',
  data: {
    message: ''
  },
  methods: {
    displayMsg : function () {
      this.message = "Hello World!!";
    }
  }
})
</script>

</body>
</html>

上記htmlを実行すると味気ないボタンが出ます。ボタン押下で「こんにちは世界」。

上記コードについて

const app = new Vue({
  el: '#app', // 1
  data: {
    message: '' // 2
  },
  methods: {
    displayMsg : function () { // 3
      this.message = "Hello World!!";
    }
  }
})

js側

  • 1 : el
    • html上でVueインスタンスの管理下に置く要素。セレクタで指定。
  • 2 : data
    • Vueインスタンスがもつプロパティ
    • v-bind, v-model等でバインディングされるデータを保持
  • 3 : methods
    • Vueインスタンスが保持するメソッド
    • イベントのハンドリングを行う際に使用。
    • methodsオブジェクト下にメソッド名 : 関数で定義。
    • アロー関数には気を付けろ、、、
<div id="app"> // 1
<p>{{ message }}</p> // 2
<button v-on:click="displayMsg">Click!!</button> // 3
</div>

html側

  • 1 : id="app"
    • Vueインスタンスで管理下に置いた要素。
  • 2 : {{ message }}
    • データバインディングの対象となる項目。今回はappインスタンスのmessageとバインディング。
    • Mustache構文(二重中括弧)で記載すること。
  • 3 : v-on:click="displayMsg"
    • v-で始まる特別な記法をディレクティブと呼ぶ。属性値の変化があったときに作用する。
    • 今回のv-onはdomイベントの受け取りを実施。:の後に記述されたイベントに対しバインディングする。 -v-on:clickでクリックされた時にdisplayMsgメソッドを実行。
    • v-on:click@clickと略すことも可能。

その他の主なVueのディレクティブ

v-text

任意の文字列オブジェクトを指定要素に展開する

<span v-text="msg"></span>
<span>{{msg}}</span>

は両方同じ

v-html

<span>hoge</span>みたいな文字列を単なる文字列ではなく生htmlとして展開したいときに使用する

脆弱性とかを生むものでもあるのでご利用は計画的に

v-show

評価式に応じて指定要素のCSSプロパティdisplayに作用する

<div v-show="hoge">ほげ</div> <!-- hogeがtrueなら表示 -->

v-if, v-else, v-if-else

評価式に応じて指定要素を描画するか決定する

v-showと違って要素すら描画しない!! 用途に合わせて使い分けるのが吉。

<div v-if="hoge">  <!-- hogeがtrueなら描画 -->
  ほげ
</div>
<div v-else-if="fuga">  <!-- fugaがtrueなら描画 -->
  ふが
</div>
<div v-else> <!-- hoge,fugaがfalseなら描画, v-if, v-else-ifの直後でない場合は認識されないです -->
  not ほげふが
</div>

v-for

配列、オブジェクトを元にループを実行し要素を複数回描画する

<html>
<ul id="example">
  <li v-for="hoge in hoges">
    {{ hoge }} <!-- ほげ1、ほげ2がリスト形式で描画される -->
  </li>
</ul>
<script>
const hogeVue = new Vue({
  el: '#example',
  data: {
    hoges: ['ほげ1', 'ほげ2']
  }
})
</script>
</html>

所感

関わっている案件で使用していますが、ちゃんと使い分けとか考えてみると面白いと感じました。
次回の社内共有会ではコンポーネントやライフサイクルあたりを話そうかなあと思ってます。僕の知識が足りなくて今回の社内共有会でうまくまとめられなかったのは内緒
これからもぼちぼち頑張ろうと思います。

参考

Vue公式様
とほほ様

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

【JavaScript】画像のファイルタイプをバイナリレベルで判別する方法

リンク

こちらを参考
すごく勉強になりました。

概要

  1. readAsArrayBufferでバイナリに変更
  2. Uint8Arrayを使って変換 new Uint8Array(binary)
  3. 初めの10個分を読む
  4. 1文字ずつ16進数列に変換 toString(16)
  5. 変換した文字列がどの画像形式か判別する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

チュートリアル - 3D空間を使った簡単webサイト 5/5

前回

Publishして誰でも見れるように公開!

プロジェクトを作成してビルドまでやってくれるのもPlayCanvasのすごいところ。

ここまで作成したものをPlayCanvasの公開環境に公開してみましょう。

左のメニュー、またはシーンの左上のManage Scenesから以下のPublish画面を開きます。

スクリーンショット 2019-06-21 11.15.25.png

PUBLISH TO PLAYCANVASを選択して、一番下のPUBLISH NOWをクリックしてビルドは完了です。
URLでPC、スマホ、他ユーザーにもログインすることなく共有することができます。

スクリーンショット 2019-06-21 11.18.57.png

以上がチュートリアルの大枠となる説明でした。
おつかれさまでした。

以降はWebサイトとして追加したいものを説明するおまけです。


おまけ

SCENEの名前を変更する

sceneの名前もUntitledのままじゃカッコがつかないですね。
これはPlayCanvasの設定から変更ができます。

左上のPlayCanvasのロゴをクリックするとメニューが開きます。
その中のSettingsでインスペクター上にSETTINGSを開き、上のScene Nameから名前を変更できます。

スクリーンショット 2019-06-24 19.35.21.png


headerの処理

headerのhtmlも追加しているので、同じようにglobal変数を使って同じように実装していきます。

addhtml.jsのinitializeに以下に書き換えます。
さらに、headerのナビゲーションをクリックするためgnavClickのイベントの処理も追加します。
headerのナビゲーションではどのsceneを選択したのかを、data-scene属性を作成して参照します。

Addhtml.prototype.initialize = function() { // init

    globalPc.scene = 0; // どのシーンページを開いているか保管

    var htmlNameArr = []; // attributesのデータの名前を配列にします
    var arrIndex = Addhtml.attributes.index;
    for(var i = 0; i < Object.keys(arrIndex).length; i++){ // attributesのデータを取得してfor文で回す
        if(!Object.keys(arrIndex)[i].indexOf("scene")){ // 名前にhtmlが入っているデータをif文
            htmlNameArr.push(Object.keys(arrIndex)[i]); // htmlデータの名前だけ配列化する
        }
    }

    var head = document.getElementsByTagName("head")[0]; // headタグ取得
    var body = document.getElementsByTagName("body")[0]; // bodyタグ取得

    var wrapper = document.createElement("div"); // DOMを囲う要素を作成
    wrapper.className = "wrapper"; // クラス名指定
    body.appendChild(wrapper); // bodyタグの最後に要素を追加

    container = document.createElement("main"); // DOMを囲う要素を作成
    container.className = "container"; // クラス名指定
    wrapper.appendChild(container); // bodyタグの最後に要素を追加

    var style = document.createElement("style"); // cssのstyleタグ

    wrapper.insertAdjacentHTML("afterbegin", this.header.resource); // attrで追加したヘッダーを追加

    // ヘッダー内のgnaviを取得
    var gnavLists = document.querySelector(".gnav_lists"); 
    gnavLists.innerHTML = ""; // .gnav_lists内のhtmlを空っぽに
    var gnavItem = document.createElement("div"); // gnavの各リンクのdivを作成
    gnavItem.className = "gnav_item"; // gnavリンク要素のclass名を指定
    var gnavItemCl = []; // gnav_itemを整理する

    style.append(this.css._resources[0]);
    head.appendChild(style); // headの最後にstyleを追加

    for(var i = 0; i < htmlNameArr.length; i++){

        container.insertAdjacentHTML("beforeend", this[htmlNameArr[i]].resource); // アセットから取得したhtmlを追加

        // 今回はgnavのリンクの数とhtmlのアセットの数が同じなのでどう処理を行う。
        gnavItemCl[i] = gnavItem.cloneNode(true); // gnavItemのクローンを作成
        gnavItemCl[i].innerHTML = '<label for="menu"><span>' + htmlNameArr[i] + '</span></label>'; // innerHTMLを作成
        gnavItemCl[i].setAttribute("data-scene",i+1); // scene1,scene2,scene3を切り替えるため数値をdata-sceneに追加
        gnavLists.append(gnavItemCl[i]); // gnav_lists内に追加
        gnavItemCl[i].addEventListener("click", gnavClick, false); // gnav_item各々にイベントをセット
    }
};
function gnavClick(e){ // gnav_itemをクリックしたら発火
    if(globalPc.scene === 0)  {
        globalPc.scene = this.getAttribute("data-scene"); // クリックしたgnav_itemのdata-sceneを取得
    }
}

ホバーとクリックのアクションを追加

クリックでhtmlにイベントを取得させましたが、アニメーションを追加します。
ホバーすればscaleが少し大きくなり、クリックすればワイヤーフレームになるといった感じです。
このアクションではもっと色んな広がりを見せることができます。
PlayCanvasではアニメーションを作れるTweenライブラリが公開されていますのでご参照ください。
https://developer.playcanvas.com/en/tutorials/tweening/

hotspot.jsを以下にまるっと書き換えます。

var Hotspot = pc.createScript('hotspot');

// canvasのclickやhoverなどの処理を行う

Hotspot.attributes.add("cameraEntity", {type: "entity", title: "Camera Entity"}); // カメラのentityを取得
Hotspot.attributes.add("radius", {type: "number", title: "Radius"}); // entityのヒットエリアの範囲を指定

Hotspot.prototype.initialize = function() { // init
    this.hitArea = new pc.BoundingSphere(this.entity.getPosition(), this.radius); // ヒットエリアを作成。BoundingSphereがentityの境界エリアを作成(Photoshopでいうバウンディングボックス的な)
    this.ray = new pc.Ray(); // cameraからentityへ直進する線のデータを作成。(Rayは光線の意でstart pointからentityまでの距離を測ったりすることが可能)

    this.directionToCamera = new pc.Vec3(); // Vector座標の型を取得

    this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseHover, this); // マウスカーソルがホバーした時
    this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this); // クリックが押された時

    this.localScale = this.entity.getLocalScale().clone(); // 初期のscaleを保持
    this.hoverScale = this.localScale.clone().scale(1.2); // ホバー時のentityのscaleを保持

};

Hotspot.prototype.update = function(dt) { // update
    this.cameraPosition = this.cameraEntity.getPosition(); // cameraのポジション(座標)を取得
    this.directionToCamera.sub2(this.cameraPosition, this.entity.getPosition()); // 2つの3次元ベクトルの値を互いに減算
    this.directionToCamera.normalize(); // 3次元ベクトルを単位ベクトルに変換

    if(globalPc.scene === 0){ // sceneが選択されていない場合
        for (var i = 0; i < this.entity.model.meshInstances.length; i++) {
            this.entity.model.meshInstances[i].renderStyle = pc.RENDERSTYLE_SOLID; // レンダラーのスタイルをソリッドに変更
        }
    }
    if(this.entity.tags._list[0].replace(/[^0-9]/g, '') === globalPc.scene) { // gnav_itemから選択されたsceneを参照
        this.entity.model._model.generateWireframe(); // モデルのワイヤーフレームを準備
        for (var j = 0; j < this.entity.model.meshInstances.length; j++) { // モデルのメッシュを取得
            this.entity.model.meshInstances[j].renderStyle = pc.RENDERSTYLE_WIREFRAME; // ワイヤーフレームにレンダリング
        }
    }
};

Hotspot.prototype.doRayCast = function (screenPosition) { // レイキャスト処理(ある地点から特定方向に直線で線を引いて、その線上で物体があるか検知する処理)
    if (this.hitArea.intersectsRay(this.ray)) {
        globalPc.scene = this.entity.tags._list[0].replace(/[^0-9]/g, ''); // entityで設定したタグを取得し、s1、s2...の数字以外をreplaceで削除し数字のみ代入
        this.entity.model._model.generateWireframe(); // モデルのワイヤーフレームを準備
        for (var i = 0; i < this.entity.model.meshInstances.length; i++) { // モデルのメッシュを取得
            this.entity.model.meshInstances[i].renderStyle = pc.RENDERSTYLE_WIREFRAME; // ワイヤーフレームにレンダリング
        }
    }
};

Hotspot.prototype.onMouseHover = function(screenPosition) { // マウスホバー時
    this.cameraEntity.camera.screenToWorld(screenPosition.x, screenPosition.y, this.cameraEntity.camera.farClip, this.ray.direction); // ポジションを2Dスクリーンから3D空間へ変換
    this.ray.origin.copy(this.cameraEntity.getPosition()); // レイのオリジナルのポジションにカメラのポジションをコピー
    this.ray.direction.sub(this.ray.origin).normalize(); // 3次元ベクトルを他の場所から減算し、単位ベクトルに変換

    if (this.hitArea.intersectsRay(this.ray) && globalPc.scene === 0) { // sceneが選択されていなくて、ヒットエリアとレイが交差した場合
        this.entity.setLocalScale(this.hoverScale); // entityのスケールを大きく
    }else{
        this.entity.setLocalScale(this.localScale); // entityのスケールを小さく
    }
};

Hotspot.prototype.onMouseDown = function(event) { // クリックが押されている時
    if (event.button == pc.MOUSEBUTTON_LEFT) { // 左クリックが押された時
        this.doRayCast(event); // レイキャストを呼ぶ
    }
};

さらにおまけで、マウスのクリックボタンの判定を以下のように取っていましたが、

CameraMove.prototype.onMouseDown = function (event) { // クリックダウンしたら
    if (event.button === pc.MOUSEBUTTON_LEFT) {} // 左クリック
    if (event.button === pc.MOUSEBUTTON_MIDDLE) {} // 中クリック
    if (event.button === pc.MOUSEBUTTON_RIGHT) {} // 右クリック
};

以下のようにもっと簡単に左・中・右クリックのbooleanを取ることもできます。

CameraMove.prototype.onMouseDown = function (event) { // クリックダウンしたら
    if (event.buttons[0]) {} // 左クリック
    if (event.buttons[1]) {} // 中クリック
    if (event.buttons[2]) {} // 右クリック
};

3Dオブジェクトのモデルを変えたい!

PlayCanvasにはAssets Storeなるものがあり、ここから指定のプロジェクトのAssetsライブラリにアセットを追加することができます。
http://store.playcanvas.com/

スクリーンショット 2019-06-21 10.53.49.png

アセットを選択し、Downloadをするとログインしているアカウントが所有しているプロジェクトの一覧が表示されますので、指定のプロジェクトを選択することでアセットが使用できます。

スクリーンショット 2019-06-21 10.56.58.png

スクリーンショット 2019-06-21 10.59.01.png

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

チュートリアル - 3D空間を使った簡単webサイト 4/5

前回

3Dオブジェクトに応じたHTMLのページを表示させる

マウスからイベントが取得できたので、クリックしたらHTMLを表示するようにします。

予めAssetsにmarkupというディレクトリ内に使用されるhtmlとcssのサンプルがあります。
事前にサンプルを用意していますが、作る際には自分のローカルで作成する必要があるのでご注意ください。

今回は3つの3Dオブジェクトと3つのhtmlを関係を持たせて、
「3Dオブジェクトがクリックされたら、それにリンクするhtmlが表示される」 までを作ります。

ここで使用するscriptはaddhtml.jsですが、これもまたEntityにADD SCRIPTする必要があります。
使用するaddhtml.jsは特定のEntityにADD SCRIPTする必要はありません。
今回はcharaというグループのEntityにADD SCRIPTします。

スクリーンショット 2019-07-10 20.13.04.png

addhtml.jsをADD SCRIPTしたら、コードエディターを開きます。

以下のコードを追加します。

Addhtml.attributes.add("css", {type: 'asset', assetType:'css', title: 'CSS Style'}); // アセットのcss読み込み
Addhtml.attributes.add("header", {type: 'asset', assetType:'html', title: 'HTML Header'}); // アセットのhtmlのheader読み込み
Addhtml.attributes.add("scene1", {type: 'asset', assetType:'html', title: 'HTML Scene1'}); // アセットのhtmlのscene1読み込み
Addhtml.attributes.add("scene2", {type: 'asset', assetType:'html', title: 'HTML Scene2'}); // アセットのhtmlのscene2読み込み
Addhtml.attributes.add("scene3", {type: 'asset', assetType:'html', title: 'HTML Scene3'}); // アセットのhtmlのscene3読み込み

早速、markupディレクトリのファイルを登録していきます。

下のアセットからmarkupというディレクトリに登録するファイルがあります。

スクリーンショット 2019-07-10 20.16.05.png

スクリーンショット 2019-07-31 12.19.50.png

htmlとcssを登録していきます。

登録したコードを追加するために

とのエレメントを取得する必要があります。
なぜ取得するのかというと、取得したheadやbodyに対して、appendChild()やinnerHTML、insertAdjacentHTML()などでhtmlを追加するからです。

以下のコードを追加していきます。

var container;

Addhtml.prototype.initialize = function() { // init
    var htmlNameArr = []; // attributesのデータの名前を配列にします
    var arrIndex = Addhtml.attributes.index;
    for(var i = 0; i < Object.keys(arrIndex).length; i++){ // attributesのデータを取得してfor文で回す
        if(!Object.keys(arrIndex)[i].indexOf("scene")){ // 名前にhtmlが入っているデータをif文
            htmlNameArr.push(Object.keys(arrIndex)[i]); // htmlデータの名前だけ配列化する
        }
    }

    var head = document.getElementsByTagName("head")[0]; // headタグ取得
    var body = document.getElementsByTagName("body")[0]; // bodyタグ取得

    var wrapper = document.createElement("div"); // DOMを囲う要素を作成
    wrapper.className = "wrapper"; // クラス名指定
    body.appendChild(wrapper); // bodyタグの最後に要素を追加

    container = document.createElement("main"); // DOMを囲う要素を作成
    container.className = "container"; // クラス名指定
    wrapper.appendChild(container); // bodyタグの最後に要素を追加

        var style = document.createElement("style"); // cssのstyleタグ

    wrapper.insertAdjacentHTML("afterbegin", this.header.resource); // attrで追加したヘッダーを追加

    style.append(this.css._resources[0]);
    head.appendChild(style); // headの最後にstyleを追加

    for(var i = 0; i < htmlNameArr.length; i++){ // htmlのアセットを配列管理するためにfor文でattrの名前の配列を回す

        container.insertAdjacentHTML("beforeend", this[htmlNameArr[i]].resource); // アセットから取得したhtmlを追加

    }
};

これでhtmlとcssの情報を追加できました。
headの中にcssのスタイルが入り()、bodyの中にhtmlの要素が入っています。

スクリーンショット 2019-06-20 18.05.48.png

次にオブジェクトをクリックしたらそれに連動したhtmlを表示するやり方です。

attributesで登録したhtmlでscene1、scene2、scene3とありました。
この数字を3Dオブジェクトとの関係性に使います。

Editorから3DオブジェクトのModelを選択し、インスペクターからTagsを登録します。
tagの名前は任意のもので大丈夫です。数字をそれぞれに入れるのを忘れないように。

スクリーンショット 2019-06-20 18.11.03.png

登録したtagをクリックした時に取得できるか確認します。
hotspot.jsのdoRayCastでtagを確認します。

Hotspot.prototype.doRayCast = function (screenPosition) { // レイキャスト処理(ある地点から特定方向に直線で線を引いて、その線上で物体があるか検知する処理)
    if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合
        console.log("click!! : ",this.entity.tags._list[0]);
    }
};

クリックした3Dオブジェクトから各々のtagが取得できたと思います。
これを使ってhtmlとリンクさせます。

addhtml.jsに以下を追加します。

globalPc = {}; // グローバルな変数(オブジェクト)

Addhtml.prototype.update = function(dt) { // update
    if(Number(globalPc.scene) > 0) { // いずれかのsceneが選択されている場合
        if(!container.classList.contains("is-open")){ // 追加したアセットのhtmlにis-openのclassが追加されていない場合(どのhtmlも表示されていない)
            container.classList.add("is-open"); // is-openのclass名をcontainerに追加
            var sectionElements = document.getElementsByClassName("section"); // sectionを取得
            for(var i = 0; i < sectionElements.length; i++){ // sectionをループ処理
                if(sectionElements[i].classList.contains("is-current")){ // sectionにis-currentのclass名を持つかif処理
                    sectionElements[i].classList.remove("is-current"); // is-currentを削除
                }
            }
            sectionElements[globalPc.scene-1].classList.add("is-current"); // 選択されたsceneにis-currentのclass名を追加
            document.getElementsByClassName("section_close")[globalPc.scene-1].addEventListener("click", btnClose, false); // 選択されたsceneの閉じるボタンにイベントをセット
        }
    }

};

function btnClose(e){ // 閉じるボタンが押されたら発火
    e.preventDefault();
    globalPc.scene = 0; // 閉じるボタンなので選択されたsceneはnullにするので、0を代入
    container.classList.remove("is-open"); // containerのis-openのclass名を削除
    var sectionElements = document.getElementsByClassName("section"); // sectionを取得
    for(var i = 0; i < sectionElements.length; i++){ // sectionをループ処理
        if(sectionElements[i].classList.contains("is-current")){ // sectionにis-currentのclass名を持つかif処理
            sectionElements[i].classList.remove("is-current"); // is-currentを削除
        }
    }
}

リンクさせる方法として、global変数のglobalPcで今開いているページを管理できるようにします。

initializeには以下を追加します。

globalPc.scene = 0; // どのシーンページを開いているか保管

先ほど確認したhotspot.jsのdoRayCastでもglobal変数に数字を与えます。
数字のみを代入するようにします。

Hotspot.prototype.doRayCast = function (screenPosition) { // レイキャスト処理(ある地点から特定方向に直線で線を引いて、その線上で物体があるか検知する処理)
    if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合
        globalPc.scene = this.entity.tags._list[0].replace(/[^0-9]/g, ''); // entityで設定したタグを取得し、scene1、scene2...の数字以外をreplaceで削除し数字のみ代入
    }
};

Launch画面でリロードして3Dオブジェクトをクリックすると、それぞれ該当したhtmlが表示されるようになります。

これで簡単なwebの大枠はできました。

[ hotspot.jsのコード ]

var Hotspot = pc.createScript('hotspot');

// canvasのclickやhoverなどの処理を行う

Hotspot.attributes.add("cameraEntity", {type: "entity", title: "Camera Entity"}); // カメラのentityを取得
Hotspot.attributes.add("radius", {type: "number", title: "Radius"}); // entityのヒットエリアの範囲を指定

Hotspot.prototype.initialize = function() { // init
    this.hitArea = new pc.BoundingSphere(this.entity.getPosition(), this.radius); // ヒットエリアを作成。BoundingSphereがentityの境界エリアを作成(Photoshopでいうバウンディングボックス的な)
    this.ray = new pc.Ray(); // cameraからentityへ直進する線のデータを作成。(Rayは光線の意でstart pointからentityまでの距離を測ったりすることが可能)

    this.directionToCamera = new pc.Vec3(); // Vector座標の型を取得

    this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseHover, this); // マウスカーソルがホバーした時
    this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this); // クリックが押された時
};


Hotspot.prototype.doRayCast = function (screenPosition) { // レイキャスト処理(ある地点から特定方向に直線で線を引いて、その線上で物体があるか検知する処理)
    if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合
        globalPc.scene = this.entity.tags._list[0].replace(/[^0-9]/g, ''); // entityで設定したタグを取得し、s1、s2...の数字以外をreplaceで削除し数字のみ代入
    }
};


Hotspot.prototype.onMouseHover = function(screenPosition) { // マウスホバー時
    this.cameraEntity.camera.screenToWorld(screenPosition.x, screenPosition.y, this.cameraEntity.camera.farClip, this.ray.direction); // ポジションを2Dスクリーンから3D空間へ変換
    this.ray.origin.copy(this.cameraEntity.getPosition()); // レイのオリジナルのポジションにカメラのポジションをコピー
    this.ray.direction.sub(this.ray.origin).normalize(); // 3次元ベクトルを他の場所から減算し、単位ベクトルに変換

    if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合
        console.log("hover");
    }
};

Hotspot.prototype.onMouseDown = function(event) { // クリックが押されている時
    if (event.button == pc.MOUSEBUTTON_LEFT) { // 左クリックが押された時
        this.doRayCast(event); // レイキャストを呼ぶ
    }
};

[ addhtml.js のコード ]

var Addhtml = pc.createScript('addhtml');

// htmlを追加などする処理を記入

Addhtml.attributes.add("css", {type: 'asset', assetType:'css', title: 'CSS Style'}); // アセットのcss読み込み
Addhtml.attributes.add("header", {type: 'asset', assetType:'html', title: 'HTML Header'}); // アセットのhtmlのheader読み込み
Addhtml.attributes.add("scene1", {type: 'asset', assetType:'html', title: 'HTML Scene1'}); // アセットのhtmlのscene1読み込み
Addhtml.attributes.add("scene2", {type: 'asset', assetType:'html', title: 'HTML Scene2'}); // アセットのhtmlのscene2読み込み
Addhtml.attributes.add("scene3", {type: 'asset', assetType:'html', title: 'HTML Scene3'}); // アセットのhtmlのscene3読み込み

globalPc = {}; // グローバルな変数(オブジェクト)
var container;

Addhtml.prototype.initialize = function() { // init
    globalPc.scene = 0; // どのシーンページを開いているか保管

    var htmlNameArr = []; // attributesのデータの名前を配列にします
    var arrIndex = Addhtml.attributes.index;
    for(var i = 0; i < Object.keys(arrIndex).length; i++){ // attributesのデータを取得してfor文で回す
        if(!Object.keys(arrIndex)[i].indexOf("scene")){ // 名前にhtmlが入っているデータをif文
            htmlNameArr.push(Object.keys(arrIndex)[i]); // htmlデータの名前だけ配列化する
        }
    }

    var head = document.getElementsByTagName("head")[0]; // headタグ取得
    var body = document.getElementsByTagName("body")[0]; // bodyタグ取得

    var wrapper = document.createElement("div"); // DOMを囲う要素を作成
    wrapper.className = "wrapper"; // クラス名指定
    body.appendChild(wrapper); // bodyタグの最後に要素を追加

    container = document.createElement("main"); // DOMを囲う要素を作成
    container.className = "container"; // クラス名指定
    wrapper.appendChild(container); // bodyタグの最後に要素を追加

    var style = document.createElement("style"); // cssのstyleタグ

    wrapper.insertAdjacentHTML("afterbegin", this.header.resource); // attrで追加したヘッダーを追加

    style.append(this.css._resources[0]);
    head.appendChild(style); // headの最後にstyleを追加

    for(var i = 0; i < htmlNameArr.length; i++){ // htmlのアセットを配列管理するためにfor文でattrの名前の配列を回す

        container.insertAdjacentHTML("beforeend", this[htmlNameArr[i]].resource); // アセットから取得したhtmlを追加

    }
};

Addhtml.prototype.update = function(dt) { // update
    if(Number(globalPc.scene) > 0) { // いずれかのsceneが選択されている場合
        if(!container.classList.contains("is-open")){ // 追加したアセットのhtmlにis-openのclassが追加されていない場合(どのhtmlも表示されていない)
            container.classList.add("is-open"); // is-openのclass名をcontainerに追加
            var sectionElements = document.getElementsByClassName("section"); // sectionを取得
            for(var i = 0; i < sectionElements.length; i++){ // sectionをループ処理
                if(sectionElements[i].classList.contains("is-current")){ // sectionにis-currentのclass名を持つかif処理
                    sectionElements[i].classList.remove("is-current"); // is-currentを削除
                }
            }
            sectionElements[globalPc.scene-1].classList.add("is-current"); // 選択されたsceneにis-currentのclass名を追加
            document.getElementsByClassName("section_close")[globalPc.scene-1].addEventListener("click", btnClose, false); // 選択されたsceneの閉じるボタンにイベントをセット
        }
    }

};

function btnClose(e){ // 閉じるボタンが押されたら発火
    e.preventDefault();
    globalPc.scene = 0; // 閉じるボタンなので選択されたsceneはnullにするので、0を代入
    container.classList.remove("is-open"); // containerのis-openのclass名を削除
    var sectionElements = document.getElementsByClassName("section"); // sectionを取得
    for(var i = 0; i < sectionElements.length; i++){ // sectionをループ処理
        if(sectionElements[i].classList.contains("is-current")){ // sectionにis-currentのclass名を持つかif処理
            sectionElements[i].classList.remove("is-current"); // is-currentを削除
        }
    }
}

次はここまで作成したSceneをPublishして公開します。

次回

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

チュートリアル - 3D空間を使った簡単webサイト 3/5

前回

3Dオブジェクトでマウスのイベントを起こす

3Dオブジェクトとマウスカーソルとの関係性を作ります。

ヒエラルキーからchara->Modelを選択。
ADD COMPONENTからScriptを選択し、hotspotのscriptを登録します。
このModelを一つずつ選択する動作は面倒ですが、複数選択して設定を行うこともできます。

スクリーンショット 2019-07-31 12.05.20.png

hotspotの中身をコードエディターで開きます。

配置された3Dオブジェクトとマウスカーソルとの関係を持たせるにはレイキャストと呼ばれるものを使用します。
レイキャストとは、特定の地点から特定の方向へ直線の線を引き、その線上のどこかでぶつかるものがあるか検知するものです。
ここでのレイキャストは、「マウスがクリックされた時、カメラから見て、3Dオブジェクトとそのマウスカーソルが重なっているか」を取得するような処理を作ります。

ここから以下の処理を追加していきます

  • 3Dオブジェクトにレイキャストの線が当たるヒットエリアを作成
  • 使用するカメラのEntityとヒットエリアの大きさをAttributesでEditorから設定
  • ヒットエリアにマウスホバーしてイベントが取れる

以下のコードをhotspot.jsに書き換えます。

var Hotspot = pc.createScript('hotspot');

Hotspot.attributes.add("cameraEntity", {type: "entity", title: "Camera Entity"}); // カメラのEntityを取得
Hotspot.attributes.add("radius", {type: "number", title: "Radius"}); // Entityのヒットエリアの範囲を指定

Hotspot.prototype.initialize = function() { // init
    this.hitArea = new pc.BoundingSphere(this.entity.getPosition(), this.radius); // ヒットエリアを作成。BoundingSphereがentityの境界エリアを作成(Photoshopでいうバウンディングボックス的な)
    this.ray = new pc.Ray(); // cameraからentityへ直進する線のデータを作成。(Rayは光線の意でstart pointからentityまでの距離を測ったりすることが可能)

    this.directionToCamera = new pc.Vec3(); // Vector座標の型を取得

    this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseHover, this); // マウスカーソルがホバーした時

};

Hotspot.prototype.onMouseHover = function(screenPosition) { // マウスホバー時
    this.cameraEntity.camera.screenToWorld(screenPosition.x, screenPosition.y, this.cameraEntity.camera.farClip, this.ray.direction); // ポジションを2Dスクリーンから3D空間へ変換
    this.ray.origin.copy(this.cameraEntity.getPosition()); // レイのオリジナルのポジションにカメラのポジションをコピー
    this.ray.direction.sub(this.ray.origin).normalize(); // 3次元ベクトルを他の場所から減算し、単位ベクトルに変換

    if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合
        console.log("hover");
    }
};

新しくattributesというのが言葉がありますが、これは属性設定ができ、PlayCanvasのEditorからデータを変更することができるようになります。
ここではカメラのEntityの選択と、対象の3Dオブジェクトのヒットエリアの大きさを指定できます。

Hotspot.attributes.add("cameraEntity", {type: "entity", title: "Camera Entity"}); // カメラのEntityを取得
Hotspot.attributes.add("radius", {type: "number", title: "Radius"}); // Entityのヒットエリアの範囲を指定

以下画像のように入力エリアが表示されていない場合は、Editアイコン横のParseで更新され表示されます。

スクリーンショット 2019-06-20 12.00.58.png

initializeではヒットエリア、レイキャストのRayの設定、マウス移動のイベントを取得しています。

Hotspot.prototype.initialize = function() {
    this.hitArea = new pc.BoundingSphere(this.entity.getPosition(), this.radius); // ヒットエリアを作成。BoundingSphereがentityの境界エリアを作成(Photoshopでいうバウンディングボックス的な)
    this.ray = new pc.Ray(); // 直進する線のデータを作成。(Rayは光線の意でstart pointからentityまでの距離を測ったりすることが可能)

    this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseHover, this); // マウスカーソルがホバーした時
};

マウス移動のイベントで3Dオブジェクトとカメラから見てホバーしているかonMouseHoverで取得しています。

Hotspot.prototype.onMouseHover = function(screenPosition) { // マウスホバー時
    this.cameraEntity.camera.screenToWorld(screenPosition.x, screenPosition.y, this.cameraEntity.camera.farClip, this.ray.direction); // ポジションを2Dスクリーンから3D空間へ変換
    this.ray.origin.copy(this.cameraEntity.getPosition()); // レイのオリジナルのポジションにカメラのポジションをコピー
    this.ray.direction.sub(this.ray.origin).normalize(); // 3次元ベクトルを他の場所から減算し、単位ベクトルに変換

    if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合
        console.log("hover");
    }
};

マウス移動のイベントで3Dオブジェクトとカメラから見てホバーしているかonMouseHoverで取得しています。

これでマウスオーバー(ホバー)のイベントを取得できました。
次はクリックのイベントです。

initializeに以下を追加

    this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this); // クリックが押された時

クリックイベントの左クリックを取得

Hotspot.prototype.onMouseDown = function(event) { // クリックが押されている時
    if (event.button == pc.MOUSEBUTTON_LEFT) { // 左クリックが押された時
        this.doRayCast(event); // レイキャストを呼ぶ
    }
};

クリックしたらレイキャストを走らせる

Hotspot.prototype.doRayCast = function (screenPosition) { // レイキャスト処理(ある地点から特定方向に直線で線を引いて、その線上で物体があるか検知する処理)
    if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合
        console.log("click!!");
    }
};

Launch画面をリロードしてコンソールから確認ができます。

ここまでのコードは以下になります。(hotspot.js)

var Hotspot = pc.createScript('hotspot');

// canvasのclickやhoverなどの処理を行う

Hotspot.attributes.add("cameraEntity", {type: "entity", title: "Camera Entity"}); // カメラのentityを取得
Hotspot.attributes.add("radius", {type: "number", title: "Radius"}); // entityのヒットエリアの範囲を指定

Hotspot.prototype.initialize = function() { // init
    this.hitArea = new pc.BoundingSphere(this.entity.getPosition(), this.radius); // ヒットエリアを作成。BoundingSphereがentityの境界エリアを作成(Photoshopでいうバウンディングボックス的な)
    this.ray = new pc.Ray(); // cameraからentityへ直進する線のデータを作成。(Rayは光線の意でstart pointからentityまでの距離を測ったりすることが可能)

    this.directionToCamera = new pc.Vec3(); // Vector座標の型を取得

    this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseHover, this); // マウスカーソルがホバーした時
    this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this); // クリックが押された時
};


Hotspot.prototype.doRayCast = function (screenPosition) { // レイキャスト処理(ある地点から特定方向に直線で線を引いて、その線上で物体があるか検知する処理)
    if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合
        console.log("click!!");
    }
};


Hotspot.prototype.onMouseHover = function(screenPosition) { // マウスホバー時
    this.cameraEntity.camera.screenToWorld(screenPosition.x, screenPosition.y, this.cameraEntity.camera.farClip, this.ray.direction); // ポジションを2Dスクリーンから3D空間へ変換
    this.ray.origin.copy(this.cameraEntity.getPosition()); // レイのオリジナルのポジションにカメラのポジションをコピー
    this.ray.direction.sub(this.ray.origin).normalize(); // 3次元ベクトルを他の場所から減算し、単位ベクトルに変換

    if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合
        console.log("hover");
    }
};

Hotspot.prototype.onMouseDown = function(event) { // クリックが押されている時
    if (event.button == pc.MOUSEBUTTON_LEFT) { // 左クリックが押された時
        this.doRayCast(event); // レイキャストを呼ぶ
    }
};

次は3Dオブジェクトに応じてHTMLのページを表示させるようにします。

次回

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

チュートリアル - 3D空間を使った簡単webサイト 2/5

前回

カメラを移動させる

まずはドラッグして3D空間を移動させます。

事前に用意している scripts ディレクトリ内の cameraMove.js を開きます。

ファイル内のコードはファイル作成時にデフォルトで書かれているもので、上から以下になります。

  • initialize : 初回読み込み
  • update : 毎フレームごと
  • swap : scriptファイル更新時(hot reloading)

マウスイベントを取得

PlayCanvasにはエンジンのRootのネームスペースがあります。
https://developer.playcanvas.com/en/api/pc.html

pc.MOUSEBUTTON_LEFTなどからイベントを取得し判別することができます。
以下のコードを追加します。

CameraMove.prototype.onMouseDown = function (event) { // クリックダウンしたら
    if (event.button === pc.MOUSEBUTTON_LEFT) {} // 左クリックのとき
    if (event.button === pc.MOUSEBUTTON_MIDDLE) {} // 中クリック
    if (event.button === pc.MOUSEBUTTON_RIGHT) {} // 右クリック
};

CameraMove.prototype.onMouseUp = function (event) { // クリックアップしたら
    if (event.button === pc.MOUSEBUTTON_LEFT) {} // 左クリック
    if (event.button === pc.MOUSEBUTTON_MIDDLE) {} // 中クリック
    if (event.button === pc.MOUSEBUTTON_RIGHT) {} // 右クリック
};

CameraMove.prototype.onMouseMove = function (event) { // マウスカーソルが動いたら
  console.log(event);
};

上記で追加した関数を使いたいので、initializeでイベントを取得できるようにします。

this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this); // クリックが押された時
this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this); // クリックを押している状態から離れる時
this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this); // マウスカーソルが動いている時

ここのon()はjQueryのon()と同じようなもので、イベント処理を行い、関数を呼び出します。

このままではこのscriptは呼び出されません。対象となるEntityに登録する必要があります。
ヒエラルキーからCameraを選択し、インスペクターのADD COMPONENTからScriptを選択。

スクリーンショット 2019-06-19 15.37.45.png

SCRIPTSの設定のADD SCRIPTからcameraMoveを選択します。
cameraMoveが設定されたらOKです。
ちなみに、名前の横のEditをクリックするとコードエディターを開きます。
そのさらに横のParseをクリックすると設定した scriptを再読み込みします。

スクリーンショット 2019-06-19 15.41.26.png

Lauch画面をリロードしConsoleを確認するとonMouseMoveのconsole.logがマウスが動くたびに呼び出されています。

これをクリックしている間だけに変更し、ドラッグしたら呼び出すようにします。
initializeにクリックしているか判定するフラッグを用意します。

this.f_click = false;

これに合わせて、onMouseDownとonMouseUpに適当なものを追加します。
追加すると以下のようなコードになると思います。

var CameraMove = pc.createScript('cameraMove');

// カメラ移動などの処理

CameraMove.prototype.initialize = function() {
    this.f_click = false;

    this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this); // クリックが押された時
    this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this); // クリックを押している状態から離れる時
    this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this); // マウスカーソルが動いている時
};

CameraMove.prototype.update = function(dt) {};

CameraMove.prototype.onMouseDown = function (event) { // クリックダウンしたら
    if (event.button === pc.MOUSEBUTTON_LEFT) { this.f_click = true; } // 左クリックのとき
    if (event.button === pc.MOUSEBUTTON_MIDDLE) {} // 中クリック
    if (event.button === pc.MOUSEBUTTON_RIGHT) {} // 右クリック
};

CameraMove.prototype.onMouseUp = function (event) { // クリックアップしたら
    if (event.button === pc.MOUSEBUTTON_LEFT) { this.f_click = false; } // 左クリック
    if (event.button === pc.MOUSEBUTTON_MIDDLE) {} // 中クリック
    if (event.button === pc.MOUSEBUTTON_RIGHT) {} // 右クリック
};

CameraMove.prototype.onMouseMove = function (event) { // マウスカーソルが動いたら
  if(this.f_click){
    console.log(event);
  }
};

これでクリックしドラッグした時だけconsole.logが呼ばれるようになりました。
ここで呼ばれているデータからカメラも移動するようにします。

initializeで以下を設定。
カメラのEntityの情報を取得しています。ここではカメラのPositionを取得します。
https://developer.playcanvas.com/en/api/pc.Entity.html

    this.cameraEntity = this.entity; // このjsを受けているEntityを取得(カメラであることを前提に)
    this.pos = this.cameraEntity.getPosition(); // カメラの座標を取得 

onMouseMoveの中身を以下に差し替えます。
クリックされていない場合はreturnするように変更。
eventからベクトル量を取得します。取得したベクトル量からカメラのポジションを再設定します。

    if(!this.f_click) return; // クリックが押されている時
    var posDX = event.dx/500; // x座標の変化量
    var posDY = event.dy/500; // y座標の変化量

    var posX = this.pos.x - posDX; // ドラッグ量からカメラの座標を計算
    var posZ = this.pos.z - posDY; // ドラッグ量からカメラの座標を計算

    this.cameraEntity.setPosition(posX,this.pos.y,posZ); // カメラの座標をセット

これでカメラの移動ができるようになりました。

最終的にコードは以下のようになります。

var CameraMove = pc.createScript('cameraMove');

// カメラ移動などの処理

CameraMove.prototype.initialize = function() {
    this.f_click = false;

    this.cameraEntity = this.entity; // このjsを受けているEntityを取得(カメラであることを前提に)
    this.pos = this.cameraEntity.getPosition(); // カメラの座標を取得 

    this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this); // クリックが押された時
    this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this); // クリックを押している状態から離れる時
    this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this); // マウスカーソルが動いている時
};

CameraMove.prototype.update = function(dt) {};

CameraMove.prototype.onMouseDown = function (event) { // クリックダウンしたら
    if (event.button === pc.MOUSEBUTTON_LEFT) { this.f_click = true; } // 左クリックのとき
    if (event.button === pc.MOUSEBUTTON_MIDDLE) {} // 中クリック
    if (event.button === pc.MOUSEBUTTON_RIGHT) {} // 右クリック
};

CameraMove.prototype.onMouseUp = function (event) { // クリックアップしたら
    if (event.button === pc.MOUSEBUTTON_LEFT) { this.f_click = false; } // 左クリック
    if (event.button === pc.MOUSEBUTTON_MIDDLE) {} // 中クリック
    if (event.button === pc.MOUSEBUTTON_RIGHT) {} // 右クリック
};

CameraMove.prototype.onMouseMove = function (event) { // マウスカーソルが動いたら
    if(!this.f_click) return; // クリックが押されている時
    var posDX = event.dx/500; // deltaX
    var posDY = event.dy/500; // deltaY

    var posX = this.pos.x - posDX; // ドラッグ量からカメラの座標を計算
    var posZ = this.pos.z - posDY; // ドラッグ量からカメラの座標を計算

    this.cameraEntity.setPosition(posX,this.pos.y,posZ); // カメラの座標をセット
};

次は3Dオブジェクトをクリックしてイベントを取得する処理です。

次回

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

チュートリアル - 3D空間を使った簡単webサイト 1/5

※このチュートリアルはPlayCanvas運営事務局で使用しているハンズオンの資料です。

PlayCanvasでWebサイトを作る

ゲームエンジンでwebサイトを作る?

PlayCanvasはWebGL/HTML5ゲームエンジンであり、ゲームだけでなくwebへの展開もできます。
そこでPlayCanvasを使ってどんなwebサイトが作れるのか、実際に作っていきたいと思います。

このチュートリアルでは、PlayCanvasを使って3D空間のコンテンツを作成し、その上にhtmlの要素を配置して、LPページ(ランディングページ)やキャンペーンサイトのようなwebサイトを作成していきたいと思います。

できるページは以下のようなページができます。
https://playcanv.as/p/uQN2hNcm/

3Dオブジェクトをクリックするとそれに応じたhtmlが表示されるサイト。
ドラッグするとカメラの位置も変わり、3Dオブジェクトを好きな位置に配置して探す、といったこともできます。

今回は上記のものを作成するべく、チュートリアルを始めましょう。


Projectをforkする

チュートリアル用に準備したProjectがあるので、こちらをforkしてチュートリアルを始めます。

以下のProjectからforkします。
https://playcanvas.com/project/623766/

スクリーンショット 2019-06-17 17.50.27.png

forkしたProjectのページに飛ぶので、forkしたプロジェクトのEDITORボタンからエディット画面に飛びます。

スクリーンショット 2019-06-17 18.00.17.png

エディット画面のGUIの説明

まずSCENESの画面からeditするsceneを選びます。
Untitledしかないのでこちらを選択。(新規にプロジェクトを作る際もUntitledのsceneがあります)

スクリーンショット 2019-06-17 18.03.12.png

Untitledをクリックすると以下の画面になります。

スクリーンショット 2019-06-17 18.14.26.png

上の画像を基に、GUIを簡単に説明していきます。

  • メニュー(MENU)
    Photoshopなどでいうツールバーとメニューバーが合わさっている感じ。
    プロジェクトの設定やシーンの設定などもここで操作できます。
  • ヒエラルキー(HIERARCHY)
    シーン内に存在するオブジェクトの一覧が表示されます。
    このシーンではどんなentityが使われているのかなどここで管理。
    まとめてグループ化や名前の変更などもできる。
  • シーン(SCENE)
    3D空間が表示され、自由な位置・角度から眺めることができます。
    3DコンテンツやカメラのPositionやRotateなどを弄ることができます。
  • アセット(ASSETS)
    プロジェクトhtmlやcss、imgといったファイルや3Dモデルのデータなどもここで表示され、ここに入れて使われるデータたちは合わせてアセットと呼ばれています。
    ディレクトリで分けたり名前を変更することもできます。各ファイルはアップロード時にIDが付与され管理されるため、同じ名前のファイル名をアップロードしても上書きアップロードにはならないことがあるので注意。
  • インスペクター(INSPECTOR)
    ヒエラルキーやシーンで選択中のオブジェクトの設定を編集できます。
    3Dモデルのメッシュや衝突判定、物理制御に関するパラメータもここで定義することが可能。

コードエディター

基本的なGUIはこの5つで、他にコードエディターがあります。

コードエディターはメニューの下の方にあります。

スクリーンショット 2019-06-17 18.34.30.png

スクリーンショット 2019-06-17 18.33.35.png

コードエディターは普段使用されているであろうエディターと基本同じです。

JavaScriptのsnippetやコードのhighlightなども基本付いています。

基本ここでコードを書いて反映していきます。

Lauch実行

右上の再生ボタン的な矢印のボタンをクリックすると、Launch実行されます。

スクリーンショット 2019-06-19 13.35.05.png

Sceneの現状をLaunch実行してくれるため、開発しながら確認ができます。

Launch画面を開いたままにしておくと、Editor画面とライブリンクが持続した状態になり、Editor画面でPositionmの数値などを変更するとリアルタイムでLaunch画面でもプレビューされます。

スクリーンショット 2019-06-19 13.41.03.png

プロジェクトを確認できたところで、次はカメラ移動の処理をつけていきます。

次回

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

GASでGoogleドライブ監視システムを作成

ZOZOテクノロジーズ #4 Advent Calendar 2019の記事です。
昨日は @mikankitten さんの「NginxにgRPCとHTTP/1.1の共存の設定をする」でした。

本記事では、業務効率化の一環で作成したツールについて書いていきます。

はじめに

自分が関わっているプロジェクトでは、ファイルのやりとり等をGoogleドライブ経由で行なっています。
特にアパレル関係の資料のやりとりは量、頻度が多く、ファイルの更新やアップロードをうまく共有しきれていない場面が発生していました。

そんな中、以下のようなドライブ監視ツールがあればという要望を受け、今回のツールを作成しました。

  • 更新されたファイルがあればSlackに通知がきてほしい
  • 監視対象のフォルダを使う側で編集出来る様にしてほしい

スプレッドシートの内容

今回は以下の様に2行目から下に向かって記載していきます。
フォルダIDは200行目まで読み込むようにしていますが、Webhook URLは2行目のみの読み込みとなります。

1.png

GoogleドライブのフォルダIDの取得方法につきましては以下のページの中盤辺りで説明されています。

【GAS/非エンジニア向け】GoogleDriveの特定のファイルデータをSpread Sheet書き出すプログラムまとめ

Webhook URLは以下のページを参考に取得してみてください。

Slack でのIncoming Webhook の利用

GASソースコード

ソースコード自体は上記のスプレッドシートの中に記載しています。

2.png

「ツール」の「スクリプトエディタ」を選択します。

3.png

表示されたエディター上に以下のソースコードを入力。

function myFunction() {

  // フォルダIDの取得
  var folderIdList = getFolderId()

  var text = ""

  for(var i = 0; i < folderIdList.length; i++)
  {
    var latestUpdate = getLatestUpdate(folderIdList[i])

    for(var j = 0; j < latestUpdate.length; j++)
    {
      if(i == 0 && j == 0)
      {
        // 改行しない
      }
      else
      {
        text += "\n\n"
      }

      text += latestUpdate[j] + "\n"
    }
  }

  // 何もなければ終了
  if(text == "")
  {
    return
  }

  var payload = {
    "text" : "<!here>" + "\n" + "Googleドライブの更新がありました。\n以下ご確認お願いします。\n```" + text + "```", // メッセージの本文
    "channel" : "slackのチャンネル名",
    "icon_url" : "アイコン画像のURL",
    "username" : "アカウントの名前",
  }

  postSlack(payload);


}

// フォルダIDの取得処理
function getFolderId()
{
  var sheet=SpreadsheetApp.getActiveSheet()
  var values=sheet.getRange("A2:A200").getValues()
  var list = []

  for(var i = 0; i < values.length; i++){

    if(values[i] == ""){
      break;
    }
    list.push(values[i])
  }

  return list
}

// webhook URLの取得
function getWebhook()
{
  var sheet=SpreadsheetApp.getActiveSheet()
  var url=sheet.getRange("B2").getValues()

  return url
}



// 最新の更新を取得
function getLatestUpdate(folderId)
{
  // フォルダ情報を取得
  var folder = DriveApp.getFolderById(folderId);
  // ファイル一覧を取得
  var files = folder.getFiles()
  var list = []
  var count = 0

  var fileNames = '';
  while (files.hasNext()) {
    var file = files.next();
    var last = file.getLastUpdated()

    var d = new Date()
    // 30分以内に更新されたファイルを検索
    if(last.getTime() >= (d.getTime() - 1800000))
    {
      list.push("【FileName】 : " + file.getName() + "【FilePath】 : " + file.getUrl())
    }
    count++
  }

  return list
}

function postSlack(payload)
{
  var options = {
    "method" : "POST",
    "payload" : JSON.stringify(payload)
  }

  // webhook URLの取得
  var url = getWebhook();
  var response = UrlFetchApp.fetch(url, options);
  var content = response.getContentText("UTF-8");

}

トリガーの設定方法

プログラムが起動するタイミングを設定します。

4.png

スクリプトエディタの「編集」から「現在のプロジェクトのトリガー」を選択します。

6.png

「トリガーを追加」を選択。

7.png

今回は上記の様に設定し保存します。
これでプログラムが時間ごとに動き出します。

実行結果

5.png

こんな感じで通知が送られてきます。
実行間隔やフォルダの読み込み数等はお好みでカスタムしていただければと思います。

おわりに

今回初めてGASを使ってみたのですが、身近な業務効率化ツールをさらっと作るにはおすすめです。
マクロに慣れている方にはJSは使いづらいかもしれませんが、ネット上で記事が多くあるので機会があれば使ってみてください!

ZOZOテクノロジーズ #4 Advent Calendar 2019 明日は @tkani さんによる投稿です。

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

HTMLでJSファイルが読み込めなかった時に行った対処<Mac>

ストップウォッチをブラウザで動かすプログラム(stopwatch.js)を組んだ時にhtmlがjsファイルを読み込んでくれないエラーが発生しました。

まず、html上にて動作が行われなかったのでGoogle Chromeにてディベロッパーツールを起動。

その際に出たエラー表示は

'Uncaught SyntaxError: Invalid or unexpected token'

このエラー表示を調べてみると
主に文法上の間違いが原因らしいので文法を一つ一つ確認。ですが、文法の間違いは無かったです。

あれこれ調べていると読み込んだjsファイルの結果が文字化けしていることに気づいたので文字コードが原因かなと思いましたが
html:index.html <meta charset="UTF-8">
でhtmlのheadに書いているので理由がわからず...

他にも「javascript 文字化け」や「jsファイル 読み込めない」などで検索しましたがそれっぽい記事が出てきませんでした。

困ったな〜と思ってなんとなく画面を見ていたら

読み込もうとしたjsファイルだけ他のファイルと形式がなんだか違うことに気づきました。

他のjsファイルは「PlainTextFile」とFinderでは表示されていましたが読み込めなかったファイルだけ「エイリアス」と表示されていました。

なんだこれは、と思いエイリアスとはなんぞやと調べたところ

いわゆるショートカットらしいです。複製とは違うみたいで。
(このサイトを参考にしました! エイリアスってなに?)

確かに、複製では無いならエラーが起きてしまうわ!

早速そのjsファイルのオリジナルを移動させました。
(ちなみにオリジナルはDocumentsにありました。)

そしたらなんと、jsファイルが読み込めて無事ストップウォッチが起動!

なんという初歩的なミスで時間を食っていたんだろう(笑)

これから気をつけたいと思います。

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

年末まで毎日webサイトを作り続ける大学生 〜39日目 JavaScriptで要素の位置を取得して、スクロールに合わせて�動かす〜

はじめに

こんにちは!@70days_jsです。

JavaScriptで要素の位置を取得して、スクロールに合わせて動かしてみました。

今日は39日目。(2019/11/26)
よろしくお願いします。

サイトURL

https://sin2cos21.github.io/day39.html

やったこと

JavaScriptで要素の位置を取得して、スクロールに合わせて動かしました。↓
test2.gif

要素をスクロールに合わせてバラバラに動かすパララックスを作ってみようと思い、今日はその原理を確認しました。
ポイントはpositionをabsolute、fixed、relativeのどれかにすること、要素の現在位置、スクロール量を取得することです。

html全体↓

<body>
    <div id="box1" class="box"></div>
    <div id="box2" class="box"></div>
    <div id="box3" class="box"></div>
    <div id="display">
        スクロール量: <span id="data1"></span><br>
        box1 Top: <span id="box1-top"></span><br>
        box2 Top: <span id="box2-top"></span> box2 Left: <span id="box2-left"></span><br>
        box3 Top: <span id="box3-top"></span>
    </div>
</body>

今回は要素をfixedにしました。absoluteでもrelativeでもokです。

  .box {
    position: fixed;
 }

次はJavaScriptを紹介しますが、ここからが本題です。
まずそれぞれのboxの位置を取得します。↓

// box1のY軸の最初の位置
let box1TopPosition = box1.offsetTop;

// box2のX軸Y軸の最初の位置
let box2TopPosition = box2.offsetTop;
let box2LeftPosition = box2.offsetLeft;

// box3のY軸の最初の位置
let box3TopPosition = box3.offsetTop;

box2だけは横移動も加えたのでleftの値も取得しています。

次にwindowにスクロールで発火するイベントリスナーをつけます。

window.addEventListener('scroll', function () {
    // スクロール量
    let scroll = window.pageYOffset;

    // スクロールに合わせてbox1を移動させる
    let b1Top = box1TopPosition + (scroll / 2) + 'px';
    box1.style.top = b1Top;
    // box1のY軸の現在の位置
    let box1TopPositionNow = box1.offsetTop;
    // htmlにbox1の現在の位置を描画
    data1.innerHTML = scroll;
    box1Top.innerHTML = box1TopPositionNow;


    // box2の現在の位置
    let box2TopPositionNow = box2.offsetTop;
    let box2LeftPositionNow = box2.offsetLeft;
    // スクロールに合わせてbox2を移動させる
    let b2Top = box2TopPosition + (scroll / 3) + 'px';
    let b2Left = box2LeftPosition + (scroll / 4) + 'px';
    box2.style.top = b2Top;
    box2.style.left = b2Left;

    // htmlに値を描画
    box2Top.innerHTML = box2TopPositionNow;
    box2Left.innerHTML = box2LeftPositionNow;

    // box3のY軸の現在の位置
    let box3TopPositionNow = box3.offsetTop;
    // スクロールに合わせてbox3を移動させる
    let b3Top = box3TopPosition + (scroll / 4) + 'px';
    box3.style.top = b3Top;
    // htmlに値を描画
    box3Top.innerHTML = box3TopPositionNow;
})

これでスクロールされると自動でfunctionを実行してくれます。

スクロール量の取得はこれです。↓

let scroll = window.pageYOffset;

boxの移動はこの一行です。↓

let b1Top = box1TopPosition + (scroll / 2) + 'px';

ここで(scroll/2)の/2の部分を変えていけば様々な速度にできます。
今回は、スクロール量を2で割っているので、スクロールに対して半分しか動かないようにしています。

おまけ

  • コンテンツ y軸の位置 取得方法
    • offsetTop・・borderの位置から(marginは無視)
  • コンテンツ 高さ 取得方法

    • clientHeight・・・paddingまで含む
    • offsetHeight・・・borderまで含む
  • スクロール量 Y軸 取得方法

    • window.pageYOffset
    • document.documentElement.scrollTop

感想

調べているとブラウザの互換性がうんたら、返すオブジェクトがうんたらとweb業界の闇を覗いてしまいました。
統一してくれればいいのにと思うのですが、そこらへんは大人の事情があるんでしょうか・・・?
とはいえ、今日はスクロールで要素が動く謎を解明できてよかったです。これもブラウザを作ってくれた人が使いやすいプロパティやメソッドを用意してくれたからですね。

最後までお読みいただきありがとうございます。明日も投稿しますのでよろしくお願い致します。

参考

  1. JavaScriptでスクロール量を取得する方法 (https://lab.syncer.jp/Web/JavaScript/Snippet/1/)

とても分かりやすかったです。ありがとうございます!

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

Vue.js+Blumaでダイアログを表示してみる

目的

JavaScriptのフレームワークであるVue.jsとCSSのフレームワークであるBlumaを使用してダイアログを表示してみる。

image.png

サンプル

Vue.jsにbulmadialog-componentコンポーネントを追加します。
その後、任意のタイミングで親側からコンポーネントのメソッドshowDialogを実行します。
この際、以下のプロパティを持つオブジェクトをパラメータに指定します。

  • title : ダイアログのタイトル
  • contents : ダイアログの内容。HTMLタグは無効
  • html : ダイアログの内容。HTMLのタグは有効
  • buttons : ボタン情報の配列
    • caption : ボタンのタイトル
    • callback : コールバック関数

コンポーネント側

IE11で動くようにしています。

bluma_dialog.js
/**
 * bulma + vue.jsでダイアログを表示します。
 * html: vue.jsの管理下に以下を追加します
 *   <bulmadialog-component ref="dialog"></bulmadialog-component>
 * js:Vue.jsを作成するときにコンポーネントを追加する
 *  components: {
      'bulmadialog-component': BulmaDialog,
    },        
 * js:Vue.jsの親のメソッドにて以下を実行
 *  this.$refs.dialog.showDialog({
        title:'わっふるる',
        //contents:'わっふるぼでぃ0\nsadfasfd',
        html : 'あたえたt<br>awrawtあたえたt<br>',
        buttons : [
          {
            caption : 'はい',
            callback : function () {
              console.log('はい');
            }
          },
          {
            caption : 'いいえ',
            callback : function () {
              console.log('いいえ');
            }
          }
        ]
      });
 */
// eslint-disable-next-line no-unused-vars
const BulmaDialog = {
  /* eslint-disable max-len */
  template: (function() {/*
      <div v-bind:class="{ 'is-active': isShow }" class="modal">
        <div class="modal-background"></div>
          <div class="modal-card">
              <header class="modal-card-head">
                  <p class="modal-card-title">{{data.title}}</p>
              </header>
              <div >
              </div>
              <section v-if="data.html" v-html="data.html" class="modal-card-body"></section>
              <section v-else class="modal-card-body">{{data.contents}}</section>
              <footer class="modal-card-foot"  style="justify-content: flex-end;">
                  <button v-for="btnObj in data.buttons" type="button" class="button" @click="btnObj.callback(); isShow = false;">{{btnObj.caption}}</button>
              </footer>
          </div>
      </div>
      </div>
    */}).toString().match(/\/\*([^]*)\*\//)[1],
  /* eslint-enable */
  data: function() {
    return {
      isShow: false,
      data: {
        title: '',
        body: '',
        html: '',
        buttons: [],
      },
    };
  },
  methods: {
    showDialog: function(data) {
      this.isShow = true;
      this.data.title = data.title;
      this.data.contents = data.contents;
      this.data.html = data.html;
      this.data.buttons = data.buttons;
    },
  },
};

使う側

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Hello Bulma!</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.min.css">
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="/phptest/codeview/js/bluma_dialog.js"></script>
  </head>
  <body>
  <div id="app">
    <bulmadialog-component ref="dialog"></bulmadialog-component>
    <button class="button is-primary" @click="test1">ダイアログA</button>
    <button class="button is-primary" @click="test2">ダイアログB</button>
  </div>
<script>
var app = new Vue({
  el: '#app',
  components: { //Scopedが使える
    'bulmadialog-component': BulmaDialog,
  },
  data: function() {
      return {
      }
  },
  methods: {
    test1 : function () {
      this.$refs.dialog.showDialog({
        title:'確認(TEXT)', 
        contents: "ふんぐるい むぐるうなふ くとぅるう るるいえ うがふなぐる ふたぐん<br>Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn",
        buttons : [
          {
            caption : 'はい',
            callback : function () {
              console.log('test1 はい');
            }
          },
          {
            caption : 'いいえ',
            callback : function () {
              console.log('test2 いいえ');
            }
          }
        ]
      });
    },
    test2 : function () {
      this.$refs.dialog.showDialog({
        title:'確認(HTML)', 
        html: "ふんぐるい むぐるうなふ くとぅるう るるいえ うがふなぐる ふたぐん<br>Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn",
        buttons : [
          {
            caption : 'YES',
            callback : function () {
              console.log('Yes');
            }
          },
          {
            caption : 'No',
            callback : function () {
              console.log('No');
            }
          }
        ]
      });
    }
  }
})
</script>

  </body>
</html>

参考

SafariでもエラーにならないJavascriptのヒアドキュメントの書き方
https://qiita.com/ampersand/items/c6c773ba7ae9115856d0

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

Vue.js+ bulmaでダイアログを表示してみる

目的

JavaScriptのフレームワークであるVue.jsとCSSのフレームワークである bulmaを使用してダイアログを表示してみる。

image.png

サンプル

Vue.jsにbulmadialog-componentコンポーネントを追加します。
その後、任意のタイミングで親側からコンポーネントのメソッドshowDialogを実行します。
この際、以下のプロパティを持つオブジェクトをパラメータに指定します。

  • title : ダイアログのタイトル
  • contents : ダイアログの内容。HTMLタグは無効
  • html : ダイアログの内容。HTMLのタグは有効
  • buttons : ボタン情報の配列
    • caption : ボタンのタイトル
    • callback : コールバック関数

コンポーネント側

IE11で動くようにしています。

bulma_dialog.js
/**
 * bulma + vue.jsでダイアログを表示します。
 * html: vue.jsの管理下に以下を追加します
 *   <bulmadialog-component ref="dialog"></bulmadialog-component>
 * js:Vue.jsを作成するときにコンポーネントを追加する
 *  components: {
      'bulmadialog-component': BulmaDialog,
    },        
 * js:Vue.jsの親のメソッドにて以下を実行
 *  this.$refs.dialog.showDialog({
        title:'わっふるる',
        //contents:'わっふるぼでぃ0\nsadfasfd',
        html : 'あたえたt<br>awrawtあたえたt<br>',
        buttons : [
          {
            caption : 'はい',
            callback : function () {
              console.log('はい');
            }
          },
          {
            caption : 'いいえ',
            callback : function () {
              console.log('いいえ');
            }
          }
        ]
      });
 */
// eslint-disable-next-line no-unused-vars
const BulmaDialog = {
  /* eslint-disable max-len */
  template: (function() {/*
      <div v-bind:class="{ 'is-active': isShow }" class="modal">
        <div class="modal-background"></div>
          <div class="modal-card">
              <header class="modal-card-head">
                  <p class="modal-card-title">{{data.title}}</p>
              </header>
              <div >
              </div>
              <section v-if="data.html" v-html="data.html" class="modal-card-body"></section>
              <section v-else class="modal-card-body">{{data.contents}}</section>
              <footer class="modal-card-foot"  style="justify-content: flex-end;">
                  <button v-for="btnObj in data.buttons" type="button" class="button" @click="btnObj.callback(); isShow = false;">{{btnObj.caption}}</button>
              </footer>
          </div>
      </div>
      </div>
    */}).toString().match(/\/\*([^]*)\*\//)[1],
  /* eslint-enable */
  data: function() {
    return {
      isShow: false,
      data: {
        title: '',
        body: '',
        html: '',
        buttons: [],
      },
    };
  },
  methods: {
    showDialog: function(data) {
      this.isShow = true;
      this.data.title = data.title;
      this.data.contents = data.contents;
      this.data.html = data.html;
      this.data.buttons = data.buttons;
    },
  },
};

使う側

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Hello Bulma!</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.min.css">
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="/phptest/codeview/js/bulma_dialog.js"></script>
  </head>
  <body>
  <div id="app">
    <bulmadialog-component ref="dialog"></bulmadialog-component>
    <button class="button is-primary" @click="test1">ダイアログA</button>
    <button class="button is-primary" @click="test2">ダイアログB</button>
  </div>
<script>
var app = new Vue({
  el: '#app',
  components: { //Scopedが使える
    'bulmadialog-component': BulmaDialog,
  },
  data: function() {
      return {
      }
  },
  methods: {
    test1 : function () {
      this.$refs.dialog.showDialog({
        title:'確認(TEXT)', 
        contents: "ふんぐるい むぐるうなふ くとぅるう るるいえ うがふなぐる ふたぐん<br>Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn",
        buttons : [
          {
            caption : 'はい',
            callback : function () {
              console.log('test1 はい');
            }
          },
          {
            caption : 'いいえ',
            callback : function () {
              console.log('test2 いいえ');
            }
          }
        ]
      });
    },
    test2 : function () {
      this.$refs.dialog.showDialog({
        title:'確認(HTML)', 
        html: "ふんぐるい むぐるうなふ くとぅるう るるいえ うがふなぐる ふたぐん<br>Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn",
        buttons : [
          {
            caption : 'YES',
            callback : function () {
              console.log('Yes');
            }
          },
          {
            caption : 'No',
            callback : function () {
              console.log('No');
            }
          }
        ]
      });
    }
  }
})
</script>

  </body>
</html>

参考

SafariでもエラーにならないJavascriptのヒアドキュメントの書き方
https://qiita.com/ampersand/items/c6c773ba7ae9115856d0

メモ

Bulma自体がIEのサポートを部分的にのみしかしていないので、テンプレートリテラルを使ってIEをあきらめた方が無難かも

Bulma uses autoprefixer to make (most) Flexbox features compatible with earlier browser versions. According to Can I use, Bulma is compatible with recent versions of:

  • Chrome
  • Edge
  • Firefox
  • Opera
  • Safari

Internet Explorer (10+) is only partially supported.

https://github.com/jgthms/bulma

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

FizzBuzz問題やってみた

Qiitaでの初めての投稿となります。

はじめに

面接を受けたとき、
FizzBuzz問題をホワイトボードに書かされたのですが、
私としてはイマイチな結果となってしまった為、
戒めの意味を込めつつ、FizzBuzz問題を復習しようと思いました。
筆者は javascript メインに触っているので javascript で問題を解いていきます。

FizzBuzz問題

1~100までを順に出力して、出力する数字が

3で割り切れる場合は「Fizz
5で割り切れる場合は「Buzz
両者で割り切れる場合は「Fizz Buzz

と数字の代わりに出力する

参考元:Fizz Buzz (Wikipedia)

やってみる

const num = 100;

for (let i = 1; i <= num; i++) {
  if(i % 3 === 0 && i % 5 === 0) {
    console.log('FizzBuzz');
  } else if(i % 3 === 0) {
    console.log('Fizz');
  } else if(i % 5 === 0) {
    console.log('Buzz');
  } else {
    console.log(i);
  }
}

結果

1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz
Fizz 22 23 Fizz Buz 26 Fizz 28 29 FizzBuzz 31 32 Fizz 34 Buzz Fizz 37 38 Fizz Buzz
41 Fizz 43 44 FizzBuzz 46 47 Fizz 49 Buzz Fizz 52 53 Fizz Buzz 56 Fizz 58 59 
FizzBuzz 61 62 Fizz 64 Buzz Fizz 67 68 Fizz Buzz 71 Fizz 73 74 FizzBuzz 76 77 Fizz 
79 Buzz Fizz 82 83 Fizz Buzz 86 Fizz 88 89 FizzBuzz 91 92 Fizz 94 Buzz Fizz 97 98 
Fizz Buzz

ルールの追加①

console.log を一度だけしか使わない。

const num = 100;

for (let i = 1; i <= num; i++) {
  let printWard = i;
  if(i % 3 === 0 && i % 5 === 0) {
    printWard = 'FizzBuzz';
  } else if(i % 3 === 0) {
    printWard = 'Fizz';
  } else if(i % 5 === 0) {
    printWard = 'Buzz';
  } 
  console.log(printWard);
}

コレはまだ簡単だった

ルールの追加②

if文を三項演算子で記述

const num = 100;

for (let i = 1; i <= num; i++) {
  console.log(i % 3 === 0 ? (i % 5 === 0 ? 'FizzBuzz' : 'Fizz') : (i % 5 === 0 ? 'Buzz' : i));
}

直ぐには出来なかったのですが
時間をかけつつ、なんとか出来ました。

ルールの追加②修正

const num = 100;

for (let i = 0; ++i <= num;) {
  console.log(
    (i % 3 === 0 ? '' : 'Fizz'
    + i % 5 === 0 ? '' : 'Buzz')
    || i);
}

シンプルに短く掛けた気がする。
個人的にはとても満足

最後

僕みたいに、
プログラミング問題は知っているが、やったことが無いって方はいるのではないでしょうか?
いざやってみると同じ問題でも、
テクニカルに書いたり、分かりやすく書いてみたりと
様々な解き方ができますし、
実際、面接の場で書いてたのにも関わらず楽しくなって書いてしまってました。

あと、ホワイトボードにプログラム書いたのはいいのですが、
手がめっちゃ汚れるので、ウェットティッシュください。

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

【React】クリックされたボタンのフォーカスを外す

Reactにおいて、ボタンがクリックされた後もフォーカスされるのが嫌だったので外したかった。

const Button: React.FC = () => {
  const onClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
    e.currentTarget.blur();
  }
  return (
    <button onClick={onClick}>ボタン</button>
  )
}

これでいけた。

e.target

これにはイベントが 発火した DOM要素が入っていて、

e.currentTarget

これにはイベントが 発火された DOM要素が入ってるらしい。
(参考:Reactビギナーズガイドをtypescriptで勉強し直してわかったこと③【event.targetの作法】)

blurってのはfocusの対義語らしい。

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

【記事散乱から卒業】目的別に記事をリスト化できるサービスを開発してみた

「散らばった情報を自分の切り口でリスト化して、みんなにシェアできるサービス」 をリリースしました!

毎日新しい情報が、莫大に発信されていて、自分の求めている情報はすぐに埋もれてしまいます。また、自分と同じような情報を求めている人も他にいるのに、その人たちも、良質な情報を求めて、同じように莫大な情報を掻き分けて、宝探しをしています。

このような作業の重複を改善するために、Ownlistというサービスを作りました。

image.png

(このサービスの特徴となる機能を作る上で参考にした情報を添付しておいたので、興味のある方は、ご覧ください。)

こんな人に使って欲しい

  • エラー対応で使った記事を再び探すことがある人
  • 勉強の参考にした記事をストックしておきたい人
  • 「後で読む」とブックマークしたが、それらが散乱してしまっている人
  • インプットに使った記事を他の人にシェアしたい人

開発言語

  • フロント
    • JavaScript(Vue.js)
  • バックエンド
    • Python(Flask)

特徴①時系列でまとめたリストが表示される

作成したリストは時系列順に並びます。

1ヶ月前、1年前に「勉強のため」「エラー解決のため」などにまとめたリストが見返しやすい状態で表示されます。
自分の振り返りに使っても良いでしょうし、後輩や同僚に過去の参考になった記事を目的ごとにまとめてシェアできます。

04b0f7dc17873de63f206e5032739759.gif

時系列で表示するために参考にした記事リスト

特徴②まとめる目的をタイトルに付ける必要がある

「後で読むからとりあえずブックマーク!」「良さそうな記事だからとりあえずslackでシェア!」など、なんとなく良さそうだからとか、何かのランキング上位の記事だから、という理由で、ストックしたりシェアしたりする人もいると思います。

ただ、それらをシェアされた側の人や後から見返した自分は、 なぜ、これらが重要だと感じたのか がわからなくなってしまうことは、少なくありません。

なので、ブックマークする前、シェアする前に、 どんな目的でこの記事が役立つのか を明記してまとめておくことで、読むべき理由を知らせることができ、より有益な情報になります。

image.png

Vue.jsを勉強するために参考にしたページ

まとめ

流れてきた記事をなんとなくブックマークしてしまっていた人、過去見た記事と同じ内容の記事を求めてネットの世界で迷子になっていた人、勉強の記録をつていなかった人などの、情報集積をないがしろにしてきた方に 自分のため仲間のため に使っていただきたいサービスです。

ownlistはこちら

image.png

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

JavaScriptで連想配列をつくる方法

連想配列

連想配列は普通の配列と違い、数字だけでなく文字列も添字にして使うことができます。連想配列の添字は、keyと言います。
JavaScriptでは、Mapオブジェクトを提供しているので、それを使います。

const myMap = new Map();

let keyString = "文字列",
    keyObj = {},
    keyFunc = function() {};

// 値を設定(追加)する
myMap.set(keyString, "'文字列' と関連付けられた値");
myMap.set(keyObj, "keyObj と関連付けられた値");
myMap.set(keyFunc, "keyFunc と関連付けられた値");

myMap.size; // 3

// 値を取得する(取り出す)
myMap.get(keyString);    // "'文字列' と関連付けられた値"
myMap.get(keyObj);       // "keyObj と関連付けられた値"
myMap.get(keyFunc);      // "keyFunc と関連付けられた値"

また、for...of...ループを使用して反復処理を行うこともできます。

const myMap = new Map();
myMap.set(0, 'zero');
myMap.set(1, 'one');
for (let [key, value] of myMap) {
  console.log(key + ' = ' + value);
}
// 0 = zero
// 1 = one

また、Array.fromを使えば、連想配列を普通の配列にすることもできます。
ただし、配列の中に配列があるようになります。

const myMap = new Map();
myMap.set(0, 'zero');
myMap.set(1, 'one');
let mapArray = Array.from(myMap);
console.log(mapArray);
//[['0', 'Zero'], ['1', 'one']]

console.log(mapArray[0]);
//[ 0, 'zero' ]

//キーを取り出す。
let mapKey = Array.from(myMap.keys());
console.log(mapKey);
// ['0', '1'];

//値を取り出す。
let mapValue = Array.from(myMap.values());
console.log(mapValue);
// ['Zero', 'one'];

参考・引用:MDN Mapの説明

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

JavaScript: ブラウザを開いているのがPCかモバイルかを切り分ける ー とくにタッチ操作に絡む場合

01 User-Agentを使う

ブラウザを開いているのがWindows PCやMacなのか、モバイル端末なのかはwindow.navigator.userAgentで調べられます。User-Agentはつぎのような構文の文字列です。

<product> / <product-version> <comment>

webブラウザの一般的な書式はつぎのとおりで、platformがオペレーティングシステム(OS)を示します。PCあるいはMacの場合に含まれる文字列は、それぞれWindowsMacintoshです。Linuxの場合はモバイルもあり得ますので、もう少し細かく調べなければなりません。本項では省くことにします。

Mozilla/<version> (<system-information>) <platform> (<platform-details>) <extensions>

確かめ方としては、navigator.userAgentの戻り値にplatformの文字列が含まれるか調べればよいでしょう。

const agent = navigator.userAgent.toLowerCase();
if (agent.indexOf('windows') > -1) {
    console.log('Windows');
} else if (agent.indexOf('macintosh') > -1) {
    console.log('Mac');
} else {
    console.log('mobile');
}

もっとも、MDNのリファレンスには、つぎのような脅迫めいた「注記」が加えられています。

ユーザエージェントを表す文字列を検出することに基づいたブラウザ識別は、信頼できないものであり、また、推奨されません。なぜなら、ユーザエージェントを表す文字列は、ユーザによって変更することができるからです。

02 判定に同時タッチコンタクト数を加える

あえてユーザエージェントを書き替えるユーザーが多いとも思えません。けれど、保険をかけるに越したことはないでしよう。そこで使ったのが、Navigator.maxTouchPointsです。タッチパネルがなければ、数値0が戻り値です。タッチパネルが備わっていないLinuxデスクトップも拾えます。

つぎのようなサンプルコードを書いてみました。併せて、試していただけるよう、CodePenにも掲げます(サンプル001)。

const info = getDeviceInfo();
const result = (info.windows || info.mac || info.touchPoints === 0) ? 'PC!' : 'mobile';
console.log(result);
function getDeviceInfo() {
    const agent = navigator.userAgent.toLowerCase();
    const touchPoints = ('maxTouchPoints' in navigator) ? navigator.maxTouchPoints : 0;
    const result = {
        windows: agent.indexOf('windows') > -1,
        mac: agent.indexOf('macintosh') > -1,
        linux: agent.indexOf('linux') > -1,
        touchPoints: touchPoints
    }
    return result;
}

サンプル001■JavaScript: Evaluating PC or mobile

See the Pen JavaScript: Evaluating PC or mobile by Fumio Nonaka (@FumioNonaka) on CodePen.

なお、今回はタッチ操作に関わってPCとモバイルを切り分けました。画面サイズやレイアウトを考えるときは、また少し違ってくるでしょう。ユーザーエージェントに頼らない手法については、MDN「ユーザーエージェント文字列を用いたブラウザーの判定」が参考になります。たとえば、以下の引用は画面サイズの場合です。

画面のサイズについては、window.innerWidthwindow.addEventListener("resize", function(){ /*refresh screen size dependent things*/ })を使用するだけです。

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

JavaScript: ブラウザを開いているのがPCかモバイルかを切り分ける

01 User-Agentを使う

ブラウザを開いているのがWindows PCやMacなのか、モバイル端末なのかはwindow.navigator.userAgentで調べられます。User-Agentはつぎのような構文の文字列です。

<product> / <product-version> <comment>

webブラウザの一般的な書式はつぎのとおりで、platformがオペレーティングシステム(OS)を示します。PCあるいはMacの場合に含まれる文字列は、それぞれWindowsMacintoshです。Linuxの場合はモバイルもあり得ますので、もう少し細かく調べなければなりません。本項では省くことにします。

Mozilla/<version> (<system-information>) <platform> (<platform-details>) <extensions>

確かめ方としては、navigator.userAgentの戻り値にplatformの文字列が含まれるか調べればよいでしょう。

const agent = navigator.userAgent.toLowerCase();
if (agent.indexOf('windows') > -1) {
    console.log('Windows');
} else if (agent.indexOf('macintosh') > -1) {
    console.log('Mac');
} else {
    console.log('mobile');
}

もっとも、MDNのリファレンスには、つぎのような脅迫めいた「注記」が加えられています。

ユーザエージェントを表す文字列を検出することに基づいたブラウザ識別は、信頼できないものであり、また、推奨されません。なぜなら、ユーザエージェントを表す文字列は、ユーザによって変更することができるからです。

02 判定に同時タッチコンタクト数を加える

あえてユーザエージェントを書き替えるユーザーが多いとも思えません。けれど、保険をかけるに越したことはないでしよう。そこで使ったのが、Navigator.maxTouchPointsです。タッチパネルがなければ、数値0が戻り値です。タッチパネルが備わっていないLinuxデスクトップも拾えます。

つぎのようなサンプルコードを書いてみました。併せて、試していただけるよう、CodePenにも掲げます(サンプル001)。

const info = getDeviceInfo();
const result = (info.windows || info.mac || info.touchPoints === 0) ? 'PC!' : 'mobile';
console.log(result);
function getDeviceInfo() {
    const agent = navigator.userAgent.toLowerCase();
    const touchPoints = ('maxTouchPoints' in navigator) ? navigator.maxTouchPoints : 0;
    const result = {
        windows: agent.indexOf('windows') > -1,
        mac: agent.indexOf('macintosh') > -1,
        linux: agent.indexOf('linux') > -1,
        touchPoints: touchPoints
    }
    return result;
}

サンプル001■JavaScript: Evaluating PC or mobile

See the Pen JavaScript: Evaluating PC or mobile by Fumio Nonaka (@FumioNonaka) on CodePen.

なお、今回はタッチ操作に関わってPCとモバイルを切り分けました。画面サイズやレイアウトを考えるときは、また少し違ってくるでしょう。ユーザーエージェントに頼らない手法については、MDN「ユーザーエージェント文字列を用いたブラウザーの判定」が参考になります。たとえば、以下の引用は画面サイズの場合です。

画面のサイズについては、window.innerWidthwindow.addEventListener("resize", function(){ /*refresh screen size dependent things*/ })を使用するだけです。

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

JavaScriptの引数の数チェックについて

JavaScriptの引数の性質(備忘録)

JavaScriptでは呼び出し側の引数の数が呼び出し元と一致しなくてもエラーとならず、下記の結果となる。

console.log(message);
}

displayMessage(); //undefind
displayMessage('hoge'); //hoge
displayMessage('hoge', 'foo');//hoge

参考文献

改定新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで 

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

ローグライクゲームを作ってみるその5 ダンジョンのサイズ

過去記事一覧

現在のコードについては前回の記事の最後の項を参照してください。

ダンジョンのサイズ

今までダンジョンのフロアのサイズは25マスx25マスで固定でした。

今回はより広いサイズのフロアを生成したり、描画したりできるようにしたいと思います。

ゲーム画面のサイズは変更しませんので、より広いサイズのフロアを生成すると必然的にゲーム画面には一度にフロアの一部しか表示できないということになります。

そのため、プレイヤーの位置に応じてフロアのどの部分を描画するかを決めなければならなくなります。


まずフロアの生成処理を変更しましょう。

create_field関数を下のように変更します。

nx変数とny変数がフロアの大きさを表します。0階は25マスx25マスのままとすることにしますが、1階以降は50マスx50マスに変更してみることにします。

そうすると、マスの生成処理はnx変数とny変数を使用するようにしなければなりません。また、他の幾つかの部分でもnx変数とny変数を使用するようにしなければならなくなります。

また、create_field関数の返り値となるフロアを表すオブジェクトにはnxプロパティとnyプロパティとしてフロアのサイズを含めることにします。

function create_field (depth, upstairs, base_seed) {
    var random = new Random(base_seed + ',' + depth.toString(10));

    var nx = 25;
    var ny = 25;
    if (depth > 0) {
        nx = 50;
        ny = 50;
    }

    var blocks = [];
    for (var i = 0; i < nx; i++) {
        blocks[i] = [];
        for (var j = 0; j < ny; j++) {
            if ((i === 0 || j === 0) || (i === nx - 1 || j === ny - 1)) {
                blocks[i][j] = {
                    base: B_WALL
                };
            }
            else {
                blocks[i][j] = {
                    base: B_FLOOR
                };
            }
        }
    }

    if (depth === 0) {
        blocks[12][5] = {
            base: B_DOWNSTAIR
        };

        return {
            nx: nx,
            ny: ny,
            blocks: blocks
        };
    }

    var rs = [{
        x1: 1,
        x2: nx - 2,
        y1: 1,
        y2: ny - 2
    }];
    var ers = [];
    var dps = [1, 1, 1, 1, 1, 1, 0.5, 0.5, 0.5, 0.5];
    while (rs.length > 0 && dps.length > 0) {
        var r = rs.shift();
        var nrs = split_room(blocks, r.x1, r.x2, r.y1, r.y2, dps.shift(), random);
        for (var i = 0; i < nrs.length; i++) {
            rs.push(nrs[i]);
        }
        if (nrs.length === 0) {
            ers.push(r);
        }
    }
    while (rs.length > 0) {
        ers.push(rs.shift());
    }

    var nds = 1;
    while (nds > 0) {
        var x = random.num(nx - 2) + 1;
        var y = random.num(ny - 2) + 1;
        var f = true;
        for (var i = 0; i < upstairs.length; i++) {
            if (x === upstairs[i].x && y === upstairs[i].y) {
                f = false;
                break;
            }
        }
        if (f) {
            blocks[x][y].base = B_DOWNSTAIR;
            nds--;
        }
    }

    for (var i = 0; i < upstairs.length; i++) {
        if (blocks[upstairs[i].x][upstairs[i].y].base = B_WALL) {
            blocks[upstairs[i].x][upstairs[i].y].base = B_FLOOR;
        }
    }

    return {
        nx: nx,
        ny: ny,
        blocks: blocks
    };
}

次にキー入力の処理を変更します。

2つ目のkeydownイベントハンドラを下のように変更します。

矢印キーが押された場合にはまずプレイヤーが現在いるフロアのサイズを取得し、nx変数とny変数に格納します。

そして、これらの変数の値をプレイヤーがフロアの端にいるかを判定するために使用するようにします。

    c.on('keydown', function (e) {
(省略)
        if (e.keyCode >= 37 && e.keyCode <= 40) {
            var nx = fields[player.depth].nx;
            var ny = fields[player.depth].ny;
            var x = player.x;
            var y = player.y;
            if (e.shiftKey) {
                if (keyl && keyu) {
                    if (x === 0 || y === 0) {
                        return;
                    }
                    x--;
                    y--;
                }
                else if (keyr && keyu) {
                    if (x === nx - 1 || y === 0) {
                        return;
                    }
                    x++;
                    y--;
                }
                else if (keyl && keyd) {
                    if (x === 0 || y === ny - 1) {
                        return;
                    }
                    x--;
                    y++;
                }
                else if (keyr && keyd) {
                    if (x === nx - 1 || y === ny - 1) {
                        return;
                    }
                    x++;
                    y++;
                }
                else {
                    return;
                }
            }
            else {
                if (e.keyCode === 37) {
                    if (x === 0) {
                        return;
                    }
                    x--;
                }
                else if (e.keyCode === 38) {
                    if (y === 0) {
                        return;
                    }
                    y--;
                }
                else if (e.keyCode === 39) {
                    if (x === nx - 1) {
                        return;
                    }
                    x++;
                }
                else if (e.keyCode === 40) {
                    if (y === ny - 1) {
                        return;
                    }
                    y++;
                }
            }
(省略)
        }
        else if (e.keyCode === 32) {
(省略)
        }
        else {
            return;
        }

        draw(con, env);
    });

次に描画処理を変更します。

描画処理においてはゲーム画面でフロアのマスを何マスx何マス描画するかという情報が必要となりますので、下のようなSX変数とSY変数を定義します。

var SX = 25;
var SY = 25;

これらの値は25とします。つまり、ゲーム画面では25マスx25マスを一度に描画することにします。これは前回までと同じサイズです。

今まではフロアのサイズも25マスx25マスだったのでゲーム画面にフロア全体を一度に描画できていましたが、今後25マスx25マスより広いフロアを描画する場合には一度にフロアの一部しか描画できないということになります。

フロアは基本的にプレイヤーを中心としてプレイヤーの周りの25マスx25マスを描画することにします。

ただし、プレイヤーがフロアの端の方にいる場合にはプレイヤーを中心とした描画を行うとゲーム画面に余白が現れてしまいます(たとえば、プレイヤーがフロアの左端にいる場合にはプレイヤーを中心とした描画を行った場合、ゲーム画面のフロアを描画する部分の左半分に描画するものがなく、余白となってしまいます)ので、余白が現れないように描画します。

つまり、ゲーム画面にはプレイヤーのフロア内での位置に拘わらず常に25マスx25マスが描画されます。

このような描画を実現するにはdraw関数を下のように変更します。

プレイヤーが現在いるフロアのサイズを取得し、nx変数とny変数に格納します。

そして、ox変数とoy変数にゲーム画面において描画を行うフロアのマスの左上端の位置を格納します。

描画を行うフロアのマスの左上端の位置が分かれば、マスの描画は二重for文で簡単に行うことができます。

そして、プレイヤーのゲーム画面における描画位置はプレイヤーのフロアにおける位置からゲーム画面において描画を行うフロアのマスの左上端の位置を差し引くことによって求められます。

function draw (con, env) {
(省略)
    var nx = fields[player.depth].nx;
    var ny = fields[player.depth].ny;

    var ox = 0;
    if (player.x <= Math.floor(SX / 2)) {
        ox = 0;
    }
    else if (player.x >= nx - Math.floor(SX / 2)) {
        ox = nx - SX;
    }
    else {
        ox = player.x - Math.floor(SX / 2);
    }

    var oy = 0;
    if (player.y <= Math.floor(SY / 2)) {
        oy = 0;
    }
    else if (player.y >= ny - Math.floor(SY / 2)) {
        oy = ny - SY;
    }
    else {
        oy = player.y - Math.floor(SY / 2);
    }

    for (var i = 0; i < SX; i++) {
        for (var j = 0; j < SY; j++) {
            var block = fields[player.depth].blocks[ox + i][oy + j];
(省略)
        }
    }

    var px = player.x - ox;
    var py = player.y - oy;

    con.textBaseline = 'middle';
    con.textAlign = 'center';
    con.fillStyle = 'red';
    con.fillText('🚶\uFE0E', px * PX + (PX / 2), py * PY + (PY / 2));

    if (env.diagonal) {
        con.save();

        con.strokeStyle = 'white';
        con.translate(px * PX + (PX / 2), py * PY + (PY / 2));
        con.rotate(Math.PI / 4);
(省略)
    }
}

See the Pen roguelike-5-1 by pizyumi (@pizyumi) on CodePen.

今回はここまで

今回はここまでです。

game.jsは下のようになりました。

var TITLE = 'シンプルローグライク';

var TEXT_START = 'はじめる';

var SCREEN_X = 1600;
var SCREEN_Y = 800;

var SX = 25;
var SY = 25;
var PX = 32;
var PY = 32;

var B_FLOOR = 0;
var B_WALL = 1;
var B_DOWNSTAIR = 2;

var B_CAN_STAND = [];
B_CAN_STAND[B_FLOOR] = true;
B_CAN_STAND[B_WALL] = false;
B_CAN_STAND[B_DOWNSTAIR] = true;

var img = new Image();
img.src = 'Dungeon_B_Freem7.png';

var seed = Date.now().toString(10);

var startf = false;

var fields = null;
var player = null;

$(function(){
    var canvas = document.getElementById('game');
    var con = canvas.getContext('2d');

    var keyl = false;
    var keyu = false;
    var keyr = false;
    var keyd = false;

    var env = {
        diagonal: false
    };

    var c = $('body');
    c.on('keydown', function (e) {
        if (e.keyCode === 37) {
            keyl = true;
        }
        else if (e.keyCode === 38) {
            keyu = true;
        }
        else if (e.keyCode === 39) {
            keyr = true;
        }
        else if (e.keyCode === 40) {
            keyd = true;
        }
        else {
            keyl = false;
            keyu = false;
            keyr = false;
            keyd = false;
        }
    });
    c.on('keyup', function (e) {
        if (e.keyCode === 37) {
            keyl = false;
        }
        else if (e.keyCode === 38) {
            keyu = false;
        }
        else if (e.keyCode === 39) {
            keyr = false;
        }
        else if (e.keyCode === 40) {
            keyd = false;
        }
    });
    c.on('keydown', function (e) {
        if (!startf) {
            if (e.keyCode === 90) {
                startf = true;

                init();

                draw(con, env);
            }

            return;
        }

        if (e.keyCode === 16) {
            if (!env.diagonal) {
                env.diagonal = true;

                draw(con, env);
            }

            return;
        }

        if (e.keyCode >= 37 && e.keyCode <= 40) {
            var nx = fields[player.depth].nx;
            var ny = fields[player.depth].ny;
            var x = player.x;
            var y = player.y;
            if (e.shiftKey) {
                if (keyl && keyu) {
                    if (x === 0 || y === 0) {
                        return;
                    }
                    x--;
                    y--;
                }
                else if (keyr && keyu) {
                    if (x === nx - 1 || y === 0) {
                        return;
                    }
                    x++;
                    y--;
                }
                else if (keyl && keyd) {
                    if (x === 0 || y === ny - 1) {
                        return;
                    }
                    x--;
                    y++;
                }
                else if (keyr && keyd) {
                    if (x === nx - 1 || y === ny - 1) {
                        return;
                    }
                    x++;
                    y++;
                }
                else {
                    return;
                }
            }
            else {
                if (e.keyCode === 37) {
                    if (x === 0) {
                        return;
                    }
                    x--;
                }
                else if (e.keyCode === 38) {
                    if (y === 0) {
                        return;
                    }
                    y--;
                }
                else if (e.keyCode === 39) {
                    if (x === nx - 1) {
                        return;
                    }
                    x++;
                }
                else if (e.keyCode === 40) {
                    if (y === ny - 1) {
                        return;
                    }
                    y++;
                }
            }

            if (x !== player.x || y !== player.y) {
                var block = fields[player.depth].blocks[x][y];
                if (B_CAN_STAND[block.base]) {
                    player.x = x;
                    player.y = y;
                }
                else {
                    return;
                }
            }
            else {
                return;
            }
        }
        else if (e.keyCode === 32) {
            var block = fields[player.depth].blocks[player.x][player.y];
            if (block.base === B_DOWNSTAIR) {
                player.depth++;
                if (!fields[player.depth]) {
                    fields[player.depth] = create_field(player.depth, [{
                        x: player.x,
                        y: player.y
                    }], seed);
                }
            }
        }
        else {
            return;
        }

        draw(con, env);
    });
    c.on('keyup', function (e) {
        if (e.keyCode === 16) {
            if (env.diagonal) {
                env.diagonal = false;

                draw(con, env);
            }
        }
    });
    $(window).on('blur', function (e) {
        if (env.diagonal) {
            env.diagonal = false;

            draw(con, env);
        }
    });

    draw(con, env);
});

function init () {
    fields = [];
    fields[0] = create_field(0, [], seed);
    player = {
        depth: 0,
        x: 12,
        y: 17
    };
}

function create_field (depth, upstairs, base_seed) {
    var random = new Random(base_seed + ',' + depth.toString(10));

    var nx = 25;
    var ny = 25;
    if (depth > 0) {
        nx = 50;
        ny = 50;
    }

    var blocks = [];
    for (var i = 0; i < nx; i++) {
        blocks[i] = [];
        for (var j = 0; j < ny; j++) {
            if ((i === 0 || j === 0) || (i === nx - 1 || j === ny - 1)) {
                blocks[i][j] = {
                    base: B_WALL
                };
            }
            else {
                blocks[i][j] = {
                    base: B_FLOOR
                };
            }
        }
    }

    if (depth === 0) {
        blocks[12][5] = {
            base: B_DOWNSTAIR
        };

        return {
            nx: nx,
            ny: ny,
            blocks: blocks
        };
    }

    var rs = [{
        x1: 1,
        x2: nx - 2,
        y1: 1,
        y2: ny - 2
    }];
    var ers = [];
    var dps = [1, 1, 1, 1, 1, 1, 0.5, 0.5, 0.5, 0.5];
    while (rs.length > 0 && dps.length > 0) {
        var r = rs.shift();
        var nrs = split_room(blocks, r, dps.shift(), random);
        for (var i = 0; i < nrs.length; i++) {
            rs.push(nrs[i]);
        }
        if (nrs.length === 0) {
            ers.push(r);
        }
    }
    while (rs.length > 0) {
        ers.push(rs.shift());
    }

    var nds = 1;
    while (nds > 0) {
        var x = random.num(nx - 2) + 1;
        var y = random.num(ny - 2) + 1;
        var f = true;
        for (var i = 0; i < upstairs.length; i++) {
            if (x === upstairs[i].x && y === upstairs[i].y) {
                f = false;
                break;
            }
        }
        if (f) {
            blocks[x][y].base = B_DOWNSTAIR;
            nds--;
        }
    }

    for (var i = 0; i < upstairs.length; i++) {
        if (blocks[upstairs[i].x][upstairs[i].y].base = B_WALL) {
            blocks[upstairs[i].x][upstairs[i].y].base = B_FLOOR;
        }
    }

    return {
        nx: nx,
        ny: ny,
        blocks: blocks
    };
}

function split_room (blocks, r, dp, random) {
    var ap = random.fraction();
    if (ap <= dp) {
        var dir = random.num(2);
        if (r.x2 - r.x1 > (r.y2 - r.y1) * 2) {
            dir = 0;
        }
        else if ((r.x2 - r.x1) * 2 < r.y2 - r.y1) {
            dir = 1;
        }

        if (dir === 0) {
            if (r.x2 - r.x1 <= 6) {
                return [];
            }

            var x = random.num(r.x2 - r.x1 - 6) + 3 + r.x1;
            if (blocks[x][r.y1 - 1].base !== B_WALL) {
                return [];
            }
            if (blocks[x][r.y2 + 1].base !== B_WALL) {
                return [];
            }
            var y = random.num(r.y2 - r.y1) + r.y1;
            for (var i = r.y1; i <= r.y2; i++) {
                if (i !== y) {
                    blocks[x][i].base = B_WALL;
                }
            }

            var r1 = {
                x1: r.x1,
                x2: x - 1,
                y1: r.y1,
                y2: r.y2
            };
            var r2 = {
                x1: x + 1,
                x2: r.x2,
                y1: r.y1,
                y2: r.y2
            };
            var ord = random.num(2);
            if (ord === 0) {
                return [r1, r2];
            }
            else {
                return [r2, r1];
            }
        }
        else if (dir === 1) {
            if (r.y2 - r.y1 <= 6) {
                return [];
            }

            var y = random.num(r.y2 - r.y1 - 6) + 3 + r.y1;
            if (blocks[r.x1 - 1][y].base !== B_WALL) {
                return [];
            }
            if (blocks[r.x2 + 1][y].base !== B_WALL) {
                return [];
            }
            var x = random.num(r.x2 - r.x1) + r.x1;
            for (var i = r.x1; i <= r.x2; i++) {
                if (i !== x) {
                    blocks[i][y].base = B_WALL;
                }
            }

            var r1 = {
                x1: r.x1,
                x2: r.x2,
                y1: r.y1,
                y2: y - 1
            };
            var r2 = {
                x1: r.x1,
                x2: r.x2,
                y1: y + 1,
                y2: r.y2
            };
            var ord = random.num(2);
            if (ord === 0) {
                return [r1, r2];
            }
            else {
                return [r2, r1];
            }
        }
    }
    return [];
}

function draw (con, env) {
    con.fillStyle = 'black';
    con.fillRect(0, 0, SCREEN_X, SCREEN_Y);

    if (!startf) {
        con.textBaseline = 'alphabetic';
        con.textAlign = 'center';
        con.fillStyle = 'white';

        con.font = "48px consolas";
        con.fillText(TITLE, SCREEN_X / 2, SCREEN_Y / 4);

        con.font = "32px consolas";
        con.fillText('> ' + TEXT_START, SCREEN_X / 2, SCREEN_Y / 4 * 3);

        return;
    }

    var nx = fields[player.depth].nx;
    var ny = fields[player.depth].ny;

    var ox = 0;
    if (player.x <= Math.floor(SX / 2)) {
        ox = 0;
    }
    else if (player.x >= nx - Math.floor(SX / 2)) {
        ox = nx - SX;
    }
    else {
        ox = player.x - Math.floor(SX / 2);
    }

    var oy = 0;
    if (player.y <= Math.floor(SY / 2)) {
        oy = 0;
    }
    else if (player.y >= ny - Math.floor(SY / 2)) {
        oy = ny - SY;
    }
    else {
        oy = player.y - Math.floor(SY / 2);
    }

    for (var i = 0; i < SX; i++) {
        for (var j = 0; j < SY; j++) {
            var block = fields[player.depth].blocks[ox + i][oy + j];
            if (block.base === B_FLOOR) {
                con.fillStyle = 'white';
                con.beginPath();
                con.arc((i + 0.5) * PX, (j + 0.5) * PY, 1, 0, Math.PI * 2);
                con.closePath();
                con.fill();
            }
            else if (block.base === B_WALL) {
                con.strokeStyle = 'white';
                con.strokeRect(i * PX, j * PY, PX, PY);
                con.beginPath();
                con.moveTo(i * PX, j * PY);
                con.lineTo((i + 1) * PX, (j + 1) * PY);
                con.moveTo((i + 1) * PX, j * PY);
                con.lineTo(i * PX, (j + 1) * PY);
                con.closePath();
                con.stroke();
            }
            else if (block.base === B_DOWNSTAIR) {
                con.drawImage(img, 4 * 32, 5 * 32, 32, 32, i * PX, j * PY, PX, PY);
            }
        }
    }

    var px = player.x - ox;
    var py = player.y - oy;

    con.textBaseline = 'middle';
    con.textAlign = 'center';
    con.fillStyle = 'red';
    con.font = '24px consolas';
    con.fillText('🚶\uFE0E', px * PX + (PX / 2), py * PY + (PY / 2));

    if (env.diagonal) {
        con.save();

        con.strokeStyle = 'white';
        con.translate(px * PX + (PX / 2), py * PY + (PY / 2));
        con.rotate(Math.PI / 4);
        con.beginPath();
        con.moveTo((PX / 2) + 4, -4);
        con.lineTo((PX / 2) + 4 + 8, -4);
        con.lineTo((PX / 2) + 4 + 8, -4 - 4);
        con.lineTo((PX / 2) + 4 + 8 + 8, 0);
        con.lineTo((PX / 2) + 4 + 8, 4 + 4);
        con.lineTo((PX / 2) + 4 + 8, 4);
        con.lineTo((PX / 2) + 4, 4);
        con.closePath();
        con.stroke();
        con.rotate(Math.PI / 4 * 2);
        con.beginPath();
        con.moveTo((PX / 2) + 4, -4);
        con.lineTo((PX / 2) + 4 + 8, -4);
        con.lineTo((PX / 2) + 4 + 8, -4 - 4);
        con.lineTo((PX / 2) + 4 + 8 + 8, 0);
        con.lineTo((PX / 2) + 4 + 8, 4 + 4);
        con.lineTo((PX / 2) + 4 + 8, 4);
        con.lineTo((PX / 2) + 4, 4);
        con.closePath();
        con.stroke();
        con.rotate(Math.PI / 4 * 2);
        con.beginPath();
        con.moveTo((PX / 2) + 4, -4);
        con.lineTo((PX / 2) + 4 + 8, -4);
        con.lineTo((PX / 2) + 4 + 8, -4 - 4);
        con.lineTo((PX / 2) + 4 + 8 + 8, 0);
        con.lineTo((PX / 2) + 4 + 8, 4 + 4);
        con.lineTo((PX / 2) + 4 + 8, 4);
        con.lineTo((PX / 2) + 4, 4);
        con.closePath();
        con.stroke();
        con.rotate(Math.PI / 4 * 2);
        con.beginPath();
        con.moveTo((PX / 2) + 4, -4);
        con.lineTo((PX / 2) + 4 + 8, -4);
        con.lineTo((PX / 2) + 4 + 8, -4 - 4);
        con.lineTo((PX / 2) + 4 + 8 + 8, 0);
        con.lineTo((PX / 2) + 4 + 8, 4 + 4);
        con.lineTo((PX / 2) + 4 + 8, 4);
        con.lineTo((PX / 2) + 4, 4);
        con.closePath();
        con.stroke();

        con.restore();
    }
}

function hash (seed) {
    var sha256 = new jsSHA('SHA-256', 'TEXT');
    sha256.update(seed);
    return sha256.getHash('HEX');
}

class Random {
    constructor (seed) {
        this.seed = seed;
        this.hash = hash(seed);
        this.pointer = 0;
    }

    byte () {
        if (this.pointer === 64) {
            this.hash = hash(this.hash);
            this.pointer = 0;
        }
        var value = this.hash.substring(this.pointer, this.pointer + 2);
        this.pointer += 2;
        return parseInt(value, 16);
    }

    num (max) {
        if (max <= 0) {
            throw new Error('max of random.num must be positive.');
        }
        else if (max <= 256) {
            return this.byte() % max;
        }
        else {
            throw new Error('not supported.');
        }
    }

    fraction () {
        return this.byte() / 256;
    }
}

function test_random_class_byte () {
    var a = [68, 9, 150, 66, 71, 184, 42, 152,
        84, 31, 148, 195, 79, 121, 253, 235,
        87, 142, 108, 87, 64, 95, 18, 186,
        184, 92, 200, 43, 179, 155, 117, 136,
        209, 241, 173, 107, 190, 11, 178, 50];
    var r = new Random('yurina');
    for (var i = 0; i < a.length; i++) {
        if (r.byte() !== a[i]) {
            throw new Error('test_random_class_byte');
        }
    }
}

function test_random_class_num () {
    var a = [0, 1, 0, 2, 1, 4, 0, 0, 3, 1, 5, 3, 1, 9, 13, 11];
    var r = new Random('yurina');
    for (var i = 0; i < a.length; i++) {
        if (r.num(i + 1) !== a[i]) {
            throw new Error('test_random_class_num');
        }
    }
}

次回はゲームメッセージとプレイヤーのステータスの描画について考えたいと思います。

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

Object.assign()で深い部分もコピーしたい!

やりたいこと

Const  State = {
    name: Hanako,
    stay: {
        Japan: {
              Hiroshima: ‘実家’,
              Karuizawa: ‘別荘’,
               },
        America: {
              NewYork: ‘別荘’,
              Arizona: ‘おじいちゃんち’
                 }
          }
};

これのKaruizawaを'実家2'にしたコピーが欲しい!
StateのstayのJapanのKaruizawaが書き換わるのはいやだ

Object.assign()は浅いコピーのため、一回のコピーでご丁寧に入れ子の中までコピーはしてくれません。

今の状態で、State2を定義して、Stateをコピーした後、Karuizawaの中身をみると、

Const State2 = Object.assign({}, State);
Console.log(State.stay.Japan.Karuizawa);
-> 別荘

試しに、State2.stay.Japan.Karuizawa に'実家2'を代入してみます。

失敗例1
State2.stay.Japan.Karuizawa = ‘実家2;
Console.log(State.stay.Japan.Karuizawa);
-> 実家2

これだと、最初のStateの中のKaruizawaが'別荘'ではなく、'実家2'に書き変わってしまっています。
そうじゃない。Karuizawaが'別荘'のバージョンと'実家2'のバージョンの二つが欲しい、書き変わってほしくないので、このコピーは失敗です。

失敗例2
State2= Object.assign({}, State);
State2.stay.Japan= Object.assign({}, State.stay.Japan, { Karuizawa: ‘実家2} )
Console.log(State.stay.Japan.Karuizawa);
-> 実家2

↑このパターンも書き変わってしまいます。失敗・・・

では、成功例はというと、

成功例
State2= Object.assign({}, State);
State2.stay = Object.assign({}, State.stay);
State2.stay.Japan = Object.assign({}, State.stay.Japan);
State2.stay.Japan = Object.assign({}, State.stay.Japan, { Karuizawa: '実家2'});

一旦ここまで丁寧にコピーしてやります。
それぞれの中身をみてみましょう。

console.log(State2);
=>{ name: 'Hanako',
  stay:
   { Japan: { Hiroshima: '実家', Karuizawa: '実家2' },
     America: { NewYork: '別荘', Arizona: 'おじいちゃんち' } } }

console.log(State2.stay.Japan.Karuizawa);
=>実家2

console.log(state.stay.Japan.Karuizawa);
=>別荘

Stateの方のKaruizawaは '別荘' のままです。書き変わってません!成功です!!

シャローコピーで粘りたい方は、この方法おすすめです。

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

Vue.js + Quasarで爆速プロトタイピング(3)〜Side Drawer編〜

1.概要

Vue.js + Quasar Frameworkでプロトタイプ作成を行うことがあったので、メモがてら記録しておきます。

前回の記事 -> Vue.js + Quasarで爆速プロトタイピング(2)〜Toolbar編〜

今回はサイドメニューを作成します。

2.コーディング

2-1.フォルダ構成

/vue-sample
 ├ css/
 │ └ style.css
 ├ pages/
 │ └ main.html
 └ app.js

2-2.main.html

main.html
<!DOCTYPE html>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <link rel="shortcut icon" type="image/x-icon" href="../../favicon.ico" />
    <title>Vue.js + Quasarで爆速プロトタイピング</title>
    <!-- Material Icons -->
    <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet" type="text/css" />
    <!-- animations -->
    <link href="https://cdn.jsdelivr.net/npm/animate.css@^3.5.2/animate.min.css" rel="stylesheet" />
    <!-- Finally, add Quasar's CSS -->
    <link href="https://cdn.jsdelivr.net/npm/quasar-framework@0.17.20/dist/umd/quasar.mat.min.css" rel="stylesheet" type="text/css" />

    <link rel="stylesheet" type="text/css" href="../css/style.css" />
  </head>

  <body>
    <!-- Vue.js -->
    <script src="https://cdn.jsdelivr.net/npm/vue@latest/dist/vue.min.js"></script>

    <div id="app">
      <q-layout id="main">
        <!-- ヘッダー -->
        <q-layout-header>
          <q-toolbar color="secondary">
<!-- ヘッダーのメニューボタン押下時にドロワーの表示フラグを更新します。 -->
            <q-btn flat round dense icon="menu" @click="drawer = !drawer"></q-btn>

            <q-toolbar-title>
              {{ title }}
            </q-toolbar-title>
          </q-toolbar>
        </q-layout-header>

<!-- 今回追加START ドロワー -->
        <q-layout-drawer overlay side="left" v-model="drawer">
          <q-list link>
            <q-item>
              <q-item-side>
                <q-icon name="thumb_up"></q-icon>
              </q-item-side>
              <q-item-main>
                <q-item-tile>メニュー1</q-item-tile>
              </q-item-main>
            </q-item>
            <q-item>
              <q-item-side>
                <q-icon name="thumb_up"></q-icon>
              </q-item-side>
              <q-item-main>
                <q-item-tile>メニュー2</q-item-tile>
              </q-item-main>
            </q-item>
            <q-item>
              <q-item-side>
                <q-icon name="thumb_up"></q-icon>
              </q-item-side>
              <q-item-main>
                <q-item-tile>メニュー3</q-item-tile>
              </q-item-main>
            </q-item>
          </q-list>
        </q-layout-drawer>
<!-- 今回追加END -->

        <!-- コンテンツ -->
        <q-page-container>
          <div class="page">
            <q-input stack-label="greet" v-model="greet"></q-input>
          </div>
        </q-page-container>
      </q-layout>
    </div>

    <!-- IE support -->
    <script src="https://cdn.jsdelivr.net/npm/quasar-framework@0.17.20/dist/umd/quasar.ie.polyfills.umd.min.js"></script>
    <!-- Add Quasar's JS -->
    <script src="https://cdn.jsdelivr.net/npm/quasar-framework@0.17.20/dist/umd/quasar.mat.umd.min.js"></script>
    <!-- If you want to add a Quasar Language pack (other than "en-us"). -->
    <script src="https://cdn.jsdelivr.net/npm/quasar-framework@0.17.20/dist/umd/i18n.ja.umd.min.js"></script>

    <script src="../app.js"></script>
  </body>
</html>

2-3.app.js

app.js
// Quasar
Vue.use(Quasar);

var app = new Vue({
  el: "#app",
  data: function() {
    return {
      drawer: false, // <- 今回追加
      title: "Vue.js + Quasarで爆速プロトタイピング",
      greet: "Hello World!"
    }
  }
});

app.$mount("#app");

2-4.説明

Drawerのリファレンスはこちらです。
https://v0-17.quasar-framework.org/components/layout-drawer.html

プロパティoverlayをつけることで画面全体にかぶせるように表示されます。
side = "right"を指定してあげれば右から表示されます。

中身のメニューはq-listを使って実現しています。
https://v0-17.quasar-framework.org/components/lists-and-list-items.html

レイアウトは以下の図のような構成です。
この後もガンガン使っていきますので、そちらも参考にしてください。
スクリーンショット 2019-11-26 8.52.29.png

3.完成!

これまたお洒落なサイドメニューが表示されました!
動かしてもらえばわかりますが、しっかり画面外からスライドアニメーションして表示してくれます。
設定ひとつで右から出すことも可能です!

次回からは中身のコンテンツを作っていきたいと思います!

読んでいただき、ありがとうございました!

スクリーンショット 2019-11-26 8.43.49.png
d

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

Nuxt.jsに飛びつく前に~Nuxt.jsを習得するための前提技術と、その勉強方法の紹介~

概要

Nuxt.jsは今最もイケてるゥ!最高にCooooooolなWEB開発フレームワークです。巷でNuxt.jsについての記事も増えていますね。
しかし、ネット上のNuxt.jsの記事では、Nuxt.jsを始める上で前提となる前提知識の存在が省略されているように思います。Nuxt.jsはVue.jsの発展形(=Nuxt.jsを触る人はVue.jsの経験があるという前提)なので当然と言えば当然なのですが。

本記事では、これからWEB開発者を目指す人を対象に、
・Nuxt.jsを習得するには何が必要か?
・そのための勉強方法
を紹介します。

自己紹介

Nuxt.jsとFirebaseを用いたWEBアプリ開発を担当することになりました。
Pythonはある程度触ってきたものの(機械学習やスクレイピング等)、WEB開発は全くの未経験。Progateや入門書を触って、簡単なWEBサイトを公開した経験がある程度でした。

失敗談「とりあえずNuxtアプリを作ってみたけど・・・・・・・?」

さて、Nuxt.jsは公式ドキュメントの内容が充実しており、"分かりやすい"です。
しかも驚くべきことに日本語に翻訳されています。

例えば、npxさえ事前にインストールされていれば下記の簡単なコマンドで簡単にプロジェクト(※)を始めることができます。
(※厳密には違うのですが、初心者の方は「プロジェクトを作る=アプリを作る」と読みかえてください)
Nuxt.js - ユニバーサル Vue.js アプリケーション/インストール

//任意のディレクトリで下記を実行すると、<project-name>という名前のプロジェクトが作成されます
$ npx create-nuxt-app <project-name>

//サーバーを立ち上げます。http://localhost:3000で確認できます
$ npm run dev

また、Nuxt.jsとFirebaseを組み合わせることも簡単です。
私は下記記事を参考にユーザー認証を実装しました。
『FirebaseとNuxt.jsを使ってユーザ認証関係を簡単に作ってみる+1ヶ月前の自分に教えたいリンク集
あとはTwitterでログインできる簡単なチャットなんかも作りましたね。

ここまでで、カタチがあり動くものを何となく作れました。

しかし、WEBエンジニアとして自分が殆ど成長していないことに気付きます。なぜなら、Nuxt.jsの中身を理解できているわけではないから。「このままだとググってコピペすることしかできないし、永遠にWEBエンジニアとして自立できない」と危機感を募らせました。

Nuxt.jsに飛びつく前にまず基本をちゃんと押さえよう

「Nuxt.jsは公式ドキュメントが充実していて分かりやすい」とよく言われていますが、正直当初はさっぱり分かりませんでした。
ええ、Nuxt.jsのドキュメントはとても優良なドキュメントです。
しかし但書をつけると、「(Vue.jsの経験がある人には)Nuxt.jsは公式ドキュメントが充実していて分かりやすい」んですね。なぜなら、Nuxt.jsはVue.jsを発展させて、その弱点を補ったものだからです。

なので、Nuxt.jsに飛びつく前にまず基本をきちんと押さえましょう。(自戒を込めて)

Nuxt.jsを習得するために必要な技術

さて、いよいよ本題です。WEB開発未経験者がNuxt.jsを習得するために必要な要素を列挙します。

Vue.js
「コンポーネントとは?」「テンプレートとは?」等、基本を抑えていますか? ごく簡単なもので良いので、フルスクラッチ※(後述)で一つ一つのファイルやコードの意味をきちんと理解しながらアプリを作ってみると良いです。
JavaScript ES6
Progateレベルだと少し物足りないです。特にアロー関数は頻繁に使いますね。おすすめは「MDN web docs」を読むことです。
JSON
超大事です!エンジニアにとって当たり前の知識ですが、何となくで済ませてきた人も多いのではないでしょうか。この機会にしっかりと理解しましょう。おすすめは「MDN web docs」を読むことです。
HTML
やはりProgateレベルだと少し物足りないです。おすすめは「MDN web docs」を読むことです。
CSS
CSSはProgateで十分ですね。
WEB開発の基本知識
コードを書くのと直接は関係ないですが、きちんと理解しておくと学習が捗ります。おすすめは「MDN web docs」を(略)

「こんなにやってられないよ」と思う方。安心してください!
それぞれのおすすめの教材もきちんとご紹介します。
目標(=Nuxt.jsを扱えるようになる)と目標に向けて自分に必要なカリキュラムが明確に分かっていれば、短期間で効率的に学習することができます。具体的には合わせて2週間程度で、無理なくNuxt.jsを習得するための準備学習を完了できます。

おすすめの学習教材・方法

「Vue.jsは未経験だがES6は分かる」「正直HTMLも怪しい・・・」とレベル感に差があると思うので、三つのペルソナに分けました。ご自身のレベルに応じて参考になさってください。

「Vue.jsの経験がある、基本がわかる」人向け

公式ドキュメントや『Nuxt.jsビギナーズガイド Vue.jsベースのフレームワークによるシングルページアプリケーション』がおすすめです。

「Vue.js未経験、Vueだけ分からない」人向け

『Vue.js&Nuxt.js超入門』
正直、時間がない方はこれ一冊でも良いかもしれません。
Nuxt.jsを習得するためのVue.jsやJavaScriptを網羅しています。
特に素晴らしいのですが、フルスクラッチで(=一枚のHTMLファイルから手作業で)Vue.jsのアプリを作っていくので、Vue.jsの構造やファイルの中身を一つ一つきちんと理解できます。
僕は本書がきっかけで急に理解が深まりました(掌田さんの本は片っ端から揃えようと思います)。

「Progateは終わったけど・・・」「正直WEB開発の基本も自信ない」「JSONって何?」人向け

「MDN web docs」を読みましょう!
「MDN web docs」はGoogleやMicroSoft等、世界のトップレベルの人たちが中心に作成しているサイトです。つまり情報の信頼性がこの上なく高いので、正確で丁寧な理解ができます。「でも難しいんでしょ?」と思われた方、そんなことはありません。むしろ、文章量が紙幅に制限されないので懇切丁寧に解説が記述されており、とても分かりやすいです。しかも完全無料。(ほぼ)日本語対応。あらゆる点で書籍や大学の情報系の講義より遥かに優れた教材です。
何とWEB入門JavaScriptの第一歩などの体系的なカリキュラムまで備えています。

ただ、プログラミングの知識が全くのゼロの状態で読むと少し辛いと思うので、プログラミング初心者の方はまずProgateから始めるのが良いでしょう。

終わりに

さあ、これで貴方もNuxt.js公式ドキュメントが「分かりやすい」と感じられるようになります。
自分のレベルに合わせて、無理なく学習を進めてください。
良いエンジニアライフを〜

参考文献

Nuxt.js 公式ドキュメント
MDN web docs
『Nuxt.jsビギナーズガイド Vue.jsベースのフレームワークによるシングルページアプリケーション』
『Vue.js&Nuxt.js超入門』

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

列の中に高さの違う要素があった時の対処法

はじめに

最近受けた案件で列ごとに高さや横幅が違う画像が動的に生成をされて、テキストの位置がずれて表示されてしまうというようなことがあった。下記のような感じです。
スクリーンショット 2019-11-26 1.09.12.png

個人的には画像の大きさが違うなんてやめてくれ。。なんて思いましたが。
ですが便利なプラグインでjquery.heightLine.jsというものがあり対象の要素の高さを揃えてくれるらしい。
とりあえずやってみよう。

script.js
    var list = $(".block .box li .color");
    list.heightLine();

スクリーンショット 2019-11-26 1.09.58.png

お、高さが揃った!と思ったんですが要素に対して実行する指定した要素の一番大きな高さを基準にされた。。上の行がスカスカや。。
できれば列ごとの一番大きな高さを持ったものを基準にして欲しい。

結果はこうなりました。

script.js
    const box = $(".block .box");
    let count = 0;
    box.each(function(){
      $(this).find("li").each(function(index){
        const li = $(this);
        index = li.index();
        if ((index % 3) === 0){
          count++;
        }
        li.find(".color").addClass("line" + count);
      });
    });
    for (let i = count; i > 0; i--) {
      $(".block .box li .color.line" + i).heightLine();
    }

やっていることを簡単にまとめると
・リスト要素をeachで回す。
・0と3の倍数でcountを増やす。
・countの数字をクラス名に付与する。
・countの数はリストの列の数と等しくなるのでfor文でそのクラスの数だけheightLine()を使い高さを揃える。

スクリーンショット 2019-11-26 1.10.13.png

ちなみにこちらは会社の上司がサクッとやってくれたやつなんですが、僕自身もこのくらいをサクッとできるようになりたい。

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

テンプレート文字列について

テンプレート文字列、コードレビューで教えていただいたので、はじめて知ったのでメモ :pencil: (JavaScript初歩的なことをわかってなすぎる... 勉強になりました :pray:

変更前

  • あまりテンプレート文字列のことを意識せず以下のようなコードを書いていた
<nuxt-link :to="'/sample/request/' + `${book.id}`">
  詳細
</nuxt-link>

変更後

  • テンプレート文字列使うならこんな感じで書いた方が良さそうとアドバイスしてもらった
<nuxt-link :to="`/sample/request/${book.id}`">
  詳細
</nuxt-link>

参考

テンプレート文字列 - JavaScript | MDN

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

プロパティ名がキーと一致しているときは省略して書ける

表題の件、コードレビューで教えていただいた :pray:

キー名と変数名が同じなら省略できる :pencil: (簡単なことかもだけどすぐ忘れそうなのでメモ)

修正前

const data = await this.SampleRequest({ id: id })

修正後

const data = await this.SampleRequest({ id })

参考

あなたが知らないJavaScriptの便利すぎるショートハンド19選 - WPJ

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