- ff7風のポートフォリオを作ってみました。(レスポンシブ非対応なので、PCで見てください)
- URLはこちら、https://ryuzo-nakata.github.io/portfolio-ff7/
- ソースはこちら、https://github.com/ryuzo-nakata/portfolio-ff7
#経緯
ff7のリメイクがもうすぐ発売されますね!!!
早くプレイしたいです。
待ち遠しさを紛らわすために、遊び心でポートフォリオとして作成してみました。
マテリアをCSSで記載するなど、しなくてよい努力を詰め込んでますw
#使ったもの
- Vue.js
- Nuxt.js
- Vuetify
- Typescript
- Scss
フロントの勉強かつ、Typescriptの練習を兼ねて上記のような構成にしています。
メニュー画面にマウスカーソルを合わせると、あのお馴染みのカーソルを表示するようにしています。クリックするとページを遷移するように作成していますが、現在は「マテリア」ページのみ作成しております。
###背景色
ff7の背景色は、下記のscssで作成しています。ff7-card
クラスを指定すれば、あの青い背景色になります。
$text-color: #eff1ff;
$background-color: #04009d;
$background-color-dark: #06004d;
.ff7-card {
border: solid 1px #424542;
box-shadow: 1px 1px #e7dfe7, -1px -1px #e7dfe7, 1px -1px #e7dfe7,
-1px 1px #e7dfe7, 0 -2px #9c9a9c, -2px 0 #7b757b, 0 2px #424542;
padding: 5px 10px;
background: $background-color;
background: -moz-linear-gradient(top, $background-color 0%, $background-color-dark 100%);
background: -webkit-gradient(
linear,
left top,
left bottom,
color-stop(0%, $background-color),
color-stop(100%, $background-color-dark)
);
background: -webkit-linear-gradient(top, $background-color 0%, $background-color-dark 100%);
background: -o-linear-gradient(top, $background-color 0%, $background-color-dark 100%);
background: -ms-linear-gradient(top, $background-color 0%, $background-color-dark 100%);
background: linear-gradient(to bottom, $background-color 0%, $background-color-dark 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='$background-color', endColorstr='$background-color-dark',GradientType=0 );
-webkit-border-radius: 7px;
-moz-border-radius: 7px;
border-radius: 7px;
* {
color: $text-color;
text-shadow: 2px 2px #212421, 1px 1px #212021;
font-family: Verdana, sans-serif;
font-weight: normal;
}
}
###メニューの部分
メニューの部分を説明していきます。
<template>
<section>
<v-container class="top-menu-list ff7-card">
<v-row
v-for="(item, index) in topMenuItems"
:key="index"
no-gutters
@mouseleave="menuMouseleave(index)"
@mouseover="menuMouseover(index)"
>
<v-col cols="12">
<cursor-parts v-if="menuChoice == index" />
<nuxt-link
v-if="item.display == true"
:to="item.path"
class="top-menu-item"
>
{{ item.name }}
</nuxt-link>
</v-col>
</v-row>
</v-container>
</section>
</template>
topMenuItems
の内容を表示するようにしています。また、マウスカーソルの操作は@mouseleave
@mouseover
で対応しています。クリックした場合nuxt-link
で指定先に遷移します。
<script lang="ts">
import { Component } from 'vue-property-decorator'
import PortfolioVueEx from '~/logic/vue/PortfolioVueEx'
import CursorParts from '~/components/parts/CursorParts.vue'
@Component({
components: { CursorParts }
})
export default class TopMenu extends PortfolioVueEx {
menuChoice: number = -1
topMenuItems: {
name: string
path: string
display: boolean
}[] = [
{
name: 'アイテム',
path: '/',
display: true
},
{
name: 'まほう',
path: '/',
display: true
},
{
name: 'マテリア',
path: 'materia',
display: true
},
{
name: 'そうび',
path: '/',
display: true
},
{
name: 'ステータス',
path: '/',
display: true
},
{
name: 'たいけい',
path: '/',
display: true
},
{
name: 'リミット',
path: '/',
display: true
},
{
name: 'コンフィグ',
path: '/',
display: true
},
{
name: 'PHS',
path: '/',
display: true
},
{
name: 'セーブ',
path: '/',
display: true
}
]
menuMouseover(i: number) {
this.menuChoice = i
}
menuMouseleave() {
this.menuChoice = -1
}
}
</script>
<style scoped lang="scss">
.top-menu-item {
margin: 10px;
}
.top-menu-list {
text-align: left;
width: 143px;
}
a:link {
text-decoration: none;
color: white;
}
a:visited {
text-decoration: none;
color: white;
}
</style>
メニューの内容は、topMenuItems
にリスト型で保持しています。
###キャラクター
<template>
<section>
<v-container class="top-team-list ff7-card">
<v-row
v-for="(item, index) in $store.state.players"
:key="index"
align="center"
>
<v-col cols="4">
<v-row justify="center">
<img :src="item.image" width="75" />
</v-row>
</v-col>
<v-col cols="3">
{{ item.name }}{{ $store.state.level }}
<v-row>
<span class="status-item">LV</span>
<span class="level-margin status-content">{{ item.level }}</span>
</v-row>
<v-row>
<span class="status-item">HP</span>
<span class="status-content">
<span>
{{ item.hp }}/{{ item.maxHp }}
<div class="progress-linear">
<progress-hp-parts
:parent-max.sync="item.maxHp"
:parent-value.sync="item.hp"
/>
</div>
</span>
</span>
</v-row>
<v-row>
<span class="status-item">MP</span>
<span class="status-content">
<span class="mp-margin">{{ item.mp }}/</span>
<span class="mp-margin">{{ item.maxMp }}</span>
<div class="progress-linear">
<progress-mp-parts
:parent-max.sync="item.maxMp"
:parent-value.sync="item.mp"
/>
</div>
</span>
</v-row>
</v-col>
<v-col cols="3">
<v-row>
<span class="next-level">つぎのレベルまであと</span>
<progress-parts
:parent-max.sync="item.maxExp"
:parent-value.sync="item.exp"
class="next-level-margin"
/>
</v-row>
<br />
<v-row>
<span class="limit">リミットレベル {{ item.limitLevel }}</span>
<progress-parts
:parent-max.sync="item.maxLimit"
:parent-value.sync="item.limit"
class="limit-margin"
/>
</v-row>
</v-col>
</v-row>
</v-container>
</section>
</template>
<script lang="ts">
import { Component } from 'vue-property-decorator'
import PortfolioVueEx from '~/logic/vue/PortfolioVueEx'
import ProgressParts from '~/components/parts/ProgressParts.vue'
import ProgressHpParts from '~/components/parts/ProgressHpParts.vue'
import ProgressMpParts from '~/components/parts/ProgressMpParts.vue'
@Component({
components: {
ProgressParts,
ProgressHpParts,
ProgressMpParts
}
})
export default class TopTeam extends PortfolioVueEx {
menuChoice: number = -1
}
</script>
<style scoped lang="scss">
.top-team-list {
padding: 0px 0px 0px 30px;
width: 457px;
height: 360px;
}
.progress-linear {
margin: -15px 0px 0px 0px;
}
.status-item {
color: #00ddd6;
margin-right: 10px;
font-size: 100%;
}
.status-content {
font-size: 80%;
}
.level-margin {
margin-top: 3px;
margin-left: 18px;
}
.mp-margin {
margin-left: 6px;
}
.next-level {
font-size: 60%;
}
.next-level-margin {
margin-left: 15px;
margin-right: 0px;
}
.limit {
font-size: 60%;
}
.limit-margin {
margin-left: 15px;
margin-right: 0px;
}
</style>
'store'でキャラクターの情報(LV/HP/MPなど)を保持しており、それらを呼び出すようにしています。
ゲージは別途コンポーネントを作成しております。
-
ProgressHpParts
は、HPのゲージ -
ProgressMpParts
は、MPのゲージ -
ProgressParts
は、経験値とリミットのゲージ
マテリアにマウスカーソルを合わせると、カーソルとマテリアの説明が表示されます。マテリア毎に、自分のスキルを乗せるようにしてみました。
詳細説明とマテリアリストの部分は時間が足りなかったので、今後追加していきます。
ちなみに、マテリアを外すと下記のようになります。マテリア穴までCSSで作成しています。見えないところにもこだわる!
###マテリア
マテリアはコンポーネントとして作成しています。画像でしたらはるかに楽なのですが、CSSで無駄に開発しました。
<template>
<div class="wrapper">
<div class="materia" :style="{ background: color }" />
<div class="downlight1" />
<div class="highlight2" />
<div class="highlight3" />
</div>
</template>
<script lang="ts">
import { Component, Prop } from 'vue-property-decorator'
import PortfolioVueEx from '~/logic/vue/PortfolioVueEx'
@Component({})
export default class MateriaParts extends PortfolioVueEx {
@Prop()
color: string
}
</script>
<style scoped lang="scss">
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
background: linear-gradient(135deg, #f7f9fc 0%, #e1e7f0 100%);
display: flex;
justify-content: center;
align-items: center;
}
.wrapper {
width: 20px;
height: 20px;
position: relative;
}
.materia {
width: 20px;
height: 20px;
z-index: 0;
border-radius: 50%;
background: rgb(43, 100, 21, 1);
}
.downlight1 {
position: absolute;
top: 10%;
left: 15%;
z-index: 1;
width: 12px;
height: 12px;
background: rgba(0, 0, 0, 1);
border-radius: 50%;
filter: blur(2px);
opacity: 0.6;
}
.highlight2 {
position: absolute;
top: 20%;
left: 25%;
z-index: 1;
width: 3px;
height: 1px;
border-radius: 50%;
border-top: 0.5px solid #fff;
transform: rotate(-70deg) scaleX(0.9) scaleY(1.5) skewY(18deg);
filter: blur(0.6px);
}
.highlight3 {
position: absolute;
top: 0%;
left: 0%;
width: 18px;
height: 18px;
background-color: transparent;
box-shadow: inset -3px -6px 0 -3px rgba(255, 255, 255, 1);
border-radius: 50%;
filter: blur(1px);
opacity: 0.15;
}
</style>
球型に、ハイライトを2種類、ダウンライトを1種類を乗せてマテリアを表現しています。マテリアの色は、Prop
で指定できるようにしています。
###魔法マテリア
各マテリアは、MateriaParts.vue
に色を指定しています。魔法マテリアは下記になります。
<template>
<materia-parts :color="'rgb(43, 100, 21, 1)'" />
</template>
<script lang="ts">
import { Component } from 'vue-property-decorator'
import PortfolioVueEx from '~/logic/vue/PortfolioVueEx'
import MateriaParts from '~/components/parts/MateriaParts.vue'
@Component({
components: { MateriaParts }
})
export default class MagicMateria extends PortfolioVueEx {}
</script>
###マテリア画面
突貫で作ったので、改良の余地がありまくりです。。。
<template>
<v-container>
<v-row align="center" justify="center">
<div class="materia-box">
<div class="character-box ff7-card">
<v-row align="center">
<v-col cols="2">
<v-row justify="center">
<img :src="$store.state.players[0].image" width="75" />
</v-row>
</v-col>
<v-col cols="3">
{{ $store.state.players[0].name }}{{ $store.state.level }}
<v-row>
<span class="status-item">LV</span>
<span class="level-margin status-content">
{{ $store.state.players[0].level }}
</span>
</v-row>
<v-row>
<span class="status-item">HP</span>
<span class="status-content">
<span>
{{ $store.state.players[0].hp }}/{{
$store.state.players[0].maxHp
}}
<div class="progress-linear">
<progress-hp-parts
:parent-max.sync="$store.state.players[0].maxHp"
:parent-value.sync="$store.state.players[0].hp"
/>
</div>
</span>
</span>
</v-row>
<v-row>
<span class="status-item">MP</span>
<span class="status-content">
<span class="mp-margin">
{{ $store.state.players[0].mp }}/
</span>
<span class="mp-margin">
{{ $store.state.players[0].maxMp }}
</span>
<div class="progress-linear">
<progress-mp-parts
:parent-max.sync="$store.state.players[0].maxMp"
:parent-value.sync="$store.state.players[0].mp"
/>
</div>
</span>
</v-row>
</v-col>
<v-col cols="7">
<v-row>
<span class="attack-margin">
<span class="status-item">武器:</span>
<span>ノートPC</span>
</span>
</v-row>
<v-row class="equipment-margin">
<div
v-for="(item, index) in attackMaterias"
:key="index"
no-gutters
class="equipment"
@mouseleave="menuMouseleave(index)"
@mouseover="menuMouseover(item, index)"
>
<cursor-parts v-if="menuChoice == index" />
<command-materia v-if="item.type == 1" class="content" />
<independent-materia
v-else-if="item.type == 2"
class="content"
/>
<magic-materia v-else-if="item.type == 3" class="content" />
<summon-materia v-else-if="item.type == 4" class="content" />
<support-materia v-else-if="item.type == 5" class="content" />
<div v-if="item.type !== 0" class="highlight4" />
</div>
</v-row>
<v-row>
<span class="defence-margin">
<span class="status-item">防具:</span>
<span>お供のコーヒー</span>
</span>
</v-row>
<v-row class="equipment-margin">
<div
v-for="(item, index) in defenceMaterias"
:key="index"
no-gutters
class="equipment"
@mouseleave="menuMouseleave(index + 8)"
@mouseover="menuMouseover(item, index + 8)"
>
<cursor-parts v-if="menuChoice == index + 8" />
<command-materia v-if="item.type == 1" class="content" />
<independent-materia
v-else-if="item.type == 2"
class="content"
/>
<magic-materia v-else-if="item.type == 3" class="content" />
<summon-materia v-else-if="item.type == 4" class="content" />
<support-materia v-else-if="item.type == 5" class="content" />
<div v-if="item.type !== 0" class="highlight4" />
</div>
</v-row>
</v-col>
</v-row>
</div>
<div class="message-box ff7-card">
<v-row v-if="selectedMateria !== ''">
<command-materia class="content" v-if="selectedMateria.type == 1" />
<independent-materia
class="content"
v-else-if="selectedMateria.type == 2"
/>
<magic-materia
class="content"
v-else-if="selectedMateria.type == 3"
/>
<summon-materia
class="content"
v-else-if="selectedMateria.type == 4"
/>
<support-materia
class="content"
v-else-if="selectedMateria.type == 5"
/>
{{ selectedMateria.name }}
</v-row>
</div>
<div class="materias-box ff7-card"></div>
<div class="status-box ff7-card">
{{ description }}
</div>
<div class="page-box ff7-card">
マテリア
</div>
</div>
</v-row>
</v-container>
</template>
<script lang="ts">
import { Component } from 'vue-property-decorator'
import PageBase from '~/logic/vue/PageBase'
import CommandMateria from '~/components/templates/CommandMateria.vue'
import IndependentMateria from '~/components/templates/IndependentMateria.vue'
import MagicMateria from '~/components/templates/MagicMateria.vue'
import SummonMateria from '~/components/templates/SummonMateria.vue'
import SupportMateria from '~/components/templates/SupportMateria.vue'
import ProgressParts from '~/components/parts/ProgressParts.vue'
import ProgressHpParts from '~/components/parts/ProgressHpParts.vue'
import ProgressMpParts from '~/components/parts/ProgressMpParts.vue'
import CursorParts from '~/components/parts/CursorParts.vue'
@Component({
components: {
CommandMateria,
IndependentMateria,
MagicMateria,
SummonMateria,
SupportMateria,
ProgressParts,
ProgressHpParts,
ProgressMpParts,
CursorParts
}
})
export default class Materia extends PageBase {
selectedMateria: any = ''
description!: string
details!: string
menuChoice: number = -1
attackMaterias: {
name: string
description: string
details: string
type: number
}[] = [
{
name: 'Golang',
description: 'Golang を使えます。',
details: '',
type: 3
},
{
name: 'Solidity(Ethereum)',
description: 'Solidity(Ethereum) を使えます。',
details: '',
type: 3
},
{
name: 'Python',
description: 'Python を使えます。',
details: '',
type: 3
},
{
name: 'C言語',
description: 'C言語を使えます。',
details: '',
type: 3
},
{
name: 'Java',
description: 'Java を使えます。',
details: '',
type: 3
},
{
name: 'Mysql',
description: 'Mysqlを使えます。',
details: '',
type: 3
},
{
name: 'Typescript',
description: 'Typescriptを使えます。',
details: '',
type: 1
},
{
name: 'SCSS',
description: 'SCSS を使えます。',
details: '',
type: 1
}
]
defenceMaterias: {
name: string
description: string
details: string
type: number
}[] = [
{
name: 'AWS',
description: 'Amazon Web Services を使えます。',
details: '',
type: 4
},
{
name: 'Kubernetes',
description: 'Kubernetesを使えます。',
details: '',
type: 4
},
{
name: 'SpringBoot',
description: 'SpringBoot のフレームワークを使えます。',
details: '',
type: 2
},
{
name: 'Vue.js',
description: 'Vue.js のフレームワークを使えます。',
details: '',
type: 2
},
{
name: 'Nuxt.js',
description: 'Nuxt.js のフレームワークを使えます。',
details: '',
type: 2
},
{
name: 'Vuetify',
description: 'Vuetify のフレームワークを使えます。',
details: '',
type: 2
},
{
name: 'GitHub',
description: 'GitHubを使えます。',
details: '',
type: 5
},
{
name: 'Unity',
description: 'Unity を使えます。',
details: '',
type: 5
}
]
menuMouseover(item: any, i: number) {
this.menuChoice = i
this.selectedMateria = item
this.description = item.description
}
menuMouseleave() {
this.menuChoice = -1
}
}
</script>
<style scoped lang="scss">
.materia-box {
position: relative;
}
.character-box {
position: relative;
width: 600px;
height: 150px;
margin: 6px 0 6px 0;
}
.message-box {
position: relative;
z-index: 2;
width: 386px;
height: 300px;
margin: 0px 0px 0px 0px;
padding: 60px 0px 0px 20px;
float: left;
.content {
margin: 0px 6px 0px 0px;
}
}
.materias-box {
position: relative;
z-index: 1;
left: 380px;
width: 220px;
height: 300px;
padding: 60px 0px 0px 15px;
}
.status-box {
position: relative;
z-index: 3;
width: 600px;
height: 50px;
top: -300px;
display: flex;
align-items: center;
}
.page-box {
position: relative;
z-index: 2;
width: 150px;
height: 40px;
top: -506px;
left: 450px;
display: flex;
justify-content: center;
align-items: center;
}
.progress-linear {
margin: -15px 0px 0px 0px;
}
.status-item {
color: #00ddd6;
margin-right: 10px;
font-size: 100%;
}
.status-content {
font-size: 80%;
}
.level-margin {
margin-top: 3px;
margin-left: 18px;
}
.mp-margin {
margin-left: 6px;
}
.attack-margin {
margin: 6px 0px 6px 0;
}
.defence-margin {
margin: 6px 0px 6px 0;
}
.equipment {
margin: 0px 3px 0px 3px;
width: 24px;
height: 24px;
z-index: 0;
border-radius: 50%;
background: radial-gradient(
closest-side at 49% 49%,
rgb(150, 150, 150) 0%,
rgb(40, 40, 40) 25%,
rgb(40, 40, 40) 70%,
rgb(100, 100, 100) 92%
);
background-color: transparent;
box-shadow: inset 6px 6px 2px -6px rgba(200, 200, 200, 1);
.content {
position: relative;
top: 2.2px;
left: 1px;
}
}
.equipment-margin {
margin-left: 30px;
}
.highlight4 {
position: relative;
top: -40%;
left: 40%;
width: 4px;
height: 4px;
z-index: 2;
border-radius: 50%;
background: rgba(150, 150, 150, 1);
filter: blur(1px);
}
</style>
attackMaterias
、defenceMaterias
にマテリアを指定しています。type
でマテリアの種類を指定します。ポートフォリオとして、プログラミングスキルを下記のように当てはめています。
- 0: マテリアなし
- 1: コマンドマテリア→ フロントスキル
- 2: 独立マテリア → フレームワーク
- 3: 魔法マテリア → バックエンドスキル
- 4: 召喚マテリア → インフラ周りのスキル
- 5: サポートマテリア→ ツール類
#おわりに
思い付きで始めたものの、面白くできました。途中感は否めないですが、ひとまず形にはなりました。
何かアイディアある方はコメント頂けると嬉しいです。