20190501のvue.jsに関する記事は13件です。

Vue.js入門しました

はじめに

こんばんは。またたびです。
これまで書いてきた3つの記事全て100viewを超えており、ブログ等を書いたことのないしがない大学院生にとってはとても嬉しい限りです。
読んでくださりありがとうございます。
今回はゴールデンウィーク直前位から勉強を始めたVue.jsについて、簡単にまとめていきたいと思います。
まだVue.jsを触ったことがない方の参考になれば幸いです。
いつも通り参考にさせていただいた本と、プラスでおすすめの本を記述しておくのでよければ読んでみてください。

Vue.jsとは

Vue.jsとはJavaScriptのフレームワークです。
近年注目されているフレームワークで、書きやすい、拡張性が高い、学習コストが低いといったメリットがあります。
PHPのフレームワークであるLarabelのフロントエンドに用いられていることが多くあるそうで、実際に私のアルバイト先でも用いられています。

HTML、Javascriptについての理解がない方は、その2つについて学習してから手を付けることをおすすめします。また、それと同時にCSSについても学習しておくと、非常に便利です。
コードはHTMLと大差ないため、非常に書きやすいと思います。

また、インターネットがつながる状況であれば、特別なパッケージを導入することなくプログラムの記述が可能であることも利点の一つに感じています。

以下にコードの一例を示します。(動作確認済みです)

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Vue</title>
    <link href="main.css" rel="stylesheet">
</head>

<body>
    <div id="app">
        <!-- ここに処理を書く -->
        <p> {{ text }} </p>
    </div>
    <script src ="https://cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue.js"></script>
    <script src="main.js"></script>
</body>
</html>
main.java
var app = new Vue({
    el: '#app',
    data: {
        text: 'Hello Vue'
    }
})

HTMLを勉強した方ならご存知かもしれませんが、index.html内の

はページを表示した際のtabに表示される文字になっています。
そして、index.htmlに{{ text }}と記述してある部分にmain.javaに記述されているtext: 'Hello Vue'が表示されるわけです。
同じディレクトリにいれてHTMLを表示すればわかりやすいと思うのでやってみてください。

このように、HTML中をごちゃごちゃさせることなくなるのも利点かなと思います。
アルバイト先でVue.jsを用いていない案件だと、HTMLの中に長文が書いてあることが多々ある…

今回は入門ということでHTML、Javascript、は理解しているが、Vue.jsはまだ理解出来ていないという方に記事を書いてみました。
もう少し理解したらまた記事を書いていきます。
本日はここまでにします。

おわりに

GW毎日連続して記事をあげると意気込んでいましたが、良いinputをしていないにも関わらずoutputをするのはよろしくないことに今更ながら気が付いたのでもう少し質を上げて投稿しようと思います。明日から3日間は私的な旅行のためおやすみさせていただきます。
また、Vue.jsのおすすめの本や勉強の仕方、間違いや表現の悪い部分があればご指摘いただけると幸いです。

参考文献

私が実際に参考にさせていただいた本(イラストや色があり読みやすいです)
基礎から学ぶ Vue.js

後輩に少し読ませてもらった本(文章とコードが非常に多くなりますが、非常にわかりやすく実務的な力を身に着けたいのであればこちらがいいかなと)
Vue.js入門 基礎から実践アプリケーション開発まで

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

Vue.js+FirebaseでNewsをランダムで閲覧できるアプリを作りました

トップニュースをざっと知りたい人向けにトップニュースをランダムで閲覧できるサービスを作りました

サービス

News Catch

ページをロードしたときに1つニュースを取得し表示します。
newscatch.png

三角のボタンを押すことでニュースを再取得し表示します。
nextbtn.png

Twitterボタンを押すことでTwitterでニュースのURLをシェアすることができます。
twitterbtn.png

使った技術

フロントエンド

フロントエンドはVue.jsを使用しました。
Nuxt.jsを使おうと思ったのですがSEO対策するほどの規模のサービスではないなと思いVue.jsを今回は使用しました。
CSSに関しては今回はシンプルなデザインなのでCSSフレームワークを使わずSCSSを使いました。
SCSSはCSSの構文で入れ子構造にできるのでとても便利です。

バックエンド

バックエンドはFirebaseを使用しました。
FirebaseはGoogleが提供しているmBaaSと呼ばれるサービスです。
今回はFirebase HostingとFirebase Cloud Storageを使用しました。

Firebase Hostingはデプロイ用、Firebase Cloud StorageはOGP画像の保存用で使用しました。

API

使用したAPIはNews APIです。
News API

非商用なら無料で利用可能で日本のニュースもあります。
ドキュメントもきちんと整備されておりとても使いやすいと感じました。

まとめ

サービスを開発していくにあたって自分の力のなさが目にみえたのでもっと勉強しようと思います。

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

VuePress 1系で爆速に爆速ブログを作ってみる

vuepress v1でブログを作ってみる

自分のブログを作ってみたいなーと思いつつ、なかなか時間もかけることができないし、かと言って、自由にデザインをいじれないのは嫌だなーとことで色々と方法を模索していたのですが、最近出たvuepress1系であれば、手軽に簡単にブログを作ることが出来そうだったのでやってみました。

実際に作ったものがこちらです。
https://vigorous-einstein-5b6e33.netlify.com/
動作はスピーディーで気持ちがいいです。

ちなみに全ては紹介できませんが、以下の技術を用いて、今回ブログを作ってみました

作成し始めて、リリースさせるまで、おそらく半日あれば完成させることができるかと思います。

vuepress導入

まずは、vuepressをyarnでインストールするところから。

yarn add -D vuepress@next

インストールするとですね、node_modules、package.json、yarn.lockが生成されます。

これでvuepressの環境自体はできているのですが、ページやvueコンポーネントは初期の段階では作成されず、自分でこれから作成して行くことになります。

ということで、まずはディレクトリを作っていきましょう!(公式では、docsとなっているのですが、なんかsrcの方が個人的にはしっくりくるので、srcにしちゃいました)

mkdir src

package.jsonのscriptをいじります。

{
  "scripts": {
    "dev": "vuepress dev src",
    "build": "vuepress build src"
  }
}

これで、yarn devでsrc配下のファイルをビルドしてサイトのコンテンツを作ってくれるようになります。

ここで試しに、トップページを作ってみましょう。

echo '# Hello VuePress' > src/README.md

yarn dev

http://0.0.0.0:8080/にアクセスすると、Hello VuePressの文字が出ているはずです。

設定ファイルをいじってカスタムする

ここからは設定ファイルをいじりながら、vuepressを個人仕様にカスタマイズしていきましょう。下記のディレクトリ構造のように、.vuepressディレクトリを作り、その下にconfig.jsファイルを作りましょう。

ディレクトリ構成
blog
├─ src
│  ├─ README.md
│  └─ .vuepress
│     └─ config.js
└─ package.json

詳しくは、ドキュメントを見れば、どんなことを書けばいいのか、わかると思います。configドキュメント

最初はこんな感じでしょうか。

.vuepress/config.js
module.exports = {
  title: "blog",
  themeConfig: {
    description: "ここにはディスクリプションを入れます。",
    nav: [
      { text: "About", link: "/about/" } // ここはnav barのメニュー表示/aboutページは後ほど
    ]
  }
};

トップページの設定

トップページに出ている内容は、まだ、Hello, VuePressだけで味気ないので、ここもカスタマイズしましょう。
vuepressでは先ほど作ったmdファイルにfrontmatterを設定すると、各ページごとのメタ情報などの設定をすることができます。
またhome:trueとすると、vuepressのトップページテーマを使うことができます。

src/README.md
---
home: true
heroText: Yudium
description: ここにはディスクリプションを入れます。
footer: Copyright © 2019-present Youdai
---

Aboutページを作ってみる

/about/のリンクがメニューに出ていると思うので、/about/にアクセスした際にページが表示されるようにaboutページを作ってみましょう。と言っても簡単です。下記のように、aboutディレクトリを作り、その下にmarkdown形式のファイルを置くだけです。

ディレクトリ構成
blog
├─ src
│  ├─ README.md
│  │─ about
│  │└─ index.md
│  │
│  └─ .vuepress
│     └─ config.js
│
└─ package.json

about/index.md
## About

このようにすれば、/about/にアクセスした際に、ページの内容が表示されます。

ブログ機能作成

Vuepress1系からプラグインを入れて、簡単に機能を実装することができるようになったようです。
詳しいプラグインについてはこちら
なので、このプラグインを用いて、ブログ機能をチャチャっと作っちゃいます!

使うのは、vuepress/plugin-blogというプラグインになります。
まずは、yarnでプラグインを入れます。
yarn add -D @vuepress/plugin-blog@next

そしたら、config.jsにpluginsを記述してください。これで設定はできましたが、まだブログ機能はできていません。

.vuepress/config.js
module.exports = {
  plugins: [
    "@vuepress/blog",
  ],
  // 中略 // 
}

ここからは実際にブログの投稿、そして、ブログのカテゴリーやタグの一覧機能を作っていきます。
まず最初に、ブログ機能を完成させた時のディレクトリ構造は以下のようになります。
ブログの投稿については、.vuepress/_posts配下にmdファイルを作成して行く形で、markdown形式で記事を投稿することができます。

blog
├─ src
│  ├─ README.md
│  │─ about
│  │└─ index.md
│  │─ tag
│  │└─ index.md
│  │─ category
│  │└─ index.md
│  │
│  └─ .vuepress
|     └─ _posts
|     │     └─ 1.md(_posts配下にファイルを置いて行くと、ブログ記事として見なされます。)
│     └─ config.js
│     └─ components
│      │─ Tags.vue
│      │─ Tag.vue
|      │─ Category.vue
│      │─ Categories.vue
│      │─ Blogs.vue 
│
└─ package.json

投稿する際の、ブログのメタ情報は、Front Matterで設定できます。

1.md
---
title: ブログ始めました!
description: ディスクリプション
date: 2019-04-27
img: ./img/post_1.jpg
sidebar: auto
category: 雑記
tags:
  - 日記
  - 独り言
---

vuepressはmd形式で作成したページが$pageの中に格納されています。
また、_posts配下にあるブログ記事はtypeがpostになっているので、下記のように、type == 'post'になっているページををループすれば、ブログページ一覧ができます。

ブログ一覧を作るコンポーネント

Blogs.vue
<template>
  <div v-for="post in posts">
    <a href="post.path">{{post.title}}</a>
  </div>
</template>

<script>
    export default {
        computed: {
            posts() {
                return this.$site.pages
                    .filter(x => x.type === 'post')
                    .sort((a, b) => new Date(b.frontmatter.date) - new Date(a.frontmatter.date));
            }
        }
    }
</script>

作成したタグは$tag、カテゴリーは$categoryに格納されるので、こちらも同じ感じで、ループさせれば一覧ページを作るコンポーネントが出来上がります。

タグ一覧を作るコンポーネント

Tags.vue
<template>
  <div>
      <a v-for="tag in Object.keys($tags._metaMap)":href="tag">{{tag}}</a>
  </div>
</template>

カテゴリー一覧を作るコンポーネント

Categories.js
<template>
  <div>
    <ul v-for="category in Object.keys($categories._metaMap)">
      <h3><a :href="$categories._metaMap[category].path">{{ category }}</a></h3>
      <li v-for="post in $categories._metaMap[category].posts">
          <a :href="post.path">{{post.title}}</a>
      </li>
    </ul>
  </div>
</template>

ここまで作ったコンポーネントは、mdファイルの中で、コンポーネントとして呼び出すことができるので、例えばタグ一覧だったら、
下のような感じで呼び出すことができます。

src/tag/index.md
----
title:タグ一覧です
----
<Tags />

まとめ

割と、駆け足で書いてしまいましたが、こんな感じになります。
まだまだプラグインが足りなかったり、ドキュメントも薄かったりして、苦戦するとは思うのですが、Vue.jsで書かれているので、コンポーネントを作ったりするのは、すごく楽です。

またWordpressと同じように、すぐ使えるテーマみたいなものもあるので、こちらを使ってもいいかもしれません。

Theme Guide | vuepress-theme-meteorlxy
VuePressにテーマを適用させる その2 | web系のメモ

まだまだドキュメントが足りなかったり、苦戦する部分もありましたが、今後vuepressがどのように進化していくのか期待大ですね。


参考
Getting Started | VuePress 1.x

https://to-hutohu.com/2018/12/30/vuepress-blog/#%E8%A8%98%E4%BA%8B%E3%82%92%E6%9B%B8%E3%81%8F

Information on blog and pagination plugin missing from website · Issue #1091 · vuejs/vuepress · GitHub

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

VuePress 1.xでブログを作ってみる

vuepress v1でブログを作ってみる

自分のブログを作ってみたいなーと思いつつ、なかなか時間もかけることができないし、かと言って、自由にデザインをいじれないのは嫌だなーと、色々と方法を模索していたのですが、最近出たvuepress1.xであれば、手軽に簡単にブログを作ることが出来そうだったので、試しに作ってみました。ちなみに、vuepress0.xと1.xは、大きく変わっているので、その辺り間違えないように注意です!

実際に作ったものがこちらです。
https://vigorous-einstein-5b6e33.netlify.com/
動作はスピーディーで気持ちがいいです。

ちなみに全ては紹介できませんが、以下の技術を用いて、今回ブログを作ってみました

(ミーハーなので、Tailwindも使ってみました。こちらはまた時間があれば、記事としてまとめます)

作成し始めて、リリースさせるまで、おそらく半日あれば完成させることができるかと思います。

vuepress導入

まずは、vuepressをyarnでインストールするところから。

yarn add -D vuepress@next

インストールするとですね、node_modules、package.json、yarn.lockが生成されます。

これでvuepressの環境自体はできているのですが、ページやvueコンポーネントは初期の段階では作成されず、自分でこれから作成して行くことになります。

ということで、まずはディレクトリを作っていきましょう!(公式では、docsとなっているのですが、なんかsrcの方が個人的にはしっくりくるので、srcにしちゃいました)

mkdir src

package.jsonのscriptをいじります。

{
  "scripts": {
    "dev": "vuepress dev src",
    "build": "vuepress build src"
  }
}

これで、yarn devでsrc配下のファイルをビルドしてサイトのコンテンツを作ってくれるようになります。

ここで試しに、トップページを作ってみましょう。

echo '# Hello VuePress' > src/README.md

yarn dev

http://0.0.0.0:8080/
にアクセスすると、Hello VuePressの文字が出ているはずです。

設定ファイルをいじってカスタムする

ここからは設定ファイルをいじりながら、vuepressを個人仕様にカスタマイズしていきましょう。下記のディレクトリ構造のように、.vuepressディレクトリを作り、その下にconfig.jsファイルを作りましょう。

ディレクトリ構成
blog
├─ src
│  ├─ README.md
│  └─ .vuepress
│     └─ config.js
└─ package.json

詳しくは、ドキュメントを見れば、どんなことを書けばいいのか、わかると思います。

最初はこんな感じでしょうか。

.vuepress/config.js
module.exports = {
  title: "blog",
  themeConfig: {
    description: "ここにはディスクリプションを入れます。",
    nav: [
      { text: "About", link: "/about/" } // ここはnav barのメニュー表示/aboutページは後ほど
    ]
  }
};

トップページの設定

トップページに出ている内容は、まだ、Hello, VuePressだけで味気ないので、ここもカスタマイズしましょう。
vuepressでは先ほど作ったmdファイルにfrontmatterを設定すると、各ページごとのメタ情報などの設定をすることができます。
またhome:trueとすると、vuepressのトップページテーマを使うことができます。

src/README.md
---
home: true
heroText: Yudium
description: ここにはディスクリプションを入れます。
footer: Copyright © 2019-present Youdai
---

Aboutページを作ってみる

/about/のリンクがメニューに出ていると思うので、/about/にアクセスした際にページが表示されるようにaboutページを作ってみましょう。と言っても簡単です。下記のように、aboutディレクトリを作り、その下にmarkdown形式のファイルを置くだけです。

ディレクトリ構成
blog
├─ src
│  ├─ README.md
│  │─ about
│  │└─ index.md
│  │
│  └─ .vuepress
│     └─ config.js
│
└─ package.json

about/index.md
## About

このようにすれば、/about/にアクセスした際に、ページの内容が表示されます。

ブログ機能作成

ここからが本題。Vuepress0.xでは、ブログ投稿については自分で実装しなければならなかったのですが、Vuepress1.xからプラグインを入れて、簡単に機能を実装することができるようになりました。他にも、analyticsを入れるプラグインなど、まだまだ量は少ないですが、これは使い勝手が良さそう。
詳しいプラグインについてはこちら

今回は、公式にも乗っている、@vuepress/plugin-blogを用いて、ブログ機能を作っていきます。

まずは、yarnでプラグインを入れます。
yarn add -D @vuepress/plugin-blog@next

そしたら、config.jsにpluginsを記述してください。これで設定はできましたが、まだブログ機能はできていません。

.vuepress/config.js
module.exports = {
  plugins: [
    "@vuepress/blog",
  ],
  // 中略 // 
}

ここからは実際にブログの投稿、そして、ブログのカテゴリーやタグの一覧機能を作っていきます。
まず最初に、ブログ機能を完成させた時のディレクトリ構造は以下のようになります。
ブログの投稿については、.vuepress/_posts配下にmdファイルを作成して行く形で、markdown形式で記事を投稿することができます。

blog
├─ src
│  ├─ README.md
│  │─ about
│  │└─ index.md
│  │─ tag
│  │└─ index.md
│  │─ category
│  │└─ index.md
│  │
│  └─ .vuepress
|     └─ _posts
|     │     └─ 1.md(_posts配下にファイルを置いて行くと、ブログ記事として見なされます。)
│     └─ config.js
│     └─ components
│      │─ Tags.vue
│      │─ Tag.vue
|      │─ Category.vue
│      │─ Categories.vue
│      │─ Blogs.vue 
│
└─ package.json

投稿する際の、ブログのメタ情報は、Front Matterで設定できます。

1.md
---
title: ブログ始めました!
description: ディスクリプション
date: 2019-04-27
img: ./img/post_1.jpg
sidebar: auto
category: 雑記
tags:
  - 日記
  - 独り言
---

vuepressはmd形式で作成したページが$pageの中に格納されています。
また、_posts配下にあるブログ記事はtypeがpostになっているので、下記のように、type == 'post'になっているページををループすれば、ブログページ一覧ができます。

ブログ一覧を作るコンポーネント

Blogs.vue
<template>
  <div v-for="post in posts">
    <a href="post.path">{{post.title}}</a>
  </div>
</template>

<script>
    export default {
        computed: {
            posts() {
                return this.$site.pages
                    .filter(x => x.type === 'post')
                    .sort((a, b) => new Date(b.frontmatter.date) - new Date(a.frontmatter.date));
            }
        }
    }
</script>

作成したタグは$tagに、カテゴリーは$categoryに格納されるので、こちらも同じ感じで、ループさせれば一覧ページを作るコンポーネントが出来上がります。

タグ一覧を作るコンポーネント

Tags.vue
<template>
  <div>
      <a v-for="tag in Object.keys($tags._metaMap)":href="tag">{{tag}}</a>
  </div>
</template>

カテゴリー一覧を作るコンポーネント

Categories.js
<template>
  <div>
    <ul v-for="category in Object.keys($categories._metaMap)">
      <h3><a :href="$categories._metaMap[category].path">{{ category }}</a></h3>
      <li v-for="post in $categories._metaMap[category].posts">
          <a :href="post.path">{{post.title}}</a>
      </li>
    </ul>
  </div>
</template>

ここまで作ったコンポーネントは、mdファイルの中で、コンポーネントとして呼び出すことができるので、例えばタグ一覧だったら、
下のような感じで呼び出すことができます。

src/tag/index.md
----
title:タグ一覧です
----
<Tags />

まとめ

割と、駆け足で書いてしまいましたが、こんな感じになります。
まだまだプラグインが足りなかったり、ドキュメントも薄かったりして、苦戦するとは思うのですが、Vue.jsで書かれているので、コンポーネントを作ったりするのは、すごく楽です。

またWordpressと同じように、すぐ使えるテーマみたいなものもあるので、こちらを使ってもいいかもしれません。

Theme Guide | vuepress-theme-meteorlxy
VuePressにテーマを適用させる その2 | web系のメモ

まだまだドキュメントが足りなかったり、苦戦する部分もありましたが、今後vuepressがどのように進化していくのか期待大ですね。


参考
Getting Started | VuePress 1.x

https://to-hutohu.com/2018/12/30/vuepress-blog/#%E8%A8%98%E4%BA%8B%E3%82%92%E6%9B%B8%E3%81%8F

Information on blog and pagination plugin missing from website · Issue #1091 · vuejs/vuepress · GitHub

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

便利ページ:元号を変換してみた(令和記念)

前回、なにかと便利なページを作成しました。

 便利ページ:Javascriptでちょっとした便利な機能を作ってみた
 便利ページ:自分のQiita記事を一覧表示

今回は元号(和暦)を西暦に変換するページを追加しました。
令和になって最初の投稿はやっぱこれでしょう!

元号は以下のWikiを参考にしています。
 https://ja.wikipedia.org/wiki/%E5%85%83%E5%8F%B7%E4%B8%80%E8%A6%A7_(%E6%97%A5%E6%9C%AC)

毎度の通り、デモページとGitHubです。

GitHub
 https://github.com/poruruba/utilities

デモページ
 https://poruruba.github.io/utilities/

元号一覧

このデータを作るのがすごくつらかったです。
こんな感じの元号データのjavascriptファイルです。
大化の改新から始まって、現在元年の令和までです。

gengou.js
const gengou_list = [
    {"name":"大化","yomi":"たいか","start":645,"end":650,"extra":""},
    {"name":"白雉","yomi":"はくち","start":650,"end":654,"extra":""},

・・・

    {"name":"昭和","yomi":"しょうわ","start":1926,"end":1989,"extra":""},
    {"name":"平成","yomi":"へいせい","start":1989,"end":2019,"extra":""},
    {"name":"令和","yomi":"れいわ","start":2019,"extra":""},
];

変換ロジックを作成する

あとは、和暦から西暦、西暦から和暦に変換するスクリプトを書いていきます。
元号一覧から対象元号を検索します。

start.js
        /* 元号 */
        gengou_search_era: function(era){
            for( var i = 0 ; i < this.gengou_list.length ; i++ ){
                if( this.gengou_list[i].name == era )
                    return this.gengou_list[i];
            }

            return null;
        },
        gengou_search_anno: function(anno){
            for( var i = 0 ; i < this.gengou_list.length ; i++ ){
                if( this.gengou_list[i].start <= anno && (this.gengou_list[i].end ? anno <= this.gengou_list[i].end : true ))
                    return this.gengou_list[i];
            }

            return null;
        },
        gengou_to_anno: function(){
            var era_name = (this.gengou_era_name == 'その他') ? this.gengou_era_other : this.gengou_era_name;
            var gengou = this.gengou_search_era(era_name);
            if( !gengou ){
                alert('入力が不正です。');
                return;
            }

            var year = Number(this.gengou_era_year);
            if( year <= 0 ){
                alert('入力が不正です。');
                return;
            }

            var anno_year = gengou.start + year - 1;
            if( gengou.end && anno_year > gengou.end ){
                alert('入力が不正です。');
                return;
            }
            this.gengou_anno_year = anno_year;
        },
        gengou_to_era: function(){
            var year = Number(this.gengou_anno_year);
            var gengou = this.gengou_search_anno(year);
            if( !gengou ){
                alert('入力が不正です。');
                return;
            }

            if( gengou.name == '令和' || gengou.name == '平成' || gengou.name == '昭和' || gengou.name == '大正' || gengou.name == '明治' ){
                this.gengou_era_name = gengou.name;
            }else{
                this.gengou_era_name = 'その他';
                this.gengou_era_other = gengou.name;
            }

            this.gengou_era_year = year - gengou.start + 1;
        },

補足

ちなみに、元号は大化から全部入れているのですが、まあ、あまり使わないでしょう。
もし使う場合の注意点なのですが、途中複数の元号が同時に存在していた時期があるようです(1329年から1394年のあたり)。また、大化の後でも、元号が存在しない時期があるようです(654年、686年)。そういったところは、上記の変換ロジックは不十分です。(困る人はいなそうなので、このままにしています)

以上

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

便利ページ:元号を変換してみた

前回、なにかと便利なページを作成しました。

 便利ページ:Javascriptでちょっとした便利な機能を作ってみた
 便利ページ:自分のQiita記事を一覧表示

今回は元号(和暦)を西暦に変換するページを追加しました。

元号は以下のWikiを参考にしています。
 https://ja.wikipedia.org/wiki/%E5%85%83%E5%8F%B7%E4%B8%80%E8%A6%A7_(%E6%97%A5%E6%9C%AC)

毎度の通り、デモページとGitHubです。

GitHub
 https://github.com/poruruba/utilities

デモページ
 https://poruruba.github.io/utilities/

元号一覧

このデータを作るのがすごくつらかったです。
こんな感じの元号データのjavascriptファイルです。
大化の改新から始まって、現在元年の令和までです。

gengou.js
const gengou_list = [
    {"name":"大化","yomi":"たいか","start":645,"end":650,"extra":""},
    {"name":"白雉","yomi":"はくち","start":650,"end":654,"extra":""},

・・・

    {"name":"昭和","yomi":"しょうわ","start":1926,"end":1989,"extra":""},
    {"name":"平成","yomi":"へいせい","start":1989,"end":2019,"extra":""},
    {"name":"令和","yomi":"れいわ","start":2019,"extra":""},
];

変換ロジックを作成する

あとは、和暦から西暦、西暦から和暦に変換するスクリプトを書いていきます。
元号一覧から対象元号を検索します。

start.js
        /* 元号 */
        gengou_search_era: function(era){
            for( var i = 0 ; i < this.gengou_list.length ; i++ ){
                if( this.gengou_list[i].name == era )
                    return this.gengou_list[i];
            }

            return null;
        },
        gengou_search_anno: function(anno){
            for( var i = 0 ; i < this.gengou_list.length ; i++ ){
                if( this.gengou_list[i].start <= anno && (this.gengou_list[i].end ? anno <= this.gengou_list[i].end : true ))
                    return this.gengou_list[i];
            }

            return null;
        },
        gengou_to_anno: function(){
            var era_name = (this.gengou_era_name == 'その他') ? this.gengou_era_other : this.gengou_era_name;
            var gengou = this.gengou_search_era(era_name);
            if( !gengou ){
                alert('入力が不正です。');
                return;
            }

            var year = Number(this.gengou_era_year);
            if( year <= 0 ){
                alert('入力が不正です。');
                return;
            }

            var anno_year = gengou.start + year - 1;
            if( gengou.end && anno_year > gengou.end ){
                alert('入力が不正です。');
                return;
            }
            this.gengou_anno_year = anno_year;
        },
        gengou_to_era: function(){
            var year = Number(this.gengou_anno_year);
            var gengou = this.gengou_search_anno(year);
            if( !gengou ){
                alert('入力が不正です。');
                return;
            }

            if( gengou.name == '令和' || gengou.name == '平成' || gengou.name == '昭和' || gengou.name == '大正' || gengou.name == '明治' ){
                this.gengou_era_name = gengou.name;
            }else{
                this.gengou_era_name = 'その他';
                this.gengou_era_other = gengou.name;
            }

            this.gengou_era_year = year - gengou.start + 1;
        },

補足

ちなみに、元号は大化から全部入れているのですが、まあ、あまり使わないでしょう。
もし使う場合の注意点なのですが、途中複数の元号が同時に存在していた時期があるようです(1329年から1394年のあたり)。また、大化の後でも、元号が存在しない時期があるようです(654年、686年)。そういったところは、上記の変換ロジックは不十分です。(困る人はいなそうなので、このままにしています)

以上

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

Vue.jsのコンポーネント間の値のやりとり

Vue.jsでコンポーネント間で値のやりとりを行う方法には、3種類方法があると思います。

  1. propsを使用して親コンポーネントから子コンポーネントへ値を渡す方法
  2. カスタムイベントを使用をして子コンポーネントから親コンポーネントへ値を渡す方法
  3. 親子関係にないコンポーネントの場合は、イベントバスを用いる方法

「1. propsを使用して親コンポーネントから子コンポーネントへ値を渡す方法」は、直感的でとでも分かりやすいです。
ですが、その他の方法は個人的に混乱しやすいポイントな気がしたのでまとめてみます。

propsを使用して親コンポーネントから子コンポーネントへ値を渡す方法

<div id="app">
  <div class="parent">{{ parentMsg }}</div>
  <child-component :text="childMsg"></child-component>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
  Vue.component('child-component', {
    template: '<div class="child">{{ text }}</div>',
    props: {
      text: String
    }
  });

  const app = new Vue({
    el: '#app',
    data: {
      parentMsg: 'これは親コンポーネントです。',
      childMsg: 'これは子コンポーネントです。',
    }
  })
</script>

カスタムイベントを使用をして子コンポーネントから親コンポーネントへ値を渡す方法

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

【Vetur】"Cannot find module" with Typescript

Nuxtでsvgを使いたくてvue-svg-loaderを導入するもVSCodeのVeturと
buildエラーが出て解決に困ったので明記します。

表題のエラー解決方法を今すぐ知りたい方はこちら
https://qiita.com/kawa64372358/items/aa327ea55f7df0d510bc#解決方法

vue-svg-loaderの導入

vue-svg-loader:https://www.npmjs.com/package/vue-svg-loader

これを導入するとSVGファイルをVueコンポーネントとして使用できます。
SVGをインライン化しているのでスタイルを設定できたり、SSRでも問題なく利用できます。

$ yarn add -D vue-svg-loader

nuxt.configに下記を追記
Vue CLIの場合は記述が変わるのでbasic-configurationを確認してください

nuxt.config.ts
  build: {
    extend(config: any, ctx: any) {
        // svg
        const svgRule = config.module.rules.find((rule: any) => rule.test.test('.svg'));

        svgRule.test = /\.(png|jpe?g|gif|webp)$/;

        config.module.rules.push({
          test: /\.svg$/,
          loader: 'vue-svg-loader'
        });
       }

実際にsvgを読み込みたいcomponentに下記を追記

GlobalNavigatioon.vue
<template>
  <ThreeLinesIcon />
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import ThreeLinesIcon from '~/assets/svg/icons/ThreeLinesIcon.svg';

@Component({
  components: {
    ThreeLinesIcon
  }
})
export default class GlobalNavigation extends Vue {}
</script>

buildエラー

ブラウザにはsvgが通常表示されるも、下記ビルドエラーが発生

Cannot find module '~/assets/svg/icons/ThreeLinesIcon.svg'.
スクリーンショット 2019-04-30 19.55.02.png

Veturにもエラーが出ています
スクリーンショット 2019-04-30 21.06.52.png

同じような問題がvuejs/veturのissuesにありました
Cannot find module 'xxxx' #762

tsconfig.json
{
"compilerOptions": {
  "baseUrl": ".",
  "paths": {
    "@/*": [
      "src/*"
    ]
  }
}

自分の場合は、すでにcompilerOptionsでpathsを書いていたので関係なさそう・・

解決方法

vue-svg-loaderのissuesに解決方法が書いてありました
"Cannot find module" with Typescript #37

vue-svg-loaderのdocumationにも書いてありました
https://vue-svg-loader.js.org/faq.html#how-to-use-this-loader-with-typescript

If you want to use this loader in a project that is written in TypeScript, you will get the "Cannot find module" error. To fix that you need to provide a type definition which is needed by TypeScript to know how to handle SVG components.

assetsのsvgを格納しているディレクトリに下記のような型ファイルを追加してあげればいいみたい

index.d.ts
declare module '*.svg' {
  const content: any;
  export default content;
}

無事解決しました。これでbuildも通ります!

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

【Vue/TypeScript】vue-svg-loaderのbuildエラーを解決

Nuxtでsvgを使いたくてvue-svg-loaderを導入するもVSCodeのVeturと
buildエラーが出て解決に困ったので明記します。

表題のエラー解決方法を今すぐ知りたい方はこちら
https://qiita.com/kawa64372358/items/aa327ea55f7df0d510bc#解決方法

vue-svg-loaderの導入

vue-svg-loader:https://www.npmjs.com/package/vue-svg-loader

これを導入するとSVGファイルをVueコンポーネントとして使用できます。
SVGをインライン化しているのでスタイルを設定できたり、SSRでも問題なく利用できます。

$ yarn add -D vue-svg-loader

nuxt.configに下記を追記
Vue CLIの場合は記述が変わるのでbasic-configurationを確認してください

nuxt.config.ts
  build: {
    extend(config: any, ctx: any) {
        // svg
        const svgRule = config.module.rules.find((rule: any) => rule.test.test('.svg'));

        svgRule.test = /\.(png|jpe?g|gif|webp)$/;

        config.module.rules.push({
          test: /\.svg$/,
          loader: 'vue-svg-loader'
        });
       }

実際にsvgを読み込みたいcomponentに下記を追記

GlobalNavigatioon.vue
<template>
  <ThreeLinesIcon />
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import ThreeLinesIcon from '~/assets/svg/icons/ThreeLinesIcon.svg';

@Component({
  components: {
    ThreeLinesIcon
  }
})
export default class GlobalNavigation extends Vue {}
</script>

"Cannot find module" with TypeScript [Vetur]

ブラウザにはsvgが通常表示されるも、下記ビルドエラーが発生

Cannot find module '~/assets/svg/icons/ThreeLinesIcon.svg'.
スクリーンショット 2019-04-30 19.55.02.png

Veturにもエラーが出ています
スクリーンショット 2019-04-30 21.06.52.png

同じような問題がvuejs/veturのissuesにありました
Cannot find module 'xxxx' #762

tsconfig.json
{
"compilerOptions": {
  "baseUrl": ".",
  "paths": {
    "@/*": [
      "src/*"
    ]
  }
}

自分の場合は、すでにcompilerOptionsでpathsを書いていたので関係なさそう・・

解決方法

vue-svg-loaderのissuesに解決方法が書いてありました
"Cannot find module" with Typescript #37

vue-svg-loaderのdocumentationにも書いてありました
https://vue-svg-loader.js.org/faq.html#how-to-use-this-loader-with-typescript

If you want to use this loader in a project that is written in TypeScript, you will get the "Cannot find module" error. To fix that you need to provide a type definition which is needed by TypeScript to know how to handle SVG components.

assetsのsvgを格納しているディレクトリに下記のような型ファイルを追加してあげればいいみたい

index.d.ts
declare module '*.svg' {
  const content: any;
  export default content;
}

無事解決しました。これでbuildも通ります!

参考

Cannot find module '~/components/Card.vue'. Vetur
https://qiita.com/kanzume/items/2ff1be119d7e64089d30

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

VueでKonva.jsとcanvasを使ってお絵描き(その4)

その3はこちら

戻る および やり直しの処理を実装(undo, redo)

キャンバスの状態を1つ前の状態に戻したり(undo)、やり直したり(redo)してみます。
ここを参考にしました。

描画時:描画する前に、描画前のキャンバスの状態を配列に保存しておく
戻す時:戻す前のキャンバスの状態をやり直し配列に保存し、戻す配列からキャンバスイメージを取り出して反映
やり直す時:やり直し前のキャンバスの状態を戻す配列に保存し、やり直し配列からキャンバスイメージを取り出して反映

※配列への保存と取り出しはFIFO方式で。
※キャンバスの状態はcontext.getImageDataメソッドで取得できるので、それを利用する
※context.putImageDataで、取り出したキャンバスの状態を反映する

親側(CallCanvas.vue)
メソッドを用意して、子の戻す、やり直すメソッドを読んでいるだけです。

CallCanvas.vue
<template>
    <div>
        <div class="md-layout md-gutter" style="margin-left: 340px">
          <div class="md-layout-item">
            ...
            ..
            .

            <md-field style="float: left; margin-top: -8px">
              <md-button
                class="md-dense md-raised md-primary"
                @click="onUndo" <!-- この行を追加 -->
              >
                戻る
              </md-button>
            </md-field>

            <md-field style="float: left; margin-top: -8px">
              <md-button
                class="md-dense md-raised md-primary"
                @click="onRedo" <!-- この行を追加 -->
              >
                進む
              </md-button>
            </md-field>

            ...
            ..
            .
          </div>
        </div>
        ...
        ..
        .
    </div>
</template>

<script>
import FreeDrawing from './FreeDrawing.vue'

export default {
  ...
  ..
  .
  methods: {
    ...
    ..
    .
    // 元に戻す
    onUndo: function () {
      this.$refs.freeDrawing.undo()
    },
    // やり直す
    onRedo: function () {
      this.$refs.freeDrawing.redo()
    }
  },
  ...
  ..
  .
}
</script>
...
..
.

次は子です(FreeDrawing.vue)
undoメソッドとredoメソッドの新規追加に伴い、
dataプロパティに2つ追加 および mousedownメソッドとonClearCanvasメソッドに1行ずつ追加しています。

FreeDrawing.vue
...
..
.

<script>
import Konva from 'konva'

export default {
  ...
  ..
  .
  data: () => ({
    ...
    ..
    .
    /** 追加 */
    undoDataStack: [], // 元に戻す配列
    redoDataStack: [] // やり直し配列
  }),
  ...
  ..
  .
  methods: {
    mousedown: function () {
      ...
      ..
      .
      // 戻す配列に描画前のキャンバスの状態を保存
      this.undoDataStack.push(this.context.getImageData(0, 0, this.canvas.width, this.canvas.height))
    },
    ...
    ..
    .
    onClearCanvas: function () {
      // キャンバスをクリアする前の状態を戻す配列に保存しておく
      this.undoDataStack.push(this.context.getImageData(0, 0, this.canvas.width, this.canvas.height))

      ...
      ..
      .
    },
    ...
    ..
    .
    /** 元に戻す */
    undo: function () {
      // 戻す配列が空の場合は何もしない
      if (this.undoDataStack.length <= 0) {
        return
      }

      // 戻す前の状態をやり直し配列に保存
      this.redoDataStack.push(this.context.getImageData(0, 0, this.canvas.width, this.canvas.height))

      // 元に戻す(戻す配列からキャンバスの状態を取り出して反映)
      this.context.putImageData(this.undoDataStack.pop(), 0, 0)
      this.drawingLayer.draw()
    },
    /** やり直す */
    redo: function () {
      // やり直し配列が空の場合は何もしない
      if (this.redoDataStack.length <= 0) {
        return
      }

      // やり直す前の状態を戻す配列に保存
      this.undoDataStack.push(this.context.getImageData(0, 0, this.canvas.width, this.canvas.height))

      // やり直す(やり直し配列からキャンバスの状態を取り出して反映)
      this.context.putImageData(this.redoDataStack.pop(), 0, 0)
      this.drawingLayer.draw()
    }
  },
  ...
  ..
  .
}
</script>

いざ、戻してみます。
image.png

戻る1回目
image.png

戻る2回目
image.png

戻る3回目
※少しわかりづらいですが、「1」の下の棒?が消えてくれました
image.png

今度は進めてみます(やり直しのこと)。
進む1回目
image.png

やり直し2回目
image.png

やり直し3回目
image.png

リセットして戻してみます。
リセット
image.png

戻る
image.png

無事、戻る処理とやり直す処理を実装できました。
モードの「直線」が未実装なので、次は直線を引いてみようと思います。
(多分最後。)

備考

リセットを連打すると、初期の令和だけ表示されてるキャンバス状態が配列にスタックされまくるので、
そこはよしなに処理を追加していただければと思います。

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

VueでKonva.jsとcanvasを使ってお絵描き(その3)

その2はこちら

モードを切り替える

その2で感のいい人は気づいていると思います、
親(CallCanvas.vue)のmodeプロパティを子(FreeDrawing.vue)に渡してあげればいいだけですね。
今回、親と子のプロパティ名を一緒にしてしまって紛らわしいので、以下構文。
【:<子のプロパティ名="<親のプロパティ名>">】
(※子のpropsプロパティにmodeを宣言しておいてください)

CallCanvas.vue
<template>
    <div>
        ...
        ..
        .
        <FreeDrawing
          :backgroundImage="imageFile"
          :mode="mode" <!-- この行を追加-->
        />
    </div>
</template>

...
..
.

ペンで適当に描いて・・・
image.png

モードを消しゴムに切り替えて消します。
(キャンバスに表示している画像は消えません、また消しゴムで消えるように切り替える処理はすでに入れてあります。)
image.png

ペンの色を切り替える

これも同様に、親のプロパティを子に渡して、子側でペンの色をキャンバスに設定してあげればよいです。

CallCanvas.vue
<template>
    <div>
        ...
        ..
        .
        <FreeDrawing
          :backgroundImage="imageFile"
          :mode="mode"
          :brushColor="brushColor" <!-- この行を追加-->
        />
    </div>
</template>

...
..
.

子のFreeDrawing.vueにはすでにペンの色が変更された場合のウォッチャー(watch)があるので、何もしなくてよいです。

FreeDrawing.vue
...
..
.

<script>
import Konva from 'konva'

export default {
  name: 'FreeDrawing',
  // propsは親のCallCanvasから値を受け取るためのプロパティ
  props: {
    ...
    ..
    .
  },
  data: () => ({
    ...
    ..
    .
  }),
  mounted: function () {
    ...
    ..
    .
  },
  methods: {
    ...
    ..
    .
  },
  watch: {
    // ペンの色変更
    brushColor: function () {
      this.context.strokeStyle = this.brushColor
    }
  }
}
</script>

ペンの色を変えることができました。
image.png

image.png

リセットボタンでキャンバスをクリアする

毎回ページをリロードさせるのはめんどくさいので、キャンバスのリセットを実装します。

子のFreeDrawing.vueにはすでに「onClearCanvas」メソッドがあるので、
親からそのメソッドを呼ぶだけです。

CallCanvas.vue
<template>
    <div>
        <div class="md-layout md-gutter" style="margin-left: 340px">
          <div class="md-layout-item">
            ...
            ..
            .
            <md-field style="float: left; margin-top: -8px">
              <md-button
                @click="clearCanvas" <!-- この行を追加 -->
                class="md-dense md-raised md-primary"
              >
                  リセット
              </md-button>
            </md-field>

            ...
            ..
            .
          </div>
        </div>
        <FreeDrawing
          ref="freeDrawing" <!-- この行を追加 -->
          :backgroundImage="imageFile"
          :mode="mode"
          :brushColor="brushColor"
        />
    </div>
</template>

<script>
import FreeDrawing from './FreeDrawing.vue'

export default {
  ...
  ..
  .
  methods: {
    ...
    ..
    .
    // キャンバスをクリアする
    clearCanvas: function () {
      this.$refs.freeDrawing.onClearCanvas()
      this.init()
    }
  },
  ...
  ..
  .
}
</script>
...
..
.

落書きして、モードを意味もなく消しゴムにします。
image.png

リセットボタン押下!
image.png
元通りになりました。

オマケ

親から子のキャンバスクリアメソッドを呼ぶだけでしたが、
子から親のメソッドを呼ぶこともできます。

以下の構文で親のイベントを渡して、子側で任意のイベント名称を使用して渡されたイベントを実行できます。
【@<任意のイベント名称>="<イベント>"】

まず親
「init()メソッド」を"on-init"という名称で子に渡します。
小側で受け取るためのプロパティ等は不要です。

CallCanvas.vue
<template>
    <div>
        ...
        ..
        .
        <FreeDrawing
          ref="freeDrawing"
          :backgroundImage="imageFile"
          :mode="mode"
          :brushColor="brushColor"
          @on-init="init" <!-- この行追加 -->
        />
    </div>
</template>

<script>
import FreeDrawing from './FreeDrawing.vue'

export default {
  ...
  ..
  .
  methods: {
    // モードとペンの色を初期状態にする
    init: function () {
      this.mode = this.defaultMode
      this.brushColor = this.defaultBrushColor
    },
    // キャンバスをクリアする
    clearCanvas: function () {
      this.$refs.freeDrawing.onClearCanvas()
      // this.init()
    }
  },
  ...
  ..
  .
}
...
..
.

では子側です。
$emitを使用すれば、親からもらったイベントを実行することができます。
【this.$emit('<親からもらったイベント名称>')】

今回"on-init"というイベントをもらっているので、this.$emit('on-init')とします。

FreeDrawing.vue
...
..
.

<script>
import Konva from 'konva'

export default {
  ...
  ..
  .
  methods: {
    ...
    ..
    .
    onClearCanvas: function () {
      this.context.globalCompositeOperation = 'destination-out'
      this.context.fillRect(0, 0, this.width, this.height)
      this.drawingLayer.draw()

      this.$emit('on-init') // この行を追加
    },
    ...
    ..
    .
  },
  ...
  ..
  .
}
</script>

結果的に挙動は変わりませんが、こういうこともできるよという小技的なオマケでした。

次は「戻る」「進む」ボタンを実装しようと思います。
その4:戻る(undo) と やり直し(redo)の実装

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

VueでKonva.jsとcanvasを使ってお絵描き(その2)

その1はこちら

キャンバスに画像を表示する

親のCallCanvas.vueから、子のFreeDrawing.vueに画像ファイルパスを渡して、それをキャンバスに表示してみます。

まず、親のCallCanvas.vue。
親のテンプレートに記載したFreeDrawingタグに、子のpropsに定義した「backgroundImage」を追記し、
そこに値を指定します。
今回は算出プロパティ「imageFile」を用意し、それを値に指定してます。
(require()を使って渡さないと画像を表示できないとこでハマりました。。。)

src/paint.CallCanvas.vue
<template>
    <div>
        ...
        ..
        .
        <FreeDrawing 
          :backgroundImage="imageFile"
        />
    </div>
</template>

<script>
import FreeDrawing from './FreeDrawing.vue'

export default {
  name: 'CallCanvas',
  ...
  ..
  .
  computed: {
    imageFile: function () {
      return require('../assets/reiwa.png');
    }
  }
}
...
..
.

次に子のFreeDrawing.vue

FreeDrawing.vue
<template>
  <div>
    <div ref="container">
      <canvas
        :width="width/2"
        :height="height/2"
        ref="canvas">
      </canvas>
    </div>
  </div>
</template>

<script>
import Konva from 'konva'

export default {
  ...
  ..
  .
  data: () => ({
    ...
    ..
    .
    /** 追加 */
    imageObj: null,
    backgroundLayer: null,
    backgroundImageScope: null
  }),
  mounted: function () {
    ...
    ..
    .
    /** 追加 */
    this.imageObj = new Image()
    this.imageObj.addEventListener('load', this.imageOnload)
    this.imageObj.src = this.backgroundImage
  },
  methods: {
    ...
    ..
    .
    /** 追加 */
    imageOnload: function () {
      // 背景レイヤ
      this.backgroundLayer = new Konva.Layer()

      // 背景イメージ(xとy座標はthis.drawingScopeと同じにする)
      this.backgroundImageScope = new Konva.Image({
        image: this.imageObj,
        x: this.width / 4,
        y: 5,
        width: this.canvas.width,
        height: this.canvas.height
      })

      // 背景レイヤに背景イメージを追加
      this.backgroundLayer.add(this.backgroundImageScope)
      this.stage.add(this.backgroundLayer)

      // 背景イメージを最背面に移動。これをしないとペンの描画が画像の下に潜ってしまう。
      this.backgroundLayer.moveToBottom()
    }
  },
  ...
  ..
  .
}
</script>

サーバー起動して確かめます。
image.png

表示できました、描画もできます。
また、画像がキャンバスサイズより大きい場合は、キャンバスサイズに縮小されますし、
小さい場合は拡大されます。

image.png

その3はモードをペンと消しゴムで切り替えられるようにしてみます。
その3:モード および ペンの色切り替え、キャンバスのクリア

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

VueでKonva.jsとcanvasを使ってお絵描き(その1)

vueとkonva.jsを使った記事が全然ないので作ってみました。
(vue-konvaを使ってる記事はちょいちょいあるんですけどね。)

はじめに

その1では、お絵描きできるCanvasの実装まで行います。
その1の内容はこのデモページをvue用に書き換えただけです。
また、MacOSを使って実装してます。

最終的な完成画面イメージはこんなん。
モードでペン、消しゴム、直線を切り替えられるようにして、各種ボタン押下時はそれに応じた処理ができればなと。
image.png

バージョン情報

$ vue -V
3.6.3

$ node -v
v11.14.0

$ npm -v
6.7.0

vueプロジェクト作成

「vue init webpack <プロジェクト名>」でvueのプロジェクトを作成します。
最初の質問(Project Name)だけ、vueプロジェクトを作成するプロジェクトの名前を指定してあげて、あとはエンターで問題ないです。
(僕はkonvastudyというプロジェクトに作成したので、「konvastudy」としました。)

$ vue init webpack konvaproject

? Project name konvastudy
? Project description A Vue.js project
? Author Butterthon <~.0805@gmail.com>
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? Yes
? Pick an ESLint preset Standard
? Set up unit tests Yes
? Pick a test runner jest
? Setup e2e tests with Nightwatch? Yes
? Should we run `npm install` for you after the project has been created? (recommended) npm

作成できたら、vueプロジェクトのディレクトリに移動してサーバーを起動してみます。
ブラウザが立ち上がって、以下のような画面が表示されれば成功です。

$ cd konvaproject
$ npm run dev

image.png

キャンバスの外側を作る

冒頭の完成イメージでいう、モードやペンの色、各種ボタンのことです。
その前に、Vue Materialやkonvaを使う準備をしましょう。
Vue Materialは色々なUIコンポーネントが提供されており、それっぽいデザインが簡単に作れるので便利。
Vue Materialの公式ページはここから

npm install --save vuex vue-material@1.0.0-beta-10.2 babel-plugin-transform-object-rest-spread vue-style-loader ts-loader@3.5.0 css-loader sass-loader node-sass konva

上記プラグインをinstallしたら、main.jsに以下を追記します。

main.js
...
..
.
import VueMetarial from 'vue-material'
import 'vue-material/dist/vue-material.min.css'
import 'vue-material/dist/theme/default.css'

Vue.use(VueMetarial)

...
..
.

では外側の作成に入っていきます。
src直下に「paint」フォルダを作成し、その中にCallCanvas.vueを用意します。
中身はこんな感じ

src/paint/CallCanvas.vue
<template>
    <div>
        <div class="md-layout md-gutter" style="margin-left: 340px">
          <div class="md-layout-item">
            <md-field style="float: left">
              <label for="mode">モード</label>
              <md-select v-model="mode" name="mode" id="mode">
                <md-option value="brush">ペン</md-option>
                <md-option value="eraser">消しゴム</md-option>
                <md-option value="line">直線</md-option>
              </md-select>
            </md-field>

            <md-field style="float: left">
              <label for="brushColor">ペンの色</label>
              <md-input type="color" v-model="brushColor" />
            </md-field>

            <md-field style="float: left; margin-top: -8px">
              <md-button
                class="md-dense md-raised md-primary"
              >
                戻る
              </md-button>
            </md-field>

            <md-field style="float: left; margin-top: -8px">
              <md-button
                class="md-dense md-raised md-primary"
              >
                進む
              </md-button>
            </md-field>

            <md-field style="float: left; margin-top: -8px">
              <md-button
                class="md-dense md-raised md-primary"
              >
                  リセット
              </md-button>
            </md-field>

            <md-field style="float: left; margin-top: -8px">
              <md-button
                class="md-dense md-raised md-primary"
              >
                  保存
              </md-button>
            </md-field>
          </div>
        </div>
    </div>
</template>

<script>
export default {
  name: 'CallCanvas',
  data: () => ({
    mode: '',
    brushColor: '',
    defaultMode: 'brush',
    defaultBrushColor: '#FFFFFF'
  }),
  mounted: function() {
    this.init();
  },
  methods: {
    // モードとペンの色を初期状態にする
    init: function() {
      this.mode = this.defaultMode;
      this.brushColor = this.defaultBrushColor;
    }
  }
}
</script>

<style lang="scss" scoped>
  .md-field {
    max-width: 110px;
  }
</style>

てっとり早くサーバー立ち上げたときに確認したいので、サーバー立ち上げて最初に表示される「HelloWorld.vue」を修正します。

src/components/HelloWorld.vue
<template>
  <div>
    <!-- componentに追加したものはタグとして使用できるようになる -->
    <CallCanvas />
  </div>
</template>

<script>
import CallCanvas from '../paint/CallCanvas.vue' // 作成したCallCanvas.vueをインポート

export default {
  name: 'HelloWorld',
  components: {
    CallCanvas // componentsにインポートしたCallCanvasを追加する。
  }
}
</script>

サーバー起動して確かめます(npm run devを実行)。
image.png
バッチグー。
ロゴが邪魔な人はApp.vueから該当する行を削除したら消えます。

Canvasを実装してお絵描きしてみる

CallCanvas.vueと同じフォルダに、「FreeDrawing.vue」を用意します。
中身はこんな感じ(propsやthis.$emit(...)は今は気にしなくていいです。)

src/paint/FreeDrawing.vue
<template>
  <div>
    <div ref="container">
      <canvas
        :width="width/2"
        :height="height/2"
        ref="canvas">
      </canvas>
    </div>
  </div>
</template>

<script>
import Konva from 'konva';

export default {
  name: 'FreeDrawing',
  // propsは親の「CallCanvas.vue」から値を受け取るためのプロパティ
  props: {
    mode: {
      type: String,
      default: ''
    },
    brushColor: {
      type: String,
      default: ''
    },
    backgroundImage: {
      type: String,
      default: ''
    }
  },
  data: () => ({
    width: window.innerWidth,
    height: window.innerHeight,
    stage: null,
    canvas: null,
    context: null,
    drawingLayer: null,
    drawingScope: null,
    lastPointerPosition: {},
    localPos: {
      x: 0,
      y: 0
    },
    pos: null,
    isPaint: false
  }),
  mounted: function () {
    var container = this.$refs.container;
    this.stage = new Konva.Stage({
      container,
      width: this.width,
      height: this.height
    })
    this.drawingLayer = new Konva.Layer()
    this.stage.add(this.drawingLayer)

    this.canvas = this.$refs.canvas
    this.drawingScope = new Konva.Image({
      image: this.canvas,
      x: this.width / 4,
      y: 5,
      stroke: 'black'
    })
    this.drawingLayer.add(this.drawingScope)
    this.stage.draw()

    this.context = this.canvas.getContext('2d')
    this.context.strokeStyle = this.brushColor
    this.context.lineJoin = 'round'
    this.context.lineWidth = 5

    // イベント追加
    this.drawingScope.on('mousedown', this.mousedown)
    this.stage.addEventListener('mouseup', this.mouseup)
    this.stage.addEventListener('mousemove', this.mousemove)
    this.drawingScope.on('touchstart', this.mousedown)
    this.stage.addEventListener('touchend', this.mouseup)
    this.stage.addEventListener('touchmove', this.mousemove)
  },
  methods: {
    mousedown: function () {
      this.isPaint = true

      // マウスダウン時の座標を取得しておく
      this.lastPointerPosition = this.stage.getPointerPosition()
    },
    mouseup: function () {
      this.isPaint = false
    },
    mousemove: function () {
      if (!this.isPaint) {
        return;
      }
      // ペンモード時
      if (this.isTargetMode('brush') || this.isTargetMode('line')) {
        this.context.globalCompositeOperation = 'source-over';
      }
      // 消しゴムモード時
      if (this.isTargetMode('eraser')) {
        this.context.globalCompositeOperation = 'destination-out';
      }

      this.context.beginPath()

      this.localPos.x = this.lastPointerPosition.x - this.drawingScope.x()
      this.localPos.y = this.lastPointerPosition.y - this.drawingScope.y()

      // 描画開始座標を指定する
      this.context.moveTo(this.localPos.x, this.localPos.y)

      this.pos = this.stage.getPointerPosition()
      this.localPos.x = this.pos.x - this.drawingScope.x()
      this.localPos.y = this.pos.y - this.drawingScope.y()

      // 描画開始座標から、lineToに指定された座標まで描画する
      this.context.lineTo(this.localPos.x, this.localPos.y)
      this.context.closePath()
      this.context.stroke()
      this.drawingLayer.draw()

      this.lastPointerPosition = this.pos
    },
    onClearCanvas: function () {
      this.context.globalCompositeOperation = 'destination-out'
      this.context.fillRect(0, 0, this.width, this.height)
      this.drawingLayer.draw()

      this.$emit('on-reset')
    },
    // 現在のモードが指定されたモードと一致するかどうか
    isTargetMode: function (targetMode) {
      return this.mode === targetMode
    }
  },
  watch: {
    // ペンの色変更
    brushColor: function () {
      this.context.strokeStyle = this.brushColor
    }
  }
}
</script>

これをCallCanvas.vueに組み込みます。
・FreeDrawing.vueをインポート
・インポートしたものをcomponentsに指定する
・templateに記述する(厳密にはそのなかのdivタグ内)

src/paint/CallCanvas.vue
<template>
    <div>
        ...
        ..
        .
        <FreeDrawing />
    </div>
</template>

<script>
import FreeDrawing from './FreeDrawing.vue'

export default {
  name: 'CallCanvas',
  components: {
    FreeDrawing
  },
  ...
  ..
  .
}
</script>

...
..
.

サーバー起動してみます。
image.png
キャンバスが表示されて、描画もできます。

image.png

その2ではキャンバスに画像を表示して、その画像に落書きできるようにしたいと思います。
(冒頭の完成イメージでいうと、「令和」に落書きできるようにするイメージ)
その2:キャンバスに画像表示および落書き

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