- 投稿日:2020-07-11T22:25:36+09:00
新しいMacでNode.jsのインストールからVue.js + nuxt.js + Vuetifyで開発環境手順(zsh対応版)
概要
macOS Catalina におけるフロントエンドの開発環境作成
Catalinaからbashからzshが標準になったため、zshでの環境構築方法を記載
※ バージョンは全て2020年6月30日時点のものです
新しいものが入っていても基本的には問題ないはずbrew
Mac用のパッケージ管理ソフト
インストール$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"coreutils
GNUコマンドを使用する
インストール$ brew install coreutilsjq
JSONを成形、絞り込みを行う
インストール$ brew install jq例$ echo ‘[{“hoge”: “hoge”, “fuga”: “fuga"}]’ | jqxz
圧縮、解凍
インストール$ brew install xz例$ xz -z hoge $ xz -d hoge.xzwget
ファイルのダウンロード
インストール$ brew install wget例$ wget -r http://………/download.zip確認
上記でインストールしたパッケージが表示されることを確認
$ brew list
nvm
Node.jsのバージョン管理ツール
nvmのGitHubを参照して最新バージョンをインストールする
https://github.com/nvm-sh/nvm#install-scriptインストール$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash環境設定$ vim ~/.zshrc
vimが起動するので~/.zhrcに下記を追加
~/.zshrcexport NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"ターミナルを再起動
バージョン確認$ nvm --version0.35.3
Node.js
JavaScript実行環境
インストール可能なバージョンを確認$ nvm ls-remote --lts | grep Latestv4.9.1 (Latest LTS: Argon)
v6.17.1 (Latest LTS: Boron)
v8.17.0 (Latest LTS: Carbon)
v10.21.0 (Latest LTS: Dubnium)
v12.18.1 (Latest LTS: Erbium)お好きなバージョンをインストール
基本的には最新で良いと思います。インストール$ nvm install v12.18.1バージョン確認$ node -vv12.18.1
npm
JavaScriptのパッケージ管理システム
yarn
npmと同じくパッケージ管理システム
今回はyarnを使用するインストール$ npm install -g yarnserverless
Serverless Application(AWS, Azure, GCPなど)を構成管理デプロイするためのツール
インストール$ npm install -g serverlessnpmのライブラリを確認
バージョン確認$ npm ls --depth=0 -g/Users/[ユーザー名]/.nvm/versions/node/v12.18.1/lib
├── npm@6.14.5
├── serverless@1.74.1
└── yarn@1.22.4Nuxt.js
vueのフレームワークの1つ
Routerの記述をする必要がなくなってめっちゃ便利インストール// プロジェクトを作成したいフォルダに移動 $ cd ***/*** $ yarn create nuxt-app <my-project>上記コマンドを入力すると対話型のインストーラーが起動します
入力内容は下記を参考に自分に適したものを選択してください? Project name: (my-project) ? Programming language: (Use arrow keys) > TypeScript ? Package manager: (Use arrow keys) > Yarn ? UI framework: (Use arrow keys) > Vuetify.js ? Nuxt.js modules: (Press <space> to select, <a> to toggle all, <i> to invert selection) ◉ Axios ◉ Progressive Web App (PWA) ◯ Content ? Linting tools: (Press <space> to select, <a> to toggle all, <i> to invert selection) ◉ ESLint ◉ Prettier ◯ Lint staged files ◯ StyleLint ? Testing framework: (Use arrow keys) > None ? Rendering mode: (Use arrow keys) > Single Page App ? Deployment target: (Use arrow keys) > Server (Node.js hosting) ? Development tools: ◉ jsconfig.json (Recommended for VS Code) ◯ Semantic Pull Requestsインストールが完了したら下記のコマンドを実行
$ cd <my-project> $ yarn dev ? Are you interested in participation? (Y/n) nhttp://localhost:3000/
アクセスしてVueのアイコンが表示されれば構築完了あとはかっこいいWebサイトを作ってください!
- 投稿日:2020-07-11T19:58:03+09:00
Vueコマンドが使えない vue/cli [環境構築難民を救いたい]
挫折しそうな君を救う
HTMLやCSSの勉強も終わって、さぁ次はVue.jsやLaravel,Rubyの勉強だ!
そう思って、なんとかインストールし、いざコマンドを叩くと
zsh: command not found: vue「どういうことだ?何をやってもこうなってしまう...」
せっかくやる気もあるのに...一体私が何をしたというのか...?
5分後、君はVue.jsを始めている
Vue自体はCDNを使えば簡単に体験できる。
本記事では、Vue/cliを使うための解説をする
前提としてnode.jsをインストールし、npmが使えているとする
なお、エディターはVSCodeを使う参考書等にしたがって、Vue-cliで学習を進めようとした君
どうすれば良いのか?
結論から書こう1.デスクトップに新規フォルダを作る
2.作ったフォルダをVSCodeのアイコンまでドラッグアンドドロップして、フォルダを開く
3.ターミナルを開く
4.コマンドを打つ1,2の解説は不要だろう
3は画面上部を見るんだ。
ターミナルから、新しいターミナルをクリックだそして、4だ。次のコマンドを打つ
npm init -yこれでフォルダ内にpackage.jsonというファイルが出来上がるだろう
それを開くと、難しげな文字列が現れる。だが、大丈夫だ。全部事細かに理解する必要ない次に
npm install vue -Dと打ち込もう。すると、package-lock.jsonとnode_modulesフォルダが生成され、
先ほど開いたpackage.jsonに
"vue": バージョン
が自動的に追記される。これで、このローカル環境でvueが使えるようになった。
確認のために以下のコマンドを君は打つだろうvue --versionここで君を挫折させる憎き文字列の登場だ
zsh: command not found: vue君を救うのはたった一文だ。
今開いているpackage.jsonに以下の文が確認できるだろうか?
package.json"scripts": { "test": "echo \"Error: no test specified\" && exit 1" }ここに魔法の一文を追記する
package.json"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "vue": "vue" }"test": ......
の末尾に,(カンマ)を書き忘れないようにしようしっかりと保存し、ターミナルに以下のコマンドを打ち込む
npm run vue --versionバージョンが表示されるはずだ。おめでとう、君の勝利だ
では、vue-cliをインストールしよう。
なお、インストールのコマンドは変わる可能性がある。
公式ページで確認しようnpm install @vue/cli -Dその後、
npm run vue create myappmyappの部分は君が決めていい。君の考えたイケてる名前をつけてあげよう
さぁ、あとは君の見ている参考書やサイトに書いてあることに従って進めてくれ何が起きたのか?
「ちょ、ちょっと待ってくれよ!
-Dだとかinitだとか、"vue": "vue"だとかnpm runとか
意味が分からないじゃないか!進めたけど何も解決していないのではないか?」落ち着け。本質を忘れてはならない。君が今したいのはVueの勉強だろう
そのためにせっせとググり、この記事までたどり着いたのだ。ここまで示したことは、プログラミングを学習していけばなんとなく分かるようになってくる。
そう、私もよく分かってないからしっかり説明できないのだ。だが、それでいい。君は振り返らず、存分にVueの勉強を初めてくれ。
- 投稿日:2020-07-11T19:45:23+09:00
【Vue】配列の追加・削除には注意が必要?
【Vue】オブジェクト追加・削除には注意が必要? の配列版です。
オブジェクトと同じく、参照するだけなら普通のJSと同だが、要素の追加・削除で嵌るポイントがあったので備忘録として。
↓の記事も参考に。次のようなVueインスタンスの
data()
にcharas
という空の配列がセットしてあるとする。export default { data () { return { charas: [] } } }要素を追加する
オブジェクトでは
$set
を使ったが、追加するときは末尾であれば通常通りpush()
でOKらしい。先頭に追加などは試していないのでわからず。// 通常通り、`push`を使えばOK this.charas.push('範馬刃牙')要素をまとめて追加したい
スプレッド演算子
...
を使って配列を展開した要素をpush()
で追加すればOK。// まとめて要素を追加する this.charas.push(...['範馬勇次郎', '列海王'])要素を変更する
要素を変更したい時、いつも通りインデックスを使ってしまいたくなるがこれだとリアクティブにデータが反映されない(=データ更新してもテンプレート(画面)上に反映されない)。
splice()
を使う。構文は
<配列>.splice(<始まりのインデックス>, <変更する要素数>, <変更後の要素>)
と書くとのこと。
(書き方をいつも忘れてしまう)見ての通り複数個まとめて変更することもできる。
// リアクティブにデータが反映されないのでこの書き方はダメ this.charas[0] = '範馬勇一郎'// これだとリアクティブになる this.charas.splice(0, 1, '範馬刃牙')要素を削除する
変更のときと一緒で
splice()
を使う。書き方は
<配列>.splice(<始まりのインデックス>, <削除する要素数>)
見ての通り複数の要素をまとめて削除できる。ちなみに戻り値は削除した要素を含む配列で破壊的メソッドとして働く。
// リアクティブにデータを削除 this.charas.splice(0, 2)全削除したい
通常通り、
arr.length = 0
のイディオムを使いたくなるが、これだと配列の長さを変える操作なのでいけないみたい。
(一見、動いているようにみえるが、Vue作者もそう言っているので使うのはやめた方がよさそう)// これは本当はダメ this.charas.length = 0要素をまとめて削除できる
splice()
を使う。第二引数に配列の長さを渡せば全削除できる。// これで全削除できる。 this.charas.splice(0, this.charas.length) // => []要素を全て入れ替えたい
配列を一度空にしてからまとめて追加すればよい
// 一旦、全削除して要素を入れ替える this.charas.splice(0, this.charas.length) this.charas.push(...['範馬刃牙', '範馬勇次郎', '列海王'])配列自体を削除したい
あまり使うケースは無さそうだが、要素ではなく配列自体を削除したい場合はどうするか?
この場合はオブジェクトと同じで
null
を入れればよいみたい。// charasを削除する this.charas= null
- 投稿日:2020-07-11T19:43:06+09:00
Rails 6 + Vue(単一コンポーネント) + vue-router + Vuetify だけどMPA(SPAではない) - Multi SPA
構成
ひとつのRailsアプリケーション上に、単一コンポーネントで構成されたSPAを配置します。
まずはひとつのSPAをおくことを目標にしますが、同様に複数のSPAを置くことを意図しています。(よって完全なSPAではありません。またMulti-SPAとなります。)構成のイメージ:
- /shared
- /private
- /private/episode/:id/schedule
- ここをSPAにする
Rails
Rails は、MVCなMPAアプリケーションをつくります。
Rails 6 webpacker を利用して vue を用います。webpacker と Vue の導入については説明を省きます。Layout
SPA 用の layout を設けて、コントローラ(あるいはアクション)で切り替えます。ここでは
application_spa
とします。application_spa は後述します。コントローラ#アクション schedule で layout を指定すると下記のようになります。これで episode_controller の schedule だけ application_spa を利用することになりました。
episode_controller.rbdef schedule respond_to do |format| format.html { render :schedule, :layout => 'application_spa' } end endのちに指定する vue-router と、ここで SPA対象となるRails controller#actionは、同じURLで同じSPAを使う必要があります。(そうでない場合、ひとつのURLで別のものが表示されてしまいます)
Vue 周りの構成
Vue を SFC(単一ファイルコンポーネント)で利用します。SFCなVueからは axios で API を利用します。Vuetify, vue-router, axios については説明を省略します。
この先の手順は Vue のSingle File Components 利用における Vuetify, vue-router, axios と同じです。
用意するもの:
- Vuejs
- vue-router
- Vuetify
- axios使わないもの:
- Nuxt.jsRails View
action view
View への routing は、Railsの routes.rb でaction schedule に向けられているとします。それにより
schedule.html.haml
が用いられるため、layout と schedule.html.haml をSPA用にします。
schedule.html.haml
は、content_for を利用して、 layout で使用する javascript_pack_tag と stylesheet_pack_tag を切り替えられるようにします。切り替えられるようにする理由は、複数のSPAを作成する予定のためです(つまりMulti-SPAです)。view/../schedule.html.haml- content_for :pack, 'schedules' #schedules
#schedules
の指定は、のちほどJavaScriptで利用します。layout
layoutは、 view で指定されたcontent_forによってJSファイルを読み込むようにします。stylesheet_pack_tag も指定しないと Vuetify のデザインが当たらないので注意しましょう。
layout/application_spa.html.haml!!! 5 %html %head %title= "" = csrf_meta_tags = yield :meta = stylesheet_pack_tag "#{yield :pack}" = javascript_pack_tag "#{yield :pack}" %body = yieldRails側のViewは以上です。大変シンプルですね。
Vue の呼び出し
javascript_pack_tag が呼び出す schedule.js が起点(エントリーポイント)となります。(よく見かける application.js を呼び出す部分に相当します。)
schedule.js
Rails View で指定した ID #schedules を使います。
packs/schedule.js// 様々なimport や Vue.use の記述は省略 document.addEventListener('DOMContentLoaded', () => { const app = new Vue({ el: '#schedules', // schedule.html.haml で指定したもの vuetify, // import した vuetify router, // import した vue-router render: h => h(App) }).$mount() })app.vue
schedule.js が
render: h => h(App)
で指定した app.vue は、router-view
を呼びます。記述には pug を用いています。packs/app.vue<template lang="pug"> v-app v-app-bar( dark ) v-toolbar-title Service Name is Unknown v-main v-container v-layout( wrap ) router-view </template>router-view に割り当てられたコンポーネントは、次の router の設定で行っています。
vue router
vue 側の router の設定です。
アプリケーションが/
から始まらないため、mode: 'history'
を忘れないようにします。
この先、複数のSPAを作成するため、router も複数にします。そのため、 pack 配下に router/ を作成します。 router を schedule.js へ書かずに別ファイルとし packs/router/schedule.js として管理します。packs/router/schedule.jsimport VueRouter from 'vue-router'; import Schedule from '@/../components/schedule.vue'; const routes = [{ path: '/episode/:id/schedule', component: Schedule, // 増えた path は、Rails 側も対応 }]; export default new VueRouter({ base: '/private/', mode: 'history', routes });
base: '/private/'
を指定して、アプリケーションの階層を /private 配下にしています。指定すると $route.path に含まれません。 router-link で :to を用いる際にも含まれなくなります。この router/schedule.js は schedule.js から import されています。
axios
SFC 内で axios を用いてAPIにアクセスします。CSRF_TOKENはこちらを参照しました。
.vue の中では、methods で axios を用います。
遷移を伴う一覧をrouters-list
で記述しました。しかし、同じpathに一致する更新は、APIで取得した値を取り直してくれません。 そこで watch により変更を監視します。 vue-router公式ではbeforeRouteUpdate
も使えると記載があります。今回は
beforeRouteUpdate
を利用しました。@/../components/schedule.vue// template は省略 // props や data は省略 methods: { getEpisode: function (event) { this.axios .get('/api/v1/episodes/' + this.$route.params.id + '') .then(response => (this.episode = response.data)) // catch error 省略 } }, beforeRouteUpdate(to, from, next){ this.getEpisode() next() }, // watch を使う場合1 // watch: { // $route (to, from) { // this.getEpisode() // } // }, // // watch を使う場合2 // watch: { // '$route': 'getEpisode' // }, // mounted () { this.getEpisode() },Vue側は以上です。これで
/private/episode/:id/schedule
を起点としたSPAができました。まとめ
Rails の layout をSPA用に分け、Railsで完結するアプリケーションと、Vue によるSPAを並存させました。Railsアプリケーション全体からみると、部分的にSPAを提供できます。
複数SPAを目的にした理由
ここでは、SPAを複数置くことを考えました。どの単位に分けると良いのかは、まだ明確な指針を持っていません。そのため、複数SPAを意図した理由についても簡単に記載します。
Vueのroutingで対応ができる範囲で複数のSPAに分けるのは、あまり意味はないだろうと考えています。エントリーポイントが schedule.js になるのかどうかの違いしかないためです。
一方で、起点ごとに異なるコンポーネントを利用する場合や、vue-router の path が遷移で繋がらない場合は、エントリーポイントを分ける意味があると考えました。
今回、構築したRailsアプリケーションのうち、スケジュール管理部分をVueのSPAにしたいと思いました。それだけでひとつのアプリケーションになり得る単位です。
また、同じアプリケーションを Episode 以外のモデルでも利用するため、再利用性の高いアプリケーションにしたいと考えました。そのためSPAによってModelを横断して取り扱えるようにしようと考えました。今後、schedule 以外のエントリーポイントが増えることを想定しました。schedule 同様に、単独でひとつのアプリケーションになり得る単位です。たとえばログ管理を考えています。
その際に、SPAを統合するイメージが湧かず、別々に切りたいと考えました。Railsが管理する単位では1つのサービスですが、クライアント側に期待する単位では、それぞれ完結しています。たとえばスケジュール管理とログ管理はまったく別の操作性を期待しています。ログとスケジュールを同時に管理したいこともありません。
以上が、複数SPAを考えた理由です。
エントリーポイント間の差分
仮に
schedule
以外にlog
を増やしたいとしましょう。Rails側の作業はわずかです。
SPAにしたい箇所を、layoutapplication_spa
指定することと、log.html.haml view の中で #logs を使うこと、です。Vue は、packs/schedule.js と同等のエントリーポイントとして
packs/logs.js
を用意し、el: '#logs'
とします。(もちろん他のelementでも構いません。)
また packs/logs.js は、log 用の routerpacks/router/logs.js
を読み込みます。axios や app.vue, Vuetify は作業が不要です。
つまり、複数のエントリーポイント間の差分は、対応する router/*.js の path と、利用するコンポーネント群の違いになります。
おわり
以上、Rails の layout をSPA用に分け、エントリーポイントを複数にすることで、Vue によるSPAを複数作成することができました。
- 投稿日:2020-07-11T18:44:12+09:00
Vue.js/Nuxt.jsでパララックス効果を実装
Vue/Nuxtでパララックスしようぜ!
※パララックス効果とは:スクロールしたときに、スクロール量に合わせてずれたり動いたりするやつ。
Vue.jsプラグインもいくつかあるけど、自前で実装した方が自由度高くて楽だった。勉強にもなるし。
実装方法概要
実装方法の大まかな説明
- scrollイベントをlisten
- スクロール量やら色々パラメータを取って移動量を計算
- imgのstyleに
object-position: {移動量X}% {移動量Y}%
をぶち込む
- ※
object-position
を知らない方は先に 事前知識 : CSS object-position について をどうぞ。index.vue<template> <img ref="backgroundImg" class="background-img" src="~/assets/img/background.png" :style="`object-position: 50% ${objPosY}%;`" > </template> <script> export default { data() { return { objPosY: 0 } }, mounted(){ window.addEventListener('scroll', this.calculateScrollY) // スクロールイベントのlisten }, methods: { calculateScrollY() { // this.objPosY に計算した移動量をぶち込む } } }以上の実装方法ですが、場合によって移動量の計算方法が異なります。
- img要素が完全に隠れている場合。(スクロールしないとimg要素が現れない場合)
- img要素が最初から見えている場合。(スクロールしなくてもimg要素が少しでも見えていて、これからスクロールする場合。)
1. img要素が完全に隠れている場合
:style="`object-position: 50% ${objPosY}%;`" // または :style="`background-position: 50% ${objPosY}%;`"CSSの
object-position
(またはbackground-position
)で以下のように画像の位置が変えれるわけですが、その移動量を制御したい。
-50%とか、150%とかの状態がちょうど見えないようにスクロールしたい。0%から100%までで動かしたい。
img要素が完全に隠れている場合、画像の移動量は、以下の通りである。
- スクロールして要素が見え始めた時は、移動量が0%である。
- = ブラウザの表示領域のbottomが、img要素のtop に到達した時
- スクロールして要素が見えなくなった時は、移動量が100%である。
- = ブラウザの表示領域のtopが、img要素のbottom に到達した時
以上のように、
this.objPosY
がスクロール量に合わせて0から100まで動けばよい。1.1 要素が見え始めた時にスクロール量が0となるやつを作る
getBoundingClientRect()
を使ってスクロール量を取得し、新たに変数topVisibleScrollY
を定義した。const rect = this.$refs.backgroundImg.getBoundingClientRect(); console.log(rect.top) // ブラウザの表示領域を基準とする、img要素の絶対座標(top) スクロールによって可変 const innerHeight = window.innerHeight // ブラウザの表示領域の高さ。 const topVisibleScrollY = -rect.top + innerHeight // 要素が見え始めた時にスクロール量が0。スクロールによって可変 console.log(topVisibleScrollY)
getBoundingClientRect().top
は、ブラウザの表示領域を基準とする、img要素の相対座標である。
つまり、ブラウザの表示領域のtopとimg要素のtopが重なった時に0になる。
「img要素のtop」を「img要素のbottom」に変えるために、img要素の高さを引いてやれば良い。ただ、
getBoundingClientRect().top
は、下へスクロールするごとに値が小さくなっていくので、マイナスにして正負反転させていることに注意。こうすることで、imgが画面内に現れた後、下へのスクロールによって値は正の方へ増えていく。これで、「スクロールして要素が見え始めた時は、移動量が0%である。」の条件がクリアできたので、今度は「じゃぁ
topVisibleScrollY
が何pxのときに100%とみなせばいいのか」を求めていきます。1.2
topVisibleScrollY
が何pxのときに100%とみなせばいい?要素が見えなくなった時に
topVisibleScrollY
が何pxかがわかれば良い。
要素が見え始めた時を0としたスクロール量がtopVisibleScrollY
だから、計算は簡単。答えは、img要素の高さと表示領域の高さを足したやつになる。
// img要素の高さ const height = this.$refs.backgroundImg.clientHeight // `topVisibleScrollY`が何pxのときに100%とみなせばいいか const bottomVisibleEndY = height + innerHeight1.3 要素が見えなくなった時に、移動量が100%となるやつを作る
topVisibleScrollY
がbottomVisibleEndY
に達した時に100になるようにすればいい。this.objPosY = topVisibleScrollY / bottomVisibleEndY * 100 // 見え始めた時は0、見えなくなった時100
topVisibleScrollY
もbottomVisibleEndY
も単位がピクセル量なのでパーセントに変換。
おしまい!1.4 全体のコード
index.vue<template> <img ref="backgroundImg" class="background-img" src="~/assets/img/background.png" :style="`object-position: 50% ${objPosY}%;`" > </template> <script> export default { data() { return { objPosY: 0 } }, mounted(){ window.addEventListener('scroll', this.calculateScrollY) // removeEventListenerは別で書きましょう。 }, methods: { calculateScrollY() { const rect = this.$refs.backgroundImg.getBoundingClientRect(); const innerHeight = window.innerHeight // 表示領域の高さ const topVisibleScrollY = -rect.top + innerHeight // 要素が見え始めた時にスクロール量が0。スクロールによって可変。 const height = this.$refs.backgroundImg.clientHeight // img要素の高さ const bottomVisibleEndY = height + innerHeight // topVisibleScrollYが何pxのときに100%とみなせばいいか this.objPosY = topVisibleScrollY / bottomVisibleEndY * 100 // 見え始めた時は0、見えなくなった時100 } } } </script> <style> .background-img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 50%; overflow: hidden; } </style>2. img要素が最初から見えている場合
この場合、imgが上側へ隠れることがないので、これまでの実装方法だと移動量が0%になることがない。スクロールしてない状態でも、40%とか30%とかになってしまう。
スクロールしてない状態なら0%にしたい。ということで以下に変えれば解決。
calculateScrollY() { const scrollY = window.scrollTop; const rect = this.$refs.backgroundImg.getBoundingClientRect(); const height = this.$refs.backgroundImg.clientHeight const bottomVisibleEndY = height + rect.top + scrollY this.objPosY = scrollY / bottomVisibleEndY * 100 }スクロール量については、途中から0で始める必要がなくなったので、rect.topをやめて
window.scrollTop
に変更。
bottomVisibleEndY
(scrollTopが何pxのときに100%とみなせばいいか) はimg要素の高さとimg要素のY座標を足し合わせればOK。
img要素のY座標はrect.top + scrollY
で求められます。事前知識 : CSS
object-position
について今回は CSS の
object-position
を使って画像をずらしていたのでその説明。
参考:https://developer.mozilla.org/ja/docs/Web/CSS/object-position画像の width と height を指定した上で、画像自体は cover にしたい時、よく使われるのは以下のようなコード。
backgroundに画像を指定する方法である。.hoge-img { /* img要素ではなくdiv要素とかにつける */ width: 100px; height: 100px; background: url('image.png') no-repeat; background-position: 50% 50%; /* center; でも可 */ background-size: cover; }ただこの方法は、CSSに画像のパスを配置する必要がある。なので、パスが動的に変わるような場合は、imgタグを使いたいときもある。
その際に登場するのが、object-fit
とobject-position
。img.hoge-img { /* img要素につける */ width: 100px; height: 100px; object-fit: cover; object-position: 50% 50%; /* center; でも可 */ overflow: hidden; }これでimg要素でも自由なサイズにクリッピングできる。ちなみにIEでは使えません。
https://caniuse.com/#feat=object-fit今回は
object-position
を使ったが、background-position
を使っても同様にパララックスできる。
こうすればいいだけ↓↓↓index.vue<template> <div ref="backgroundImg" class="background-img" :style="`background-position: 50% ${objPosY}%;`" > </template> ...ついでに:addEventListenerについて
mixins.js
をpluginsフォルダにつくって、nuxt.config.jsにplugins: plugins: ['~/plugins/mixins'],
とやる。
これでEventListenerを使う時は、this.listen(window, 'scroll', this.calculateScrollY)
とするだけでよい。~/plugins/mixins.jsimport Vue from 'vue' Vue.mixin({ destroyed() { if (this._eventRemovers) { this._eventRemovers.forEach(function(eventRemover) { eventRemover.remove() }) } }, methods: { listen(target, eventType, callback) { if (!this._eventRemovers) { this._eventRemovers = [] } target.addEventListener(eventType, callback) this._eventRemovers.push({ remove() { target.removeEventListener(eventType, callback) } }) } } })remove()をいちいち書かなくて良いの天才すぎる。以下から参考にさせていただきました。(多分)
- Nuxt.jsで異なるコンポーネントから共通で利用できる関数を定義する(mixin編) - Qiita
- Vue.js 外側をクリックすると閉じるドロップダウンメニュー - Qiita
- ここでイベントリスナーの知見を得た。あざます。
PS : Twitterで「パララックス、SafariとiOSで動かなかったはずでは?」って言われたのでビビってます。とりあえずSafariでは動作確認できました。
- 投稿日:2020-07-11T16:48:42+09:00
Vuexでstoreのstateを初期化する
はじめに
Vuexを使用しているとき、ログアウト処理などでstoreのstateを初期状態に戻したいことがあると思います。いくつかやり方はあると思うのですが、私が手軽だと思った情報を記載します。
方法
こんな感じです。実際に利用する際はactionsなどから呼び出すことになると思いますが、ここでは最低限のコードのみ記載しています。
こちらを参考にしました。参考というよりそのまま持ってきただけですが。。store/sample.js// stateの初期値としたい任意のデータを定義する function getDefaultState() { return { idToken: null, uid: null } } // stateを初期化する export const state = getDefaultState() export const mutations = { // stateを初期化するmutationを定義 clearAuthData(state) { Object.assign(state, getDefaultState()) } }おわり
とくに問題ないと思っていますが、何かご指摘あれば教えてくださいm(__)m
- 投稿日:2020-07-11T13:09:04+09:00
Laravelでvueを書く時のimage参照方法
vueで画像を表示する時はimgタグとbackground-imageを使う場合が考えられる。
ビミョーにやり方が違うので、備忘録として書いておきます。imgタグを書く場合
Index.vueなど、vueファイルでimgタグを書く時は以下のように書く。
<img :src="'/images/top-appeal.jpg'" alt="" class="c-img">やること
①
src
をv-bindする
②srcのダブルクォーテーション内部にシングルクォーテーションを書く
③vueファイルはトランスパイルされpublic/
がディレクトリとしての起点になるので、そこからの相対パスを入力する。上記サンプルは、↓の画像を参照していることになる。
background-imageを書く場合
vueファイルのstyleタグ内で
background-image
を書く時は、以下のようにする。background-image: url('../image/hero-baner.jpeg');やること
①画像ファイルを
resources
ディレクトリ(scssやvueを置く場所)内に置く。
②vueでbackground-image
を書く時に、resources
ディレクトリを起点にした相対パスを書く。
↑の場合、↓の画像を参照している。
npm run watch
などでコンパイルした時にpublic/images
に同様の画像をコピーが作られているのがわかるハズ。
- 投稿日:2020-07-11T13:09:04+09:00
Laravel + vueで画像を参照する時の書き方
vueで画像を表示する時はimgタグとbackground-imageを使う場合が考えられる。
ビミョーにやり方が違うので、備忘録として書いておきます。imgタグを書く場合
Index.vueなど、vueファイルでimgタグを書く時は以下のように書く。
<img :src="'/images/top-appeal.jpg'" alt="" class="c-img">やること
①
src
をv-bindする
②srcのダブルクォーテーション内部にシングルクォーテーションを書く
③vueファイルはトランスパイルされpublic/
がディレクトリとしての起点になるので、そこからの相対パスを入力する。上記サンプルは、↓の画像を参照していることになる。
background-imageを書く場合
vueファイルのstyleタグ内で
background-image
を書く時は、以下のようにする。background-image: url('../image/hero-baner.jpeg');やること
①画像ファイルを
resources
ディレクトリ(scssやvueを置く場所)内に置く。
②vueでbackground-image
を書く時に、resources
ディレクトリを起点にした相対パスを書く。
↑の場合、↓の画像を参照している。
npm run watch
などでコンパイルした時にpublic/images
に同様の画像をコピーが作られているのがわかるハズ。
- 投稿日:2020-07-11T07:43:21+09:00
NuxtプロジェクトにStorybookとStoryshotsを導入してcomponentをテストする
はじめに
試験的にNuxtプロジェクトにStorybookとStoryshotsを導入してみました。いくつかはまったところがあるので、今回、導入したときの手順を残しておきます。主なパッケージのバージョンは以下の通りであり、バージョンによって挙動が変わるのでお気を付けください!
"nuxt": "^2.13.0" "@storybook/addon-storyshots": "^5.3.19", "@storybook/addon-storyshots-puppeteer": "^5.3.19", "@storybook/vue": "^5.3.19", "babel-core": "7.0.0-bridge.0", "babel-plugin-require-context-hook": "^1.0.0", "jest": "^25.5.4", "puppeteer": "^5.0.0",また、今回作成したサンプルプロジェクトはgithubにあげているので、詳細を確認したいときはこちらにアクセスくださいm(__)m
Nuxtプロジェクトの作成
はじめにに、Nuxtプロジェクトを作成します。今回はUIにBulmaを使ってみます。
実際にプロジェクトを作っていくときは、ディレクトリ構造をデフォルトから変えていくことが多いと思うのですが、今回はあくまでもStorybookなどにフォーカスするため、そのままにします。>npx create-nuxt-app sample-project ? Project name: sample-project ? Programming language: JavaScript ? Package manager: Npm ? UI framework: Bulma ? Nuxt.js modules: (Press <space> to select, <a> to toggle all, <i> to invert selection) ? Linting tools: ESLint, Prettier ? Testing framework: Jest ? Rendering mode: Single Page App ? Deployment target: Static (Static/JAMStack hosting) ? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)テスト対象のコンポーネントを用意
今回は、componetsディレクトリ直下に以下のようなcomponentを用意しました。このcomponentをテスト対象としてみます。
Button.vue<template> <div> <button :class="['button', colors || 'is-primary', size || 'is-normal']" :Disabled="disable" @click="clickHandler" > <slot> <span>{{ btnText }}</span> </slot> </button> </div> </template> <script> export default { props: { btnText: String, colors: String, size: String, disable: Boolean, }, methods: { clickHandler(event) { this.$emit('click', event) }, }, } </script>Storybookのセットアップ
パッケージのインストール
まずはインストールします!
npm i -D @storybook/vue @storybook/addon-actions環境設定
ルートディレクトリに .storybookディレクトリを作成し、以下のファイルを作成、保存します。今回はaddon-actionsを利用してみるのでaddons.jsを用意しています。
config.jsimport { configure } from '@storybook/vue' import 'bulma/css/bulma.css' // Nuxt上で読み込んでいるbulmaをstorybookでも使えるようにする // ルートディレクトリ直下のstoriesディレクトリにある*.stories.jsファイルを読み込む configure(require.context('../stories', true, /\.stories\.js$/), module)webpack.config.jsconst path = require('path') const rootPath = path.resolve(__dirname, '..') // プロジェクトのルートパスを指定する module.exports = async ({ config }) => { mode = 'development' config.resolve.extensions = ['.js', '.vue', '.json'] config.resolve.alias['~'] = rootPath // パス解決に必要 config.resolve.alias['@'] = rootPath // パス解決に必要 // 必要に同じてloaderを設定することもできる // 今回は不要なのでやらない return config }addons.jsimport '@storybook/addon-actions/register'storiesの追加
Button.vueを利用したstoriesを追加します。プロジェクトルートディレクトリの直下にstoriesディレクトリを作成し、以下のようなファイルを追加していきます。
Button.stories.jsimport { storiesOf } from '@storybook/vue' import { action } from '@storybook/addon-actions' import Button from '~/components/Button.vue' storiesOf('atoms.Button', module).add('colors', () => ({ components: { Button }, template: ` <div style="display: flex; flex-direction:column; align-items:center;"> <Button colors="is-primary" @click="action" btn-text="is-primary" style="margin: 16px 0;"></Button> <Button colors="is-info" @click="action" btn-text="is-info" style="margin: 16px 0;"></Button> <Button colors="is-danger" @click="action" btn-text="is-danger" style="margin: 16px 0;"></Button> </div> `, methods: { action: action('クリックされました') }, }))試しに、以下のようにstorybookの起動コマンドを追加して…
package.json"scripts": { // 以下のコマンドを追加する "storybook": "start-storybook -p 6006" },Storybookを起動してみます。
npm run storybookやり遂げました。
…ではなくて、ここからStoryshotsを追加していきます。。
StoryshotsによるSnapShotテスト
Storybookの出力をスナップショット(画像ではなくテキスト情報です。念のため。。)として保存しておき、テスト時に変更があったかを自動で判定することができます。
私がプロジェクトを作成したときのJest(26.0.1)とStoryshots(5.3.19)の組み合わせでは"TypeError: require.requireActual is not a function"が出て動作しませんでした。Jestを25に落とせば動くとのことでしたので、該当する場合はJestのバージョンを25に指定してから以下の作業を進めてください。(他の解決策もあるかもです)
パッケージのインストール
Storyshotsを利用するために以下のパッケージを読み込んでいきます。
npm i -D @storybook/addon-storyshots babel-plugin-require-context-hookStorybookでimportしているCSSをMockする
Mockを追加しない場合、config.jsでBulmaをimportしている箇所で"SyntaxError: Invalid or unexpected token"と怒られてしまいます。以下に記載するものと別の手順などもありますので、詳細を確認したい方はこちらからご確認ください。
Mockファイルの作成
まずは、ルートディレクトリにmocksディレクトリを作成し、以下のファイルを追加していきます。
styleMock.jsmodule.exports = {}Jestの設定
以下のようにjest.config.jsを修正してcssにMockを割り当てます。
jest.config.jsmoduleNameMapper: { // 以下の行をmoduleNameMapperに追加する '\\.(css)$': '<rootDir>/__mocks__/styleMock.js', },babelの設定
babelのプラグインであるbabel-plugin-require-context-hookをbabel.config.jsに設定します。今回使用したバージョンのStoryshotsでは、webpackのrequire.contextがサポートされていないということで、この設定をしないとStorybookのconfig.jsを読み込んだときに"TypeError: require.context is not a function"となってしまいます。(こちらのissueより)
babel.config.js// 以下の設定を追加する env: { test: { plugins: ['require-context-hook'], }, },スナップショットテストのテストファイルを作成
テストファイルを保存しているディレクトリにテストファイルを追加します。今回はルートディレクトリあるtestディレクトリに以下のファイルを追加しました。
storyshots.spec.jsimport path from 'path' import initStoryshots from '@storybook/addon-storyshots' initStoryshots({ configPath: path.resolve(__dirname, '../.storybook'), })テストを実行する
テストを実行してみます。
npm run testテストが成功すると、テストファイルを保存しているディレクトリに__snapshots__/storyshots.spec.js.snapが作成されます。このファイルは以下のように、Storybookで読みこんだButton.vueコンポーネントのスナップショットです。
storyshots.spec.js.snap// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Storyshots atoms.Button colors 1`] = ` <div style="display: flex; flex-direction: column; align-items: center;" > <div style="margin: 16px 0px;" > <button class="button is-primary is-normal" > <span> is-primary </span> </button> </div> <div style="margin: 16px 0px;" > <button class="button is-info is-normal" > <span> is-info </span> </button> </div> <div style="margin: 16px 0px;" > <button class="button is-danger is-normal" > <span> is-danger </span> </button> </div> </div> `;試しに、Button.vueのボタン内テキストを変更してみると、変更が検知されてテストが失敗します。
<button class="button is-info is-normal" > <span> - is-primary + 変更した文字列 </span> </button>StoryshotsによるVisual Regressionテスト
Storybookがビルドした結果を画像として保存しておき、テスト時に変更があったかを自動で判定することができます。
パッケージのインストール
必要なパッケージをインストールします。
npm i -D @storybook/addon-storyshots-puppeteer puppeteerStorybookのビルド
以下のようにpackage.jsonにビルドコマンドを追加してそのコマンドを叩くなどしてStorybookをビルドします。
package.json"scripts": { // 以下のコマンドを追加する "build-storybook": "build-storybook" },npm run build-storybookVisual Regressionテストファイルの追加
テストファイルを保存しているディレクトリにテストファイルを追加します。今回はルートディレクトリあるtestディレクトリに以下のファイルを追加しました。
image.spec.jsimport path from 'path' import initStoryshots from '@storybook/addon-storyshots' import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer' initStoryshots({ suite: 'Image storyshots', test: imageSnapshot({ storybookUrl: `file://${path.resolve(__dirname, '..', 'storybook-static')}`, // Storybookをビルド出力先を指定 }), configPath: path.resolve(__dirname, '..', '.storybook'), })テストを実行する
テストを実行してみます。
npm run testテストが成功すると、テストファイルを保存しているディレクトリに__image_snapshots__ディレクトリが作成され、その中に画像ファイルが保存されています。このファイルは以下のように、Storybookで読みこんだButton.vueコンポーネントの画像スナップショットです。こちらは画像で比較を行い、画素に変更があればテスト失敗となります。
おわりに
何度かエラーに出くわしながらも、調べながら動作させることができました。今後、Storybook周りがアップデートされたらもっと簡単に導入できるようになるのかな???
運用にあたっての追加設定やテストの自動化など、いくつか調べていこうと思います。
- 投稿日:2020-07-11T02:42:37+09:00
キーコードを使ってvue.jsのキーイベントの処理
key.html<body> <div id="app"> <form action=""> <label for="name">氏名:</label> <input type="text" id="name" v-on:keyup.27="clear" v-model="name"> </form> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script src="key.js"></script> </body>key.jsnew Vue ({ el: '#app', data: { name: '匿名' }, methods: { clear: function() { this.name = ''; } }, });なお、keyup.escでも同じ処理となる。
さらにキーエイリアスの作成も可能。key.jsnew Vue ({ el: '#app', data: { name: '匿名' }, methods: { clear: function() { this.name = ''; } }, }); Vue.config.keyCodes = { // camelCase won`t work 'no-change': 29, };key.html<div id="app"> <form action=""> <label for="name">氏名:</label> <input type="text" id="name" v-on:keyup.no-change="clear" v-model="name"> </form> </div>なお、同時押しはalt.exact.81やalt.exact.enterなど重ねれば問題なし。exactがない場合は他のボタンが押されても反応する。
- 投稿日:2020-07-11T02:26:07+09:00
修飾子onceを使った、一度しか引けないおみくじボタン
omikuji.html<body> <div id="app"> <input type="button" value="結果表示" v-on:click.once="onclick">//clickの後にonceをつける <p v-cloak>今日の運勢は{{ result }}です</p> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script src="omikuji.js"></script> </body>omikuji.jsnew Vue ({ el: '#app', data: { result: '_' }, methods: { onclick: function(){ var ranNum = Math.floor(Math.random() * 10) +1; if(ranNum === 1) { this.result = '大吉' } else { this.result = '大凶' } } }, });
- 投稿日:2020-07-11T02:10:29+09:00
Vue.jsで起こる、ページ読み込み時のマスタッシュ構文のちらつきを回避
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vue.js</title> </head> <body> <div id="app"> <p v-cloak>{{ message }}</p><!-- cloakはid="app"のタグにかいても大丈夫。 --> </div> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.5/lodash.min.js"></script> <script src="vue.js"></script> </body> </html>[v-cloak] { display: none; }new Vue ({ el: '#app', data: { message: 'こんにちは' }, });もはや必須ですね。
- 投稿日:2020-07-11T01:40:29+09:00
v-スタイルプロパティの設定
styleをバインディングするにあたっての書き方
1パターン目
style.html<body> <div id="app"> <div v-bind:style="{ backgroundColor: 'Aqua', fontSize: '1.5em' }">こんにちわ</div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script src="vue.js"></script> </body> </html>style.jsnew Vue ({ el: '#app', });2パターン目
style.html<body> <div id="app"> <div v-bind:style="[color,size]">こんにちわ</div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script src="vue.js"></script> </body> </html>style.jsnew Vue ({ el: '#app', data: { color: { backgroundColor: 'Aqua', color: 'Red' }, size: { fontSize: '1.5em' } }, });styleに配列、オブジェクトを入れることができるということを知った。
改めて見ると結構勉強になるなぁ。
こちらの方がとても詳しく書いてくださってます。
https://qiita.com/yuta-38/items/94cdde004fb225f8b1b2
- 投稿日:2020-07-11T01:17:01+09:00
vueで属性をバインディングし、画像の大きさを動的に変更する
タグの属性も動的に変えることができる。
attr.html<body> <div id="app"> <select v-model="attr"> <option value="height">高さ</option> <option value="width">幅</option> </select> <input type="text" size="5" v-model="size"><br> <img src="https://wings.msn.to/image/wings.jpg" v-bind:[attr]="size"><!-- []を使う --> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> </body>image.jsvar app = new Vue({ el: '#app', data() { return { attr: 'width', size: 100 } }, })あまり見慣れないし、使う機会もなさそうだが、知ってて損はなさそう。
- 投稿日:2020-07-11T00:34:48+09:00
複数の属性(value, type,classなど)をVueのdataでまとめて管理する方法
こういうやり方もあるのかって感じで書いておく。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vue.js</title> </head> <body> <div id="app"> <form action=""> <label for="memo">メモ:</label> <!-- <input type="text" id="memo" size="20" max-length="14" required> 本来(HTMLのみ) --> <!-- <input type="text" id="memo" :size="size" max-length="maxLength" :required="required"> ひとつずつ指定 --> <input type="text" id="memo" v-bind="attrs"> </form> </div> <script src="vue.js"></script> </body> </html>new Vue ({ el: '#app', data: { attrs: { size: 20, maxlength: 14, required: true, } }, });属性ごとに段落を変えるVueのやり方があまり好きでないので、長くなる場合はこっちで書いてみようかな。
コンポーネントでは最近この書き方を始めた。
- 投稿日:2020-07-11T00:16:38+09:00
v-ボタンを押して配列の一部を変更する
Vueでは配列の一部を変更しても検知されない。
そういった場合は、Vue.set(配列, 配列の番号, 値');array.html<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> </head> <body> <div id="app"> <div> <form action=""> <input type="button" value="追加" v-on:click="addPajama"> <input type="button" value="変更" v-on:click="changePajama"> <input type="button" value="削除" v-on:click="deletePajama"> </form> <ul> <li v-for="item in list" v-bind:key="item">{{ item }}</li> </ul> </div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <script> var app = new Vue({ el: '#app', data() { return { list: ['赤パジャマ', '青パジャマ', '黄パジャマ'] } }, methods: { addPajama(){ this.list.push('茶パジャマ');//末尾に追加。先頭ならunshift() }, changePajama(){ Vue.set(this.list, 1, '茶パジャマ');/this.$set()でもよい }, deletePajama(){ this.list.shift(); }, }, }) </script> </body> </html>