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

Vuetifyで画面サイズで変わるBreakpointsを使って表示するスタイルや文字数を制限した話

画面サイズ毎に表示する文字数制限したい!!

今回Vueitfyを使ってレスポンシブな細かいスタイル調整を行おうと思ったんですが
文字列の長さが不明確で且つ改行ありのデザインだったので少々手こずってしまいました。

Vuetifyでうまくやる方法があるのかも知れませんが、今回はこのような形で解決しました。

結論

  • $vuetify.breakpointでlgやmdといったブレークポイントを取得
  • $vuetify.breakpointはtemplate上でも使える
  • styleなどに必要な要素を変数に代入(computedで定義すると動的に変わる)
  • trancateメソッドを使って文字切り捨て
  • cssのtext-overflowは改行無しなら使える!(veutifyのclassならtext-truncate

※ templateにはVuetifyを使っているものとします

<template>
  <div>
    <!-- 高さをブレークポイントで動的に変える -->
    <v-card :height="bkPoint.cardHeight">
      <v-card-title>
        {{ title | trancate(bkpoint.titleLength) }}
      </v-card-title>
        <!-- 省略の末尾を指定する -->        
        {{ text | trancate(bkpoint.textLength, '...続き') }}
      </v-card-text>
      <v-card-text>
    </v-card>
  </div>
</template>

<script>
export default {
  filter: {
    /**
    * 文字を切り捨てる [色んな所で使うので共通化するといいよ!]
    * @param {String} text
    * @param {Number} length
    * @param {String} clamp
    */
    truncate (text, length, clamp) {
      text = text || ''
      clamp = clamp || '...'
      length = length || 30

      if (text.length <= length) return text
      return text.substring(0, length) + clamp
    }
  },
  data() {
    return {
       title: 'タイトルは長くても改行なしなら省略する方法が色々ある',
       text: 'テキストは改行ありだとcssだけで対処しきれないのでtrancate関数を作って対応する'
    }
  },
  computed: {
    bkPoint () {
      // $vuetify.breakpointでブレークポイントを取得
      const bkPt = this.$vuetify.breakpoint
      const point = { name: bkPt.name, cardHeight: 200, titleLength: 10, textLength: 15 }
      switch (bkPt.name) {
        case 'xl':
          point.titleLength = 30
          point.textLength = 100
          point.cardHeight = 150
          break
        case 'lg':
          point.titleLength = 20
          point.textLength = 80
          point.cardHeight = 150
          break
        case 'md':
          point.titleLength = 10
          point.textLength = 60
          point.cardHeight = 350
          break
        case 'sm':
          point.titleLength = 10
          point.textLength = 100
          point.cardHeight = 570
          break
        case 'xs':
          point.titleLength = 8
          point.textLength = 100
          point.cardHeight = 600
          break
        default:
          break
      }
      return point
  }
}
</script>

$vuetify.breakpoint

https://vuetifyjs.com/en/customization/breakpoints/

このようにテンプレート内に直接書き込んでコードを書くことを放棄する方法も取れます。

<template>
  <v-dialog :fullscreen="$vuetify.breakpoint.xsOnly">
    ...
  </v-dialog>
</template>

このようにcomputedなどで動的に変動する値として使うこともできます

<script>
export default {
  computed: {
    breakpointName () {
      return this.$vuetify.breakpoint.name // ex: 'lg'
    }
  }
}
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Typescript&VueでのPropsで値を渡す方法

子のコンポーネントはこんな感じで書くと割と便利

<template>
// 省略
</template>
<script lang="ts">
import { Vue, Prop } from 'nuxt-property-decorator';
// 省略
export default class Hoge extends Vue {
  @Prop(Number) readonly hogeNum!: number;
  @Prop(String) readonly hogeStr!: string;
  @Prop(Array) readonly hogeAry!: string[]; // 文字列が入る配列の型
  @Prop(Object) readonly hogeObj!: { [key: string]: number }; // 適当な文字列をkeyに数値が入る型
  @Prop({ type: Boolean, default: true }) readonly hogeBool!: boolean; // 何も親コンポーネントから渡されない場合はtrueが入る

  hogeFunc(): void {
    console.log(this.hogeNum); // thisで呼び出す
  }
}
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HTML / CSS から Nuxt.js で webアプリを開発するまでのロードマップ

はじめに

この記事では、HTML / CSS から学び、Nuxt.js で webアプリを開発するまでの過程を紹介します。

本記事は、以下のような方が対象です。同じ初学者を意識しています。

  1. web開発に興味がある。
  2. HTML / CSS を学んだが、そこからどうすれば良いか謎。不可思議。
  3. フレームワークとは何ぞや。フレームワークのフレームワーク?
  4. どこまでやれば、どういうものができるのか具体的に知りたい。
  5. とりあえず、web開発に興味はあるよ。

その道の方でも、「趣味で学ぼうとする人は、こういう学び方をしているんだな」と参考になるかもしれません。

筆者であるうたかたは、web開発を始めて早2ヶ月ですが、わからないことでいっぱいです。基本的に表面的な理解に終始しています。ただ「初学者でも、今あるものを使うと、こういうものができるよ」という具体例とその学習過程を紹介することで、何か少しでもお役に立てたらと思います。Qiita も利用し始めたばかりなので、何かありましたら色々とご指摘いただけると助かります。

開発中の音楽SNS「UTAKATA」の紹介・解説動画を YouTube に投稿しているので、「この記事で紹介しているもの(Vue.js・Vuetify.js・Nuxt.js・Firebase)を学ぶと、少なくともこういう webアプリを作ることができるよ」という具体例として参考にしていただければと思います。近日中にベータ版(?)を公開する予定です。

最後に、本記事は各言語・フレームワークについて詳細な解説をするものではありません。「何を学べば良いかわからない」「何を学べば、何ができるかわからない」という方に、一つの指針を提供することが目的です。

少し前置きが長くなりましたが、最後までお読みいただけると嬉しいです。

ロードマップ

これまでの学習の軌跡を振り返りつつ、web開発のロードマップを紹介します。

学習の流れは、HTML / CSS → JavaScript → Vue.js & Firebase → Vuetify.js → Nuxt.js でした。

学習期間と内容は、概ね以下の通りです。

  • 前半(1ヶ月):HTML / CSS / JavaScript の基本を学び、その後何を学べば良いか調べてた。もはや「web開発」とは何ぞや状態。そして今も。
  • 後半(1ヶ月):Vue.js → Firebase → Vuetify.js → Nuxt.js の存在を辿り、動画を見て、公式ドキュメントを見ながらコードを書いた。

主に前半は The Net Ninja さんの動画(YouTube & Udemy)、後半は公式ドキュメントを頼りにしています。

The Net Ninja さんについては、偶然見つけただけで、特に深い理由はありません。運命の出会いでした。英語ですが、とてもわかりやすいです。このわかりやすさに慣れると、少しつらいものもあります。ストリートファイターの春麗(Chun Li)が好きみたい。マリオも出てくる。ほぼ全て無料で見ることができるのには、感謝しかないです。謝謝。

「どこまでやれば、どういうものができるのか」が想像もできなかったので、一度動画を(コードを書かないで)見るだけみて、Nuxt.js に行き着いてから、公式ドキュメントを参照しつつ、コードを書き始めました。

この記事を参考にする場合も、一度最後まで読み通して見通しをつけてから、各言語・フレームワークを学習し始めると良いと思います。知らない用語が出ても、「そういうものがあるんだね」と思って進んでください。知らないことばかりですもん。

学習項目は以下の通りです。

  • HTML
  • CSS
  • JavaScript
  • Vue.js:JavaScript のフレームワーク。
  • Vuetify.js:Vue.js のデザイン担当。
  • Nuxt.js:Vue.js の開発環境・機能強化担当。
  • Firebase:ユーザー認証(登録・ログイン)やデータベースを管理。

1. HTML / CSS

自分は「Flexbox とは何ぞや」という人間だったので、以下の動画も見ました。あとで紹介する Vuetify.js で採用されている Grid システムをそのまま理解することができます。

動画が洗練されていくのを肌で感じることができるのは、複数見た人の特権です。

今回紹介している動画は有料(Udemy)2つを含みますが、それ以外はほぼ全て無料なので、ぜひ覗いてみてください。

最近では、Firebase Functions に関するシリーズ(無料)が投稿されていたようです。まだ見ていませんが、きっと参考になると思います。

2. JavaScript

フルバージョンは Udemy(有料) で公開されています。動画の説明欄にクーポン付きのリンクがあるので、そちらからご利用ください。

基本的な文法(「こうやって書くよ」)・関数(「こういうことがしたい時、こういう機能があるよ」)・概念(「こういう考え方で、コードを組み立てるよ」)を一通り学ぶことができます。

学習の指針としては、動画を見て基本的な概念を理解しつつ、文法・関数(の存在)を何となく知っておきます。実際にコードを書く段階で、その関数名やしたいことで検索して、例を見ながら書きます。「検索できるようになる」こと、そして「書かれている内容を理解できるようになる」ことが大事だと思います。全てのコードを覚えておく必要はありません。未だに何でも調べてます。

この後も基本的に、動画を見て何となく理解する → ドキュメントを参照してコードを書くという流れです。

3. Vue.js & Firebase

Udemy(有料)で公開されている動画の紹介動画です。動画の説明欄にクーポン付きのリンクがあるので、そちらからご利用ください。

web開発を学ぶのにかけた費用は、以上の動画2つ分(約2400円)です。
安いと思います。感謝しかないです。

Vuex については、以下の動画(無料)を参考にしました。

Vue については、以下のサイトも参考になりました。Vuex についての記事を載せておきます。目を通せてはいないですが、初学者にも、とてもわかりやすい解説です。謝謝。

一応、Firebase [Firestore/Storage] セキュリティルールの具体例を以下の記事で紹介しています。とても参考になったブログ・記事についても触れているので、いつの日か覗いてみてください。喜びます。

4. Vuetify.js

使用しているコンポーネント(デザインを形作るパーツ。Buttonコンポーネントなど)や使い方が古い場合もありましたが、コメント欄で有志により全て補足されていました。

コードを書くときには公式ドキュメントの例を見ながら書くので、まずは動画で雰囲気を掴めれば良いと思います。その点で、とても参考になりました。

5. Nuxt.js

存在は Vuetify.js の公式ドキュメントで知りました。

Nuxt.js については、はじめから公式ドキュメントのガイドに沿って何となく理解していきました。一応、Vue School の動画も見たと思います。

Vue.js・Vuetify.js・Nuxt.js・Firebase は、いずれも日本語の公式ドキュメントが充実しているので、動画を見て雰囲気を掴んだら、そのまま公式ドキュメントを読むと何となく理解できます。どんなにわかりやすいサイトがあっても、参照しているのは最終的に公式ドキュメントのはずなので、それを読むことで安心感を得るのです。公式ドキュメントのわかりにくいところは、検索すれば先人が知恵を貸してくれます。

おわりに

この記事では、HTML / CSS から学び、Nuxt.js で webアプリを開発するまでの過程を紹介してきました。

「何があるかわからない」「何から手をつけていいかわからない」という方の参考になれたら幸いです。

web開発を学ぶ上で、数多くのサイト・記事を参考にさせていただいています。本記事では全て列挙することはできませんが、この場を借りてお礼申し上げます。いつもありがとう。謝謝。

趣味で NCS 等の和訳を YouTube に投稿しているので、よければそちらもご覧ください。
YouTube チャンネル:うたかた / UTAKATA

少し長くなりましたが、ここまで読んでいただきありがとうございました。ではでは!

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

microCMSで用意したデータをGridsomeで表示させる

microCMSは純日本製のHeadlessCMSで、分かりやすいUIと完全日本語対応が最高に使いやすいサービスです。

この記事ではGridsomeというSSGを使い、microCMSで用意したデータを取得し、GraphQLに変換し画面に表示するまでの流れをメモ代わりに書いていきたいと思います。

実装概要

流れとしては、
1. 『お知らせ』をmicroCMSに投稿
2. Gridsomeのサーバ起動時にmicroCMSからデータ取得
3. GraphQLに変換しindex.vueに一覧表示

していきたいと思います。

microCMSでデータを準備

こちらの手順 に従い、お知らせデータの作成を行います。

APIスキーマ
FireShot Capture 018 - 管理画面 - microCMS - nuxtmicrocms.microcms.io.png

中身
FireShot Capture 019 - 管理画面 - microCMS - nuxtmicrocms.microcms.io.png

microCMSでの作業は以上です。簡単!

Gridsome プロジェクト作成

これも公式ドキュメント通りにすれば問題なく作成できるはず。

それと dotenvaxios を使用するのでついでにインストールしておきましょう。

gridsome develop でサーバーが起動できることを確認出来たら次に進みます。

サーバー起動時にmicroCMSからデータを取得する

サーバーが起動したときの処理はgridsome.server.js に記述します。

gridsome.server.js
require('dotenv').config()
const axios = require('axios')

module.exports = function (api) {
  api.loadSource(async actions => {
    const { data } = await axios
      .get(`https://yourapi.microcms.io/api/v1/news`, {
        headers: { 'X-API-KEY': process.env.API_KEY }
      })

    const news = actions.addCollection({
      typeName: 'PostNews'
    })

    for (const item of data.contents) {
      news.addNode({
        id: item.id,
        title: item.title,
        body: item.body
      })
    }
  })
}

ここでコレクションを準備すると、他のファイルでも参照できるようになるみたいですね。

詳細はこちら => Collections - 公式ドキュメント

.env ファイルも準備

.env
API_KEY=xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx

GraphQLに変換し一覧表示

この辺はよくわかってないですが、とりあえずここでGraphQLに変換してるみたいです。

Index.vue
<template>
  <Layout>
    <ul>
      <li v-for="edge in $page.news.edges" :key="edge.node.id">
        <h2></h2>
        <g-link :to="'/news/' + edge.node.id">
          {{ edge.node.title }}
        </g-link>
      </li>
    </ul>

  </Layout>
</template>

<page-query>
query {
  news: allPostNews {
    edges {
      node {
        id
        title
        body
      }
    }
  }
}
</page-query>

きちんと表示されればOK!

こんな感じに表示されるはず、です。

FireShot Capture 020 - Hello, world! - Gridsome - localhost.png

今はまだリンク先を準備していないので遷移してもエラーになりますが、ひとまず表示は問題なさそうですね。

参考ページ

(追記)詳細画面

gridsome.config.js
module.exports = {
  siteName: 'Gridsome',
  templates: {
    PostNews: [
      {
        path: '/news/:id',
        component: './src/templates/PostNews.vue'
      }
    ]
  },
  plugins: []
}
/src/templates/PostNews.vue
<template>
  <Layout>
    <h1>{{ $page.news.title }}</h1>
    <main>
      <div v-html="$page.news.body"></div>
    </main>
  </Layout>
</template>

<page-query>
  query news($id: ID!) {
    news: postNews(id: $id) {
      title
      body
    }
  }
</page-query>

詳細画面の実装方法は他のCMSと同じなので、Contentful版で説明しているものを参考にしました。
サーバの再起動をしないと詳細画面が反映されないみたいで少し焦りましたが、そんなに難しくなく実装できました。

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

謎解き用メモ#1 パスワード

脱出ゲームの謎解きでよくある、3つの数字を入れると進めるやつ

nazotoki.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="nazotoki.css">
    <title>謎解きWEB</title>
</head>

<body>
    <div id="app">
        <input type="text" v-model="keyWord">
        <button v-on:click="onclick">ボタン</button>
        {{message}}
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="nazotoki.js"></script>
</body>
</html>
vue.js
new Vue({
    el: '#app',
    data: {
        message: 'hello',
        keyWord: ''
    },
    methods: {
        onclick: function(){
            if (this.keyWord === '000'){
                this.message = 'good';
            }else{
                this.message = 'bad';
            };
        }    
    }
});


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

画像ファイルのアップロード時に複数のプレビュー画像を表示する【Vue.js】

ファイルアップロード時に複数の画像プレビューを表示する

題名の通りですが、今回は備忘録として、
画像アップロードを行う際に複数の画像プレビューを
Vue.jsを利用して表示する方法について書いていこうと思います。

FileAPI

FIleAPIについては以下のドキュメントを参照してください。
https://developer.mozilla.org/ja/docs/Web/API/File

実装例

<template>
  <div>
    <input
      @change="inputFileList($event)"
      multiple="multiple"
      accept="image/*"
    >
    <div
      v-for="(uploadFile, index) in updatedFileList"
      :key="index"
    >
      <p>ファイル名: {{ uploadFile.fileName }}</p>
      <p>ファイルタイプ: {{ uploadFile.fileType }}</p>
      <p>サイズ: {{ uploadFile.size.width }}×{{ uploadFile.size.height }}</p>
      <img :src="uploadFile.url">
    </div>
  </div>
</template>
<script>
export default {
  data: {
    uploadFileList: []
  }
  computed: {
    // this.uploadFileListに変更が加わった際に検知するためです
    uploadFileList() {
      return this.uploadFileList
    }
  }
  methods: {
    async inputFileList(event) => {
      // event.target.filesはファイルデータが格納されたリストです
      const fileList = event.target.files
      if (fileList === 0) {
        return
      }
      // FileAPIは、APIなので複数ファイルを扱う時は、
      // for文の中でawaitする必要があります
      for (file of fileList) {
        // ファイルデータ用のオブジェクトを用意します
        const fileData = {
          name: '',
          type: '',
          size: { width: 0, height: 0 },
          url: ''
        }
        const image = new Image()
        const fileReader = new FileReader()
        // 即時関数をawaitすることで処理の完了までループが終わりません
        await (async () => {
          fileReader.onload = async () => {
            image.src = fileReader.result
            // ファイルURL取得
            fileData.url = fileReader.result
            // 即時関数をawaitすることでfileData.sizeのデータを担保します
            await (() => {
              image.onload = () => {
                // ファイルサイズ取得
                fileData.size = { width: image.naturalWidth, height: image.naturalHeight }
              }
            })()
          }
          // FileAPIの起動
          fileReader.readAsDataURL(file)
        })()
        // 取得したファイルデータのオブジェクトをuploadFileListにpushします
        this.uploadFileList.push(fileData)
      }
    }
  }
}

おわりに

間違いや質問などありましたらコメントお願いします。

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

Component UIのテーブル内でHTMLを有効化したい

Componentテーブル内に表示するデータをHTMLを表示したいと考えてはまった時のメモ。
データそのものは有効化できなかったが、以下の方法で目的が達成できた。

変更前

name_telデータ内でHTMLデータを記載すると、そのままエスケープされて出てきてしまう。

// 氏名と電話をまとめて取得
<el-table-column
 prop="name_tel"
 label="氏名"
 width="200px">
</el-table-column>

変更後

<template>で表示内容を分岐させて、装飾させて解決。

// 氏名と電話を分けて取得し、分けて表示
<el-table-column
 prop="name_tel"
 label="氏名">
  <template slot-scope="scope">
   <div>{{ scope.row.name}}</div>
   <div v-if="scope.row.tel"><i class="fas fa-phone-square"></i>{{ scope.row.tel }}</div>
  </template>
</el-table-column>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue】mountedを雑に扱うから痛い目を見るのです。

概要

まあ、アホな実装してたので戒めのために記録します。

通信中は「Loading中です。」表示をすることになった。

例えば、外部のサービスに通信をして「本」のデータを一覧で取得するケースがあるとします。
私は、本一覧を表示するためのコンポーネントを作り、その中でAPIを呼びたいと思いました。
APIを呼んでいる間は、親コンポーネントのほうで「Loading中です。。。」と表示したいと思いました。

親コンポーネント

<template>
  <div id="app">
    <BookList @startLoading="isLoading=true" @endLoading="isLoading=false"/>
    <template v-if="isLoading">Loading中です。。。</template>
  </div>
</template>

<script>
import BookList from "./components/BookList";

export default {
  name: "App",
  components: {
    BookList
  },
  data: function() {
    return {
      isLoading: false
    };
  },
  mounted: function() {
    this.isLoading = false; // ...(※)
  }
};
</script>

本一覧を表示する子コンポーネント

<template>
  <div class="hello">
    <h1>本リスト</h1>
    <ul>
      <li v-for="(book, index) in books" v-bind:key="index">{{book}}</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: "BookList",
  data: function() {
    return {
      books: []
    };
  },
  mounted: async function() {
    console.log("非同期処理-start");
    this.$emit("startLoading");
    // ※疑似的にAPI通信を実現しています。5秒間疑似通信します。
    await new Promise(resolve =>
      setTimeout(() => resolve((this.books = ["本A", "本B"])), 5000)
    );
    this.$emit("endLoading");
    console.log("非同期処理-end");
  }
};
</script>

親側に「isLoading」という通信中の時は「true」になって、通信が終わると「false」になる用のフラグを設けました。
子コンポーネントの呼び出す際に、<BookList @startLoading="isLoading=true" @endLoading="isLoading=false"/>のように、カスタムイベントを設置しました。
子側でAPI通信を開始したらthis.$emit("startLoading");を、API通信が完了したらthis.$emit("endLoading");と用意したイベントを発火して、
親側の「isLoading」のtrue/falseを切り替える寸法です。

上記の実装、一見上手くいくように見えて、全然「ローディング中です。。。」が出ないんです。
mounted.gif
まあお察しの通り、上記のコードの(※)の部分ですね。
実装中に、子側のコンポーネントが作成される前に、親側の mounted() で isLoading を初期化しようと誤った勘違いが原因で、(※)の部分の処理を追加してしまったのです。

  mounted: function() {
    this.isLoading = false; // ...(※)
  }

Gif画像のコンソールで色々出力しているので気づいてるとは思いますが、上記処理のせいで子側でせっかくEmitして「isLoading」をtrueに切り替えたのに、親側でまたfalseに上書きしてしまいました。
(※)の部分の処理、つまり親側のmountedはどうやら子側のDOMが作成された後に実行されるみたいですね。
子側のコンポーネントが作成される前に初期化処理をしたいのであれば、下記のようにcreated()でやるべきでした。

<template>
  <div id="app">
    <BookList @startLoading="isLoading=true" @endLoading="isLoading=false"/>
    <template v-if="isLoading">Loading中です。。。</template>
  </div>
</template>

<script>
import BookList from "./components/BookList";

export default {
  name: "App",
  components: {
    BookList
  },
  data: function() {
    return {
      isLoading: false
    };
  },
  created: function() {
    this.isLoading = false; 
  }
};
</script>

というお話しでした。

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

新コロに関する呼びかけを簡単にできるWebサービス「Yobikake」を作ったときの、見た目を作る技術と工夫の話

image.png

こんにちはー!東京都在住のため、がっつり外出自粛をしている人間です。「ぴえん?」よりも「display: flex;」を愛用しているJKです。(これが言いたいだけで女子高生という属性を乱用してしまいました。申し訳ございません。)

友だち(@nztm)と2人で「Yobikake ~あなたの呼びかけからはじめよう~」というWebサービスをつくったので、今回は自分が主に実装した部分の技術的解説・デザイン的解説をします。なお、Nuxt.js (Vue.js)を使用しています。

※今回初めて触ったので、間違った知識など記載していましたら、コメント等でご指摘いただけますと嬉しいですm(_ _)m

この記事の想定読者

  • 自分でちょっとしたWebサイトを作りたい人
  • HTML/CSS から一歩踏み出した技術をつかってWebサイトを作ってみたい人
  • Webデザインに興味がある人

何を作ったのか

今回開発した「Yobikake ~あなたの呼びかけからはじめよう~」は、OGP画像を活用した、新型コロナウイルスに関連する呼びかけができるWebサイトです。#StayHome や #うちで過ごそう といったハッシュタグを見たことはないでしょうか。現在、様々な人が世界中、各SNSで呼びかけを行っています。
「その『呼びかけ』を、ワンクリックで気軽にできるようになれば、世界を少しでも救えるのではないか」という発想で、このサービスは作られました。

仕組みは以下の図のようになっています。
yobikakeの仕組み.png

開発の流れ

4/10, 金曜日午前に「今日とか明日って忙しいですか?」と、@nztm さんから一通のメッセージが。話を聞いてみると、OGP画像を利用して、新型コロナウイルスに関するムーブメントを促すという、シンプルなWebサービスを作りたいとのこと。「面白そう!やりたい!」と、協力させていただきました。

役割の分担についてですが、コンテンツ、仕組みの実装、デザインの方向性決定は @nztm さん、見た目に関する実装は私が担当しています。コンテンツを @nztm さんが作り、それをもとに私が見た目を整えていき、それに @nztm さんがフィードバックを返し、という流れを繰り返しました。

なお、コンセプトや仕組みについての記事は以下で @nztm さんが書いています。
https://qiita.com/nztm/items/2de55be97ac1de9a435b

サービスの概念と方向性の共有は、初日にZoomを利用して行いました。ディレクトリはGitHubで管理し、コンテンツ及びデザインに関する議論はissueまたはTwitterDMで行っています。

工夫した点・語りたい点

共有ボタンの実装

まずは、このサービスの重要な機能ともいえる「共有ボタン」の実装についてです。
共有ボタンは共通で使いまわしたいので、コンポーネント化しました。

1. 共有するテキストを動的に変更できるようにする

「よびかけ」ごとに共有したい文章とURLが異なります。文章とURLのデータを受け取って、aタグのhrefが変わるようにしました。

コンポーネント化・読み込み

毎回ボタン3つ分を記述するとメンテナンス性が下がってしまうので、コンポーネント1つをそれぞれ読み込むようにしました。なにか変更を加えたいときも、一つのファイルをいじるだけで良いので、とてもスッキリとした設計になりました。
いくつか略していますが、共有ボタン3つのコンポーネントは以下のようになっています。

shareButtons.vue
<template>
  <div class="share-buttons">
    <a
      :href="
        'http://twitter.com/share?url=https://yobikake.com/' +
          url +
          '&text=' +
          encodeURIComponent(text) +
          '%0A' +
          '&related=yobikakecom,nztm_tw'
      "
      target="_blank"
      class="share-link share-link-twitter"
      ><i class="fab fa-twitter"></i> ツイートする
    </a>
    <a
      :href="
        'https://www.facebook.com/dialog/share?app_id=640540216785473&display=popup&href=https://yobikake.com/' +
          url
      "
      target="_blank"
      class="share-link share-link-fb"
      ><i class="fab fa-facebook"></i> シェアする
    </a></div>
</template>

<script>
export default {
  props: ['url', 'text'],
  
}
</script>

<style lang="scss" scoped>

</style>

これを、以下のように呼び出すことで表示できます。

index.vue
<template>
  <div>
    <shareButtons
      url="stayhome"
      text="うちで過ごそう!みんなのために?
 #うちで過ごそう #stayhome #Yobikake"
    ></shareButtons>
  </div>
</template>

<script>
import shareButtons from '~/components/shareButtons.vue'

export default {
  components: {
    shareButtons
  }
}
</script>

このように表示されます。
image.png
もう少し細かく説明します。

URLの設定について

共有するURLの前半は共通なので、urlhttps://yobikake.com/ に続くパスを入れることにしました。また、text は、メンテナンス性・可読性を考えて、UTF-8に変換する前の状態で管理できるようにしました。意外とシンプルに、encodeURIComponent() でさくっと変換できています。
(下記Twitter用のリンクの例を参照)

<a :href="
        'http://twitter.com/share?url=https://yobikake.com/' +
          url +
          '&text=' +
          encodeURIComponent(text) +
          '%0A' +
          '&related=yobikakecom,nztm_tw'
      " >
href ではなく :hrefとなっている理由

href他、HTML属性の内部では文字列の連結等操作ができません。そこで活躍するのがVue.jsさんのv-bindです。v-bindはプロパティや属性と、式を結びつけてくれるものです。便利ですねぇ。
例えば、<a v-bind:href="式"> のように書くことができます。ここでは省略記法で、v-bind:hrefではなく:hrefと記述しています。

Vue.js ガイドで、構文について書いてあるので、詳しくはそちらを読んでいただけるとわかりやすいと思います。

2. コピーボタン

できるだけ多くの方に使っていただけることが一番なのですが、Twitter, Facebook以外のサービス全てに対応することは難しいです。そこで、どんなプラットフォームでもペーストで呼びかけられるよう、簡単にURLをコピーできるボタンを設けました。

このボタンは、クリック時にcopy()というメソッドを呼び出します。ラベルはcopyLabelという変数に入っている文字列を表示します。

<span
  class="share-link share-link-copy copy"
  @click="copy('https://yobikake.com/' + url)"
  ><i class="far fa-copy fa-fw"></i>{{ copyLabel }}
</span>

copy()では、引数をクリップボードにコピーして、copyLabelを変更しています。

<script>
export default {
  props: ['url', 'text'],
  data: () => ({
    copyLabel: 'コピーする'
  }),
  methods: {
    copy(msg) {
      const copyText = msg
      navigator.clipboard.writeText(copyText)
      this.copyLabel = 'コピーしました!'
      setTimeout(
        function() {
          this.copyLabel = 'コピーする'
        }.bind(this),
        2000
      )
    }
  }
}
</script>

もう少し余裕があれば、それぞれの「呼びかけ」もコンポーネント化して管理したいところですね。ですが、まずは2日後のリリースまでに使える状態にすることが最優先だったので、このような形になりました。

きれいに魅せる工夫

さて、機能が正常に動くことも大事ですが、その配置・表示も重要です。
いくつかこだわった点をまとめます。

ヘッダーのキャッチコピー表示

ヘッダーのキャッチコピーですが、文字を表示するだけではない、小さなこだわりがあります。単なる表示だと、このように変な改行が入ってしまいます。まるで「今すぐダウンロー ⏎ド」のようですね。

image.png
そこで、文を分解(ダジャレ)して、それぞれにdisplay: inline-block;を設定します。そうすることで、自然な部分で改行されるようになります。
image.png

<span>あなたの呼びかけから</span><span>はじめよう</span>
span {
  display: inline-block;
}

一覧のレスポンシブ対応

image.png
この部分は、いろいろやりました。項目は4つなので、並べ方は2,2か4か1,1,1,1にすることで、仲間はずれがなくきれいに並べられます。
そのため、コンテンツそれぞれは基本固定幅に設定して、親要素の幅を画面幅に応じて変えるようにしました。技術的にはなにも難しくないのですが、細かなこだわりでぐっと見栄えが良くなります。

絵文字/色

最後に、絵文字と色についてです。絵文字はTwitterのオープンソースな絵文字、「twemoji」を使っています。絵文字の見た目が端末に依存しないという利点があります。また、フラットなデザインなので最近の流行りと相性が良いです。

色は、基本的に @nztm さんのデザインラフをメインに設定しています。背景色、フッターの色、影の色は、青〜水色で統一させています。

コンプレックスカラーといって、フッターよりも背景色を青寄りに設定しました。すこし意外性のある配色ですが、見ていて楽しいですよね。これ以上語ると話がそれてしまうので、以上にしたいと思います。

最後に

読んでいただきありがとうございました!
技術的な話より、考え方の話の紹介になってしまいましたが、WebサイトデザインやWebサービスづくりをやってみようかな、と考えている人の力になれていたら幸いです…!

ちなみに、ここから実際に動いているサービスを見られます?
https://yobikake.com/

この「Yobikake」はGitHubで公開しています。PRやIssueお待ちしておりますm(_ _)m

追伸: 今回のサービスは3日間で作ったのですが、爆速開発をしてみようかな、と考えている方には「睡眠は大事ですよ」とだけお伝えしておきます。

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

Nuxt.jsで初めて作成したポートフォリオをGitHub Pagesで公開しました

この記事は、「Nuxt.js ってなんですか?」な状態の人間が、やりたいことを盛り込んで作成したポートフォリオについて書いたものです。

作成したポートフォリオ

https://tayuta.github.io/portfolio/
GitHub Pages に公開しています。

https://github.com/tayuta/portfolio
ソースコードです。

キャプチャ4.PNG
キャプチャ5.PNG
キャプチャ6.PNG
キャプチャ7.PNG
キャプチャ8.PNG

作成に至った動機

ポートフォリオを作成することになったのは、社内でポートフォリオ部(正式名称:そうだポトフを作ろう! 略称:ポトフ)が発足されたことがきっかけです。
もともと業務では、JavaScriptやHTML、CSSなど、フロント系はほとんど触れあったことがなかったので、勉強になるかと思い、入部しました。

プロジェクトの作成

ポートフォリオの研究

ポートフォリオが一体どういったものか知らなかったので、ほかの人が作成しているものを片っ端から見あさりました。
また、ポートフォリオの作成だけでなく、自分でレイアウトを考えたりすることも初めてだったので、デザイン手法などについても学習しました。

【学習に利用した記事】
全部知ってる?主要なWebデザイン手法のまとめ

ポートフォリオ概要決め

つぎに、どのようなポートフォリオを作るか、なんとなく決めました。

  • SPA
  • Nuxt.jsで作成する
  • テーマは、海の中(最終的には全く違うものができました。←)
  • 以下のコンテンツを縦に並べる
    • プロフィール
    • スキル
    • これまで作ったもの(ほぼありませんが...)
    • 愛犬について(あまりにコンテンツが少なかったので後から追加)

Nuxt.js プロジェクトの作成

以下の記事を参考に、Nuxt.js プロジェクトを作成しました。
Nuxt.js を導入しよう

設定内容が少し変わっていたので、こちらの記事も参考になります。
Nuxt.js の導入 -2.8.1ver-

画面が表示できたので、あとは自分がやりたいことを好きなように盛り込みました。

盛り込んだこと

背景に泡をぷかぷかさせる

当初のテーマは海だったので、背景に泡をぷかぷかさせたいと思いました。

導入方法

bubbly-bg.js をポートフォリオに組み込みます。
GitHub : https://github.com/tipsy/bubbly-bg

※Nuxt.js の場合、JavaScriptファイルをどこに置いて、どこで読み込めばいいのか分からなかったので有識者に質問したところ、ファイルの内容を mounted にぶち込むように教わったので、そうしました。
※mounted が何なのかはもちろん分からなかったので、以下の記事を読んでなんとなく理解しました。
Vueのライフサイクルを完全に理解した

想定通りに動くことを確認した後、?ソースなので、整理をします。

index.vue
<template>
  <section class="container">
    <canvas id="bubbly"></canvas>
    <div class="body">
      <!-- 省略 -->
    </div>
  </section>
</template>
<script>
export default {
  mounted() {
    const canvas = document.getElementById('bubbly')
    const width = (canvas.width = window.innerWidth)
    const height = (canvas.height = window.innerHeight)
    const context = canvas.getContext('2d')
    context.shadowColor = '#c9ffbf'
    context.shadowBlur = 4
    const gradient = this.setGradient(context, width, height)
    const bubbles = this.setBubbles(width, height)

    const draw = () => {
      requestAnimationFrame(draw)
      this.anim(context, gradient, bubbles, width, height)
    }
    draw()
  },
  methods: {
    // メソッド内省略
  }
}
</script>
<style lang="scss">
#bubbly {
  position: fixed;
  z-index: -5;
  top: 0;
  left: 0;
  min-width: 100vw;
  min-height: 100vh;
}
</style>

以下の記事を参考にしました。
背景のグラデーションや、泡の数・動く速度を変更したい場合も、こちらが参考になります。
【JavaScript】美しい泡が漂う[Beautiful bubbly backgrounds]の使い方

背景を斜めにする

背景が斜めだとおしゃれかと思い、背景を斜めにしました。

導入方法

背景を斜めにするためには、要素全体を回転させ、その内側の要素を反対に回転させます。
ただ要素を回転させると、左右に空白ができてしまうため、横幅を大きめに指定します。

sample.vue
<template>
  <section class="container">
    <div class="contents">
      <div class="inner">
        コンテンツ
      </div>
    </div>
  </section>
</template>

<style scoped lang="scss">
.contents {
  -webkit-transform: rotate(10deg) translate3d(0, 0, 0);
  transform: rotate(10deg) translate3d(0, 0, 0);
  background-color: #ffdadf;
  width: 200%;
  margin-left: -180px;    
  .inner {
    -webkit-transform: rotate(-10deg) translate3d(0, 0, 0);
    transform: rotate(-10deg) translate3d(0, 0, 0);
    max-width: 50%;
    margin-left: 157px;
  }
}
</style>

また、このままでは、背景が横にはみ出てしまい、横スクロールが発生してしまいます。
そのため、はみ出た部分を非表示にする必要があるので、親要素である container に「overflow-x: hidden;」を指定します。

sample.vue
<style scoped lang="scss">
.container {
  overflow-x: hidden;
}
</style>

以下の記事を参考にしました。
斜めの背景を作るためのCSSは「回転させて元に戻す」で書く!

スクロールにより、要素をふわっと表示させる

要素が画面に表示されてからアニメーションを発火させるために、カスタムスクロールディレクティブを作成しました。

導入方法

まずは、plugins フォルダ内に以下のファイルを作成します。

scroll.js
import Vue from 'vue'
Vue.directive('scroll', {
  inserted: function(el, binding) {
    const f = function(evt) {
      if (binding.value(evt, el)) {
        window.removeEventListener('scroll', f)
      }
    }
    window.addEventListener('scroll', f)
  }
})

nuxt.config.js の plugins キー内にファイルパスを追加します。

nuxt.config.js
export default {
  plugins: ['~/plugins/scroll']
}

mixin.js に handleScroll メソッドを定義します。

mixin.js
export default {
  methods: {
    handleScroll: (evt, el) => {
      const top = el.getBoundingClientRect().top
      if (window.scrollY > top + window.pageYOffset - 600) {
        el.classList.add('isView')
        return true
      }
      return false
    }
  }
}

ふわっと表示させたい要素に、v-scroll ディレクティブを追加し、アニメーションを設定します。
アニメーションを変更することで、ふわっと表示以外にも、横から表示・回転しながら表示などもできるかと思います。

sample.vue
<template>
  <div v-scroll="handleScroll" class="fade-in">ふわっと表示</div>
</template>

<script>
import mixin from './mixin'
export default {
  mixins: [mixin]
}
</script>

<style scoped lang="scss">
.fade-in {
  opacity: 0;
}
.fade-in.isView {
  animation-name: fade-in;
  animation-duration: 2s;
  animation-timing-function: ease-out;
  animation-delay: 0.2s;
  animation-iteration-count: 1;
  animation-direction: normal;
  animation-fill-mode: both;
}
@keyframes fade-in {
  0% {
    opacity: 0;
    transform: translate3d(0, 20px, 0);
  }
  100% {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }
}
</style>

以下の記事を参考にしました。
Nuxt.js でスクロールでふわっと要素が出現するやつを「カスタムディレクティブ」で実装する

また、handleScroll メソッドに関しては、sample.vue に記述しても問題ありませんが、私は複数コンポーネントで利用する予定だったので、mixinに記述しました。
mixin については、以下が参考になります。
ミックスイン

スクロールしながら画面遷移を行う

メニューのリンクをクリックした際に、スクロールしながら指定場所を表示させたいと思い、「vue-scrollto」を導入しました。

導入方法

まずは、プロジェクトディレクトリで下記のコマンドを実行します。

npm i vue-scrollto

その後、plugins フォルダ内に以下のファイルを作成します。

vue-scrollto.js
import Vue from 'vue'
import VueScrollTo from 'vue-scrollto'

Vue.use(VueScrollTo, {
  duration: 700,           // スクロール継続時間
  easing: [0, 0, 0.1, 1],  // 速度の緩急
  offset: -70              // 遷移後の位置調整
})

nuxt.config.js の plugins キー内にファイルパスを追加します。

nuxt.config.js
export default {
  plugins: ['~/plugins/vue-scrollto']
}

あとは、以下のように、v-scroll-to に遷移先の要素の id を指定することで画面内遷移が可能です。

sample.vue
<template>
  <nuxt-link v-scroll-to="'#bottom'" to>Scroll to bottom</nuxt-link>
  <div id="bottom">bottom</div>
</template>

以下の記事を参考にしました。
スクロール継続時間などのオプションについても、こちらが参考になります。
「vue-scrollto」を使ってイージングスクロールを実装する

写真をスライダーで表示

コンテンツが少なかったので、愛犬の写真を表示することにしました。
複数の写真を見てもらうため、自動で写真が切り替わるようにしたかったので、実現することができそうな「vue-awesome-swiper」を導入しました。

導入方法

まずは、プロジェクトディレクトリで下記のコマンドを実行します。

npm install vue-awesome-swiper --save

その後、plugins フォルダ内に以下のファイルを作成します。

vue-awesome-swiper.js
import Vue from 'vue'
import VueAwesomeSwiper from 'vue-awesome-swiper'
import 'swiper/css/swiper.css'

Vue.use(VueAwesomeSwiper)

nuxt.config.js の plugins キー内にファイルパスを追加します。

nuxt.config.js
export default {
  plugins: ['~/plugins/vue-awesome-swiper']
}

以下のように、スライダーを組み込みます。

sample.vue
<template>
  <swiper :options="swiperOption" ref="mySwiper1">
    <swiper-slide v-for="(item, index) in items" :key="index">
      <img :src="item.img" alt="..." />
    </swiper-slide>
    <div class="swiper-pagination" slot="pagination"></div>
    <div class="swiper-button-prev" slot="button-prev"></div>
    <div class="swiper-button-next" slot="button-next"></div>
  </swiper>
</template>

<script>
export default {
  data: function() {
    return {
      items: [
        { img: require('~/static/img_1.jpg') },
        { img: require('~/static/img_2.jpg') }
      ],
      swiperOption: {
        autoplay: {
          delay: 2500,
          disableOnInteraction: false
        },
        pagination: {
          el: '.swiper-pagination',
          clickable: true
        },
        navigation: {
          nextEl: '.swiper-button-next',
          prevEl: '.swiper-button-prev'
        }
      }
    }
  }
}
</script>

<style scoped lang="scss">
.swiper-container {
  height: 300px;
  width: 100%;
}
.swiper-slide {
  text-align: center;
  font-size: 38px;
  font-weight: 700;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-pack: center;
  -ms-flex-pack: center;
  justify-content: center;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
  border-left:1px solid #fff;
}
</style>

以下の記事を参考にしました。
「swiperOption」のその他の設定についても、こちらが参考になります。
モバイルタッチスライダー「vue-awesome-swiper」の使い方

注意点

フェードアニメーションと複数カラムスライドは同時に設定できませんでした。
以下の記述の場合、「slidesPerView: 3」の設定が無視されます。

sample.vue
swiperOption: {
  effect: 'fade',
  slidesPerView: 3,
  ...
}

パララックス

パララックスを利用しているサイトかっこいい!と思い導入しましたが、どこに使えばいいか分からず、ひっそりと利用しています。
私が今回導入したプラグインは、「vue-prlx」ですが、その他に、「vue-parallax-js」などがあるようです。以下が参考になるかと思います。
nuxt.jsでパララックスをするならvue-parallax-jsがお手軽。Cool!

細かい挙動の設定が「vue-prlx」のほうが分かりやすかったので、今回は「vue-prlx」を選択しましたが、気が向いたら「vue-parallax-js」も使ってみようかな、と思っています。(思っているだけです。

導入方法

まずは、プロジェクトディレクトリで下記のコマンドを実行します。

npm i vue-prlx

その後、plugins フォルダ内に以下のファイルを作成します。

vue-prlx.js
import Vue from 'vue'
import VuePrlx from 'vue-prlx'

Vue.use(VuePrlx)

nuxt.config.js の plugins キー内にファイルパスを追加します。

nuxt.config.js
export default {
  plugins: ['~/plugins/vue-prlx']
}

あとは、パララックス効果を追加したい要素に対して、v-prlx ディレクティブを追加するだけです。

sample.vue
<template>
  <img src="sample.png" alt="" v-prlx>
</template>

以下の記事を参考にしました。
細かい挙動の設定についても、こちらを参照してください。
vue-prlxを使ってさくっとパララックス体験をしてみる

ファビコンの作成

キャプチャ3.PNG
私は、「X-Icon Editor」を利用して、作成しました。
簡単なデザインだったので、問題なく作成できました。

GitHub Pagesに公開

ポートフォリオなので、ゆくゆくは人目に触れるようにしたいと思い、ひとまず手軽にできると聞いた GitHub Pages に公開することにしました。

公開方法

まず、nuxt.config.js の router の base と favicon のパスにリポジトリ名を記述。
※ router の base を編集することにより、localhostのパスが「localhost:3000/リポジトリ名」になります

nuxt.config.js
export default {
  router: {
    base: '/リポジトリ名/'
  },

  head: {
    title: pkg.name,
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: pkg.description }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/リポジトリ名/favicon.ico' }
    ]
  }

次に、ローカルにgh-pagesブランチを作成し、gitignoreからビルド先フォルダを外します。

# Nuxt generate
# dist

ビルドコマンドを実行します。

npm run generate

dist フォルダが作成されるので、ローカルのgh-pagesブランチにコミットします。(masterブランチにコミットする必要はありません。)
最後に、以下のコマンドを実行し、dist フォルダのみを gh-pages ブランチに切り離してリポジトリに push します。

git subtree push --prefix dist/ origin gh-pages

うまく公開できている場合、GitHub の Setting から公開されたページを確認することができます。
キャプチャ1.PNG
キャプチャ2.PNG

以下の記事を参考にしました。
Nuxt.jsで作ったWebサイトをささっとGithub Pagesに公開する

悩んだこと・大変だったこと

無名関数内で this を利用する

無名関数内で、this を利用する際に、以下のように記述していました。

sample.vue
<script>
export default {
  data: function() {
    return {
      count: 0,
    }
  },
  methods() {
    increment: function() {
      const a = function() {
        this.count++
      }
      a()
    }
  }
}
</script>

この記述では、Vueインスタンスが参照できないため、「Cannot read property 'count' of undefined」というエラーが発生します。

対応方法① this を別の変数に代入しておく

sample.vue
<script>
export default {
  data: function() {
    return {
      count: 0,
    }
  },
  methods() {
    increment: function() {
      const me = this
      const a = function() {
        me.count++
      }
      a()
    }
  }
}
</script>

余分に me という変数が出現するため、個人的にはあまり好きではないと思いました。

対応方法② アロー関数を利用する

sample.vue
<script>
export default {
  data: function() {
    return {
      count: 0,
    }
  },
  methods() {
    increment: function() {
      const a = () => {
        this.count++
      }
      a()
    }
  }
}
</script>

私が実際に採用した方法です。
余分に変数が増えない分、すっきりしました。

モバイルルータで画像が表示されない

流行りのコロナウイルスによりテレワークになった際に、会社から支給されたモバイルルータが少し遅めだったこともあり、画像が表示されないという事件が発生しました。
原因は、画像サイズが大きすぎたことでした。

対応方法① png ファイルを jpeg ファイルに変更

なんとなく png ファイルにそろえていましたが、容量的には jpeg ファイルの方が良いということを初めて知りました。
今後は、透過画像以外は jpeg ファイル、と心に刻みました。

対応方法② 画像サイズを縮小

そもそも、高画質である必要がない画像ばかりだったので、画像サイズを縮小しました。

斜めレイアウトの調整

marginの調整などの際、斜めに移動するため、思い通りにレイアウトできるまでに時間がかかりました。
数学が得意であれば、計算でもう少しスムーズにできたのかもしれないです...

これからやりたいこと

  • Netlifyで正式にリリース
    • それっぽいドメインにできるそうなので、気が向いたら変えたい。
  • Lighthouseを使ってチューニング
    • 画像サイズ以外にも改善できるところを改善したい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue-good-tableをTypeScriptで使おうとして TS7016: Could not find a declaration file for module 'vue-good-table'.

使用バージョン

$ npm list vue typescript vue-good-table
app@1.0.0
├── typescript@3.8.3
├── vue@2.6.11
└── vue-good-table@2.19.1

問題

Vue.jsをTypeScriptで使っていて、以下ドキュメントどおりに進めたところ、コンパイルエラー発生。

https://xaksis.github.io/vue-good-table/guide/#installation

エラー内容

TS7016: Could not find a declaration file for module 'vue-good-table'. 
'[app]/node_modules/vue-good-table/dist/vue-good-table.cjs.js'
implicitly has an 'any' type.

解決

[app]/@types/以下にファイル作成。

vue-good-table.d.ts
declare module 'vue-good-table'

TypeScriptまだ良く分からないですが、moduleを宣言すれば良いらしい。
@types/に関しては、d.tsファイルをそこに置くとコンパイル時に読み込んでくれるよう。

※ tsconfig.json の typeRoots のデフォルト設定が @types ディレクトリになっています。
https://qiita.com/Nossa/items/726cc3e67527e896ed1e

VueGoodTableはanyだけど、とりあえずコンパイルはできました!

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

ユーザ毎にvuetifyのテーマをカスタマイズする方法(Nuxt.js)

Vue.js, Nuxt.jsを使うのであれば高確率でお世話になるVuetifyについて。

テーマの色を変更したい。ってときの対応です。
下記の2パターンについて、記載します。
1. アプリケーション設定で変更する
2. ユーザごとに設定する

※最近はVue.jsよりNuxt.jsの方が勢いがあると思うので、Nuxt.jsで。

1. アプリケーション設定で変更する

ググるとすぐに見つかるやつ。

まずはvuetifyの設定を記載したjsファイルを作ります。

vuetify.js
import Vue from 'vue'
import Vuetify from 'vuetify/lib'

Vue.use(Vuetify)

// 好きにテーマ色を設定できる
const theme = {
  primary: '#4CAF50',
  secondary: '#9C27b0',
  accent: '#9C27b0',
  info: '#00CAE3'
}

export default new Vuetify({
  theme: {
    themes: {
      dark: theme,
      light: theme
    }
  }
})

次に、作成した設定ファイルを読み込むようにnuxt.config.jsに設定を追加。

nuxt.config.js
export default {
  ...
  // 下記の設定を追加
  vuetify: {
    optionsPath: 'path/to/vuetify.js'
  },
  ...
}

2. ユーザごとに設定する

意外とサクっと見つからない。
私の調べ方がヘタクソなだけかもしれませんが。

各色のカードを並べたデバッグ用ページを作成して、好きに色を変えられるかどうか試してみます。
下記のコードですが、早い話がvuetifyオブジェクトのプロパティを一部変更するだけです。
themeオブジェクトをユーザごとにlocalstorageなりDBなりに保存しておけば、ユーザごとにテーマ色を切り替えられます。

pages/colors.vue
<template>
  <v-container>
    <v-row>
      <v-col cols="2">
        <v-card height="300px" class="primary" />
      </v-col>
      <v-col cols="2">
        <v-card height="300px" class="secondary" />
      </v-col>
      <v-col cols="2">
        <v-card height="300px" class="warning" />
      </v-col>
      <v-col cols="2">
        <v-card height="300px" class="error" />
      </v-col>
    </v-row>
    <v-row>
      <v-col cols="2">
        <v-text-field v-model="primaryColor" />
      </v-col>
      <v-col cols="2">
        <v-btn @click="changePrimaryColor">
          Primary
        </v-btn>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  data () {
    return {
      primaryColor: ''
    }
  },
  methods: {
    changePrimaryColor () {
      // 色を変更している箇所
      this.$vuetify.theme.themes.light.primary = this.primaryColor
    }
  }
}
</script>

こんな感じのページができます。
色コード打ち込んでPrimaryボタンをクリックすると、Primaryの色が変えられます。
image.png

試しに#000で黒色にしてみると、こんな感じ。
image.png

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

docker × rails × vue.js で環境構築をしてみた。

謝辞

下記に記載されている手順は少し前後があるかもしれません。
私は最初に参考サイトリンク先を参考にrailsの環境構築をしてからvueを導入しました。なのでgemfileもdockerfileもdocker-compose.ymlも
最初から下記の記述をしていた訳ではありません。手順6以降が怪しいのでご了承をお願いします。
コメントで指摘いただけますと幸いです。

都度、更新していきます。

手順

1.Dockerfile,docker-compose.yml,gemfile,gemfile.lockを作成。
※Dockerfile,docker-compose.ymlについては下記に明記。

2.gemfileに下記を記載。

Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 5.2.2'
gem 'webpacker', github: 'rails/webpacker'

3.gemfile.lockは中身は何も記載しなくてOK

4.アプリ新規作成

docker-compose run web rails new . --force --database=mysql --skip-bundle

5.database.ymlを修正

database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password # docker-compose.ymlのMYSQL_ROOT_PASSWORD
  host: db # docker-compose.ymlのservice名

6.コンテナのビルド

$docker-compose build

7.vueを導入

$ docker-compose run web rails webpacker:install
$ docker-compose run web rails webpacker:install:vue

8.vue.js ファイルのビルド

$docker-compose run web bin/webpack

9.db作成

$docker-compose run web rails db:create

10.起動

$docker-compose up

参考サイト

丁寧すぎるDocker-composeによるrails5 + MySQL on Dockerの環境構築(Docker for Mac)

Rails5.2 + Docker環境にVue.js (Webpacker) を導入する

Vue.jsをつかったRuby on Railsプロジェクトのはじめかた(with Docker)

Dockerfile

Dockerfile
ROM ruby:2.5.3

RUN apt-get update -qq && apt-get install -y build-essential
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash
RUN apt-get install -y nodejs
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt-get update -qq && apt-get install yarn

RUN mkdir /app_name
ENV APP_ROOT /app_name
WORKDIR $APP_ROOT

ADD ./Gemfile $APP_ROOT/Gemfile
ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock

RUN bundle install
ADD . $APP_ROOT

docker-compose.yml

docker-compose.yml
ersion: '3'
services:
  db:
    image: mysql:5.7
    volumes:
      - db-volume:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
    ports:
      - "3306:3306"

  web: &app_base
    build: .
    command: rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/app_name
      - bundle:/usr/local/bundle:delegated
      - node_modules:/app/node_modules:delegated
    ports:
      - "3000:3000"
    depends_on:
      - db
    tty: true
    stdin_open: true

  webpack:
    <<: *app_base
    command: "bin/webpack-dev-server"
    ports:
      - "3035:3035"
    depends_on:
      - web
    tty: false
    stdin_open: false

volumes:
  db-volume:
  bundle:
  node_modules:

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