読者です 読者をやめる 読者になる 読者になる

vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

AngularのプロジェクトをビルドしてGolangで動かす

はじめに

前回に引き続きGolangのお話。

以前作成したAngularのプロジェクトをビルドして、 Golangを使って表示してみます。

Angularのプロジェクトをビルドする

実は最近のJS系の勉強会などに参加していて気になっていたことがあります。

「本番ではどうやって表示するの?」

例えばバックエンドをSpring Bootで構築したとして、
フレームワークを使わずに作った場合はSpring Bootのプロジェクトに組み込んで、
Thymeleafなどを使って表示するかと思います。

しかし、例えばAngularの場合だとAngular-cliを使って、
Angularのプロジェクト単体で表示します(確認の段階では)。

では、それぞれを完全に別に扱うの…?

などなど考えていたのですが、実際にはAngularのプロジェクトをビルドすることで、
上の例であればSpring Bootのプロジェクトに組み込むことができる、ということでした。

ビルド自体は(とりあえずビルドするだけなら)簡単で、Angularのプロジェクト直下で下記を実行するだけです。

ng build

ビルドに成功すればプロジェクト直下に「dist」というフォルダが作成され、
必要なファイルがその中に出力されます。

あとはバックエンド側のプロジェクトにまとめて放り込んで、
「index.html」を表示してあげればOKです。

今回はGolangのファイル(webtest.go)と同じディレクトリに「templates」というディレクトリを作り、
その中に全てファイルを保存しました。

HTMLファイルを読み込む

何故か我が家にある(買った)Go言語によるWebアプリケーション開発で扱われているコードを参考にHTMLを表示してみます。

https://github.com/matryer/goblueprints/blob/master/chapter1/chat/main.go

webtest.go

package main

import (
    "html/template"
    "log"
    "net/http"
    "path/filepath"
    "sync"
)

type templateHandler struct {
    once         sync.Once
    filename     string
    viewTemplate *template.Template
}

func (t *templateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    t.once.Do(func() {
        t.viewTemplate = template.Must(template.ParseFiles(filepath.Join("templates", t.filename)))
    })
    t.viewTemplate.Execute(w, nil)
}
func main() {
    http.Handle("/", &templateHandler{filename: "index.html"})

    // Portを指定してWebサーバーを開始.
    if err := http.ListenAndServe(":8088", nil); err != nil {
        // エラーが発生すればログ出力.
        log.Fatal("ListenAndServe:", err)
    }
}

http.Handle, ServeHTTP

ファイル読み込みのキーになる部分がhttp.Handle、ServeHTTPです。

http.Handle

第一引数に対象とするURL(上記ではルートディレクトリ)を、
第二引数にhttp.Handlerという関数ServeHTTPを持つインターフェースを取ります。

で、対象URLにアクセスした時、Spring bootでいう「@RequestMapping」のように、
第二引数から関数ServeHTTPが実行され、「index.html」が表示される、という流れになっています。

ServeHTTP

ここで気になるところとして、上記では第二引数としてhttp.Handlerではなく、構造体を渡しています。

これがエラーにならないのは、
Golangでは構造体があるインターフェース(ここではhttp.Handler)が持つ関数を全て持っている場合、
そのインターフェースと同じものとして扱うことができるためです。

なお構造体に関数を持たせているのがServeHTTPの「(t *templateHandler)」の部分です。

便利ですが、なかなか慣れないとナンノコッチャ分からんですね。。。(;・∀・)

その他の詳しい解説は本を参照。

…なのですが、この状態ではLoading…の文字しか表示されません。
index.htmlから呼び出しているJavascriptにアクセスできないためです。

また同じ理由でFaviconも表示できません。

静的ファイルを読み込む

これらのファイルにアクセスできるようにするためにはhttp.Handleを使います。

webtest.go

// Javascriptのファイルを読み込む.
http.Handle("/js/", http.StripPrefix("/js/", http.FileServer(http.Dir("templates"))))
// 画像ファイルを読み込む.
http.Handle("/img/", http.StripPrefix("/img/", http.FileServer(http.Dir("templates"))))

そして、ng buildで出力したindex.htmlを少し変更します。

MainPage.html

< !doctype html>
< html>
< head>
  < meta charset="utf-8">
  < title>NgCustomer< /title>
  < base href="/">
  < meta name="viewport" content="width=device-width, initial-scale=1">
  < link rel="icon" type="image/x-icon" href="img/favicon.ico">
< /head>
< body>
  < app-root>Loading...< /app-root>
  < script type="text/javascript" src="js/inline.bundle.js">< /script>
  < script type="text/javascript" src="js/polyfills.bundle.js">< /script>
  < script type="text/javascript" src="js/styles.bundle.js">< /script>
  < script type="text/javascript" src="js/vendor.bundle.js">< /script>
  < script type="text/javascript" src="js/main.bundle.js">< /script>
< /body>
< /html>

変更の理由は、http.Handleの第一引数で指定するURLが、index.htmlと重複するのを避けるためです。

第二引数「http.StripPrefix(“/js/”, http.FileServer(http.Dir(“templates”)))」は、
(例えば)アクセスされるURL「/js/inline.bundle.js」から「/js/」の部分を取り除き、
templatesディレクトリ内にあるinline.bundle.jsを取得します。

とりあえず、これでAngularのファイルを表示できるようになりました。
まだ怪しい要素もありますが、抜けなどがあれば追加・修正します。

他からアクセスできるようにする

確認の際は「go run webtest.go」を実行していますが、
例の如く?他のマシンからこのページにアクセスすることはできません。

どうするかというと、これも例によってオプションを追加します。

go run webtest.go -serve 0.0.0.0

参照

Angular

Golang

GolangでFizzBuzzしてみた

はじめに

ひょんなことから(Twitterで見た)4月にGolangの勉強会に参加することになりました。 いくら話を聴くだけとはいえ、何もわからずに参加しても楽しくないよなぁ…。

ということでちょっと触ってみることにしました。

何を作ってみるか?というところなのですが、
実は「プログラミングの基本」のような扱いを受けるFizzBuzzってやったことがなかったので、Golangでやってみることにしました。

念の為FizzBuzzのルールですが、

  1. 1から100までカウントアップする
  2. 3で割り切れる数なら「Fizz」と出力
  3. 5で割り切れる数なら「Buzz」と出力
  4. 3でも5でも割り切れる数なら「FizzBuzz」と出力
  5. それ以外の数はそのまま出力

というルールで進めます。

インストール

まずはGolangのインストールから。

Ubuntuではaptからインストールできるのですが、これは少しバージョンが古い(ver.1.6)です。

最新のver.1.8をインストールするには、下記のような方法がありそうです。

  1. 公式サイトからtar.gzをダウンロードして展開する。
  2. goenvを使う。
  3. リポジトリを追加してapt installする。

ただ、ググった中で今ひとつスタンダードなやり方が定まっていない気がしたため、1のtar.gzを使うことにしました。

インストール方法は下記を参照。

https://golang.org/doc/install

一点、/usr/localにファイルを置く下記の部分で権限エラーになったため、sudoで実行しました。

tar -C /usr/local -xzf go$VERSION.$OS-$ARCH.tar.gz

「go version」でバージョン情報が表示されたらOKです。

エディタはIntelliJ IDEAと迷いましたが、 Angularで使っていたこともありVisual Studio CodeにGolangプラグインをインストールして使っています。

通常のFizzBuzz

ではさっそくFizzBuzzってみましょう。

まずは至って普通の方法から。

package main

import "fmt"

func main() {
    useFor()
}
func useFor() {
    for i := 1; i <= 100; i++ {
        if i%15 == 0 {
            fmt.Println("FizzBuzz")
        } else if i%3 == 0 {
            fmt.Println("Fizz")
        } else if i%5 == 0 {
            fmt.Println("Buzz")
        } else {
            fmt.Println(i)
        }
    }
}

これを「go run ファイル名」で実行すればOKです。

簡単簡単。。。

}の位置にハマる

orz

✗ 間違い

if i%15 == 0 {
    fmt.Println("FizzBuzz")
}
else if i%3 == 0 {
    fmt.Println("Fizz")
}

○ 正しい

if i%15 == 0 {
    fmt.Println("FizzBuzz")
} else if i%3 == 0 {
    fmt.Println("Fizz")
}

「}」と「else if{」が同じ行にないと、「'syntax error: unexpected else, expecting }‘」とエラーになります。
厳しい…

rangeを使う

さて、For文には配列を作ってrangeを使う方法もあります。

package main

import "fmt"

func main() {
    useRange()
}
func useRange() {
    for _, v := range getNumArray(1, 100) {
        if v%15 == 0 {
            fmt.Println("FizzBuzz")
        } else if v%3 == 0 {
            fmt.Println("Fizz")
        } else if v%5 == 0 {
            fmt.Println("Buzz")
        } else {
            fmt.Println(v)
        }
    }
}
func getNumArray(fromNum int, toNum int) []int {
    // fromNumからtoNumまでの配列(Slice)を作る.
    resultArray := make([]int, toNum-fromNum+1)
    for i := 0; i < len(resultArray); i++ {
        resultArray[i] = fromNum + i
    }
    return resultArray
}

range

For文で使用し、配列(今回は[]int)を渡すことでIndexと値を取得できます。

今回は使用しませんが、Indexが必要な場合は「for _, v := 〜」の部分を「for i, v := 〜」のように変更します。
「for i := 〜」のように書くとIndexのみが取得でき、
「i」にはIndexが、「v」には値が格納されます。

「_」のように書いているのは、Goでは未使用の変数があるとエラーになってしまうためそれを避けるためです。

slice

「getNumArray」では、1から100までの数値の配列を作っています。
Rubyのように「(1..100)」とかできると楽だったのですが。

配列には「[3]int」のように要素数を指定したものと、そうでないSliceとがあります。

素数を指定する場合、「[3]int」と「[5]int」が別の型として扱われるため、
要素を指定する必要のないSliceを使っています。

Sliceのインスタンスを初期化する場合、「make(型, 要素数)」を使うことができます。

RxGoを使う

例えばC#FizzBuzzをする場合、Linqを使ってFor文は使わずにFizzBuzzすることができます。
しかし、残念ながら?Goには標準でそのような機能が用意されていないようです。

で、ふと思いだしたのがRxJavaで、StreamAPI的な処理ができるっていってたな〜、と。

ということは、GoにもRxが用意されていれば、同じようなことできるんじゃね?

と思って調べてみたら、そのままズバリRxGoがありました。

https://github.com/ReactiveX/RxGo

そんなこんなでRxGoを使ってFizzBuzzしてみます。

go get

RxGoはGolang標準の機能ではないため、ファイルをインストールしてやる必要があります。

そのために行うのがgo getです。
READMEにある通り下記をターミナルで実行するとインストールできます。

go get -u github.com/reactivex/rxgo

今回はobservable.Range(さっきとは違うやつです)を使ってみました。

package main

import (
    "fmt"
    "github.com/reactivex/rxgo/handlers"
    "github.com/reactivex/rxgo/observable"
)
func main() {
    useRx()
}
func useRx() {
    stream := observable.Range(1, 101)
    wait := stream.Subscribe(handlers.NextFunc(func(item interface{}) {
        if v, ok := item.(int); ok {
            if v%15 == 0 {
                fmt.Println("FizzBuzz")
            } else if v%3 == 0 {
                fmt.Println("Fizz")
            } else if v%5 == 0 {
                fmt.Println("Buzz")
            } else {
                fmt.Println(v)
            }
        } else {
            fmt.Println("Invalid value")
        }
    }))
    <-wait
}
  • 「observable.Range(1, 101)」で1から100まで(101は含まない)繰り返し実行するためのStreamを生成します。
  • 「wait」の型は「channel」で、非同期で実行される処理の完了を待ちます。
  • RxGoのWikiでは「handlers.NextFunc」の引数である「func(item interface{})」に型は指定されていないのですが、
    少なくともver.1.8ではエラーとなるため、型を指定しています。
  • 引数の型を「interface{}」とすることで、型を指定せずに引数を受け取ることができます。
  • 「if v, ok := item.(int); ok {」の行で引数の型を確認し、intであった場合は「v」に格納された値を使ってFizzBuzzします。

おわりに

これまでC#やKotlinをはじめ多機能な言語ばかりを触っていたため、あれもないこれもない、おまけにフォーマットや型に厳しい(´・ω・`)
というのがGolangに対する正直な感想です。

ただ、「Goに入ってはGoに従え」という言葉もありますし、これまでの言語のやり方が完璧か?と言われればそうではない気がするので、
Golangが別の方向に進化する、というのは興味深いと思います。

一点、言語的にシンプルだったり独自路線なのは良いと思うのですが、
その先に幸せが待っている、と信じさせる何かがないと結局各々が独自にフレームワークやオレオレ実装プラグインを持ちだして大変になるのでは…?
という危惧はあります。

まぁ素人考えなので、ちゃんとその辺りも考慮して進められているのかとは思いますが。

参照

ng-kyoto Angular Meetup#5で発表してきました

はじめに

3/20に行われたng-kyoto Angular Meetup #5で発表してきました。

ng-kyoto.connpass.com

https://masanori840816.github.io/ngkyoto5/slidepage.html

振り返って

いやぁ、緊張しました(苦笑)。

(完全に言い訳ですが)いつも以上に時間が取れず、
コードの解説にしてもどこを話すか、どこを飛ばすかがまとめきれていなかったと思います。

あと舞い上がりすぎて、今自分がどのぐらいの時間話しているかがわからなくなったり、
PCとスクリーンの画面の位置が逆になっていて、カーソルがうまくスクリーン側に移動できなかった、というのも反省点です。

現在の経過時間については、ストップウォッチアプリを探すか自分で作るかして、
自分のディスプレイで確認できるようにしておくようにします。

ディスプレイの設定(解像度やおそらく配置も)は、設定 > ディスプレイからできるようなので、
次回はちゃんと設定して臨みたいと思います。

いつもながら温かく見守ってくれた皆様には感謝感謝です。

内容について

基本的にはここ最近の記事の内容をまとめた感じになっています。
(というかそのために調べた結果を記事にしていました)

この中でも積み残し課題としていたアニメーションの停止について。

そもそもAngularではアニメーションクラスの元になっているCSSアニメーションを直接書くことも可能、
ということなので、そちらで解決するのも良さそうです。

アニメーションクラスで簡単にまとめて書ける強みもあるので、状況によって切り分ける感じでしょうか。

ただ、アニメーションクラスを使わないとできない表現として、
URLを変えつつ、該当のページがアニメーションで移動してくる、といった表現をする場合が挙げられました。

これはちょっと実装が大変そうではありますが、近いうちに試してみたいところです。

他の方の発表について

Angular自体の話では、Anglar-cli、ver.4.0、DIとところどころ理解が追いつかない部分もありましたが、
どれも今後試してみたい内容でした。

※なお発表を聴いて、こっそりブログ記事の「Angular.js」を「Angular」に直したのは内緒です

ということで、フレームワーク選定のお話でも挙げられたVue.jsやReactが気にはなりつつも、
フロントについてはもう少し続けてAngularを進めたいと思います。

業務で触っていれば良いのですが、現状触れるのがプライベートのみなので…。

WebComponentについてはただただすごいなぁ〜、と思うばかりでした(苦笑)。

まぁ今のJavascriptやHTMLに取って代わるもの、というわけでもないようで、
ChromeFirefoxでの対応もあったということですし、ニュースなどは追いかけるようにしようかな、と思いました。

おわりに

ということで、最初から最後まで楽しい勉強会でした。

次回はver.5.0が出る頃、ということなので、
今回はアニメーションに関連する部分に絞っていましたが、もう少し基本的なところから触っていって、
もし機会があればまた発表にも挑戦してみたいと思います。

主催の皆様、会場を提供していただいたはてな様、参加の皆様、ありがとうございましたm( )m

おまけ

スライドにはRemark.jsを使ってGitHub Pagesを使っているのですが、
スライド資料として認識されないのは少し不便ですね。。。

MarkDown形式で書くだけでそれらしく表示されるため便利ではあるのですが。

AngularのAnimate2

はじめに

前回の続きです。

公式ドキュメントのボタンのように、マウスオーバーした時にボタンが浮き上がって見える、という動きを真似してみます。

マウスオーバーとマウスが離れた時のイベントを取得する

まずはマウスカーソルをボタン上に移動させた場合(マウスオーバー)と、ボタン外に移動させた場合(マウスアウト)とでイベントを取得します。

gui-anime.component.html

< button [@positionState]='state' (mouseover)="onButtonIn()" (mouseout)="onButtonOut()">
タイトル
< /button>

gui-anime.component.ts

〜省略〜
export class GuiAnimeComponent implements OnInit {
  private state: string;
  constructor() { }
  ngOnInit() {
  }
  private onButtonIn(){
    this.state = 'active';
  }
  private onButtonOut(){
    this.state = 'inactive';
  }
}

CSS

影や位置のしていなど、アニメーションで切り替えたい要素以外の装飾については、angular-cliで「ng g component 〜」実行時にhtmlやtsなどと同時に生成されるcssに書くことにします。

gui-anime.component.css

.positionanime{
    background-color: #fff;
    border-radius: 8px;
    width:20vw;
    height: 10vh;
    position: relative;
    -webkit-border-radius: 8px;
    -moz-border-radius: 8px;
    margin: 10px;   
}

ボタンの背景色、角丸設定などを行っています。

アニメーション

それでは、アニメーション部分にとりかかります。

今回主に実装するのは下記の動きです。

  • 枠の太さ、色を非アクティブ・アクティブな時で切り替える
  • ボタンが非アクティブなときは小さめの影を、アクティブなときは大きめの影を表示する
  • ボタンを主にZ方向に移動させる

枠の色や太さを指定します。

gui-anime.component.ts

import { Component,
  Input,
  trigger,
  state,
  style,
  transition,
  animate,
  AnimationPlayer,
  OnInit } from '@angular/core';

@Component({
  selector: 'app-gui-anime',
  templateUrl: './gui-anime.component.html',
  styleUrls: ['./gui-anime.component.css'],
  animations: [
    trigger('positionState', [
      state('active', style({
        border: '3px solid #2196F3',
        boxShadow: '0px 5px 6px 3px #CECECE',
        transform: 'translate3d(-1px, -1px, 50px) scale(1.02)',
        zIndex: 30
      })),
      state('inactive',   style({
        border: '1px solid #CECECE',
        boxShadow: '0px 1px 3px 1px #CECECE',
        transform: 'translate3d(0px, 0px, 0px) scale(1)',
        zIndex: 10
      })),
      transition('active => inactive', animate('200ms ease-in-out')),
      transition('inactive => active', animate('200ms ease-in-out'))
    ])
  ]
})
〜省略〜
  • 枠の太さの指定方法として、px指定の他にも「thin」や「medium」などを使うこともできます。
    が、今回のようにアニメーションさせる場合は一瞬枠が指定より太くなるなどの問題が発生するため、pxなどで指定しておいた方が良さそうです。

影は「boxShadow」で指定します。

gui-anime.component.ts

〜省略〜
  animations: [
    trigger('positionState', [
      state('active', style({
        border: '3px solid #2196F3',
        boxShadow: '0px 5px 6px 3px #CECECE',
        transform: 'translate3d(-1px, -1px, 50px) scale(1.02)',
        zIndex: 30
      })),
      state('inactive',   style({
        border: '1px solid #CECECE',
        boxShadow: '0px 1px 3px 1px #CECECE',
        transform: 'translate3d(0px, 0px, 0px) scale(1)',
        zIndex: 10
      })),
      transition('active => inactive', animate('200ms ease-in-out')),
      transition('inactive => active', animate('200ms ease-in-out'))
    ])
  ]
})
〜省略〜
  • 指定する値は 1:影のX座標値 2:Y座標値 3:ブラー(値が大きいほど広くぼんやりした影になる) 4:影の大きさ 5:影の色 です。

移動

アニメーションで要素を移動させたい場合、「translate」を使用します。 X、Y、Zの特定方向のみに移動させるときは「translateX」「translateY」「translateZ」、すべてを移動させる場合は「translate3d」を使います。

gui-anime.component.ts

〜省略〜
  animations: [
    trigger('positionState', [
      state('active', style({
        border: '3px solid #2196F3',
        boxShadow: '0px 5px 6px 3px #CECECE',
        transform: 'translate3d(-1px, -1px, 50px) scale(1.02)',
        zIndex: 30
      })),
      state('inactive',   style({
        border: '1px solid #CECECE',
        boxShadow: '0px 1px 3px 1px #CECECE',
        transform: 'translate3d(0px, 0px, 0px) scale(1)',
        zIndex: 10
      })),
      transition('active => inactive', animate('200ms ease-in-out')),
      transition('inactive => active', animate('200ms ease-in-out'))
    ])
  ]
})
〜省略〜
  • 上記では位置の指定をpxでしていますが、X、Yについては%を使って指定することもできます。
  • translateとscaleなど、「transform」に複数の要素を指定したい場合、上記のようにそれぞれをスペース区切りで指定します。

Z-Index

今回は必要ないのですが、例えばボタン同士がひっついていて、マウスオーバー中のボタンを前に出したい、という時に、うまく該当のボタンが前に出てくれない場合があります。

そのような場合、「z-index」を使い、マウスオーバー時の値をそれ以外より大きく設定することで解決できます
(※CSSで「position: relative;」などの指定をする必要があります)。

gui-anime.component.ts

〜省略〜
  animations: [
    trigger('positionState', [
      state('active', style({
        border: '3px solid #2196F3',
        boxShadow: '0px 5px 6px 3px #CECECE',
        transform: 'translate3d(-1px, -1px, 50px) scale(1.02)',
        zIndex: 30
      })),
      state('inactive',   style({
        border: '1px solid #CECECE',
        boxShadow: '0px 1px 3px 1px #CECECE',
        transform: 'translate3d(0px, 0px, 0px) scale(1)',
        zIndex: 10
      })),
      transition('active => inactive', animate('200ms ease-in-out')),
      transition('inactive => active', animate('200ms ease-in-out'))
    ])
  ]
})
〜省略〜

ngFor

さて、このボタンを1つだけではなく複数並べてみたいと思います。

ただし、全く同じ動作をするものをコピペでは作りたくない。。。
ということで、interfaceを作ってボタン表示に必要な要素を持つ配列を作り、「ngFor」を使って並べてみることにします。

interface

「ng g interface AnimeButton」をTerminalで実行して、interfaceを作成します。

anime-buton.ts

export interface AnimeButton {
    id: number;
    pageTitle: string;
    discription: string;
    state: string;
}

で、Componentでこのinterfaceの配列を作ります。

gui-anime.component.ts

export class GuiPositionanimeComponent implements OnInit {

  private animeButtons: AnimeButton[] = [
    {id: 0, pageTitle: 'Page1', discription: 'Page No.1', state: 'inactive'},
    {id: 1, pageTitle: 'Page2', discription: 'Page No.2', state: 'inactive'},
    {id: 2, pageTitle: 'Page3', discription: 'Page No.3', state: 'inactive'},
  ];

  constructor() { }
  ngOnInit() {
  }

  private onButtonIn(targetId: number){
    this.animeButtons[targetId].state = "active";
  }
  private onButtonOut(targetId: number){
    this.animeButtons[targetId].state = "inactive";
  }
}

これをHTMLにセットします。

gui-anime.component.html

< section class='animeButtons'>
  < div *ngFor="let aButton of animeButtons">
    < button class='positionanime' [@positionState]=aButton.state (mouseover)="onButtonIn(aButton.id)" (mouseout)="onButtonOut(aButton.id)">
    {{aButton.pageTitle}}
    < /button>
  < /div>
< /section>
  • まず親となるタグで「class=‘animeButtons'」(「animeButtons」はComponentで定義した変数名)を指定し、
    「*ngFor=“let aButton of animeButtons"」でC#のForeach文のように値を取り出しています。

これで比較的シンプルに複数ボタンが表示できます。

f:id:mslGt:20170316024419j:plain

疑問点

ngForから値を取り出す部分で、「{{aButton.pageTitle}}」のように値を取り出すサンプルは見つかったのですが、 「[@positionState]=aButton.state」の「aButton.state」部分のような書き方は見つけられませんでした。

とりあえず動作はしているのですが、これで正しいのかが不安なところです。

これについては正誤が判断つき次第追記します。

参考

角丸

移動

z-index

AngularのAnimate

はじめに

前回に続いてAngular.jsのお話。

昔Angular.jsの弱点としてアニメーションが挙げられていた気がしていたので(別のものと混同している恐れもあります)、
ちょこっと調べてみたところver.1.14位からちゃんと対応されていました。

というわけで今回は、公式ドキュメントを中心に試してみたことをメモることにします。

あ、ちなみに扱うのはver.2系だけです。

アニメーションさせてみる

まずはボタンをクリックした時に、ボタンのサイズ変更、及び背景色を変更するサンプルを試してみました。

前回作成したプロジェクトに、「ng g component gui-anime」でコンポーネントを追加し、それをあれこれいじっています。

まずはコードから。

Htmlにアニメーションのターゲット兼トリガーとなるボタンを追加し、
Typescript側でアニメーションとアニメーション開始のトリガーとなる要素(State)を作成します。

gui-anime.component.html

< p>
  gui-anime works!
< /p>
< button [@buttonState]="state" (click)="onAnimeButtonClicked()">Anime< /button>

gui-anime.component.ts

import { Component,
  Input,
  trigger,
  state,
  style,
  transition,
  animate,
  OnInit } from '@angular/core';

@Component({
  selector: 'app-gui-anime',
  templateUrl: './gui-anime.component.html',
  styleUrls: ['./gui-anime.component.css'],
  
  animations: [
    trigger('buttonState', [
      state('inactive', style({
        backgroundColor: '#eee',
        transform: 'scale(1)'
      })),
      state('active',   style({
        backgroundColor: '#cfd8dc',
        transform: 'scale(1.5)'
      })),
      transition('inactive => active', animate('100ms ease-in')),
      transition('active => inactive', animate('100ms ease-out'))
    ])
  ]
  
})
export class GuiAnimeComponent implements OnInit {
  public state: string;
  constructor() { }
  
  ngOnInit() {
    this.state = "inactive";
  }
  private onAnimeButtonClicked(){
    this.state = (this.state === "active")? "inactive": "active";
  }
}

イベント

Angularではクリックなどのイベントは()で囲まれます。

今回はボタンクリックのイベントを取得したいため、gui-anime.component.htmlで「(click)」を設定しています。

(click)="onAnimeButtonClicked()"

アニメーションのトリガーとなる要素(State)の指定と値の変更

上記のコードでは、クリックしたらアニメーションが始まるのではなく、gui-anime.component.tsで設定している「buttonState」が特定の値(inactive、active)に変更された場合に開始されるようになっています。

これをボタンに設定して、クリックイベントで「state」の中身を入れ替える、という処理を行っています。

[@buttonState]="state"

[@〜]とすることで、コンポーネントの持つ要素(今回はアニメーションのトリガーとなる「buttonState」)の「state」との関連付けができるようです。

[]は下記のPropertyBindingか?とも思うのですが、確信が持てず。。。
この辺りは追って調べてみることにします。

アニメーション

さて、今回のメインであるアニメーションです。

animations: [
    trigger('buttonState', [
      state('inactive', style({
        backgroundColor: '#eee',
        transform: 'scale(1)'
      })),
      state('active',   style({
        backgroundColor: '#cfd8dc',
        transform: 'scale(1.5)'
      })),
      transition('inactive => active', animate('100ms ease-in')),
      transition('active => inactive', animate('100ms ease-out'))
    ])
  ]

ここでは2つのstateが指定されており、
先ほど登場した「buttonState」の値が「inactive」または「active」(それぞれのstateの第一引数)に変更された時に、
第二引数でそれぞれ指定しているCSSの値(背景色、スケール値)が適用される内容となっています。

で、その後ろに連なる2つのtransitionによって、「inactive」から「active」またはその逆への変化がアニメーションで繋げられる、という処理が行われます。

transitionの第二引数にてアニメーションの時間と後述するEaseTypeが指定されています。

EaseType

transitionで指定されているEaseType。

これを指定することで、アニメーション(今回は拡大・縮小)する速度が一定ではなく最初だけゆっくり(ease-in)や、最後だけゆっくり(ease-out)といった変化をつけることができます。

UnityのTween系でも登場しますね。
そちらでは覚えきれない位たくさんの種類のEaseTypeから目的に合致したものを利用することができます。

ではAngularではどうかというと、実は今回使っているアニメーション、というのはAngular独自のものではなくCSSのアニメーションです。

で、CSSで使用できるものはというと、下記のみです。

  • ease(デフォルト)
  • linear(はじめから終わりまで一定)
  • ease-in(最初だけゆっくり)
  • ease-out(最後だけゆっくり)
  • ease-in-out(最初と最後だけゆっくり)
  • cubic-bezier()(カスタム)

SCSSなどであればEasing Function 早見表で紹介されているようなEaseTypeが使えるようなのですが、
CSSでは種類がかなり少ない(またはカスタムで作成する)ようです。

カスタムについては次回辺りためしてみたいと思います。

アニメーションの開始・終了イベント

複数のアニメーションを連続で実行したい、アニメーションの実行後に何か処理をしたい、といった場合に、それを検知するイベントが欲しいところ。

そのような場合は、クリックイベントのように「start」「done」イベントを設定します。

Start

< button [@buttonState]="state" 〜省略〜(@buttonState.start)="onAnimeStarted()">Anime< /button>

Done

< button [@buttonState]="state" 〜省略〜(@buttonState.done)="onAnimeFinished()">Anime< /button>

アニメーションのトリガーである「buttonState」に紐付けられていますね。

あとはクリックイベントと同じく、gui-anime.component.tsに「onAnimeStarted()」や「onAnimeFinished()」を追加するだけです。

参考

Animation

EaseType

UbuntuでAngular

はじめに

突然ですがラップトップのOSをUbuntuに変更しました。

VirtualBoxXubuntuを入れたりして遊んでいたのですが、Gnome3を使ってみたくなったので。

Unityの正式版がないことだけが問題なのですが、外で書くことは少ないため、
まぁMac miniで書くことにしても良いかと。

開発版Unityは依存関係の問題でインストールに失敗したのですが、こちらもおいおい試してみる予定です。

まぁ以前もXubuntuなどを使っていたこともあり、概ね問題なくやっぱり新しいマシンで表示するときれいだな〜と思ってはいるのですが、一点ディスプレイが時々点滅するのが気になってはいます。
Gnome3だけでなく、Unityにした場合も、Waylandにした場合も再現するため、ディスプレイマネージャーの問題ではないのか。。。?と思っています。

こちらも問題が解決すれば記事にしたいと思います。

さてさて、今回の本題ですが、ふとAngular.jsに挑戦してみることにしました。
ver.1系は以前少しだけ触ったことがありましたが、今回は2系です。

Angular-cliを使ってHello world(正確にはApp works)を表示するところまでをまとめることにします。

設定

ディレクトリ名

とりあえずディレクトリ名を英語に変更します。

Mozc

また、Gnome3の場合デフォルトだと日本語入力(Mozc)が起動しないようです。

設定 > 言語サポート で追加のパッケージをインストールし、「キーボード入力に使うIMシステム」を「fcitx」に変更します。
一旦ログインし直すとMozcが使えるようになる。。。はず。

ショートカット

設定 > キーボード > ランチャー で、「ホームフォルダ」のショートカットを「Super + E」にします。
これでWindowsと同じくWindowsキー + Eキーでファイルエクスプローラが開きます。

開発ツール

開発するにあたって使用するエディタはVisual stutio codeとします。

以前参加した勉強会でもおすすめされていたので、まずはそこに乗っかってみようかと。
インストール自体はdebパッケージをダウンロードして、ダブルクリックでインストール

https://code.visualstudio.com/download

ということで、細かいことを考えなければほとんどWindows同様にインストールが可能になります。

プラグインとしては「Angular 2 + Snippets」をインストールしました。

Node.jsのインストール

Angular.jsの環境を整える前に、まずはNode.jsのインストールですね。

ここを参考にaptでインストールしてみることにします。

パッケージマネージャを利用した Node.js のインストール - Node.js

curl -sL https://deb.nodesource.com/setup_7.x | sudo -E bash -
sudo apt update
sudo apt install -y nodejs

完了後、「node -v」や「npm -v」でバージョン番号が返ってくるかを確認しておきます。

Angular-cliのインストールに失敗する

さて、準備が整ったらAngular-cliをインストールします。

npmを使ってインストールするだけなので、簡単簡単…。

npm install -g @angular/cli

※「npm install -g angular-cli」としている情報が多いのですが、実行してみるとそれはDeprecatedになったから「@angular/cli」にしてね、とWarningが出ていました。

失敗しましたorz

エラーメッセージをたどると、どうやら権限エラーの様子。

ググッてみたら出てきました。

私の場合は「対策2」で解決できました。
Node.jsのインストールを管理者として行っているから…?

インストールに成功したら、「ng help」でヘルプが表示されることを確認します。

問題がなければ「ng new プロジェクト名」でプロジェクトを作成します。

IP Addressで開く

作成したプロジェクト直下で、「ng serve」を実行すると「localhost:4200」で「App works!」というページが表示されます。

これだと他の端末(スマホとか)からアクセスできないので、「localhost」部分をIP Addressに変更してアクセスしてみよう!

失敗しましたorz

どうもアクセスが拒否されている様子。

う〜ん、と思っていつつググったら方法が見つかりました。

ng serve --host 0.0.0.0

全ホストからのアクセスを許可することで、他の端末からでもアクセスすることができるようになりました。

そういえばSinatraでも同じ仕様で、同じ対処法を適用した覚えが…。

とにかくこれで、PC・スマホからアクセス可能になりました。

次回からは実装部分のお話になる…はず。

参考

Ubuntu設定

Node.js

Angular.js

【Unity】カメラを回転させるメモ

はじめに

とあるきっかけで、マウス操作などに合わせてカメラをあれこれ回転させてみたくなったので、そのメモを残します。

カメラを回転させる

まずは画面上をマウスの左クリックボタンでドラッグしたときに、カメラが回転するようにしてみます。

using UnityEngine;

public class MainCtrl : MonoBehaviour {
    public Camera MainCamera;
    private Vector3 lastMousePosition;
    private Vector3 newAngle = new Vector3(0, 0, 0);

    private void Update () {
        if (Input.GetMouseButtonDown(0))
        {
            // マウスクリック開始(マウスダウン)時にカメラの角度を保持(Z軸には回転させないため).
            newAngle = MainCamera.transform.localEulerAngles;
            lastMousePosition = Input.mousePosition;
        }
        else if (Input.GetMouseButton(0))
        {
            // マウスの移動量分カメラを回転させる.
            newAngle.y -= (Input.mousePosition.x - lastMousePosition.x) * 0.1f;
            newAngle.x -= (Input.mousePosition.y - lastMousePosition.y) * 0.1f;
            MainCamera.gameObject.transform.localEulerAngles = newAngle;

            lastMousePosition = Input.mousePosition;
        }

    }
}

これでカメラを中心に回転させることができます。

オブジェクトを中心に回転させる

例えばこれを、何らかのオブジェクトを中心に回転させたい場合。

その中心となる場所にGameObject(表示が不要なら空でOK)を置き、カメラはその子どもにし、回転処理を親オブジェクトに対して実行すればOKです。 (↑のコードのCameraをGameObjectに変更してください)

f:id:mslGt:20170218073417j:plain

対象物に向けて回転させる

マウス操作でカメラや親オブジェクトを回転させたあと、何らかのきっかけで元の角度に戻してみます。

一瞬で元に戻すだけであれば、ターゲット(今回はCube)に対してLookAtをしてあげればOK。
なのですが、今回は滑らかに動かしたかったため、DOTweenを使って実現してみました。

using UnityEngine;
using DG.Tweening;

public class MainCtrl : MonoBehaviour {
    public GameObject MainCamera;
    public GameObject TargetObject;

    private Vector3 lastMousePosition;
    private Vector3 newAngle = new Vector3(0, 0, 0);

    private void Start()
    {
        DOTween.Init(false, true, LogBehaviour.ErrorsOnly);
    }
    private void Update () {
        if (Input.GetMouseButtonDown(0))
        {
            newAngle = MainCamera.transform.localEulerAngles;
            lastMousePosition = Input.mousePosition;
        }
        else if (Input.GetMouseButton(0))
        {
            newAngle.y -= (Input.mousePosition.x - lastMousePosition.x) * 0.1f;
            newAngle.x -= (Input.mousePosition.y - lastMousePosition.y) * 0.1f;
            MainCamera.gameObject.transform.localEulerAngles = newAngle;

            lastMousePosition = Input.mousePosition;
        }

        if (Input.GetMouseButtonUp(1))
        {
            var rotation = Quaternion.LookRotation(TargetObject.transform.position - MainCamera.transform.position);

            MainCamera.transform.DORotateQuaternion(rotation, 0.5f)
                .SetEase(Ease.InOutBounce)
                .OnComplete(()=> Debug.Log("Finished"));
        }
    }
}