フロントエンドのことを全く知らなくてもポートフォリオサイトを作れた(Vue.js+Netlify)

#はじめに
みなさんはポートフォリオをお持ちですか?
エンジニアにしろ、デザイナーにしろ、その他の職業にしろ、自分をアピールするためにポートフォリオなる資料は大事ですよね。
自分を省みてみると、あれ、そんなもの持ってない(驚愕)
それなら作るしかないでしょう!!
ということで、まかりなりにもIT系のエンジニアを名乗っている手前、紙ではなくてWeb上で誰でも閲覧できるSPAとして公開したいと思い、作成に取りかかり、約1.5ヶ月で完成させることができました。
タイトル通り、私はフロントエンドの知識は皆無に等しく、ポートフォリオサイトを閲覧して頂くとわかると思いますが、正直、かけられている時間の割にはクオリティは低いと思います。
それでも、Vue.jsで何かを作りたいと思っている方や、これからポートフォリオを作ってみようとしている方の一助になればと思い、記事を投稿させて頂きます。
また、Vue.js, HTML, CSSのコーディングについて、フロントエンドの強い方からしたら、頭を抱えたくなるようクソコーディングをしているかもしれません。
そんな場合には、「こんなクソコードを書いてんじゃねえ、ぶち転がすぞ、ここはこう書くんだよ」というように、優しく指摘して頂けると嬉しいです。

#ポートフォリオ
- [ポートフォリオサイト](https://rikuro-aub-portfolio.netlify.com)
- [ソース](https://github.com/rikuro-aub/portfolio)

#環境

```bash
$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 18.04.2 LTS
Release:	18.04
Codename:	bionic

$ node --version
v11.12.0

$ npm --version
6.9.0

$ vue --version
3.5.1

$ n --version
2.1.12
```

#開発環境構築
早速、開発してゆきたいところなのですが、そのためにまず、開発環境構築という第一関門を突破せねばなりません。
Vue.jsで開発してゆくには[node.js](https://nodejs.org/ja/)と、その[node.js](https://nodejs.org/ja/)のパッケージを管理するツール([npm](https://www.npmjs.com)や[yarn](https://yarnpkg.com/lang/ja/))が必要になります。
また、[Vue CLI](https://cli.vuejs.org)も導入しています。
正直、[Vue CLI](https://cli.vuejs.org)について説明しろと言われると、よくわかっていないのですが、テンプレート作成からビルドまで行ってくれるすごいやつで、Vue.jsで開発してゆくには必須のツールだと理解しています。
個人的な考えですが、この手の環境構築で重要なことはツールのバージョン管理だと思っております。
プロダクト作成の過程でエラーに遭遇した時に、バージョン依存が原因ということが往々にしてあると思います。
なので、面倒でもバージョン管理はしっかり行っておくことをおすすめします。
とりあえずの私が構築した環境は、以下です。

[node.js](https://nodejs.org/ja/) + [npm](https://www.npmjs.com) + [@vue/cli](https://cli.vuejs.org) + [n](https://github.com/tj/n)

環境構築の具体的な導入手順や知識については以下の投稿等を参考にして、ベストな環境を模索してみてください。

- [nodebrew,npm,yarnなどNode.js関連をざっくり学びながらインストール【初学者向け】](https://qiita.com/nt_tn/items/972a40ee56a67f2a74cb)
- [Ubuntuに最新のNode.jsを難なくインストールする](https://qiita.com/seibe/items/36cef7df85fe2cefa3ea)
- [npm vs yarnどっち使うかの話](https://qiita.com/jigengineer/items/c75ca9b8f0e9ce462e99)
- [Yarn：Facebook発のパッケージマネジャーはnpmに代わるスタンダードになるか](https://www.webprofessional.jp/yarn-vs-npm/)
- [Vue CLI 3入門 01: Vue CLIとは](http://www.fumiononaka.com/Business/html5/FN1807001.html)
- [Vue CLI 3の変更点・使い方(vue-cliから@vue/cliにアップデート)](https://qiita.com/Junpei_Takagi/items/603d44f7885bd6519de2)
- [Vue CLI 3.0 が来ました！](https://jp.vuejs.org/2018/08/11/vue-cli-3-0-is-here/)
- [node.jsのバージョンを切り替えるお手軽な方法](https://note.mu/o_matsuo/n/n748e0503fc3a)
- [Windows における Node.js バージョン管理の選択（nvm-windows, nodist 等）](https://blog.clock-up.jp/entry/2018/02/10/nodejs-manager-on-windows)


#デザイン
環境の準備は整ったと思うので、あとは思う存分、開発をしてゆきましょう。
とはいっても、ポートフォリオを作りたいと思っていても、どんなデザインで作ったら良いかわからないという方がいらっしゃるかと。
そんな方は以下の、色々なポートフォリオサイトをまとめてくれている投稿があるので、デザインの参考にされてみてはどうでしょうか。

- [エンジニア向けのポートフォリオサイトまとめ](https://qiita.com/turmericN/items/37c3fbe00ee6bbc7e201)

デザインのアイディアは湧いてきたでしょうか?
色々とアイディアが湧いてきて、考えを整理したいという方は、以下のようなデザインツールを使ってカタチに残しておくのはどうでしょう。

- [2018年版：おすすめの人気UIデザインツール徹底比較](https://webdesign-trends.net/entry/6613)

私は[Figma](https://www.figma.com)を試しに使ってみました。

- [Figmaデザインサンプル](https://www.figma.com/file/RMFoRyhLdcoWGxXV5jxO6a2H/Untitled?node-id=0%3A1)

ツールの機能を使いこなせていない感アリアリですね...
そもそも、ここでデザインしたものと実装したものは結構違うし...
これならばデザインツールを使うまでもないのではと。
[Figma](https://www.figma.com)の機能はこんなものではないと思うので、皆さんは、もっと使い倒してみてください。

#Vue.jsの勉強方法がわからないという方は
新しいことを習得するのは大変ですよね。
知識がゼロベースなわけですから、何がわからないのかわからない状態で、どう手をつけていったら良いのかと悩んでいる方もいると思います。
そんな方は以下の投稿を参考してみたらどうでしょうか。

- [Vueを学び、SPA対応のポートフォリオサイトを自作するまでの道のり](https://qiita.com/guru_taka/items/c54b11b9cc2c924e23c1)

Vue.jsを理解する手順を丁寧に解説してくれている、とてもステキな投稿です。
こちらの投稿を見て、私もVue.jsでポートフォリオサイトを作成しようと思いましたし、ポートフォリオのデザインでも大いに参考にさせて頂いた部分があります。

#実装
ここまできたら、あとはガンガン、プロダクトを作ってゆきましょう。
以下の投稿等で解説されている方法でテンプレートを作成して、目的に応じて少しずつ改造してゆけば良いのではと思います。

- [Vue.js を vue-cli を使ってシンプルにはじめてみる](https://qiita.com/567000/items/dde495d6a8ad1c25fa43)

このあとの記述ではVue.jsのことを順番に解説するというよりは、私が作成したポートフォリオサイトで、少し力を入れた部分を中心に解説したいと思います。

##component化
Vue.jsの機能に[component](https://jp.vuejs.org/v2/guide/components.html)があります。
[component](https://jp.vuejs.org/v2/guide/components.html)はtemplateとJavaScriptをセットにしてUI部品として切り出したものです。
つまりは、何度も使う機能を共通の部品として切り出しておいて、必要に応じてその部品を呼び出すという感じですかね。
私のポートフォリオで具体例を出すと、以下の画面のスキルバーの表示やプロダクトの表示は[component](https://jp.vuejs.org/v2/guide/components.html)化して呼び出せるようにしています。

![portfolio1](https://cl.ly/ba317a14f870/portfolio1.png)
![portfolio2](https://cl.ly/4b1f004d1b84/portfolio2.png)

じゃあ、具体的なコードはどうなっているのか見てゆきます。
まずは、スキルバーの方は以下です。

<details><summary>親</summary><div>

```vuejs:portfolio/src/views/Skill.vue
<template>
<div class="skill">
    <h1>Skill</h1>
    <p class="section-name">Program language</p>
    <p class="skill-section">
        <skill-bar skill-name='VB.NET' active-level=3></skill-bar>
        <skill-bar skill-name='PHP' active-level=3></skill-bar>
        <skill-bar skill-name='SQL(ストアドプロシージャ)' active-level=3></skill-bar>
        <skill-bar skill-name='Bash' active-level=2></skill-bar>
        <skill-bar skill-name='HTML' active-level=1></skill-bar>
        <skill-bar skill-name='CSS' active-level=1></skill-bar>
        <skill-bar skill-name='JavaScript' active-level=1></skill-bar>
    </p>
    <p class="section-name">Framework</p>
    <p class="skill-section">
        <skill-bar skill-name='ASP.NET' active-level=2></skill-bar>
        <skill-bar skill-name='Symfony' active-level=2></skill-bar>
        <skill-bar skill-name='Vue.js' active-level=1></skill-bar>
    </p>
    <p class="section-name">Others</p>
    <p class="skill-section">
        <skill-bar skill-name='Git' active-level=2></skill-bar>
        <skill-bar skill-name='SVN' active-level=2></skill-bar>
        <skill-bar skill-name='Slack' active-level=2></skill-bar>
    </p>
</div>
</template>

<script>
import SkillBar from '@/modules/SkillBar.vue'
export default {
    components: {
        SkillBar
    }
}
</script>

<style>
.skill-section {
    padding: 0.5em 2em;
    margin-left: 15%;
    margin-right: 15%;
    margin-bottom: 3em;
    border: double 5px #4ec4d3;
    min-width: 350px;
    max-width: 80%;
}
.section-name {
    text-align: left;
    max-width: 100%;
    padding-left: 15%;
    font-size: 21px;
}
</style>

```
</div></details>

<details><summary>子</summary><div>

```vuejs:portfolio/src/modules/SkillBar.vue

<template>
<div>
    <p class="skill-name">{{skillName}}</p>
    <ol class="skill-bar">
        <li v-bind:class="{ 'is-active': activeLevel === '1' }"><span>個人開発レベル</span></li>
        <li v-bind:class="{ 'is-active': activeLevel === '2' }"><span>業務経験あり-基礎レベル</span></li>
        <li v-bind:class="{ 'is-active': activeLevel === '3' }"><span>業務経験あり-標準レベル</span></li>
        <li v-bind:class="{ 'is-active': activeLevel === '4' }"><span>業務経験あり-重箱の隅の知識あり</span></li>
        <li v-bind:class="{ 'is-active': activeLevel === '5' }"><span>なんでもこい!!</span></li>
    </ol>
</div>
</template>

<script>
export default {
    props: {
        skillName: String,
        activeLevel: String
    }
}
</script>

<style>
* {
    box-sizing: border-box;
}

:root {
    --color-white: #fff;
    --color-black: #333;
    --color-gray: #75787b;
    --color-gray-light: #bbb;
    --color-gray-disabled: #e8e8e8;
    --color-green: #53a318;
    --color-green-dark: #383;
    --color-red: #FF0000;
    --font-size-small: .75rem;
    --font-size-default: .875rem;
}

.skill-name {
    text-align: left;
}

.skill-bar {
    display: flex;
    justify-content: space-between;
    list-style: none;
    margin: 0 0 3em 0;
}

.skill-bar li {
    flex: 2;
    position: relative;
    padding: 0 0 14px 0;
    font-size: var(--font-size-default);
    line-height: 1.5;
    color: var(--color-green);
    font-weight: 600;
    white-space: nowrap;
    overflow: visible;
    min-width: 0;
    text-align: center;
    border-bottom: 2px solid var(--color-green-dark);
}

.skill-bar li:first-child,
.skill-bar li:last-child {
    flex: 1;
}

.skill-bar li:last-child {
    text-align: right;
}

.skill-bar li:before {
    content: "";
    display: block;
    width: 8px;
    height: 8px;
    background-color: var(--color-green-dark);
    border-radius: 50%;
    border: 2px solid var(--color-white);
    position: absolute;
    left: calc(50% - 7px);
    bottom: -7px;
    z-index: 3;
    transition: all .2s ease-in-out;
}

.skill-bar li:first-child:before {
    left: 0;
}

.skill-bar li:last-child:before {
    right: 0;
    left: auto;
}

.skill-bar span {
    transition: opacity .3s ease-in-out;
}

.skill-bar li:not(.is-active) span {
    opacity: 0;
}

.skill-bar .is-active:not(:first-child):after {
    content: "";
    display: block;
    width: 100%;
    position: absolute;
    bottom: -2px;
    left: -50%;
    z-index: 2;
    border-bottom: 2px solid var(--color-green);
}

.skill-bar li:last-child span {
    width: 200%;
    display: inline-block;
    position: absolute;
    left: -100%;
}

.skill-bar .is-active:last-child:after {
    width: 200%;
    left: -100%;
}

.skill-bar .is-active:before,
.skill-bar .is-hovered:before{
    background-color: var(--color-white);
    border-color: var(--color-red);
}

.skill-bar li:hover:before {
    background-color: var(--color-white);
    border-color: var(--color-green);
}

.skill-bar li:hover:before,
.skill-bar .is-hovered:before {
    transform: scale(1.33);
}

.skill-bar li:hover span,
.skill-bar li.is-hovered span {
    opacity: 1;
}

.skill-bar:hover li:not(:hover) span {
    opacity: 0;
}
</style>
```

</div><details>

まず最初にお断りしておかなければならないのですが、スキルバーを構成するCSSのコーディングは以下の投稿から転用させて頂いているということです。

- [[CSS]シンプルなHTMLで、美しいプログレスバーを実装するスタイルシートのテクニック](https://coliss.com/articles/build-websites/operation/css/pure-css-progress-bar.html)

転用しておきながらですが、このCSSを一から解説しろと言われると、正直厳しいです。
なので、今回はこのプログレスバー(私のポートフォリオサイトではスキルバーとして使用)の構造はありきで、でも、そのまま使用するだけでは能がないなというのとコード的にも美しくないなということで、component化しました。

前置きはこれぐらいにして、component化にフォーカスを当てて解説します。
構造としては、Skill.vue(親)からcomponentのSkillBar.vue(子)を呼び出しているという親子関係です。
Skill.vue(親)ではcomponentをimportし、SkillBar.vue(子)では親からデータを受け取るためにpropsを定義しています。
propsについての詳しいことは、以下を参照してみてください。

- [コンポーネントの基本](https://jp.vuejs.org/v2/guide/components.html)

Skill.vue(親)のtemplateの``skill-bar``タグで、componentを利用しています。

```html
    <p class="skill-section">
        <skill-bar skill-name='VB.NET' active-level=3></skill-bar>
        <skill-bar skill-name='PHP' active-level=3></skill-bar>
        <skill-bar skill-name='SQL(ストアドプロシージャ)' active-level=3></skill-bar>
        <skill-bar skill-name='Bash' active-level=2></skill-bar>
        <skill-bar skill-name='HTML' active-level=1></skill-bar>
        <skill-bar skill-name='CSS' active-level=1></skill-bar>
        <skill-bar skill-name='JavaScript' active-level=1></skill-bar>
    </p>
```
``import SkillBar from '@/modules/SkillBar.vue'``ではパスカルケースの``SkillBar``で定義されていますが、HTMLのtemplate内ではケバブケース(``<skill-bar></skill-bar>``)でなければなりません。そのことは公式のドキュメントでも言及されています。

- [プロパティ](https://jp.vuejs.org/v2/guide/components-props.html)

``skill-bar``タグの属性の``skill-name``と``active-level``はpropsで子に渡すデータです。
propsで渡されたデータは子のtemplateの部分で使用されています。

```html
<div>
    <p class="skill-name">{{skillName}}</p>
    <ol class="skill-bar">
        <li v-bind:class="{ 'is-active': activeLevel === '1' }"><span>個人開発レベル</span></li>
        <li v-bind:class="{ 'is-active': activeLevel === '2' }"><span>業務経験あり-基礎レベル</span></li>
        <li v-bind:class="{ 'is-active': activeLevel === '3' }"><span>業務経験あり-標準レベル</span></li>
        <li v-bind:class="{ 'is-active': activeLevel === '4' }"><span>業務経験あり-重箱の隅の知識あり</span></li>
        <li v-bind:class="{ 'is-active': activeLevel === '5' }"><span>なんでもこい!!</span></li>
    </ol>
</div>
```
マスタッシュの``{{skillName}}``に親から渡された``skillName``がbindされ、``v-bind``の``activeLevel``でclass属性の動的bindの条件判定に使用しています。
スキルバーの構造として、``is-active`` classが付与されると、赤丸の選択状態になります。
なので、``v-bind:class="{ 'is-active': activeLevel === '1' }``の``activeLevel === '1'``でTrueになると、``is-active`` classが付与されることになります。
ここで、``activeLevel``はわざわざString型にしないで、Number型で使えば良いじゃないかと感じると思います。
まったくその通りで、ですが、propsでデータ渡そうとすると、どうしてもString型になってしまい、Number型で渡せなかったんです。
はい、ここはツッコまれポイントですね...


次に、プロダクトの方もみてゆきましょう。


<details><summary>親</summary><div>

```html:portfolio/src/views/Product.vue
<template>
<div class="skill">
    <h1>Skill</h1>
    <p class="section-name">Program language</p>
    <p class="skill-section">
        <skill-bar skillName='VB.NET' activeLevel=3></skill-bar>
        <skill-bar skillName='PHP' activeLevel=3></skill-bar>
        <skill-bar skillName='SQL(ストアドプロシージャ)' activeLevel=3></skill-bar>
        <skill-bar skillName='Bash' activeLevel=2></skill-bar>
        <skill-bar skillName='HTML' activeLevel=1></skill-bar>
        <skill-bar skillName='CSS' activeLevel=1></skill-bar>
        <skill-bar skillName='JavaScript' activeLevel=1></skill-bar>
    </p>
    <p class="section-name">Framework</p>
    <p class="skill-section">
        <skill-bar skillName='ASP.NET' activeLevel=2></skill-bar>
        <skill-bar skillName='Symfony' activeLevel=2></skill-bar>
        <skill-bar skillName='Vue.js' activeLevel=1></skill-bar>
    </p>
    <p class="section-name">Others</p>
    <p class="skill-section">
        <skill-bar skillName='Git' activeLevel=2></skill-bar>
        <skill-bar skillName='SVN' activeLevel=2></skill-bar>
        <skill-bar skillName='Slack' activeLevel=2></skill-bar>
    </p>
</div>
</template>

<script>
import SkillBar from '@/modules/SkillBar.vue'
export default {
    components: {
        SkillBar
    }
}
</script>

<style>
.skill-section {
    padding: 0.5em 2em;
    margin-left: 15%;
    margin-right: 15%;
    margin-bottom: 3em;
    border: double 5px #4ec4d3;
    min-width: 350px;
    max-width: 80%;
}
.section-name {
    text-align: left;
    max-width: 100%;
    padding-left: 15%;
    font-size: 21px;
}
</style>
```
</div>
</details>

<details><summary>子</summary><div>

```html:portfolio/src/modules/ProductFigure.vue
<template>
<div class="product-figure">
    <a v-bind:href="productLink" target="_blank">
        <figure>
            <img v-bind:src="image">
            <figcaption>
                <p>{{productTitle}}</p>
                <p>{{productDescription}}</p>
            </figcaption>
        </figure>
    </a>
</div>
</template>

<script>
export default {
    props: {
        productLink: String,
        productImage: String,
        productTitle: String,
        productDescription: String
    },
    data() {
        return{
            image: require('@/assets/' + this.productImage)
        }
    }
}
</script>

<style>
figure {
    display: inline-block;
    position: relative;
    overflow: hidden;
    border: solid #000 1px;
    min-width: 200px;
    width: 300px;
}

figure img{
    width: 100%;
    height: 200px;
}

figcaption {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 2;
    width: 100%;
    height: 200px;
    background: rgba(0,0,0,.6);
    transition: 1s;
    opacity: 0;
    color: #fff;
    text-align: center;
}

figure:hover figcaption {
    transform: rotate(360deg);
    opacity: 1;
}
</style>
```

</div>
</details>


componentの基本的な考え方は先ほどと同じで、今回はpropsで渡すデータに画像のファイルパスを含んでいます。
``src``属性に``image``をbindしています。
``image``は以下の``data()``で``return``されています。

```js
data() {
        return{
            image: require('@/assets/' + this.productImage)
        }
```

``img``タグの``src ``属性にpropsで渡したStringを直接bindしようとすると、上手くbindできないため、``require``をしています。
その理由は以下のようです。

- [【Vue.js】imgタグのsrc要素は指定の仕方によって読み込み方が違う](https://qiita.com/coppieee/items/4260bd0af246aebf5557)
- [アセット URL ハンドリング](https://vue-loader-v14.vuejs.org/ja/configurations/asset-url.html)

component化については以上です。

##CSSで視覚的に面白く
ここではCSSで実装できる、視覚的な表現について解説してゆきます。
具体的には以下のプロダクト画面やホーム画面で実装しています。

![portfolio3](https://cl.ly/bb478722fd8d/portfolio3.gif)
![portfolio4](https://cl.ly/7a3a97959563/portfolio4.gif)

まずはプロダクト画面について解説してゆきます。
マウスを画像上に置くと、詳細がクルッと表示される部分です。

<details><summary>クルッと表現</summary><div>

```vue:portfolio/src/modules/ProductFigure.vue
<template>
<div class="product-figure">
    <a v-bind:href="productLink" target="_blank">
        <figure>
            <img v-bind:src="image">
            <figcaption>
                <p>{{productTitle}}</p>
                <p>{{productDescription}}</p>
            </figcaption>
        </figure>
    </a>
</div>
</template>

<script>
export default {
    props: {
        productLink: String,
        productImage: String,
        productTitle: String,
        productDescription: String
    },
    data() {
        return{
            image: require('@/assets/' + this.productImage)
        }
    }
}
</script>

<style>
figure {
    display: inline-block;
    position: relative;
    overflow: hidden;
    border: solid #000 1px;
    min-width: 200px;
    width: 300px;
    height: 200px;
}

figure img{
    position: relative;
    width: 100%;
    height: 100%;
}

figcaption {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,.6);
    transition: 1s;
    opacity: 0;
    color: #fff;
    text-align: center;
}

figure:hover figcaption {
    transform: rotate(360deg);
    opacity: 1;
}
</style>
```
</div>
</details>

まずは、この表現は以下の投稿で紹介されているコードを転用させて頂いていることを、お断りしておきます。

- [【CSS】transformの使い方を解説！要素の変形や回転も自由自在](https://creive.me/archives/16145/)

基本的に上記した転用元の投稿を参照して頂ければ、詳細な解説もしてくれています。
ですので、ここでは補足的な解説に止めます。

``figure``要素, ``img``要素, ``figcaption``要素の三者の親子関係に着目してみたいと思います。
最初、この三者の親子関係は以下の``img``要素と``figcaption``要素が子の並列関係なのかなと思っていました。

| 要素              |親子関係        |
|:-----------------|:--------------|
| figure           |親             |
| img              |子1            |
| figcaption       |子2            |

ですが、実際は以下の``figcaption``要素は``img``要素の子になるようでした。

| 要素              |親子関係        |
|:-----------------|:--------------|
| figure           |親             |
| img              |子             |
| figcaption       |孫             |

ですので、``figcaption``を重ねて表示する場合に``figure``と``img``は``position: relative``を指定して、``figcaption``は``position: absolute``を指定しています。
また、``figcaption``は``img``の子なので、``z-index``は指定しなくても``img``の上に``figcaption``が表示されています。

この親子関係の知見については、私が試行錯誤してたどり着いただけの結果であって、しっかりと裏付けのあるものではありません。
裏付けのあるドキュメント等を見つけられれば良かったのですが、見つからず(そもそも探す努力をしていない...)、ですので、誤っていたらスイマセン。

それぞれの要素(タグ)の役割は、以下の投稿を参照してみてください。

- [HTML5タグリファレンス-\<figure>](http://www.htmq.com/html5/figure.shtml)
-  [HTML5タグリファレンス-\<figcaption>](http://www.htmq.com/html5/figcaption.shtml)
-  [HTML5タグリファレンス-\<img>](http://www.htmq.com/html5/img.shtml)

``position``や``z-index``って何のことやらという方は以下の投稿等を参考にしてみてください。

- [CSSのpositionを総まとめ！absoluteやfixedの使い方は？](https://saruwakakun.com/html-css/basic/relative-absolute-fixed)
- [z-indexの使い方：CSSで重なり順を指定する](https://saruwakakun.com/html-css/basic/z-index)

次に、ホーム画面の方も見てゆきましょう。
ホーム画面の方は「Welcome to my portfolio!!」の文字が順番にフェードインするというものです。

<details><summary>Welcome to my portfolio!!</summary><div>

```vue:portfolio/src/views/Home.vue
<template>
    <div class="home">
        <span v-for="item in messageChar" v-bind:key="item.id">{{item.char}}</span>
    </div>
</template>

<script>
export default {
    data() {
        return {
            messageChar: [
                {id: 1, char: 'W'},
                {id: 2, char: 'e'},
                {id: 3, char: 'l'},
                {id: 4, char: 'c'},
                {id: 5, char: 'o'},
                {id: 6, char: 'm'},
                {id: 7, char: 'e'},
                {id: 8, char:  ' '},
                {id: 9, char: 't'},
                {id: 10, char: 'o'},
                {id: 11, char: ' '},
                {id: 12, char: 'm'},
                {id: 13, char: 'y'},
                {id: 14, char: ' '},
                {id: 15, char: 'p'},
                {id: 16, char: 'o'},
                {id: 17, char: 'r'},
                {id: 18, char: 't'},
                {id: 19, char: 'f'},
                {id: 20, char: 'o'},
                {id: 21, char: 'l'},
                {id: 22, char: 'i'},
                {id: 23, char: 'o'},
                {id: 24, char: '!'},
                {id: 25, char: '!'}
            ]
        }
    }
}
</script>

<style>
@keyframes fadeIn {
    0% {
        opacity: 0;
    }
    100% {
        opacity: 1;
    }
}

.home span {
    animation-name: fadeIn;
    animation-duration: 0.5s;
    animation-timing-function: linear;
    animation-fill-mode: both;
    display: inline-block;
    white-space: pre-wrap;
    font-weight: bold;
    font-size: 3em;
    margin-top: 3em;
}

/* @for $item from 2 through 25 {
    span:nth-of-type(#{$item}){
        animation-delay: #{($item-1)*0.5}s;
    }
} */

span:nth-of-type(2) {
    animation-delay: 0.5s;
}

span:nth-of-type(3) {
    animation-delay: 1s;
}

span:nth-of-type(4) {
    animation-delay: 1.5s;
}

span:nth-of-type(5) {
    animation-delay: 2s;
}

span:nth-of-type(6) {
    animation-delay: 2.5s;
}

span:nth-of-type(7) {
    animation-delay: 3s;
}

span:nth-of-type(9) {
    animation-delay: 3.5s;
}

span:nth-of-type(10) {
    animation-delay: 4s;
}

span:nth-of-type(12) {
    animation-delay: 4.5s;
}

span:nth-of-type(13) {
    animation-delay: 5s;
}

span:nth-of-type(15) {
    animation-delay: 5.5s;
}

span:nth-of-type(16) {
    animation-delay: 6s;
}

span:nth-of-type(17) {
    animation-delay: 6.5s;
}

span:nth-of-type(18) {
    animation-delay: 7s;
}

span:nth-of-type(19) {
    animation-delay: 7.5s;
}

span:nth-of-type(20) {
    animation-delay: 8s;
}

span:nth-of-type(21) {
    animation-delay: 8.5s;
}

span:nth-of-type(22) {
    animation-delay: 9s;
}

span:nth-of-type(23) {
    animation-delay: 9.5s;
}

span:nth-of-type(24) {
    animation-delay: 10s;
}

span:nth-of-type(25) {
    animation-delay: 10.5s;
}
</style>
```

</div>
</details>

このフェードインの効果は``@keyframes``と``animation``プロパティを利用して実装しています。
``@keyframes``と``animation``プロパティについては、以下の投稿で詳しく説明してくれていますので、そちらにおまかせします。

- [【CSS3】@keyframes と animation 関連のまとめ](https://qiita.com/7968/items/1d999354e00db53bcbd8)

``@keyframes``を定義しているのは以下です。

```CSS
@keyframes fadeIn {
    0% {
        opacity: 0;
    }
    100% {
        opacity: 1;
    }
}
```

完全に透明な状態から描画されるという、至ってシンプルな構造です。
この``@keyframes``を``span``の``animation-name``に設定しています。

```CSS
.home span {
    animation-name: fadeIn;
    animation-duration: 0.5s;
    animation-timing-function: linear;
    animation-fill-mode: both;
    /*以下省略*/
}
```

``animation-duration: 0.5s``は0.5secかけて各文字が浮き上がるように設定しています。
``animation-timing-function: linear``は文字が浮き上がる流れは一定でということです。
``animation-fill-mode: both``はアニメーションが始まるときは完全に透明な状態から開始して、終了後は描画された状態を維持するということです。

ここで、``span``にbindしているものですが、``data()``の``return``で返した、``messageChar``オブジェクトをbindしています。
``messageChar``オブジェクトには「Welcome to my portfolio!!」の各文字が順番に格納されていて、``v-for``でループさせることでbindしています。
``v-for``の詳細については、以下の投稿等を参考にしてみてください。

- [リストレンダリング](https://jp.vuejs.org/v2/guide/list.html)

そして、``span``にbindされた各文字に対して、``animation-delay``を設定しています。

```CSS
span:nth-of-type(2) {
    animation-delay: 0.5s;
}
```

``animation-delay``で各文字が浮き上がるアニメーションの開始を順繰りに遅らせています。
``span:nth-of-type(x)``は疑似要素です。
それに関連するものとして、擬似クラスもあります。
疑似要素と擬似クラスを知っていれば、CSSでの表現の幅が広がると思うので、ぜひ理解しておくことをおすすめします。
疑似要素と擬似クラスについては以下の投稿等を参考にしてみてください。

- [意外と知らない！疑似要素と疑似クラスでできること](https://liginc.co.jp/web/html-css/css/23099)

以上のような視覚的な効果の実装は難しいのかなと思っていたのですが、以外と手軽に実装できることに驚きでした。
ここで紹介したもの以外にも、アイディア次第でもっと面白い効果を実装できると思います。
以下の投稿では、CSSで実装できる効果を多数紹介しているので、参考にしてみてはどうでしょうか。

- [css3アニメーションのレパートリーがきっと増える！動きの参考になる22サイト](https://webkikaku.co.jp/blog/htmlcss/css3animation/)

視覚的な効果の解説については以上です。

##アイコンの埋め込み
わかりやすいサイトを目指すために、アイコンを使いたくなる場合があると思います。
私のポートフォリオでも、ソーシャル画面でアイコンを使用しています。

![portfolio3](https://cl.ly/af342138f922/portfolio3.png)

アイコンのデータは以下の[Font Awesome](https://fontawesome.com)のサービスを利用しています。
[Font Awesome](https://fontawesome.com)は無料プランでも種類豊富なアイコンが利用できます。
[Font Awesome](https://fontawesome.com)の詳細な利用方法は以下の投稿等を参考にしてみてください。

- [【保存版】Font Awesomeの使い方：Webアイコンフォントを使おう](https://saruwakakun.com/html-css/basic/font-awesome)

[Font Awesome](https://fontawesome.com)はアイコンの種類も豊富で、大抵のSNSのアイコンは網羅しています。
ですが、Qiitaのアイコンは用意されていないようで、自作する必要がありました。
以下の投稿でQiitaのアイコンの作成方法が紹介されているので、私のポートフォリオでも同じ方法でQiitaのアイコンを作成しています。

- [Font Awesome で Qiitaロゴっぽいアイコンを表現](https://qiita.com/hkusu/items/fda8d8178dd693f95f3c)

また、[Font Awesome](https://fontawesome.com)のアイコンは色も自由に変えることができます。
SNSや何か特定のサービスを表すようなアイコンは、イメージカラーがあったりすると思うのですが、そのイメージカラーって合わせたくなりませんか?
そんな時には、イメージカラーを一覧化して公開してくれている、以下のようなサービスもあるので、こだわりたい方は参考にしてみてください。

- [BrandColors](https://brandcolors.net/)

実装の解説は以上です。

#buildとNetlifyで公開
実装ができたら、あとはbuildを通して公開するだけです。
私の場合は、このbuildの手順で少し詰まったのと、公開のためにホスティングサービスの[Netlify](https://www.netlify.com/)を使用したため、最後にそのあたりのことを解説してゆこうと思います。

まずはbuildを通しましょう。
ですが、buldの前に一つ準備が必要です。
ビルドの出力ディレクトリ``/docs``の作成と``vue.config.js``の作成です。
手順は以下の投稿を参考にしています。
(GitHub Pagesでの公開手順ですがbuild自体は同様の手順で実行できています)

- [山崎屋の技術メモ-Vue で作成した Web アプリを GitHub Pages で公開](https://www.shookuro.com/entry/2019/02/02/174655)

``/docs``ディレクトリの作成

```
$ mkdir [自身のプロダクトのディレクトリパス]/docs
```

``vue.config.js``の作成

```js:[自身のプロダクトのディレクトリパス]/vue.config.js
module.exports = {
    outputDir: 'docs',
    assetsDir: './',
    publicPath: './'
}
```

上記の準備ができたらbuildをしましょう。
buildコマンドは以下です。

```
$ npm run build
```

``/docs``以下にbuildで出力されたファイルが生成されました。
buildは以上です。

次に[Netlify](https://www.netlify.com/)の公開手順を見てゆきましょう。
[Netlify](https://www.netlify.com/)はホスティングサービスで、ホスティングサービスを使えば自分でサーバーを用意しなくても済み、自分が作成したサービスを手軽に公開できます。
[Netlify](https://www.netlify.com/)を使用するためには[GitHub](https://github.co.jp/), [GitLab](https://about.gitlab.com/), [Bitbucket](https://bitbucket.org/)のいずれかのremote repositoryでソースが管理されている必要があります。
remote repositoryは公開化/非公開いずれでも問題ありません。

では、[Netlify](https://www.netlify.com/)での公開手順を見てゆきましょう。
といっても手順はとても簡単で、実際に操作してみるとわかると思いますが、ほとんどその場で表示される説明に従ってゆけばスルスルっと公開できてしまいます。
詳細な手順については、以下の投稿等を参考してみてください。

- [Netlifyへの自動デプロイをしよう](https://cr-vue.mio3io.com/tutorials/netlify.html#%E3%82%B5%E3%83%96%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%81%AE%E5%A4%89%E6%9B%B4)

一点だけ注意するとすれば、私と同じ手順でbuildをしている方は公開用のディレクトリは``/docs``を選択することに注意してください。
それと、[Netlify](https://www.netlify.com/)での公開手順でbuildの設定は分かりづらかったので、参考用に私の設定を掲載します。
buildの設定をしておけば、remote repositoryのソースが更新された時にわざわざ[Netlify](https://www.netlify.com/)で操作しなくても、自動でbuildしてくれます。

![portfolio4](https://cl.ly/4c748347763e/portfolio4.png)

buildとNetlifyでの公開手順は以上です。

#おわりに
初めてふれる技術はわからないことだらけで、問題を解決する苦しみを感じることもありますが、それ以上に自分がレベルアップしていることを感じられる経験だと、個人的に思っています。
まだまだ、知らないことだらけだなとも。
(これは日進月歩の技術の世界に身を置く限りは永遠に続くことかもしれませんが)
ポートフォリオに限らず、みなさんも何かプロダクト作成をしてみませんか?
そして、私にも、その技術の知識を共有をして頂けると、とてもとても嬉しいです。
長くなりましたが、ここまで投稿をご覧なって頂いてありがとうございました。