LoginSignup
15
14

More than 3 years have passed since last update.

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

Posted at

はじめに

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

ポートフォリオ

環境

$ 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と、そのnode.jsのパッケージを管理するツール(npmyarn)が必要になります。
また、Vue CLIも導入しています。
正直、Vue CLIについて説明しろと言われると、よくわかっていないのですが、テンプレート作成からビルドまで行ってくれるすごいやつで、Vue.jsで開発してゆくには必須のツールだと理解しています。
個人的な考えですが、この手の環境構築で重要なことはツールのバージョン管理だと思っております。
プロダクト作成の過程でエラーに遭遇した時に、バージョン依存が原因ということが往々にしてあると思います。
なので、面倒でもバージョン管理はしっかり行っておくことをおすすめします。
とりあえずの私が構築した環境は、以下です。

node.js + npm + @vue/cli + n

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

デザイン

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

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

私はFigmaを試しに使ってみました。

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

Vue.jsの勉強方法がわからないという方は

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

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

実装

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

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

component化

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

portfolio1
portfolio2

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

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>

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>

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

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

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

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

    <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>)でなければなりません。そのことは公式のドキュメントでも言及されています。

skill-barタグの属性のskill-nameactive-levelはpropsで子に渡すデータです。
propsで渡されたデータは子の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>

マスタッシュの{{skillName}}に親から渡されたskillNameがbindされ、v-bindactiveLevelでclass属性の動的bindの条件判定に使用しています。
スキルバーの構造として、is-active classが付与されると、赤丸の選択状態になります。
なので、v-bind:class="{ 'is-active': activeLevel === '1' }activeLevel === '1'でTrueになると、is-active classが付与されることになります。
ここで、activeLevelはわざわざString型にしないで、Number型で使えば良いじゃないかと感じると思います。
まったくその通りで、ですが、propsでデータ渡そうとすると、どうしてもString型になってしまい、Number型で渡せなかったんです。
はい、ここはツッコまれポイントですね...

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

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>


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>


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

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

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

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

CSSで視覚的に面白く

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

portfolio3
portfolio4

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

クルッと表現
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>


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

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

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

要素 親子関係
figure
img 子1
figcaption 子2

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

要素 親子関係
figure
img
figcaption

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

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

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

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

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

Welcome to my portfolio!!
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>


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

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

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

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

.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の詳細については、以下の投稿等を参考にしてみてください。

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

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

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

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

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

アイコンの埋め込み

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

portfolio3

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

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

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

実装の解説は以上です。

buildとNetlifyで公開

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

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

/docsディレクトリの作成

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

vue.config.jsの作成

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

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

$ npm run build

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

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

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

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

portfolio4

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

おわりに

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

15
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
14