- 投稿日:2020-02-18T17:50:20+09:00
html2canvas で 画像生成
vue.js で html2canvas 決定版
ラブレターみたいなのを作りたい。
ラブレターではphp側で画像を生成して出力している。参考
https://love-letter.club/
https://wood-roots.com/web/vue-js/2253特徴
PHP側
○ ユーザーの環境に依存しない
X サーバー重くなる
X 絵文字使えない (強引にライブラリ入れりゃ使えるが・・・ユーザー側の絵文字更新に応じ要変更)JS側で生成
○ サーバーに負荷をかけない
○ 絵文字使えないX ユーザーの環境によってバグったりする
X 環境がバラバラなのでデバッグに手間がかかるただ、これからの時代は jsで 生成したい。
インストール
α版を指定してインストールする
npm install html2canvas@1.0.0-alpha.12注意
・CSSで body に フォントを設定しない。
・バージョンは アルファ版を使う。
・でかいモニターで出力すると線が入る ( 画像がにじむ )
これはしょうがないので諦める。hoge.vue<style> #preview { overflow: auto; width: 100%; } #preview_inner { background-size: 600px auto; position: relative; width: 600px; } #letter_body { position: relative; border-left: 17px solid #fbbfc8; border-right: 17px solid #fbbfc8; display: inline-block; font-size: 1.5rem; color: #444; margin-top: -30px; padding: 10px 10px 10px 10px !important; min-width: 600px; text-align: center; height:200px; display: table-cell; /* IE8から使用可能 */ vertical-align: middle; white-space: pre-wrap; } </style> <template> <div> <img v-if="imgData" :src="'/gcp-storage/' + imgData" style="width: 95%;"> <el-input type="textarea" v-model="message" :rows=5></el-input> <el-button type="primary" @click="generate">画像を生成</el-button> <div id="preview" style="display: block;"> <div id="preview_inner"> <div id="letter_top"><img src="/img/tegami-top.png"></div> <div id="letter_body">--</div> <div id="letter_bottom"><img src="/img/tegami-bottom.png"></div> </div> </div> <div id="text_body">{{ message }}</div> <h2>完成品</h2> <div id="result"></div> </div> </template> <script> import html2canvas from 'html2canvas'//古いと、文字のサイズがずれるので、 vue.blade で 読みコンで使う export default { data() { return { imgData:'', message:'', }; }, created () { this.message = 'つけめんそばざるうどん恋をした夜はすべてがうまくいきそうで。'; }, watch: { }, methods: { generate(){ //まずはテキスト部分だけ生成させる let vc = this; html2canvas(document.querySelector("#text_body"),{ scale: 2 //文字がぼやけないように }).then(function(canvas){ //まずは一旦テキストを画像に var letter_body = document.querySelector("#letter_body"); letter_body.innerHTML = ''; letter_body.appendChild(canvas); vc.letterHeight = document.getElementById('letter_body').offsetHeight; //その後、メイン画像として生成 html2canvas(document.querySelector("#preview_inner"),{ scale: 2 }).then(function(canvas){ var result = document.querySelector("#result"); result.innerHTML = ''; result.appendChild(canvas); // document.getElementById('preview').style.display = "none"; // document.getElementById('result').style.display = "none"; // 以下、サーバーに送る処理 // var dataURI = canvas.toDataURL('image/png'); // let dataform = new FormData(); // // // const message_tmp = document.getElementsByName("message"); // dataform.append('id',vc.$route.params.id); // dataform.append('body',message_tmp[0].value); // dataform.append('img',dataURI); // // //サーバー側に画像を保存 // axios.post('/hoge/makeGcpOnly/', dataform).then(e => { // // vc.imgData = e.data; // vc.dialogVisible = false; // vc.message = ''; // vc.loading = false; // // }).catch((error) => { // console.log(error); // console.log("エラー"); // }); }); }); } } }; </script>これで画像が生成される。
- 投稿日:2020-02-18T17:40:09+09:00
2020年 2月のJestセットアップ
ありとあらゆるリファレンスを読みまくってまとめました。
一番スッキリJestをセットアップしよう。Jestとは
Facebookが開発したJavaScriptの単体テストフレームワーク。
Node上で動作するため、手軽にテストを実行できる事が特徴。作業環境
- Laravel 5.7
- Docker on Mac
- Vue.jsとかはLaravel mixのもの
セットアップ
Jestとかを
npm install
npm install --save-dev jest babel-jest @babel/core babel-core@bridge @babel/preset-env vue-jest @vue/test-utilsそれぞれのひとこと説明
jest
Jest本体babel-jest
babelとjestを連携させるもの@babel/core
Babel本体の最新版
Babelは八百万のJavaScript構文を一定のバージョンと互換のある構文に変換するもの。
Nodeでは最新のJavaScript構文(import / exportなど含め)が動作しません。
そのため、Babelを使ってトランスパイルしてあげます。babel-core@bridge
vue-jest
が@babel/core
ではなく、babel-core
を参照してしまうらしく、これを解決するためにインストール。
Babel 7.x系だけど名前はbabel-core
なBabel本体。@babel/preset-env
山ほどあるBabelの構文変換に関わるライブラリをいい感じに自動的に使ってくれるもの。
参考 : babel-preset-envを簡単にさわってみた。 - Qiitavue-jest
vueをjestでテストできるようにするもの@vue/test-utils
Vueのテストを書きやすくしてくれるユーティリティBabelのバージョンに注意
Babel 7.xからプラグインのプレフィックスとして
@babel/
がつくようになりました。
ECサプリに入ってるBabelは7系なのでプラグインを追加する時、@babel/
から始まっているか注意してください。6.x以前のプラグインを使うとうまく動かない事があるらしい
設定ファイルをつくる
babel.config.js (.babelrc)
Babelの設定ファイルです。
babel.config.js
でも.babelrc
でもいいのでソースルートに配置します。おそらく両方あるのはよろしく無いのでどちらか片方にしましょう。babel.config.jsmodule.exports = { presets: [ ['@babel/preset-env', {modules: false}] ], env: { test: { presets: [ ['@babel/preset-env', {targets: {node: 'current'}}] ] } } };jest.config.js
どんなファイル名のものをテスト対象とするか、どのファイル名をどのモジュールで変換するか、などを設定できます。
jest.config.jsmodule.exports = { transform: { '.*\\.(vue)$': '<rootDir>/node_modules/vue-jest', '^.+\\.js$': '<rootDir>/node_modules/babel-jest' }, moduleFileExtensions: [ 'js', 'vue' ], };package.json (既存のものを編集)
Jestの起動コマンドを書き足します。
NODE_ENV=test
とすることでNodeへテスト環境であることを明示しています。package.json{ "private": true, "scripts": { "dev": "npm run development", "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", "watch": "npm run development -- --watch", ... , "test": "NODE_ENV=test jest" <-追記 }, "devDependencies": { ... }, ... }テストを書く
テストケースを作ってみましょう。
こんなファイルをテスト対象にしてみました。
カリー化してある2つの数字を足すだけの関数です。ExampleSum.jsexport default{ sum(x){ return (y){ return x + y; } } }これをテストするスクリプトはこんな感じ。
example.test.jsimport ExampleSum from "path/to/ExampleSum"; test('test sum()', ()=>{ expect(ExampleSum.sum(1)(2)).toBe(3); }実行してみよう
npm test
とターミナルへタイプしてJestを動かしてみよう。
--
でファイル名を連結することでそのファイル名と部分一致するファイルのみをテスト対象としてくれます。npm test -- example.test.jsそのほかのCLIオプションは Jest CLI Options · Jest へ。
async
/await
もテストできるPromiseExample.jsexport default { sleep() { return new Promise(resolve => { setTimeout(() => { resolve(); }, 500); }); } }test('test async', async () => { await PromiseExemple.sleep(); expect(true).toBe(true); });
Vueのフロントエンドテストもかけます
ExampleComponent.vue<template> </template> <script> export default { name: "ExampleComponent", methods: { sum(x, y) { return x + y; } } } </script> <style scoped> </style>component.test.jsimport ExampleComponent from "./models/ExampleComponent"; const {mount} = require("@vue/test-utils"); test('Vue component test', () => { const component = mount(ExampleComponent); expect(component.isVueInstance()).toBeTruthy(); expect(component.vm.sum(1, 2)).toBe(3); });
component.vm
が僕らのよく知るVueコンポーネントインスタンスです。
DOMを操作してフォーム入力をしたり、クリックしたりもできます。
@babel/preset-env
がびっくりするほど便利だったことがわかった。
- 投稿日:2020-02-18T17:23:17+09:00
vueで動的に生成したフォームのバインディング
構成
- vue2.61
- vuetify2.26
実現したこと
v-forを使って動的に生成したそれぞれのインプット項目をリアクティブに扱う
無駄にはまったところ
余計なv-modelをつけるとインプットイベントでv-forで展開したインプット項目が初期化されてしまう。
実装したもの
sample.vue<template> <v-container v-for="item in list" :key="item.id"><!-- リアクティブだからといってここに v-model="list"としてはいけない--> <v-row> <v-col> <v-textfield label="ユーザーアドレス" v-model="item.email" ></v-textfield> </v-col> <v-col> <v-textfield label="ユーザー名" v-model="item.name" ></v-textfield> </v-col> <v-row> </v-container> </template> <script> export default { name: 'sample', data: () => ({ list: [ {id: 1, email: 'sample1@eamil.com', name: 'sample1'}, {id: 2, email: 'sample2@eamil.com', name: 'sample2'} ], }), } </script>なんでこんなことでハマったのかと思うところではまった。
バインド対象のオブジェクトを削除したり、バインド対象のオブジェクトをユーザーアクションに紐付けて追加したりし、かつ、インプットイベントに対しては対象オブジェクトの中身をリアクティブに変更できるようにする的な箇所だったので、あれこれ元のリストの更新をリアクティブにやるにはモデルで紐づけてあげなければいけないのでは?と考えたところがハマりポイントだった。
二度同じミスをしないように備忘録として残す。
- 投稿日:2020-02-18T13:04:22+09:00
【Vue.js】vue-image-lightbox で画像ギャラリーを作成!
はじめに
Googleドライブの画像表示みたいなUIを作りたいと思っていたところ、vue-image-lightbox を使うと
簡単に実現できたので、最低限の実装方法を残したいと思います。vue-image-lightboxとは?
サムネイルから、画像を拡大表示したり、スライドショーにしたりするための機能を提供しているライブラリです。
参考情報
- vue-image-lightbox https://www.npmjs.com/package/vue-image-lightbox
- ソースコード https://github.com/ryuckel/vue-playground/tree/vue-image-lightbox
今回の記事で実現した内容
以下の画像のようになるところをゴールとします。
記事中は主にライブラリに関係した部分を書いてます。ソースは参考情報から見てみてください。
環境
- Vue.js 2.5.10
- vue-cli 3.7.0
実装(準備編)
- 環境構築
vue createコマンドでプロジェクトを作成していきます。
プロジェクト名は任意で入れてください。
セットアップにあたり聞かれる質問も任意で答えていただいて構いません。vue create image-app(プロジェクト名を入れてください) cd image-app
- 必要なライブラリのインストール
yarn add vue-image-lightbox vue-lazyload※画像の遅延読み込み(Webページを先に表示して画像は読み込んだら表示させること)が必要になるため
vue-lazyloadも併せてインストールしています。
- CSSのインポート
まず、app.scssにvue-image-lightboxのCSSプロパティを読み込みます。app.scss@import '../../../node_modules/vue-image-lightbox/dist/vue-image-lightbox.min.css';
- vue-lazyloadのインポート
画像の遅延読み込みのため、main.jsにvue-lazyloadをインポートしていきます。main.jsimport VueLazyLoad from 'vue-lazyload'; Vue.use(VueLazyLoad);実装(コンポーネント作成)
vue-image-lightboxを使用したコンポーネントを作成します。
CSSはBuefy(BulmaベースのUIコンポーネントライブラリ)を使い、サムネイルだけCSSを当てています。
要点は以下の通りです。
- import文でvue-image-lightboxを読み込みます。
- 画像表示は配列(images)を用意して
v-for
でimg要素を生成し、v-lazyで遅延読み込みしています。- img要素をクリックすると
openGallery
メソッドを発火させることでクリックした画像がギャラリー表示されます。 openGalleryメソッドはvue-image-lightboxのshowImageメソッドに配列(images)のインデックスを渡しています。- ImageLightBox要素で指定しているプロパティ
show-light-box
がtrueだとブラウザにページを読み込んだタイミングでギャラリー表示されます。show-caption
がtrueにすると、ギャラリー表示時に画像オブジェクトに指定されたcaptionが表示されます。LightBox.vue<template> <div> <div class="media"> <div class="column"> <div class="columns is-multiline"> <div v-for="(image, index) in images" :key="image.id" class="column is-one-quarter has-text-centered"> <img v-lazy="image.src" @click="openGallery(index)" class="thumbnail" /> <p>{{ image.title }}</p> </div> </div> <ImageLightBox ref="lightbox" :images="images" :show-light-box="false" :show-caption="true"></ImageLightBox> </div> </div> </div> </template> <script> import ImageLightBox from 'vue-image-lightbox'; export default { name: 'LightBox', components: { ImageLightBox }, data() { return { images: [ { thumb: 'https://www.pakutaso.com/shared/img/thumb/YAMAhokkaido015_TP_V.jpg', src: 'https://www.pakutaso.com/shared/img/thumb/YAMAhokkaido015_TP_V.jpg', title: '朝日が昇る摩周湖(北海道川上郡弟子屈町)', caption: '朝日が昇る摩周湖(北海道川上郡弟子屈町)', id: 1 }, { thumb: 'https://www.pakutaso.com/shared/img/thumb/YAMAhokkaido032_TP_V.jpg', src: 'https://www.pakutaso.com/shared/img/thumb/YAMAhokkaido032_TP_V.jpg', title: '水鏡の青い池(北海道川上郡美瑛町白金)', caption: '水鏡の青い池(北海道川上郡美瑛町白金)', id: 2 }, { thumb: 'https://www.pakutaso.com/shared/img/thumb/KMKC428D354_TP_V.jpg', src: 'https://www.pakutaso.com/shared/img/thumb/KMKC428D354_TP_V.jpg', title: '悪天候の上高地と大正池', caption: '悪天候の上高地と大正池', id: 3 } ] }; }, methods: { openGallery(index) { this.$refs.lightbox.showImage(index); } } }; </script> <style lang="scss" scoped> img.thumbnail { height: 100px; cursor: pointer; } </style>最後に
画像のギャラリー表示をやりたくなった時になかなかvue-image-lightboxに行きつけなかったのですが、
vue-image-lightboxを見つけてから実装まではスムーズにいったので記事にしてみました。
ぜひお試しください!
- 投稿日:2020-02-18T11:13:36+09:00
Vuetifyのv-comboboxで自由に入力された文字列を取得したい
v-comboboxを使うということは自由入力させたいということなのに、自由入力した文字をうまく取得できない!
なぜか微妙にハマったのでメモっておきます。例のとおりリスト要素から選択された際は
selecteditem
にオブジェクトがバインドされるまではいいが、では自由に入力された値は?
オフィシャルのドキュメントをよく読むと、、valueプロパティは The input's value、つまり入力された値はvalue
プロパティに入る、と記載されております。そこでselecteditemのハンドリングとも併せて検証してみました。結果、
:value="inputedvalue"
のバインドだと入力された値は取得できず、ref="comb"
で$refs経由の取得がうまくいきました。<v-combobox v-model="selecteditem" :items="items" item-text="name" item-value="id" return-object ref="comb" :value="inputedvalue"> </v-combobox>selecteditem: null, items : [ { id:"001", name:"牛丼" }, { id:"002", name:"ピザ" }, { id:"003", name:"ラーメン" }, ]<script> var val = ""; // バインド経由では取れない val = this.inputedvalue; // $refs経由でプロパティ直見すると取れる val = this.$refs.comb.value; // itemsの中の選択肢から選ぶ判定はv-modelがobjectになってることを確認すればよい if(this.selecteditem && this.selecteditem.id) { val = this.selecteditem.id + " / " + this.selecteditem.name; } alert(val); </script>
- 投稿日:2020-02-18T10:29:37+09:00
vue勉強2
- コンポーネントは名前付きの再利用可能な Vue インスタンスです。
インスタンス
とコンポーネント
とオブジェクト
は同じもの?vueで出てくるプロパティってなんのこと?
- cssの場合は、
セレクタ {プロパティ名:値;}
なので、font-sizeとかがプロパティ- プロパティはコンポーネントに登録できるカスタム属性です。
- 具体的には
<blog-post title="My journey with Vue"></blog-post>
でいう、title
の部分。 htmlでいうと属性にあたるが、vueコンポーネントだとプロパティにあたる
- つまり、
html/css/js
のレイヤーで分けるのではなく、それらテンプレート、ロジック、スタイルをひとまとまりにしてコンポーネントという括りで分けるmounted は Vue.js におけるライフサイクル(参考)において、仮想 DOM が DOM に置き換わるタイミングを指します
Vue.js 初心者向けハンズオン - Visual Studio Code, TypeScript, Element 編
answerブランチを作るときに、目次で分けないといけないので、ローカルブランチ(uchideブランチ)でやるときから必要以上にこまめにコミットしておく
親コンポーネントと、子コンポーネントがあるとき、
親→子
に渡るのはprops(データの値)で子→親
に渡るのはemit(イベントの発火)である使っている言葉が違うと全く違うもののように見えるが、糖衣構文や省略記法など同じ処理でくくるとある程度まとめて整理できる
- この処理はこういう書き方をする => いちいち処理を書くのがめんどくさいのでまとめてこういう書き方をできる => この処理のこの部分は省略して書ける みたいな流れがループして膨大なものに見えている
命名系
- コンポーネント名は常に複数単語とするべきです
- 単一ファイルコンポーネント のファイル名は、すべてパスカルケース (PascalCase) にするか、すべてケバブケース (kebab-case) にするべきです
- アプリケーション特有のスタイルやルールを適用する基底コンポーネントは、すべて Base 、 App 、V などの固有のプレフィックスで始まるべきです
- 常に 1 つのアクティブなインスタンスしか持たないコンポーネントは、1 つしか存在しえないことを示すために The というプレフィックスで始めるべきです。これはそのコンポーネントが 1 つのページでしか使われないということを意味するのではなく、 ページごとに 1 回しか使われないという意味です。
- 親コンポーネントと密結合した子コンポーネントには、親コンポーネントの名前をプレフィックスとして含むべきです。
- コンポーネント名は、最高レベルの(たいていは最も一般的な)単語から始めて、説明的な修飾語で終わるべきです。
- コンポーネント名の形式 どこでもケバブケースを使うのもアリです。
- JS/JSX 内でのコンポーネント名はつねにパスカルケース(PascalCase)にするべきです。ただし、 Vue.component で登録したグローバルコンポーネントしか使わないような単純なアプリケーションでは、ケバブケース(kebab-case)を含む文字列になるかもしれません。
- コンポーネント名には、略語よりも完全な単語を使うべきです。
- プロパティ名は、定義の時は常にキャメルケース(camelCase)にするべきですが、テンプレートや JSX ではケバブケース(kebab-case)にするべきです。
- 【Vue】単一ファイルコンポーネントの命名規則まとめ【ファイル名から記法まで】
- 投稿日:2020-02-18T10:27:34+09:00
vue勉強
const vm = new Vue ({
のV
は大文字じゃなきゃ動かない
<input type="text" v-model="message">
のv-model
みたいなv-〇〇
はディレクティブ(属性ディレクティブ)といって、「ここを動かしまっせ」みたいな印の役割をしている
直訳で 指示 という意味
ディレクター(監督)から出る指示のニュアンスバインディング
相互に関連付けること。
双方向データバインディングとかいう
v-model
フォーム入力バインディング。
input要素とかtextarea要素とかのフォームの状態を、p要素などにリアルタイムで関連付けることができる。
textareaに入力した文言をp要素内に出力するなど。
公式idとエレメント名(
el
の値)が一致していることを確認する
v-for
のときにリストの配列名を複数形にしたときにs
の付け忘れに注意マウント:mounted は Vue.js におけるライフサイクル(参考)において、仮想 DOM が DOM に置き換わるタイミングを指します
const
定数。多分、「以下を定義します」みたいな意味vm
view model の略
v-bind:class
状況によってクラスを可変にする
チェックボックスにチェックが入っていたらクラス(スタイル)を変えるみたいなことができる
公式
filter()
メソッド
例えば、「todoリスト内のアイテムをふるいに掛けてチェックが入っていないアイテムだけを並べる」みたいなことができる。
配列にフィルターを掛けてあたらしい配列を生成する。
カッコ内の引数には関数を渡す。その関数に従ってフィルターをかける
v-for
:key
(v-bind:keyの略)を必ず付ける。
固有(ユニーク)なidなどをリストアイテムに付す役割。
理由は固有のidを振らないとスタイルやチェックの有無などの状態を紐付けられないから。
keyを持ったアイテムは、状態を維持したまま並べ替えることができるが、keyを持たせないと並べ替えや削除などを行ったときにインデックス番号が状態を持ってしまい、状態を紐付けられない
わかりやすい説明
公式
el
,props
,data
,methods
などの名前
コンポーネントオプション
推奨される順序簡単な
hello world
script src="https://cdn.jsdelivr.net/npm/vue" #app | {{testVal}} javascript: const app = new Vue({ el: '#app', data: { testVal: 'Hello World!' //testValを定義 } });
- 投稿日:2020-02-18T09:47:02+09:00
[Vuex] getters/mutatinos/actions は複数形だぞ!
- 投稿日:2020-02-18T09:33:51+09:00
OpenAPIによるスキーマファースト開発の実施サンプルとCloud Runについて
- 投稿日:2020-02-18T00:54:47+09:00
iOSのsafariでinput[type="file"]を見た目をいい感じにしたかった
Nuxt.jsで組んでいたアプリをiOS端末でデバッグしてみたらいい感じに動かなかったので、いろいろ探ってみて分かったことをメモ。
問題の起きたコード
とりあえずinputを非表示(
display: none
)にして、inputの代わりのボタンをクリックしたときに、非表示のinput要素をクリックするイベントを発火させるやつ。
See the Pen
image select(included bug) by keyakko (@keyakko)
on CodePen.
PCでは動く。
safariではいい感じに動かない。よくわからない動き
問題のコードでも、画像選択の画面は出てくるんですよね。
ただ、選択後にonchange
のイベントが発火されない。(alertがでない)試しにinput要素を表示して、inputの要素をクリックしたら当たり前のようにイベントは発火されてalertは出る。
JS経由でクリックさせて、ファイル選択させてもイベント発火されないのはなんで...?対処
とりあえず今回の対処。
1. buttonとinputの要素の順番を変える
codepenにコード書いててたまたま気づいた。完全に知識不足。
See the Pen
image select(fix pattern1) by keyakko (@keyakko)
on CodePen.
<label> <button>アップロード</button> <input type="file" /> </label>2. ボタンの上に透明にしたinput要素を乗せる
Buefyのソースが参考に。
See the Pen
image select(fix pattern2) by keyakko (@keyakko)
on CodePen.
さいごに
いろいろ探すと、
display:none
はちゃんと動かないからwidth:0; height:0; overflow:hidden;
で対応しようねって出たりするけど、それではいい感じにならなかった。
最終的には対処方法の2番に行き着いた感じ。話はそれるけど、iPhoneのsafariのコンソールが見たいのにsafariの開発タブにデバイスがすんなり表示されないの自分だけ...?
そっちでも苦しめられた...(ここを参考にWi-FiとBluetoothをオフにしたらなぜかちょっと表示されたけど)