どうもこじらです。
今回はVue.js(Nuxt.js)とSVGでステップバーを作成してみました。
こういうやつです。
ステップバーでググってもそれっぽいのがヒットしないので、そういう名前ではないようですが、これ以外の表現の仕方が分からないのでこう呼びたいと思います。
「ステップバー」の仕様
環境はNuxt.jsで使用することを前提として作成していますが、<client-only>のタグを削除して使用すれば、Vue.js用としても使用できます。
使い方
props
propsを4つ設けており、2つは必須項目です。
- step…必須, Number
- stepNames…必須, Array
- width…Number, デフォ800px
- margin…Number, デフォ150px
となっています。
stepは現在何番目のステップか、stepNamesでステップの名前を配列で指定します。
widthとmarginは変更したい場合のみ指定します。
stepNamesで指定する名前の文字数が多すぎない限りはデフォルト設定で使える想定でいます。
コンポーネント
使い方は簡単です。
この後記述するコードをコピペしてvueファイルを作成し、コンポーネントとして呼び出すだけです。
↓使い方の例
<template> <v-container> <v-row> <v-col> <StepBar :step="2" :step-names="stepNames" /> </v-col> </v-row> </v-container> </template> <script> import StepBar from '@/components/StepBar' export default { components: { StepBar }, data: () => ({ stepNames: ['チーム作成', 'チーム情報入力', '質問回答', '完了'] }) } </script>
ソースコード
こいつをコピペしてコンポーネントを作成してください。
StepBar.vue
<template> <div class="contents" :style="'max-width:' + width + 'px; max-height:' + 60 + 'px;'" > <div class="svg-wrapper"> <client-only> <svg :width="width" :height="width / 10" :viewBox="'0 0 ' + width + ' 60'" > <circle v-for="(s, index) in steps" :key="`first-${index}`" :cx="getPoint(index)" cy="20" r="10" :fill="s" /> <line v-for="index of borderCount" :key="`second-${index}`" :x1="(baseWidth * (index - 1)) + oneMargin + 10" y1="20" :x2="(baseWidth * index) + oneMargin - 10" y2="20" :stroke="steps[index]" stroke-width="3px" /> <text v-for="(sn, index) in stepNames" :key="`third-${index}`" :x="getPoint(index)" :y="45" font-size="12" font-weight="bold" text-anchor="middle" > {{ sn }} </text> </svg> </client-only> </div> </div> </template> <script> export default { name: 'StepBar', props: { step: { type: Number, required: true }, stepNames: { type: Array, required: true }, width: { type: Number, default: 800 }, margin: { type: Number, default: 150 } }, data () { return { circleCount: 0, baseWidth: 0, steps: [], borderCount: 0, glay: '#ccc', blue: 'blue', oneMargin: 0 } }, beforeMount () { this.drow() }, beforeUpdate () { this.steps = [] this.drow() }, methods: { getPoint (index) { return (this.baseWidth * index) + this.oneMargin }, drow () { this.circleCount = this.stepNames.length // circleCountを元にBoolean型の配列を作成する for (let i = 0; i < this.circleCount; i++) { if (this.step - 1 < i) { this.steps.push(this.glay) } else { this.steps.push(this.blue) } } // 文字の分左右にゆとりをもたせる this.baseWidth = (this.width - this.margin) / (this.circleCount - 1) this.borderCount = this.circleCount - 1 this.oneMargin = this.margin / 2 } } } </script> <style scoped> .contents { margin: auto; } .svg-wrapper { position: relative; width: 100%; padding-top: 100%; } .svg-wrapper svg { position: absolute; top: 0; left: 0; width: 100%; height: 10%; } </style>
こんな感じです。
え?普通にVanillaJSで書けよ遅いだろって?うるせぇ!!!(ガチギレ)
仕様
SVGを使うのが初めてで勉強も兼ね作成してみました。
circle, line, textそれぞれをv-forでループさせることによって、動的に描画させています。
個々にループさせているのがちょっとダサいですが、拡張性も考えた結果こうなりました。
少し脱線しますが、同じファイル内でv-forを複数回使う時ってちょっと複雑ですよね。
<div v-for=(s, index) in steps) :key="index"> <input value="s" /> </div>
こういうのがテンプレだと思いますが、この書き方は実はあんまり良い書き方ではないらしいです。
理由は深く理解していませんが、ユニークなものを使用する(項目から取得したID等を使用するようにする)のが良いようです。
<div v-for=(s, step.id) in steps) :key="step.id"> <input value="s" /> </div> <script> const steps = [{id: a, name: x}, {id: b, name: y}, {id: c, name: z}] </script
こういう感じですね。(書き方雑だけど許して)
今回のソースでは
:key="`first-${index}`"
とすることによって回避していますが、実際どうなのか…。
まぁ、そのままコピペして使うなり、気に入らない部分を改変して使うなりしてもらえると嬉しいです。
…うーん。バーの色くらいは受け口用意しておいた方が親切ですかね。余裕があったらバージョンアップさせておきます。
参考にした記事を載せておきます。
こじらでした
じゃ