- 投稿日:2019-05-24T16:42:07+09:00
Waypoints を jQuery から Vue.js に移植した
// この記事は、 note に投稿した記事の再掲です。
「要素が表示領域に入ったらクラスを付与」みたいな実装をするのにものすごくお世話になっている Waypoints
いままでは jQuery を使って実装していましたが、まんをじして Vue.js に移植しました。Vue.js との併用なので、利用している Waypoints ライブラリは No Framework ってやつです。
コード全文:
https://github.com/incolorsnet/note/blob/master/docs/vue-and-waypoints/index.html動作サンプル:
https://incolorsnet.github.io/note/vue-and-waypoints/Vue.js + Waypoints.js with GRAPEVINE
実装してみる
Waypoints は CDN からも提供されています。
<head></head>
内 もしくは、<body></body>
内の一番下に入れるとよいです。<script src="https://cdnjs.cloudflare.com/ajax/libs/waypoints/4.0.1/noframework.waypoints.js"></script>jQuery:
(function($){ $(document).ready(function(){ $('.fade_in').waypoint(function(){ $(this.element).addClass('visible'); }, { offset: '80%' }); }); });Vue.js:
const app = new Vue({ el: '#el', mounted () { this.waypoint() }, methods: { waypoint () { let elem = this.$el.querySelectorAll('.fade_in') elem.forEach(x => { this.waypoint = new window.Waypoint({ element: x, handler: () => { x.classList.add('visible') }, offset: '80%', }) }) }, }, beforeDestroy: function () { this.waypoint.destroy() } })
forEach
文の書き方について、鹿野先生にご助言をいただいてとってもすっきり書くことができました。勉強になった〜〜!ありがとうございます…?おまけ
クライアントワークもプライベートな制作も Waypoints を多用しています。なぜならフワッと表示させたら大体オッケーだと思っているからです。
のくら - スーツとモブ顔を愛してやまない漫画家|Suit is Good.
https://nokura.me▲フワッとさせたので大体オッケーだと思っている運用サイトです。(このウェブサイトはまだ jQuery に依存中)
- 投稿日:2019-05-24T15:22:26+09:00
Atomic Designを採用したnuxtで親から子コンポーネントのスタイルを使い分ける
対象
- nuxtないしVueをつかっている
- Atomic Designを使ってコンポーネントを分けたアプリをつくっている(Atominc Designに限らなくてもコンポーネントの共通化をしている)
- styleはscopedにしていて、コンポーネントを超えてcssを使い回していない
- Scssをつかっている(使っていない場合はcssをネストさせなければよいだけですが)
やること
背景
- アプリの中で使うbuttonやlabelを共通化したい
- アプリ内ではbuttonやlabelの見た目はいくつかのパターンしかない
- しかし、横幅とかは使う場所によってまちまちである
やりたいこと
- 見た目のパターンはそのコンポーネントの中に定義して、親コンポーネントからどのパターンかを指定したい
- 同じスタイルを何度も書きたくはないため
- 横幅などは親コンポーネントからその都度値を指定したい
- 微妙に変わる部分は子コンポーネントにいちいち定義していると限りがないため
この記事でやってみること(つまり具体例)
- MyButtonというコンポーネントをつくる
- MyButtonは通常の見た目と、強調したい見た目を親から選んで指定する
- 通常、強調のどちらでも影付きボタンにできる
- MyButtonのwidthは親から直接指定する
やり方
- Vueが用意しているクラスとスタイルのバインディングをつかう
- htmlのセレクタに対して、
:class=
や:style=
と属性を付与することで、クラスや個別のスタイルをバインディングできる通常と強調を使い分ける
こんな感じのコンポーネントを作る
MyButton.vue<template> <!-- propsで受け取ったtypeをクラスにバインディングする --> <button class="MyButton" :class="type"> <slot /> </button> </template> <script> export default { props: { // 適用したいスタイルを文字列のporpとして親から受け取る type: { type: String, default: '' } } } </script> <style lang="scss" scoped> .MyButton { <!-- 見た目のパターンをネスとしたクラスで定義する --> &._normal { background-color: white; border: solid 1px black; } &._strong { background-color: red; border: solid 1px black; } &._shadow { box-shadow: 0px 3px 6px 0px black; } } </style>親からはこのように使用する
Parent.vue<template> <div> <!-- 適用したいクラスを文字列で指定する --> <my-button class="_nomal"> 通常ボタン </my-button> <my-button class="_strong"> 強調ボタン </my-button> <!-- クラスを二つ指定する時はスペースで区切る --> <my-button class="_nomal _shadow"> 影付き通常ボタン </my-button> </div> </template>横幅を指定する
先ほどのコンポーネントに
:style
属性を書き加えるMyButton.vue<template> <!-- propsで受け取ったwidthの値をwidthスタイルにバインディングする --> <button class="MyButton" :class="type" :style="{ width: width }"> <slot /> </button> </template> <script> export default { props: { type: { type: String, default: '' }, // 適用したいスタイルの値を文字列のporpとして親から受け取る width: { type: String, default: '' } } } </script> <style lang="scss" scoped> .MyButton { <!-- こちらはそのまま --> &._normal { background-color: white; border: solid 1px black; } &._strong { background-color: red; border: solid 1px black; } &._shadow { box-shadow: 0px 3px 6px 0px black; } } </style>親からはこのように使用する
Parent.vue<template> <div> <!-- 適用したい横幅の値を単位付きの文字列で指定する --> <my-button class="_nomal" width="100px"> 通常ボタン </my-button> <!-- 単位を自由に指定できる --> <my-button class="_strong" width="10%"> 強調ボタン </my-button> </div> </template>
:style="{ width: width }"
は、{ width: width }
の左側にスタイルを書いて、右側に値を書く (要するに:style="{ width: 10px }"
ということ)- 複数のスタイルを指定したいときには
:style="{ width: hoge, height: fuga }"
のようにカンマで区切ればOKまとめ
クラスとスタイルのバインディングをうまく使って抽象的なコンポーネントを作れれば、その後が楽!
- 投稿日:2019-05-24T12:06:21+09:00
「jQueryでやっていたことを、サーバーサイド側を何も変えずにフロントエンド側をVuejsにしておくれよ」
どうしてもレンダリングはサーバーサイドがやっているものを変えたくない。でもフロントは変えろ。そんな要望ありますよね?
え?ありませんって?あったんですよ。コンテンツの中身は可変なのでSSレンダリングしたいけど、APIを作ってFE側から取ってくるほどの内容でもない。その上で、レンダリングするHTMLにはVueのためのバインディングや条件式が書けない。そんな場合を想定します。
例えばアコーディオン
こんなイメージのものを作って行こうと思います。
まずはサーバーサイドにレンダリングしてもらうHTMLです。コード
html
<div id="accordion--wrapper"> <div id="accordion--component" class="accordion"> <h2 class="accordion--title">アコーディオンメニュー<i class="icon"></i></h2> <ul class="accordion--body"> <li>メニュー1</li> <li>メニュー2</li> <li>メニュー3</li> <li>メニュー4</li> </ul> </div> </div>それではVueコンポーネントを作っていきましょう。
Vueコンポーネント
<script> export default { data() { return { isOpen: false, }; }, mounted() { this.$el.querySelector('.accordion--title').addEventListener('click', this.clicked); }, methods: { clicked() { this.isOpen = !this.isOpen; }, }, watch: { isOpen(isOpen) { const accordion = this.$el; if (isOpen) { accordion.classList.add('open'); } else { accordion.classList.remove('open'); } }, }, }; </script> <style lang="scss" scoped> .accordion { & .accordion--title { cursor: pointer; display: block; padding: 15px 15px 15px 42px; margin-bottom: 0; border: 1px solid #CCC; & i.icon { margin-left: 20px; &:before { content: "+"; } } } & .accordion--body { padding: 0; margin: 0; visibility: hidden; opacity: 0; overflow: auto; transition: all .2s ease-in; list-style: none; & li { border-bottom: 1px solid #CCC; border-left: 1px solid #CCC; border-right: 1px solid #CCC; } } &.open { & .accordion--title { & i.icon { &:before{ content: "-"; } } } & .accordion--body { visibility: visible; opacity: 1; transition: all .8s ease-in; } } } </style>今回制約上テンプレートを持てないので、
<template>
のない単一コンポーネントを作ります。css/scssは見習い中なのでごちゃごちゃしてしまっています。cssに関する追記
アコーディオンのスタイルをすべてコンポーネントに書いてしまっていますが、見た目を決める記述は画面から読み込むcssに記述して、ここには開閉等ロジックに関わるもののみを記述するべきかもしれません。
追記ここまで。
このコンポーネントでhtmlにVueで命を吹き込んでいきます。
エントリポイントJavaScript
import Accordion from './components/Accordion.vue'; const ready = (fn) => { if ( document.attachEvent ? document.readyState === 'complete' : document.readyState !== 'loading' ) { fn(); } else { document.addEventListener('DOMContentLoaded', fn); } }; ready(() => { Accordion.template = '#accordion--wrapper'; new Vue({ el: '#accordion--component', render: h => h(Accordion), }); });
Accordion.template = '#accordion--wrapper'
こちらでid="accordion-wrapper"
の要素の 内側 をAccordionコンポーネントのテンプレートとして注入します。
new Vue
する際のel: '#accordion--component'
は、指定した要素をコンポーネントで置き換えるため、templateとelで指定するidは親子(template: 親、 el: 子)になる点に注意が必要になります。よい点、わるい点
またサーバーサイドがレンダリングしたHTMLを前提とした実装になってしまうため、ロジックと見た目の分離ができていないのも少し残念なところで、普段Vueの単一コンポーネントでは利用することのないであろう
querySelector
や$el
が入ってしまいます。ただ、状態管理や
watch
等のVueの機能が利用できて、単純にES6だけで書いていくよりは可読性が高い書き方になっていくのではないでしょうか。また、プロジェクトの一部ではVueを導入できているのにSSレンダリングの都合上Vue化しにくいところにも適用できるので、フレームワークが統一されるメリットも大きいと思います。
まとめ
ちょっとタイトルは雑でしたが、いろいろ制約の多い中でVueが使えないためにフロントエンドコードの一貫性がない状況を改善しようと検討した内容を書いてみました。
これで、今までjQueryで実装していたプロジェクトもこっそりとサーバーサイドに知られずにVueに置き換えることもできるかもしれませんね。
懸念
一度レンダリングしたものを置き換えることになるので、SEO評価に影響がある可能性があります。この辺はおいおい調べていきたいなと思っています。
- 投稿日:2019-05-24T11:05:08+09:00
[Vue.js] input type="file"で画像のuploaderを作る
やりたいこと
画像をアップロードしたら、それが表示されるものを作りたい。(以下のようなもの)
component
<template> <div> <img v-show="uploadedImage" :src="uploadedImage" /> <div class="c-btn--file"> ファイルを選択 <input type="file" @change="onFileChange" /> </div> </div> </template> <script> export default { name: 'BaseFileUpload', data() { return { uploadedImage: '', } }, methods: { onFileChange(e) { let files = e.target.files || e.dataTransfer.files this.createImage(files[0]) }, // アップロードした画像を表示 createImage(file) { let reader = new FileReader() reader.onload = e => { this.uploadedImage = e.target.result } reader.readAsDataURL(file) }, }, } </script>
- 投稿日:2019-05-24T09:06:26+09:00
【Vue Test Utils】`shallowMount + stubs` で子コンポーネントを含めてテストを良い感じにする
はじめに
vue-test-utils で子コンポーネントも見たかったので
mount
を使ってテストしていましたが、やたら時間がかかってタイムアウトになってしまいます。なので、渋々
shallowMount
に変更して子コンポーネントも一緒に使えないか調べました。
shallowMount + stubs
で良い感じになるimport Foo from './Foo.vue' mount(Component, { stubs: ['registered-component'] }) shallowMount(Component, { stubs: { // 特定の実装によるスタブ 'registered-component': Foo, // デフォルトのスタブを作成します。 // このケースではデフォルトのスタブのコンポーネント名は another-component です。 // デフォルトのスタブは <${デフォルトのスタブのコンポーネント名}-stub> です。 'another-component': true } })引用元:https://vue-test-utils.vuejs.org/ja/api/options.html#stubs
- 投稿日:2019-05-24T06:47:48+09:00
Vue + Fontawesome - v-if でアイコンの切り替えが出来ない
環境
npmで以下を入れている。
package.json"@fortawesome/fontawesome-free": "^5.8.2", "vue": "^2.6.10",はじめに
Vueのv-ifでFontawesomeのアイコンを切り替えようとした。
<i v-if="flag" class="fa fa-icon"></i> <i v-else class="fa fa-icon"></i>しかし、一つ目のアイコンは表示されるが二つ目はフラグを切り替えても表示されない。
原因
断言は出来ないが、インストールしている@fortawesome/fontawesome-freeは自前のjsでアイコンの置き換えをしている。
これがVueと相性が悪いのではないかという推測が立つ。main.js// アイコンを頑張って置き換えている皆さん import '@fortawesome/fontawesome-free/js/fontawesome.js'; import '@fortawesome/fontawesome-free/js/solid.js'; import '@fortawesome/fontawesome-free/js/regular.js';解決
jQueryによる表示の切り替えだとうまくいった。
if (flag) { $('.icon-1').show(); $('.icon-2').hide(); } else { $('.icon-1').hide(); $('.icon-2').show(); }解決2
vue-fontawesomeを使う。vue-fontawesomeのコンポーネントはv-ifでも切り替えができた。
ただ、インターフェースが少し特殊なので人を選ぶかもしれない。<font-awesome-icon v-if="flag" icon="icon-1" /> <font-awesome-icon v-else icon="icon-2" /> <!-- 少し特殊なインターフェース --> <font-awesome-icon icon="['far', 'regular-icon-1']" />おわりに
jQueryによる解決はVueの流儀的には強引な気がするので、vue-fontawesomeの導入の検討がいいかもしれない。
- 投稿日:2019-05-24T01:14:26+09:00
改訂新版 Vue.jsとFirebaseで作るミニWebサービスで第4章のログインできない問題。
2019年5月25日更新
再現性があるか試したところ、ログインできませんでした。4.3 Googleログインの実装のところでつまずきました。
本ではver4ですが、今はver6のせいかもしれません。
masakinihirota/mymarkdown
https://github.com/masakinihirota/mymarkdown
mymarkdown
https://mymarkdown-222.firebaseapp.com/
OS Windows10
node -v
v8.11.2
npm -v
6.9.0Webサービスを作る本
環境
Windows10
VSCode
npm 6.7.0改訂新版 Vue.jsとFirebaseで作るミニWebサービス
4章
4.2 Firebaseでログインの設定
で「Googleアカウントでログイン」ボタンを押しても、反応が無いとき。本とは違い、現在は下記を貼り付けるよう言われる。しかしこれではログインできない(ボタンの反応がない)。
<!-- The core Firebase JS SDK is always required and must be listed first --> <script src="/__/firebase/6.0.2/firebase-app.js"></script> <!-- TODO: Add SDKs for Firebase products that you want to use https://firebase.google.com/docs/web/setup#reserved-urls --> <!-- Initialize Firebase --> <script src="/__/firebase/init.js"></script>基本的に、これまでログイン画面に関係してきて写経してきたファイルは
Editor.uve
Home.vue
App.vue
の3ファイル。しかし、認証に関与しているのは
index.html
で、このファイルを修正する。具体的には、
Firebaseコンソールの
mymarkdown – Firebase – Firebase console
https://console.firebase.google.com/u/0/project/mymarkdown-プロジェクト/overview
(mymarkdown-プロジェクトはプロジェクト名)この画面から左上のProject Overviewの隣の歯車ボタンから
プロジェクトの設定>「全般」タブ>マイアプリ>Firebase SDK snipet>「構成」ラジオボタン
を選択する。
このスクリプトをコピーして
<body>
タグの下部に貼り付けます。index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>mymarkdown</title> </head> <body> <div id="app"></div> <script src="https://www.gstatic.com/firebasejs/4.8.2/firebase.js"></script> <script> // Initialize Firebase var config = { apiKey: "???????????????????????????????????????", authDomain: "mymarkdown-プロジェクト.firebaseapp.com", databaseURL: "https://mymarkdown-80c89.firebaseio.com", projectId: "mymarkdown-プロジェクト", storageBucket: "mymarkdown-プロジェクト.appspot.com", messagingSenderId: "????????????" }; firebase.initializeApp(config); </script> <script src="./build.js"></script> </body> </html>これでローカルからでも
npm run dev
コマンドを実行することでサイトが立ち上がりボタンを押せるようになります。※最下行のappIdを貼り付けてないのは、
本のgithubソースを基本的に使用して、apiKey、messagingSenderIdなどのこれらの項目を自分の情報に置き換えたからです。
(6.0.2から4.8.2へダウングレードするようにしました。)参考
Firebase を JavaScript プロジェクトに追加する | Firebase
https://firebase.google.com/docs/web/setup?authuser=0
ここでは5.9が使われているので、メジャーアップデートされた6.0以降うまくいってないのかもしれません。その他
現在は他にもいくつか変わっていて、
ログインに関するところだと、
「プロジェクトのサポートメールを設定します。」
↑現在は設定を入力する場所がありません。