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

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

# 作成したポートフォリオ
~~[https://tayuta.github.io/portfolio/](https://tayuta.github.io/portfolio/)
GitHub Pages で公開しています。~~
[https://tayu.netlify.app/](https://tayu.netlify.app/)
現在は、Netlify で公開しています。

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

![キャプチャ4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178572/08478d28-e3ad-cd97-e1df-2c644a61ec31.png)
![キャプチャ5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178572/42a9e873-2b87-20b1-499a-fd529e442675.png)
![キャプチャ6.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178572/a20e23f3-a766-d240-3283-3be36d957b43.png)
![キャプチャ7.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178572/7a10b609-5cd3-e177-e7ba-a9b8ac9c7e70.png)
![キャプチャ8.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178572/8ea6d138-6884-b03e-dcc9-09bd0816918b.png)

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

# プロジェクトの作成

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

【学習に利用した記事】
[全部知ってる？主要なWebデザイン手法のまとめ](https://webdesign-trends.net/entry/3036)

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

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

## Nuxt.js プロジェクトの作成
以下の記事を参考に、Nuxt.js プロジェクトを作成しました。
[Nuxt.js を導入しよう](https://qiita.com/kurararara/items/9f2fb240a4310f0f562d)

設定内容が少し変わっていたので、こちらの記事も参考になります。
[Nuxt.js の導入 -2.8.1ver-](https://qiita.com/dama-a/items/c8e9053444b2aecbe5ca)

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

# 盛り込んだこと

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

### 導入方法
bubbly-bg.js をポートフォリオに組み込みます。
GitHub : [https://github.com/tipsy/bubbly-bg](https://github.com/tipsy/bubbly-bg)

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

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

```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］の使い方](https://on-ze.com/archives/6784)

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

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

```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は「回転させて元に戻す」で書く！](https://haniwaman.com/background-skew/)

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

### 導入方法
まずは、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 でスクロールでふわっと要素が出現するやつを「カスタムディレクティブ」で実装する](https://qiita.com/po3rin/items/325ac3718c73cde765d2)

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

## スクロールしながら画面遷移を行う
メニューのリンクをクリックした際に、スクロールしながら指定場所を表示させたいと思い、「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」を使ってイージングスクロールを実装する](https://www.kabanoki.net/4361/)

## 写真をスライダーで表示
コンテンツが少なかったので、愛犬の写真を表示することにしました。
複数の写真を見てもらうため、自動で写真が切り替わるようにしたかったので、実現することができそうな「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」の使い方](https://www.kabanoki.net/4783/)

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

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

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

細かい挙動の設定が「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を使ってさくっとパララックス体験をしてみる](https://www.mitsue.co.jp/knowledge/blog/frontend/202003/31_1335.html)

# ファビコンの作成
![キャプチャ3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178572/7cd94408-2c6b-f440-c51e-d9808b8331e4.png)
私は、「[X-Icon Editor](http://www.xiconeditor.com/)」を利用して、作成しました。
簡単なデザインだったので、問題なく作成できました。

# 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からビルド先フォルダを外します。

```.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178572/1792c440-a1bb-6238-70f7-b2de9857bc84.png)
![キャプチャ2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178572/68f1627c-c2d1-776e-6faf-f7832dff7917.png)

以下の記事を参考にしました。
[Nuxt.jsで作ったWebサイトをささっとGithub Pagesに公開する](https://qiita.com/Ancient_Scapes/items/73da90103a995a614ec3)

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

## 無名関数内で 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を使ってチューニング
  - 画像サイズ以外にも改善できるところを改善したい。