- 投稿日:2019-07-28T23:24:09+09:00
NuxtのHTML templateはpugを使わないメリットもある
こんにちは、プログラミングスクールのレビューサイト「スクールレポート」を運営しているアカネヤ(@ToshioAkaneya)です。
NuxtのHTML templateはpugを使わないメリットもある
タイトルの通りです。pugは誰でも綺麗にコードがかけるため素晴らしいのですが、エディターの保管が素のHTMLに比べて弱い
という傾向があります。僕のしようしているIntelliJでもその傾向があります。(ちゃんと設定すれば問題ないのかもしれませんが...)その点HTMLの補完は正確です。この記事が参考になれば幸いです。
終わりに
Ruby on RailsとVueで作成したプログラミングスクールのレビューサイトを運営しています。良ければご覧ください。https://school-report.com/
- 投稿日:2019-07-28T23:06:53+09:00
スタートアップで働いて得られた開発tips
スタートアップに入り1年以上が経ちました。日々目まぐるしく環境が変化していく中でやってしまった失敗。
どんな失敗に遭遇し、どう対応したか
を簡単にまとめてみました!ぜひ、参考になれば幸いです!
(もし、需要がありそうな項目があれば深掘りして再投稿してみます)【フロント・サーバーサイド】
ページのレスポンス速度向上
当初とにかく指摘されたのはサイトのレスポンス速度でした。
そこで、レスポンス速度向上に取り組み、
結果的にPCのトップページの評価が40点台
→MAX96点
にまで向上させることができました。
ちなみにモバイルは70点付近です(笑)
測定ツールはPageSpeed Insights(ページの読み込み時間の測定・改善策の提案をしてくれるサイト)です。行った対策は以下です。
- N+1対策
- キャッシュの導入(一旦memocacheで対応)
- 画像の最適化
- JavaScriptとCSSにasync / defer
- クエリの修正(テーブルやカラムの追加)
- ページを開いた際に大量のデータを計算するなどの処理はレスポンスを遅くします!!
【インフラ系】
ElasticBeanstalkのインスタンスタイプはElasticBeanstalkから変更する
ElasticBeanstalkで環境を作成している場合、EC2からインスタンスタイプの変更しないようにしましょう。
依存関係などを崩してしまうため、うまく行きません。(僕はこれでサーバーを半日落しました(泣))
僕はこの時、環境の再構築をすることでサーバーの復活を果たすことができました。Elastic Beanstalk > ダッシュボード > アクション > 環境の再構築
エラーページは設定しておく
こちらはサーバーを落としてしまった際、またはメンテナンスの際に
503画面
が表示されてしまうとユーザーの不信感につながりかねません。404や500はrails側で設定できますが503はサーバーが落ちてしまっているのでインフラの方で設定して置かなければなりません。CloudFrontのCustom Error Responseを利用して、S3上にあるSorryページを表示する
スロークエリの設定
クエリの重さは、目に見えているページだけでなくサーバーにも大きな負担をかけてしまいます。
その結果、バックグラウンドでも影響が出てしまうことがあります。(メールが大量に届くor届かなくなる、またはjobなど)常にチェックできるように設定しておきましょう!Amazon RDS for MySQLでスロークエリーログを出力させる手順
ヘルスが変化した際の通知
様々な要因がありますが、ヘルスが
Severe
になって気づいたら数時間サーバーが止まってしまっていた。なんてことにならないようにヘルス変化は常に通知しておきましょう。Elastic Beanstalkで環境を構築している際はElastic Beanstalk > 設定 > 通知 > 通知したい先のアドレス設定 > 適用
で設定できます!
【その他】
ドキュメント管理で効率的に時間を活用する
僕の独自の共有に時間がかかるランキングは
1位 開発フローの把握
2位 システムの環境構築
3位 ユーザーからのお問い合わせ対応特に上位2つは出入りの激しい(笑)スタートアップの開発リソースを減らしてしまう深刻な問題だと思います。
一番対応の簡単な方法としてドキュメント管理に力を入れています。
もし、こんな質問がきたらこのマニュアルを渡す
のようなパターンができていればいいドキュメント管理ができているなと思うようになりました。
(ドキュメントはGithubのwikiにまとめています)Githubのタグを使い、新加入のエンジニアのキャッチアップ迅速に
ドキュメント管理
関連でもう一つ。
途中から参加したエンジニアさんはシステムの把握
に時間がかかると思います。
システムの把握に時間をかけすぎてしまうのはもったいないのですが、かけなさすぎると思いがけないバグを生んでしまいます。これがデータ保存系・更新系だと対応が大変です。。そこで僕がよく使うのはGithubの
アサイン
とタグ付け
です。
心がけていることは①自分が担当するissue・pullrequestには必ずself assignする ②ある程度シリーズ化したorしそうなissue・pullrequestにはタグづけをするこの2つを徹底すると以下のことがおきます。
→タグ一つ検索するだけで誰がどのタスクを担当し。どんなコードを書いたかがわかる
→上記がわかるとトラブルやバグ仕様の把握をしたいときに誰とコミュニケーションをとればいいかがすぐわかる
この流れが出来上がると、間に常勤のエンジニアが入ってコミュニケーションの橋渡しをせずに済み、
コミュニケーションコストが大幅に下がります!
【イメージ】
rails本家のコードを使わせていただきました。本当はこんな開発者おりません。
おまけ
副業案件お待ちしております。得意分野はRuby on Railsです!
職務経歴書
- 投稿日:2019-07-28T21:26:54+09:00
最近使ったJavaScriptの標準オブジェクトについて
windowオブジェクト
これは使ったというより前よりちょっと理解した程度ですが。
全てのオブジェクトの上層に位置するもの。locationオブジェクト
以下のように記述する事でlocationオブジェクトからパラメータを取得できます。
あまり良い実装ではないと思いますが、取得したパラメータを使って表示内容を書き換える際に利用しました。location.searchdocumentオブジェクト
JSを使ってHTMLや値を取得する時にこのコードが動くか?
それをconsoleで試すと開発速度が3倍ほどになった気がします。(体感)historyオブジェクト
これを直接使ったわけではないですが、
history.backを参考にRailsの設定を調べることができました。最後に
他にもscreenオブジェクト、navigatorオブジェクト、self、frames[]
なんかの使ったことのないオブジェクトがあるので学んでいこうと思います。
- 投稿日:2019-07-28T21:19:06+09:00
Ruby on Railsのヘルパーメソッドで<script>タグを書く。
ビューヘルパーって慣れない内は難しいよね
結構コロンがいるのかとか、どこに括弧をつけるとかなかなか分かりにくい。
簡単にJSのテストをする時に、viewファイルに以下のタグを書くこともあると思いますが、できればビューヘルパーを使いたいですよね。sample.html.erb<script> </script>実際のコード
以下のように書くと同じ事になります。
sample.html.erb<%= javascript_tag do %> <% end %>ちなみにクラスはこうやって付けられます、
でも多分(class: "hoge")とかでも大丈夫だしこっちの方がスマート。
記事書いている今試せないのでちょっとあやふやです。sample.html.erb<%= javascript_tag(:class => :hoge) do %> <% end %>最後に
まあテストだけならビューヘルパー使わなくてもいいじゃないかという話かもしれませんが、
気分的に気になる人はこの書き方を使ってみては?
- 投稿日:2019-07-28T21:12:13+09:00
Scrapyでformを送信する際にJavaScriptを回避する
問題
- Scpapy は JavaScript で生成されたコンテンツにアクセスできないため、例えば、確認のために表示されるアラートウィンドウの「OK」をクリックして先に進む、などができないことがある。
根本的な解決策
- JavaScriptを制御できるSeleniumなどを併用する。
- (参考記事)StackOverflow
- ScrapyとSeleniumを併用するサンプルコードもある
簡単な解決策(条件付き)
- 場合によっては もっと簡単に解決できる。
- 以下のform送信ボタンのように、
onclick
処理でJavaScriptのイベントが発動している場合には、scrapyの機能で回避できる。<input type="submit" onclick="return confirm('確定していいですか?')" name="submit" value="確定" class="button">
- クラスオブジェクト
scrapy.FormRequest.from_response
のパラメータdont_click
をTrue
に指定することで、送信ボタンのクリックを省略して form を送信できる。これを利用すれば、onclick
によって発動するイベントを回避して form が送信される。def submit_form(self, response): yield scrapy.FormRequest.from_response( response, formcss = 'table#table1 > tr:nth-child(1) > td.value > form', formdata = dict( decision = '1' ), dont_click = True )〜おしまい〜
- 投稿日:2019-07-28T20:59:16+09:00
[vue-router] BeforeRouteLeaveが2度発火する
TL;DR
Vue-Router
のBeforeRouteLeave
等ナビゲーションガードが二回発火する場合がある。- 遷移先のコンポーネントに何らかのリダイレクトがあるときに発生する。
to
を調べることで回避可能。同じ問題を扱ったissue
=> https://github.com/vuejs/vue-router/issues/2102本件は仕様として扱われるようなので、今後しばらく対応される見込みはなさそう。
再現環境
- Chrome バージョン: 75.0.3770.142
- Nuxt 2.6
- vue-router 3.0
リポジトリと検証動画
https://github.com/IKKO-Ohta/invoke_brl_twice
トップページ - ページ1 - ページ2という構成で、
- ページ2のバックボタンを押すと、
window.confirm()
を表示する。「OK」を押すとページ1に遷移する。- ページ1では、ページ2からの遷移をキャッチするとトップページに飛ばす
想定としては、例えば商品販売サイトにおいて、ページ2が注文完了画面で、ページ1が決済確認ページ、くらいのとき。
しかしこのとき、ページ2のBeforeRouteLeave
が2度発火する。window.confirm()
のOKを1度押しても再度ダイアログが表示される。どうして起こるか?
予想
2 => 1, 1 => top のような遷移をしそうな気がする。
実際
実際のページ2のbeforeRouteLeave
はこのように挙動する。
beforeRouteLeave
が 2=>1 と 2 => top の2回分呼ばれる。引数のfrom.name
とto.name
の中身を調べると確かにそうなっている。1=>top へのリダイレクトがこの2回目の遷移を誘発させる。このとき1=>topの遷移でもない! あくまで from="2", to="top"のbeforeRouteLeave
が走る!解決策
ページ2
のbeforeRouteLeave
のto
を検査し、一回目の遷移についてガードしないようにすれば良い。before
beforeRouteLeave(to, from, next) { window.confirm("このページを離れますか?") ? next() : next(false); }after
beforeRouteLeave(to, from, next) { if (to.name === "first") { next(); return; } window.confirm("このページを離れますか?") ? next() : next(false); }なお、afterは可読性の低いコードなので(一見「戻る」のときにはガードしない、というコードに見える)公式issueなどを引用して適宜コメントに追記することを推奨する。
- 投稿日:2019-07-28T20:25:24+09:00
puppeteerを使ってyoutubeの新着動画を探そう
動機
良い紳士向けMMDが出ているかもしれない。
チェックしなくてはならない。成果物
youtube_puppet(github)
※slackに関する処理にtoken情報が記載されていたため、そのjsファイルだけはpushしていない状態です。
上記をbat経由で呼び出しています。
トリガーとしてタスクスケジューラーを使用しています。
(将来的にはタスクスケジューラーは止めて別のにする予定です。)概要
簡単に成果物の概要を説明をします。
まず、実現したいこととしては「本日に投稿された紳士向けMMD動画を取得し、それをslack上に特定の時間に流したい」ということです。・紳士向けMMD動画を取得する方法を考えます。
これはいわゆるスレイピング技術で解決できるでしょう。
クローラーとも呼ばれます。
以下参考
ウェブスレイピング(wiki)紳士向けMMD動画を取得することに関しては上記で完了しました。
・slackに投稿する方法を考えます
slackに投稿することに関しては以下を参考にすると良いでしょう。
postMessage
slackAPIを使いたいが、どこでtokenを発行するのか分からない人は下記リンクを参照してください。
https://api.slack.com/apps・特定の時間に起動させる方法を考えます
手段は色々ありますが、
最も簡単な方法だと簡易なbatを書き、それをタスクスケジューラーで呼び出すことだと思います。
以下参考
Windowsでbatファイルを自動実行したい時(タスクスケジューラの設定)新しいのが好きな方であればfirebaseとか使っちゃえばいいと思います。
流行ってますし。以下もうちょっと詳しく解説。
URLの取得
上記画像のように何を検索するか、対象期間はいつにするかを設定し、その際のURLを使えば望みの物が拾えると思います。
今回は「紳士向け MMD」と検索し、フィルターを使い「今日」に絞りました。puppeteerの出番です。
puppeteerを使い、スレイピングしていきましょう。
そのためにまずF12を押してhtmlを見ていきましょう。
※各自で確認お願いします。結果だけ言うと欲しいものとしては
document.getElementsByClassName('text-wrapper')
こんな感じで一覧が取れそうでした。
そこからさらに必要な情報を抜き取っていけば望みのものが出来そうです。
具体的にはtext-wrapper配下にある
#meta > #title-wrapper > .title-and-badge > a
のhrefがあれば十分です。
なのでpuppeteerを使ってそれを取得しましょう。puppeteerの使い方は下記が参考になると思います。
puppeteerでの要素の取得方法puppeteerの基礎から知りたい方は書籍を買うといいでしょう。
私は下記の書籍のみ購入し、後はネットで調べました。
Puppeteer入門 スクレイピング+Web操作自動処理プログラミング Kindle版一度書店で立ち読みしてから購入を検討されると良いと思います。
slackAPIを使って投稿
実際のソースを貼るとtokenとか丸見えだったんです。
tokenだけ別ファイルにするとかしてpushしようと思ったんですがめんどくさくなったので
コードを貼り付けます。const request_promise = require("request-promise"); const SLACK_TOKEN = 'とーくん'; const CHANNEL_ID = 'ちゃんねるID'; const USER_NAME = 'ぼっとのなまえ'; exports.send_message = items => { const messages = items.map(item => { return item.href; }); messages.forEach(message => { (async () => { console.log( await request_promise({ uri : 'https://slack.com/api/chat.postMessage', method : "POST", headers : { 'content-type' : 'application/x-www-form-urlencoded', 'charset' : 'utf-8' }, form : { token: SLACK_TOKEN, channel: CHANNEL_ID , username: USER_NAME , text: message }, json : true }) ) })() }); }slackのメッセージ投稿については下記が参考になると思います。
NodejsでSlackにメッセージ投稿(2019年1月版)最終的な成果
一番上の参考画像の通り、slackにURLが投稿されて、動画の一覧が出てきました。
あとがき
今回の成果物ですが、main.jsに記載されている1か所を変えれば恐らく好きな動画をスレイピング出来る物になってると思います。
12行目のawait page.goto('');
のURLの部分です。
良ければ変更して使ってください。
使ってくださいと言っている割にcloneしてその部分を変更するだけで使えるものになってないのは申し訳ないなと思います。
cloneもしたくないけど紳士向けMMD動画の新着が見たい方はtwitterでDMください。
何とかするかもしれません。結構簡単に作れたのでpuppeteer面白いなぁという感想です。
コードは結構拾ってきた部分が多いので記述的にこれで良いのか不安な部分があったりします。
変な部分があれば良かったら指摘してください。以上です。
最後まで読んでいただいた方ありがとうございました。
- 投稿日:2019-07-28T20:20:55+09:00
AMP(Accelerated Mobile Pages)入門
概要
こんにちは、都内でフロントエンドエンジニアをしているかめぽんです。最近、Next.jsのバージョンアップにて対応したことで一層話題になっているAMPですが、僕自身AMPのことをそこまで注目していなくてたまたま調べてみたら割とおもしろうそうだったので、インプットした内容をまとめてみました。サンプルコードなども載せていますので、AMPの基礎的な情報の参考にしていただければと思います。
本題
AMPとは、メリット
AMPとはAccelerated Mobile Pagesの略で、GoogleとTwitterが発足したモバイルページを高速に表示させるためのプロジェクト、またはAMP HTMLというフレームワークのことを指します。
AMP自体は以下の構成要素で成り立っています。
- AMP HTML
- Custom Styling(CSS)
- AMP js
- AMP Cache(CDN)
静的なHTML, CSS, jsは普通のwebと同じですがAMP HTMLはAMP独自の仕様が定められており、その仕様どおりに沿ってモバイルサイトを作成することで表示速度を高速にすることができます。
加えて、AMPで作られたサイトはクローラに取り込まれた後AMP Cacheと呼ばれるCDNに保存されます。つまり、従来のWebと異なるのは、実際にURL先にリソースを取りに行くのではなくCDN先にリソースを読みに行くため通常アクセスよりも速く表示できることが特徴です。AMPの基本的なスタンスとしては「読み込みに時間がかかるようなことはさせない」ので高速化を進めていく代わりに、現段階でjsを非同期でしか読み込むことができないという制約があり、インタラクティブで動的なサイトまたはそのようなアプリはまだ不向きなようです。
実践
ここからは実際のソースコードでやっていきたいと思います。
How to use AMP
HTML宣言
最初にHTML宣言ですが以下のように記載します。雷マークがampを意味します。
<html amp lang="ja">
でも意味は同じです。<!doctype html> <html ⚡="" lang="ja">ampはutf-8のみ対応そしてviewportの設定が必須なので記載します。
<link rel="canonical" href="index.html"> <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">application/ld+jsonの設定
ampの設定の中でも大事なのが、LD+jsonです。こちらは、本サイトのamp構造を正しく伝えるための情報になります。Goolgeがschema.orgとLD+jsonで記述することを推奨しています。
<script type="application/ld+json"> { "@context": "", "@type": "Article", "headline": "Welcom to AMP SAMPLE", "image": [ "sample.jpeg" ], "datePublished": "2019-07-28T08:00:00+08:00" } </script>スタイルの読み込み
ampはインラインスタイルしか読めないので、cssファイルを用意するのではなくamp-boilerplateとして登録しておきます。
<style amp-boilerplate>a,abbr,acronym,address,applet,article,aside,audio,b,big,blockquote,body,canvas,caption,center,cite,code,dd,del,details,dfn,div,dl,dt,em,embed,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,output,p,pre,q,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,tt,u,ul,var,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:'';content:none}table{border-collapse:collapse;border-spacing:0}</style>javascriptの読み込み
ampでは、jsは非同期で読み込むことを推奨しているので、async属性をつけましょう。
<script async src="https://cdn.ampproject.org/v0.js"></script> <script async src="./script.js"></script>あとはいつも通りテンプレートを書けば最低限の対応は大丈夫です。LD+jsonに関してはもっと書くべきですがここでは割愛します。以下、ここまでのサンプルを貼っておきます。
<!doctype html> <html ⚡="" lang="ja"> <head> <meta charset="utf-8"> <title>AMP SAMPLE</title> <link rel="canonical" href="index.html"> <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1"> <script type="application/ld+json"> { "@context": "http://schema.org", "@type": "Article", "headline": "Welcom tao AMP SAMPLE", "image": [ "" ], "datePublished": "2015-02-05T08:00:00+08:00" } </script> <style amp-boilerplate="">body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate="">body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript> <style amp-custom="">a,abbr,acronym,address,applet,article,aside,audio,b,big,blockquote,body,canvas,caption,center,cite,code,dd,del,details,dfn,div,dl,dt,em,embed,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,output,p,pre,q,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,tt,u,ul,var,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:'';content:none}table{border-collapse:collapse;border-spacing:0}</style> <style>html{font-family:ヒラギノ明朝 ProN W6,HiraMinProN-W6,HG明朝E,MS P明朝,MS PMincho,MS 明朝,serif}.wrap{position:relative;width:auto;text-align:center;margin-top:80px}.title{position:relative;font-size:36px;color:#402c2c;font-family:Cambria,Georgia,serif;font-style:normal;-webkit-font-feature-settings:normal;font-feature-settings:normal;font-variant:normal;font-weight:500;line-height:39.6px;letter-spacing:2px}.title::after{position:relative;content:"";display:block;bottom:0;width:50px;height:3px;top:18px;background:#402c2c;margin-left:auto;margin-right:auto}.sub-title{position:relative;font-size:32px;color:#402c2c;font-family:HG明朝E,MS P明朝,MS PMincho,MS 明朝,serif;font-style:normal;-webkit-font-feature-settings:normal;font-feature-settings:normal;font-variant:normal;font-weight:500;line-height:36.6px;letter-spacing:2.5px;margin-top:80px}.flex-wrap{width:970px;height:auto;margin-left:auto;margin-right:auto;display:flex;flex-wrap:wrap;justify-content:space-between;margin-top:60px}.box{width:300px;height:300px;margin-top:40px;-o-object-fit:cover;object-fit:cover;border:none;background:0 0;overflow:hidden;background-position:50%;background-size:cover;background-repeat:no-repeat;transition:all .2s ease;background-image:url(img/sample.jpg)}.box:hover{filter:drop-shadow(3px 3px 4px #909090f0);transform:translate(-1px,-1px)}@media screen and (max-width:768px){.flex-wrap{width:100%}.box{width:33vw;height:33vw;margin:5px}}</style> <script async src="https://cdn.ampproject.org/v0.js"></script> </head> <body> <div id="app"> <div class="wrap"> <h1 class="title">Portfolio</h1> <p class="sub-title">Creative</p> </div> <div class="flex-wrap"> <div class="box"></div> <div class="box"></div> <div class="box"></div> <div class="box"></div> <div class="box"></div> <div class="box"></div> <div class="box"></div> <div class="box"></div> <div class="box"></div> <div class="box"></div> <div class="box"></div> <div class="box"></div> </div> </div> </body> </html>また、今回作ったページとGithubのリンクも貼っておきます。ぜひ、参考にしていただけたらと思います。
最後にページが出来上がったらGoogleが提供しているツールでAMPページとして有効かどうかチェックしてくれますので、コードでも良いですしURLを貼り付けて調べることができます。
まとめ
- AMPとはモバイルのページを高速に表示させる技術
- AMPに対応はAMP用のhtmlが必要
- AMPはAMP独特の制約がある
静的なデータでAMP対応のサイトができました。AMP対応により、サイトがかなり高速に表示されるのはかなりのアドバンテージになると思います。特に、メディアやCMS、ニュースサイトやECサイトなどサイトの速さが命のページにとってはかなり強みになると思います。逆に、javascriptの取り扱いに制約を感じることがあるのでWebアプリケーションとしてAMPの導入はもう少し様子見かなという印象です。Webアプリに関してだとPWAという選択肢もあるので、サービスの特徴によって活用していきたい所存ではあります。また、フロントエンド領域に置いてキャッシュの取り扱いやCDNの知識が必須になってきているので、その部分のキャッチアップもしていきたいと思いました。
参考
https://search.google.com/test/amp?hl=ja
https://amp.dev/ja/
- 投稿日:2019-07-28T19:43:49+09:00
NeovimでJSDocを使う
NeovimでJSDocを使う
JSやGASでコードを書いていく時に、コメントはどのように書いているだろうか。
手軽に分かりやすく、コードを共有した人が確認しやすいものはないかと調べた結果、JSDocというものを知った。
jsdoc.vimを参考にインストールを行った。環境
インストールの仕方と使い方
~/.config/nvim/init.vim" jsdoc Plug 'heavenshell/vim-jsdoc' nnoremap j :JsDoc <Enter>上記を
~/.config/nvim/init.vim
に記入した。nnoremap j :JsDoc
の部分は、マッピングをさせている。
通常は:JsDoc
+<Enter>
で実行されるので、これをj
に置き換えた。これでビジュアルモード時にobjectやfunction上で
j
を押すだけでJSDocが作成される。以下はJSDocに関しての簡単なまとめである。詳しくは参考文献のリンクを参照にしてほしい。
コメントの構文に関して
コメントは適切であるかを確かめる必要がある。
/** * JSDocコメントはスラッシュと2つのアスタリスクから始めます。 * インラインタグは {@code this} のように波括弧で囲みます。 * @desc ブロックタグは必ず行の先頭から開始します。 */
JSDocのインデント
ブロックタグ内での改行が必要な場合はスペース4つ分のインデントが必要。
/** * 長いparam/returnアノテーションの説明文の折り返し方を示します。 * @param {string} foo これは1行でおさめるには長すぎるパラメータの * 説明文です。 * @return {number} この戻り値の説明文は長すぎて、とても1行の中には * 入りきりません。 */ project.MyClass.prototype.method = function(foo) { return 5; };ファイルへのコメント
@author
で作成者に関する情報を添付する。/** * @fileoverview ファイルの説明を行う。 * 依存関係についての情報 * @author aaa@bbb.com */
classへのコメント
/** * Humanクラス * @param {number} height ヒトの身長 * @param {number} weight ヒトの体重 * @constructor */ class Human{ constructor(height, weight){ this.height = height; this.weight = weight; } }メソッド・関数へのコメント
/** * 日付を処理し、休みの日かどうかでtrueかfaleを返します。 * @param {date} date 日付 * @return {boolean} */ function isSunday(date){ //... }参考文献
- 投稿日:2019-07-28T18:23:16+09:00
[JavaScript]undifinedをStringオブジェクトの引数に入れたら文字列undifinedに変換されてしまった話
undifinedをStringオブジェクトの引数に入れたら文字列undifinedに変換され、意図しない処理が入ってしまってびっくりしたので、投稿。
JavaScript初心者なので、間違い指摘はコメントください。結論としては、「undifinedかどうか判断してから文字列変換しよう」です。
undifinedかどうか判断は、typeof a === "undefined"で比較などで対応しよう。
(undefinedは、typeof演算子をかけると文字列"undefined"に変換されるため)実際、どのようにundifinedが文字列に変換されるか見てみましょう。
let a; console.log(a + ":" + typeof a); // undefined:undefined let b = String(a); console.log(b + ":" + typeof b); // 'undefined:string' let c = a.toString(); // Uncaught TypeError: Cannot read property 'toString' of undefined // console.log(c + ":" + typeof c); let d = a + ""; console.log(d + ":" + typeof d); // 'undefined:string' let e = a.valueOf(); // Uncaught TypeError: Cannot read property 'valueOf' of undefined // console.log(e + ":" + typeof e);toStringやvalueOfは例外が生じます。
Stringやobj+""は文字列"undefined"に変換されます。私の場合、Stringオブジェクトで変換していたため例外が生じず、気づくのが遅れてしまいました。
ただし、Stringオブジェクトは、「toStringでプリミティブ値が返されなければvalueOfを呼び、その値を文字列に変換する」というプロセスとなっているため、文字列変換において安定して使えるようです。
toStringやvalueOf、obj+""では、必ず文字列変換されるという保証がないとのことです。
詳しくは、下記のリンク先を読もう。(現在、勉強中・・・)obj+""≠obj.toString()≠String(obj)っていう話 - Qiita より
toStringはJavaのように文字列の連結に暗黙的に使用されるメソッドではない。
obj+""はまずobj.valueOf()を呼び、obj.toString()を呼ばないことがある(がしかし、実は一つだけ例外がある)。
String(obj)は間違いなく obj.toString() を呼ぶ。valueOfとtoStringメソッドの水深43cmぐらいの深さの話 - 三等兵
オブジェクトを文字列に変換するならtoStringで、数値ならvalueOfという傾向がある。(valueOfの場合正確には基本型に変換すると表現するのが正しい)
toStringのみでオブジェクトを変換するときはvalueOfに一切触れずに行う。逆にvalueOfのみで変換するときは、文字列のときはtoStringを通じて変換する。ただし、toStringが使えないようならそのままvalueOfで返す。
- 投稿日:2019-07-28T16:57:01+09:00
Vue.jsについての基礎(インストール〜基本構文)
はじめに
おはようございます。こんにちは。こんばんは。
ワタタクです。
今回はVue.jsについて見けいけたらいいなと書いています。
Vue.jsに関しては少し触ったことがある程度なので、今後の為にと勉強させていただきますので
もし間違いとかありましたらご教授、アドバイスなんかいただけたら幸いでございます。
では早速参ります。Vue.jsとは?
Vue.js(ヴュージェイエス)、またはVueは、Webアプリケーションにおけるユーザーインターフェイスを構築するための、オープンソースのJavaScriptフレームワークである。他のJavaScriptライブラリを使用するプロジェクトへの導入において、容易になるように設計されている。一方で高機能なシングルページアプリケーション(SPA)を構築することも可能である。
chromeの拡張機能のvuejs-devtoolsを入れて置くと便利。
環境構築
今回はCDNを使わずvue-cliを使います。
nodeがインストールされているか確認する
$node -v v11.2.0もし、コマンドを叩いてもversionが表示されなかったらnode.jsをインストールしてください。
以下も確認して置いてください。$npm -v 6.9.0vue.jsのインストール
上記のことを確認したら、いよいよvue.jsをインストールしていきます。
$ npm install -g vue-cli成功すれば以下を確認してください。
$vue -V 3.1.1確認できれば次のコマンドを実行してください。
$ vue init webpack test-vue※test-vueの部分はプロジェクト名&ディレクトリ名になります。
いろいろ聞かれますが、全部EnterでもOKです。
実行が終わったら下記を実行してください。$ cd test-vue $ npm run devlocalhost:8080にブラウザからアクセスしてみてください。Vueアプリケーションの土台ができており、
下記の画面が表示されるはずです。これで環境構築は終了です。
お疲れ様でした。基本構文
いろんなファイルが出来ててキョどりますが大丈夫です。
基本的に難しい設定をしなければ、だいたいsrcディレクトリの中のVueファイルだけをいじるだけです。v-showディレクティブ
まず最初に要素を表示するかどうかを決める条件付きレンダリングです。
HelloWold.vue<template> <div class="hello"> <h1>{{ msg }}</h1> <!--////////////////追加/////////////////--> <h2 v-show="showText">表示項目</h2> <!--/////////////////////////////////--> </div> </template> <script> export default { name: 'HelloWorld', data () { return { msg: 'Welcome to Your Vue.js App', //----------------------追加---------------------- showText: false //----------------------------------------------- } } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> </style>v-showディレクティブはタグの中にいれて書きます。上のコード内ではイコールでshowTextという変数をみてます。この変数の名前は任意ですが、これがfalseなら非表示、trueなら表示となります。
イベントハンドリング
ボタンをクリックしたら表示、非表示が切り替わる仕組みを作ります。
早速コードを見てみましょう。HelloWold.vue<template> <div class="hello"> <h1>{{ msg }}</h1> <!--////////////////追加/////////////////--> <button v-on:click="toggle">toggle</button> <!--/////////////////////////////////--> <h2 v-show="showText">表示項目</h2> </div> </template> <script> export default { name: 'HelloWorld', data () { return { msg: 'Welcome to Your Vue.js App', showText: false } }//追加 ,methods: { toggle: function () { this.showText = !this.showText } } //----------------------------------------------- } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> </style>v-onディレクティブ
buttonタグにv-on:clickというものがついてます。v-onでイベント発火時の JavaScript の実行が可能になります。clickしたら呼ばれる関数名をtoggleにしてます。これはmethodsオブジェクトの中で定義されています。
v-on:は@とも書けるmethodsオブジェクト
methodsオブジェクトの中に実行したい関数をガシガシ定義していきます。これをv-on:clickで読んだり、他の関数から叩いたりします。
thisについて
data内で定義した値はthis.showTextのようにthisを使って参照していきます。
v-modelディレクティブ
v-modelはVue.jsを使ってフォームを構築する際によく使う機能です。
v-modelはv-onとv-bindをまとめて一行で書くためのシュガーシンタックスです。
例としてフォームに入力した文字数をカウントする仕組みを作ります。HelloWould.vue<template> <div class="hello"> <h1>{{ msg }}</h1> <!--////////////////追加/////////////////--> <input type="text" name="text" v-model="count"> <h3>現在の文字数:{{ charaCount }}文字</h3> <!--/////////////////////////////////--> </div> </template> <script> export default { name: 'HelloWorld', data () { return { msg: 'Welcome to Your Vue.js App', //追加 count: '' } } ,//追加 computed: { charaCount: function() { return this.count.length; } } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> </style>ここで重要なのは、「computed」です。
computedという機能を使えば、dataで保持している値に変更がある度に、それを利用した値をリアルタイムに生成できます。ついでにもう一つ。
computedに似ているのですが、「watch」という機能を紹介します。
watchは、dataで保有している値に1対1で対応して、その値が変更されたときに動作するメソッドのようなものです。
Vue公式ガイドではwatchよりもcomputedを推してます。以上が双方向データバインディング(v-model)の基本的な使い方で、webアプリを作成するときには非常によく使う手法ですので、必ずマスターしてください。
v-forディレクティブ
v-forディレクティブを利用して、配列の要素を繰り返し表示させてみます。
HelloWold.vue<template> <div class="hello"> <h1>{{ msg }}</h1> <!--////////////////追加/////////////////--> <table> <thead> <tr> <th>タイトル</th> <th>著者</th> </tr> </thead> <tbody> <tr v-for="book in books" v-bind:key="book.id"> <td>{{ book.title }}</td> <td>{{ book.author }}</td> </tr> </tbody> </table> <!--/////////////////////////////////--> </div> </template> <script> export default { name: 'HelloWorld', data () { return { msg: 'Welcome to Your Vue.js App', //追加 books: [ { id: 1, title: '坊っちゃん', author: '夏目漱石' }, { id: 2, title: '人間失格', author: '太宰治' }, { id: 3, title: 'ノルウェイの森', author: '村上春樹' } ] } } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> </style>booksプロパティとしての配列からbookという変数名に置き換えたオブジェクトを取得しています。v-forディレクティブは一般的に下記のような記述をします。
v-for="変数名 in 配列やオブジェクト"また、v-forディレクティブを利用する場合、v-bindディレクティブで一意の(ユニークな)keyプロパティを設定する必要があります。
最低、今までのことが理解できればVue.jsの基本構文は大丈夫だと思います。
あとの描き方なんかは公式サイト参照でお願いします。コンポーネント
初めて触る人に対して、一番理解に苦しむと思いますが、(作者は苦労しました。)
Vue.jsに限らず、Reactなどのjavascriptフレームワークでは必須の考え方なので理解しておきましょう。
マップアプリを作りながら解説していきます。
その前に、以下のコマンドを入力してください。npm install --save materialize-css@1.0.0-rc.1次に「main.js」に以下を記述し、vue-routerの設定をコメントアウトで潰してください。
js;main.jsimport Vue from 'vue' import App from './App' //import router from './router' <!--//////////////追加///////////////////////--> import 'materialize-css' import 'materialize-css/dist/css/materialize.min.css' <!--/////////////////////////////////////--> Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', //router, components: { App }, template: '<App/>' })その次に、「index.html」に以下を記述しGoogleMapApiを読み込んでください。
これで準備が整いました。
では、「components/Map.vue」をつくります。
作れたら、App.vue(親コンポーネント)に以下のコードを記述してください。App.vue<template> <div id="app"> <nav class="blue navbar"> <div class="nav-wrapper"> <a href="#" class="brand-logo center"><i class="material-icons left" >ヘッダーだよん!!</i></a> </div> </nav> <!-- Mapをレンダリング --> <Map v-bind:center="center"></Map> </div> </template> <script> // Mapを読込 import Map from './components/Map.vue'; export default { name: 'app', components: { Map }, data () { return { // 地図のセンター位置(東京駅) //{ lat: 緯度, lng: 経度 } center: { lat: 35.681298, lng: 139.7640529 } } }, } </script> <style> </style>ここで重要なのは
import Map from './components/Map.vue';と
components: { Map }です。以下で子コンポーネント(Map)を読み込んでいます。
そして<Map v-bind:center="center"></Map>
でレンダリングしています。※v-bind, :value
親 -> 子にデータを受け渡す際、親側で記述します。親からデータを渡すための窓口のイメージです。v-bind:プロパティ名もしくは:プロパティ名という書き方をします。プロパティ名は、後述する子要素のpropsの変数名のことです。
次に「components/Map.vue」です。(子コンポネート)
Map.vue<template> <div class="main-area"> <div class="main-area-inner"> <div id="map" ref="map"></div> </div> </div> </template> <script> export default { // 親のコンポーネントからpropsでdataを受け取る props: ['center'], data () { return { // Map Objectを保存する map: null, // Markerオブジェクトを配列で保存する markers: [], } }, mounted () { // Mapの初期処理を実行する const map = this.$refs.map; this.map = new window.google.maps.Map(map, { center: this.center, zoom: 17 }); // マーカーを画面上に置く this.markers = []; const marker = new window.google.maps.Marker({ position: { lat: this.center.lat, lng: this.center.lng }, map: this.map, animation: google.maps.Animation.DROP }); } } </script> <style> </style>ここで重要なのは
props
props
は、親 -> 子にデータを受け渡す際、子側で記述します。子がデータを受け取るための窓口のイメージです。非同期通信と$emit
まず、今回非同期通信は
axios
を使います。インストール
$npm install --save axiosmain.jsにてaxiosを取り込みます。これですべてのコンポーネントにおいて「this.$axios」でaxiosが利用できるようになります。
main.jsimport Vue from 'vue' import App from './App' //import router from './router' import 'materialize-css'; import 'materialize-css/dist/css/materialize.min.css' import axios from 'axios' //追加 Vue.config.productionTip = false Vue.prototype.$axios = axios //追加 /* eslint-disable no-new */ new Vue({ el: '#app', //router, components: { App }, template: '<App/>' })※任意のコンポーネントの中にてaxiosをインポートすることも可能です。その場合は「this.$axios」ではなく「axios」となります。
さて先ほど作ったマップアプリを使いながら
axios
、$emit
の解説をしていきましょう。
※今回、楽天トラベルAPIを使います。前回、行ったように「components/Card.vue」を作成し、コンポーネントを読みこんでください。
vue;Card.vue<template> <div class="card"> <div class="row"> <div class="col s3 image"> <img v-bind:src="this.rankings.hotelImageUrl" class="responsive-img" /> </div> <div class="col s9 content"> <i class="left">ランキング:<font color="red">{{ this.rankings.rank }}</font></i> <i class="left">ホテル名:{{ this.rankings.hotelName }}</i> <i class="left">都道府県:{{ this.rankings.middleClassName }}</i> </div> </div> </div> </template> <script> export default { data () { return { rankings: "" } }, mounted () { // 楽天APIからデータを取得する this.searchPlans(); }, methods: { // 楽天APIに接続して、データを取得する searchPlans () { this.$axios.get(" 楽天APIからのURL ",{ }).then((resp) => { this.rankings = resp.data.Rankings[0].Ranking.hotels[0].hotel this.$emit("ranking", resp.data.Rankings[0].Ranking); }).catch(err => { console.log(err); }); } } } </script> <style scoped> </style>App.vue<template> <Card v-on:ranking="getRanking"></Card> </template> <script> data () { return { rankings: [], } }, methods: { getRanking(ranking) { this.rankings = ranking; }, } } </script> <style> </style>ここで重要となってくるのが
axios(非同期通信)の書き方
とthis.$emit
の使い方です。
まずaxios
の書き方は//GET axios.get('URL', { params: { キー: 値 } }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }); //※これでも可 axios.get('/URL?キー: 値') .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }); //POST axios.post('URL', { キー: 値, キー: 値 }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });です。
続いて
this.$emit
の説明をします。
簡単にいうと、子 -> 親にデータを受け渡しするときに使います。
例えば、子コンポネートの変数hoge
の値を親コンポーネントに渡したい時は子コンポートthis.$emit("イベント名", hoge);として、親コンポートで
親コンポーネント<Template> <子コンポーネント v-on:イベント名:"関数"></子コンポーネント> </Template> <script> data () { return { hoge: "" } }, methods: { 関数名 (hoge) { this.hoge = hoge; }, } </script>このように使うと子コンポーネントの値が親で使えます。
まとめ
最後にややこしいけどめっちゃ重要なことをまとめておきます。
Vueの親子コンポーネント間でデータをやりとりするときの鉄則は Events Up, Props Down です。
すなわち、
- 親 -> 子へデータを受け渡す際には
v-bind
とprops
を使う(props down)- 子 -> 親へデータを受け渡す際には
$emit
とv-on
を使う(events up)最後に
長くなりましたが今回はこの辺で、最後まで読んでいただきありがとうございました。
もし、間違い等、アドバイス、ご指摘等有れば教えていただけたら幸いです。次回はVue-routerの使い方についてです。
今後はVuex,Nuxt.jsについて触れていきたいと思います。
- 投稿日:2019-07-28T15:16:09+09:00
日付をX軸としたamChartsの折れ線グラフで初期表示(ズーム後)を直近の1週間表示に変更する
概要
amChartsのDate Based Dataを使用しグラフを作成します。
こちらのグラフは起動時に全体を表示し、”最終日からある割合”の日付をズームして表示します。(かっこいい)
Demo source
この”最終日からある割合”を”直近の1週間”に変更していきたいとおもいます。(上のDemo sourceのgifでは直近の8日にプロットされた値が表示されてますね)作成する前提
amChartsがCDNで読み込めている
参考文献amcharts 4 Demos を使ってグラフを作成(amChartsの導入方法が記載されています)
使用するグラフの種類:Date Based Data
使用するモデル:Railsにて不連続な間隔(日付など)で投稿された値をamChartsを使って折れ線グラフを作成する。において作成した体重管理アプリをモデルに作成します。コントローラーファイルなどの記述はこちらを参考にしてください。編集するファイル
・ビューファイル
ビューファイル(Demo source)
まずは、Demo sourceがどういう構造になっているか見てみましょう。
demo.erb<!-- Styles --> <style> #chartdiv { width: 100%; height: 500px; } </style> <!-- Resources --> <script src="https://www.amcharts.com/lib/4/core.js"></script> <script src="https://www.amcharts.com/lib/4/charts.js"></script> <script src="https://www.amcharts.com/lib/4/themes/animated.js"></script> <!-- Chart code --> <script> am4core.ready(function() { // Themes begin am4core.useTheme(am4themes_animated); // Themes end // Create chart instance var chart = am4core.create("chartdiv", am4charts.XYChart); // Add data chart.data = [{ "date": "2012-07-27", "value": 13 }, { "date": "2012-07-28", "value": 11 }, { "date": "2012-07-29", "value": 15 }, { //~省略~ "date": "2013-01-30", "value": 81 }]; // Set input format for the dates chart.dateFormatter.inputDateFormat = "yyyy-MM-dd"; // Create axes var dateAxis = chart.xAxes.push(new am4charts.DateAxis()); var valueAxis = chart.yAxes.push(new am4charts.ValueAxis()); // Create series var series = chart.series.push(new am4charts.LineSeries()); series.dataFields.valueY = "value"; series.dataFields.dateX = "date"; series.tooltipText = "{value}" series.strokeWidth = 2; series.minBulletDistance = 15; // Drop-shaped tooltips series.tooltip.background.cornerRadius = 20; series.tooltip.background.strokeOpacity = 0; series.tooltip.pointerOrientation = "vertical"; series.tooltip.label.minWidth = 40; series.tooltip.label.minHeight = 40; series.tooltip.label.textAlign = "middle"; series.tooltip.label.textValign = "middle"; // Make bullets grow on hover var bullet = series.bullets.push(new am4charts.CircleBullet()); bullet.circle.strokeWidth = 2; bullet.circle.radius = 4; bullet.circle.fill = am4core.color("#fff"); var bullethover = bullet.states.create("hover"); bullethover.properties.scale = 1.3; // Make a panning cursor chart.cursor = new am4charts.XYCursor(); chart.cursor.behavior = "panXY"; chart.cursor.xAxis = dateAxis; chart.cursor.snapToSeries = series; // Create vertical scrollbar and place it before the value axis chart.scrollbarY = new am4core.Scrollbar(); chart.scrollbarY.parent = chart.leftAxesContainer; chart.scrollbarY.toBack(); // Create a horizontal scrollbar with previe and place it underneath the date axis chart.scrollbarX = new am4charts.XYChartScrollbar(); chart.scrollbarX.series.push(series); chart.scrollbarX.parent = chart.bottomAxesContainer; chart.events.on("ready", function () { dateAxis.zoom({start:0.79, end:1}); }); }); // end am4core.ready() </script> <!-- HTML --> <div id="chartdiv"></div>なんだか難しそうで、なにを書いているかパッとみたただけではわかりませんね。
ただ、データが記載されているところはわかるのではないでしょうか?demo.erb<script> // Add data chart.data = [{ "date": "2012-07-27", "value": 13 }, { "date": "2012-07-28", "value": 11 }, { "date": "2012-07-29", "value": 15 }, { //~省略~ "date": "2013-01-30", "value": 81 }]; </script>↑ここです。
この部分を体重管理アプリのモデルに合わせていきます。ビューファイル(本アプリ)
まずは、コントローラーから変数をもってきて算出までします。
index.erb<script> //JSON形式で値を渡す const weights = <%== JSON.dump(@weights) %>; const dates = <%== JSON.dump(@dates) %>; //表示期間を計算 var firstDate = new Date(dates[0]) //一番最初 var lastDate = new Date(dates.slice(-1)[0]) //一番最後 var termDate= (lastDate - firstDate)/ 1000 / 60 / 60 / 24 + 1 //表示する期間 </script>細かいところはRailsにて不連続な間隔(日付など)で投稿された値をamChartsを使って折れ線グラフを作成する。を参照してください。(本記事をそのままコピペしても動きません)
さて、Demo sourceのchart.dataの値を下記のように変えてください。index.erb<script> // Add data chart.data = []; for (var j =0; j< weights.length; j++){ for (var i = 0; i < termDate; i++) { var newDate = new Date(firstDate) newDate.setDate(newDate.getDate() + i); //初日からi日分たす if ((new Date(dates[j])) - (newDate)==0){ weight =weights[j] chart.data.push({ date: newDate, weight: weight }); } } } </script>valueはRailsにて不連続な間隔(日付など)で投稿された値をamChartsを使って折れ線グラフを作成する。のときと同様にweightに変更してます。weightの変更箇所はその他にもあるのですべて置換してあげてください。
初期表示(ズーム後)を直近の1週間表示にする
本記事の本題です。
まずはDemo sourceからズームっぽい記述を探してみましょう。後方から9行目付近をみてください。demo.erb<script> chart.events.on("ready", function () { dateAxis.zoom({start:0.79, end:1}); }); </script>{start:0.79, end:1}の数値を試しに{start:0, end:1}かえてみてください。
変えるときは、【小ネタ】amChartsのDemo sourceをVScodeなどのエディタにコピペせず、ブラウザ上でプロパティを変更し表示を確認する方法を使うと便利です。
zoomされず、全体が表示されました。
ということは、startの数値を変更すれば直近の1週間が表示できそうです。全体の日数はtermDateという変数で取得できてるので、
1週間は全体の日数のどれくらいの割合か出してみます。
例:termDateが70日の場合
式: 7/termDate=0.1
0.1という数値をstartに入れては駄目です。なぜならstartは0に近いほど、小さい値(日付)を取得します。
なので0.1を入れると0.1≦x≦1の範囲になり(endが1の場合)、一番古い日付から1週間後から最新の日付を範囲としてしまいます。
では、どうするかというと1から引いてあげれば良いです。{start:1-7/termDate, end:1}としてあげます。
こうすることで0.9≦x≦1となり、直近の1週間の割合がだせます。
最後にビューファイルの全体を記載します。index.erb<!-- Styles --> <style> #chartdiv { width: 100%; height: 500px; } </style> <!-- Resources --> <script src="https://www.amcharts.com/lib/4/core.js"></script> <script src="https://www.amcharts.com/lib/4/charts.js"></script> <script src="https://www.amcharts.com/lib/4/themes/kelly.js"></script> <script src="https://www.amcharts.com/lib/4/themes/animated.js"></script> <!-- Chart code --> <script> am4core.ready(function() { // Themes begin am4core.useTheme(am4themes_kelly); am4core.useTheme(am4themes_animated); // Themes end // Create chart instance var chart = am4core.create("chartdiv", am4charts.XYChart); const weights = <%== JSON.dump(@weights) %>; const dates = <%== JSON.dump(@dates) %>; var firstDate = new Date(dates[0]) var lastDate = new Date(dates.slice(-1)[0]) var termDate= (lastDate - firstDate)/ 1000 / 60 / 60 / 24 + 1 // Add data chart.data = []; for (var j =0; j< weights.length; j++){ for (var i = 0; i < termDate; i++) { var newDate = new Date(firstDate) newDate.setDate(newDate.getDate() + i); //初日からi日分たす if ((new Date(dates[j])) - (newDate)==0){ weight =weights[j] chart.data.push({ date: newDate, weight: weight }); } } } // Set input format for the dates chart.dateFormatter.inputDateFormat = "yyyy-MM-dd"; // Create axes var dateAxis = chart.xAxes.push(new am4charts.DateAxis()); var valueAxis = chart.yAxes.push(new am4charts.ValueAxis()); // Create series var series = chart.series.push(new am4charts.LineSeries()); series.dataFields.valueY = "weight"; series.dataFields.dateX = "date"; series.tooltipText = "{weight}" series.strokeWidth = 2; series.minBulletDistance = 15; // Drop-shaped tooltips series.tooltip.background.cornerRadius = 20; series.tooltip.background.strokeOpacity = 0; series.tooltip.pointerOrientation = "vertical"; series.tooltip.label.minWidth = 40; series.tooltip.label.minHeight = 40; series.tooltip.label.textAlign = "middle"; series.tooltip.label.textValign = "middle"; // Make bullets grow on hover var bullet = series.bullets.push(new am4charts.CircleBullet()); bullet.circle.strokeWidth = 2; bullet.circle.radius = 4; bullet.circle.fill = am4core.color("#fff"); var bullethover = bullet.states.create("hover"); bullethover.properties.scale = 1.3; // Make a panning cursor chart.cursor = new am4charts.XYCursor(); chart.cursor.behavior = "panXY"; chart.cursor.xAxis = dateAxis; chart.cursor.snapToSeries = series; // Create vertical scrollbar and place it before the value axis chart.scrollbarY = new am4core.Scrollbar(); chart.scrollbarY.parent = chart.leftAxesContainer; chart.scrollbarY.toBack(); // Create a horizontal scrollbar with previe and place it underneath the date axis chart.scrollbarX = new am4charts.XYChartScrollbar(); chart.scrollbarX.series.push(series); chart.scrollbarX.parent = chart.bottomAxesContainer; chart.events.on("ready", function () { dateAxis.zoom({start:1-7/termDate, end:1}); }); }); // end am4core.ready() </script> <!-- HTML --> <div id="chartdiv"></div>完成!!
参考文献
Railsにて不連続な間隔(日付など)で投稿された値をamChartsを使って折れ線グラフを作成する。
最後に
この記事を書いた目的
・自分なりに工夫した点をアウトプットして、理解を深める。
・あわよくば有識者にフィードバックをもらいたい。
・私と同じ初学者からも奇譚のない意見をもらいたい。(自分だったらどうこうする的な)筆者について
TECH::EXPERTにて4月27日より52期夜間・休日コースでruby/railsを学習している未経験エンジニアです。
ご不備等ありましたら、ご指摘ください。ちなみに本記事が初投稿になります。
言わずもがなかもしれませんが、趣味はボディメイク・筋トレでございます。
余談ですが、120kg⇨66kgまで減量して大会出場した経験があり
ダイエットについての質問はなんでも答えられるかと思いますひとこと
最後までご覧いただきまして、ありがとうございました。
もし気に入っていただけたら、イイね・ストック・フォローご自由に!
- 投稿日:2019-07-28T15:03:08+09:00
Rails + Cropper.jsで画像トリミング機能を実装する
概要
Cropper.jsを用いてRailsでトリミング機能付き画像アップロード機能を実装してみます。
ソースコードは以下に置いてます。
https://github.com/Tak-Iwamoto/jquery-cropper-rails環境
Rails 5.2.2
Bootstrap 4.3.1
active storage
mini_magick 4.9.4active storage
active_storageを利用するために以下のコマンドを実行してください。
rails active_storage:install
rails db:migrate
Cropper.jsの読み込み
https://github.com/fengyuanchen/cropperjs
上のgithubからcropper.min.jsとcropper.min.cssを取得し、Railsの中に配置しましょう。サードパーティライブラリはvendorディレクトリの中に入れることが推奨されており、vendorの中にassets/javascriptsとassets/stylesheetsディレクトリを作成し、それぞれcropper.min.jsとcropper.min.cssを配置します。
https://github.com/fengyuanchen/jquery-cropper
また、JQueryを用いるために上のgithubからjquery-cropper.min.jsを取得し、同様にRailsの中に配置します。最後にapplication.jsとapplication.scssに以下のように編集します。
application.js
//= require rails-ujs
//= require jquery
//= require jquery_ujs
//= require cropper.min.js
//= require jquery-cropper.min.js
//= require activestorage
//= require turbolinks
//= require_tree .
//= require popper
//= require bootstrap-sprockets
application.scss
*= require_tree .
*= require cropper.min.css
*= require_self
*/
これでCropper.jsが使えるようになりました。modelとcontrollerを作成
デモのためのuserモデル、コントローラーを作成します。
user.rbclass User < ApplicationRecord attr_accessor :x, :y, :width, :height has_one_attached :image endusers_controller.rbclass UsersController < ApplicationController def new @user = User.new end def create @user = User.new(user_params) @user.save session[:crop_x] = user_params[:x] session[:crop_y] = user_params[:y] session[:crop_width] = user_params[:width] session[:crop_height] = user_params[:height] redirect_to user_path @user end def show @user = User.find(params[:id]) end private def user_params params.require(:user).permit(:image, :x, :y, :width, :height) end endCropper.jsでトリミングした結果のパラメータをsessionで保持し、のちに画像を表示するときに用いていますが、このやり方は筋が悪い気がしています... active_storageで加工した画像を保存する方法が分からなかったため、この方法で行っていますが、おそらく他にいい方法があると思います。
画像アップロードフォーム
トリミング画像をアップロードするためのフォームを作成します。
new.html.erb<div class="container"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <div class="preview" style='overflow:hidden'> <div id="beforeUpload"> <%= icon("fas fa-4x fw","file-image")%> </div> <img src="" id="croppedImage"> </div> <%= form_with model: @user, url: users_path do |f| %> <%= f.file_field :image%> <%= f.hidden_field :x, id:"dataX"%> <%= f.hidden_field :y, id:"dataY"%> <%= f.hidden_field :width, id:"dataWidth"%> <%= f.hidden_field :height, id:"dataHeight"%> <%= f.submit "button", id:"btnUpload", class: 'btn btn-success'%> <% end %> <div class="modal fade" id="cropModal" role="dialog"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-body"> <div class="img-container"> <img src="" id="imageModal" alt="picture"> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary" id="btn-save" data-dismiss="modal">Save</button> </div> </div> </div> </div> </div> </div> </div>
<div class="preview" style='overflow:hidden'>
でトリミング後の画像を表示するフィールドを作成しています。。この時、style='overflow:hidden'を指定しないと上手く表示されない場合があります。
トリミング後の画像は<img src="" id="croppedImage">
で表示します。
form_with
でフォームを作成し、トリミングした結果のパラメータはf.hidden_field
でcontrollerに渡して、以下のshow.html.erbで画像を表示する際に用います。
<div class="modal fade" id="cropModal" role="dialog">
以下ではトリミング操作を行うためのmodalをbootstrapによって表示させています。トリミングした画像を表示するために以下のhtmlを作成します。
show.html.erb
<%= image_tag @user.image.variant(crop: "#{session[:crop_width]}x#{session[:crop_height]}+#{session[:crop_x]}+#{session[:crop_y]}") %>
では、トリミング処理を行うために以下のjavascriptファイルを作成します。crop_image.js$(function () { let $image = $('#imageModal'), $img_field = $('#user_image'), $croppedImage = $('#croppedImage'), $cropModal = $('#cropModal'), $beforeUpload = $('#beforeUpload'), $button = $('#btn-save'), $dataX = $('#dataX'), $dataY = $('#dataY'), $dataWidth = $('#dataWidth'), $dataHeight = $('#dataHeight'); let options = { dragmode: 'crop', aspectRatio: 1/1, restore: false, guides: false, center: false, highlight: true, cropBoxMovable: true, cropBoxResizable: true, modal: true, crop: (e) => { $dataX.val(Math.round(e.detail.x)); $dataY.val(Math.round(e.detail.y)); $dataWidth.val(Math.round(e.detail.width)); $dataHeight.val(Math.round(e.detail.height)); } }; // when file upload $img_field.change((e) => { $image.cropper('destroy').removeAttr('src'); file = e.target.files[0]; reader = new FileReader(); if (file.type.indexOf('image') < 0) { window.alert("画像を選択してください"); return ; } reader.onload = ((e) => { $image.attr('src',""); $image.attr('src', e.target.result); $cropModal.modal('show'); $cropModal.on('shown.bs.modal', () => { $image.cropper(options); }); }); reader.readAsDataURL(file); }) // onclick save button $button.click(() => { imgCropping(); }); // modalを閉じたとき、cropper要素を初期化 $cropModal.on('hidden.bs.modal',function() { $image.cropper('destroy').removeAttr('src'); let $cropperContainer = $('.cropper-container'); $cropperContainer.remove(); }); function imgCropping() { if (!croppable) { alert('トリミングする画像が用意されていません') return false; } $beforeUpload.hide(); let croppedData = $image.cropper('getCroppedCanvas').toDataURL(); $croppedImage.attr('src', croppedData); $cropModal.modal('hide'); } });$img_fieldでidがuser_imageのDOMを指定していますが、html側にはそのようなidはありません。Railsはform_withでfile_fieldを作成すると、自動的に
<img id="モデル名_フィールド名">
のタグが作成されるため、注意が必要です。自分もこれが原因でハマってしまいました...optionsではどのように画像をトリミングするか設定しています。
$img_field.change
以下で画像がアップロードされた時にトリミングが開始されるように記述しています。
modalが表示された際、$image.cropper(options)
で先ほど設定したoptionsでcropperインスタンスを作成し、トリミングを行います。また、最初に$image.cropper('destroy')
でcropperインスタンスを初期化していることに注意してください。この処理を行わないと、前にトリミングした画像が残ったままとなり画像が複数表示されたりといった不具合が起こります。また、cropperインスタンスが作成された時点で、
<div class="cropper-container cropper-bg"></div>
のDOMが作成されます。modalを閉じた時点でこのDOMを削除しておかないと前回トリミングした画像が残ったままになっていることがあります。そのため$cropModal.on('hidden.bs.modal'
以下で削除する操作を行っています。以上です。
- 投稿日:2019-07-28T13:47:18+09:00
TypeError: Class constructor cannot be invoked without 'new'
const class
エラーの出たJavaScriptconst A = class { constructor(_a) { this.a = _a; } } const B = class extends A { constructor(_b) { super(_b); } } (() => { var b = new B("test"); console.log(b); })();とあるJavaScriptを試したところ「TypeError: Class constructor cannot be invoked without 'new'」が出た。
エラーの原因は何?
普通のclass
エラーの出ないJavaScriptclass A { constructor(_a) { this.a = _a; } } class B extends A { constructor(_b) { super(_b); } } (() => { var b = new B("test"); console.log(b); })();普通によく見るclassならエラーが出ない。
判明した原因とは
原因
const B = class extends A {}()
後ろの無名関数の即時関数とくっついて
new A()
し損ねたと判定された。constの後ろに「;」をつけてやれば解決。
提案
const class
を使わないようにしよう。
知らないだけかもだがあまり見ないと思う「;」を省略しないようにしよう。
ブックマークレットが動かなくなるし今回のようなこともある。自戒と反省を込めて
- 投稿日:2019-07-28T13:41:36+09:00
【小ネタ】amChartsのDemo sourceをVScodeなどのエディタにコピペせず、ブラウザ上でプロパティを変更し表示を確認する方法
概要
amChartsでグラフを作成した際に、初期値やzoomの比率などのプロパティを変更して
自分のイメージにあうか検討した際に便利な機能を見つけたので共有します。Open in...
最後に
この記事を書いた目的
・自分なりに工夫した点をアウトプットして、理解を深める。
・あわよくば有識者にフィードバックをもらいたい。
・私と同じ初学者からも奇譚のない意見をもらいたい。(自分だったらどうこうする的な)筆者について
TECH::EXPERTにて4月27日より52期夜間・休日コースでruby/railsを学習している未経験エンジニアです。
ご不備等ありましたら、ご指摘ください。ちなみに本記事が初投稿になります。
言わずもがなかもしれませんが、趣味はボディメイク・筋トレでございます。
余談ですが、120kg⇨66kgまで減量して大会出場した経験があり
ダイエットについての質問はなんでも答えられるかと思いますひとこと
最後までご覧いただきまして、ありがとうございました。
もし気に入っていただけたら、イイね・ストック・フォローご自由に!
- 投稿日:2019-07-28T12:34:10+09:00
Railsにて不連続な間隔(日付など)で投稿された値をamChartsを使って折れ線グラフを作成する。
概要
TECH::EXPERTのカリキュラムでオリジナルのミニアプリを作成する機会があり、
同期で学習中の方がamChartsを使っていてかっこいいなぁ〜と思い、自分なりに調べてみました。
いくつか工夫したところがあったので共有します。
体重管理アプリのようなものを想定してます。
体重を計る日とそうでない日がマチマチにある場合のグラフを作成します。
例:
7月1日 70kg
7月2日 71kg
7月4日 73kg
7月7日 68kg
7月8日 69kg
7月9日 67kg
=>7月3日と7月6日の記録がない作成する前提
amChartsがCDNで読み込めている
参考文献amcharts 4 Demos を使ってグラフを作成(amChartsの導入方法が記載されています)
使用するグラフの種類:Line Chart with Scroll and Zoom
diaryモデルにweightカラムがある
diaries_controler.rbにindexアクションが用意されてある編集するファイル
・コントローラーファイル
・ビューファイルコントローラーファイル
diaries_controller.rbclass DiariesController < ApplicationController def index @diaries=Diary.all.order('created_at ASC') @weights=@diaries.map(&:weight) @dates=@diaries.map{|diary| diary.created_at.strftime('%Y/%m/%d') } end end今回は、weigthカラムの値ビューファイルに渡します。
配列で渡したいので上記のようにmapメソッドを使いました。
eachでは下記のように記載します。diaries_controller.rb##eachの場合 @weights=[] @diaries.each do |diary| @weigth << diary.weight end"&:"の記法は処理が1つの場合しか使えないので、@datesはデフォルトの記法です。
ビューファイル
index.erb<!-- Styles --> <style> #chartdiv { width: 100%; height: 500px; } </style> <!-- Resources --> <script src="https://www.amcharts.com/lib/4/core.js"></script> <script src="https://www.amcharts.com/lib/4/charts.js"></script> <script src="https://www.amcharts.com/lib/4/themes/kelly.js"></script> <script src="https://www.amcharts.com/lib/4/themes/animated.js"></script> <!-- Chart code --> <script> am4core.ready(function() { // Themes begin am4core.useTheme(am4themes_kelly); am4core.useTheme(am4themes_animated); // Themes end // Create chart instance var chart = am4core.create("chartdiv", am4charts.XYChart); //JSON形式で値を渡す const weights = <%== JSON.dump(@weights) %>; const dates = <%== JSON.dump(@dates) %>; //表示期間を計算 var firstDate = new Date(dates[0]) var lastDate = new Date(dates.slice(-1)[0]) var termDate= (lastDate - firstDate)/ 1000 / 60 / 60 / 24 + 1 // Add data chart.data = generateChartData(); // Create axes var dateAxis = chart.xAxes.push(new am4charts.DateAxis()); dateAxis.renderer.minGridDistance = 50; var valueAxis = chart.yAxes.push(new am4charts.ValueAxis()); // Create series var series = chart.series.push(new am4charts.LineSeries()); series.dataFields.valueY = "weight"; series.dataFields.dateX = "date"; series.strokeWidth = 2; series.minBulletDistance = 10; series.tooltipText = "{valueY}"; series.tooltip.pointerOrientation = "vertical"; series.tooltip.background.cornerRadius = 20; series.tooltip.background.fillOpacity = 0.5; series.tooltip.label.padding(12,12,12,12) // Add scrollbar chart.scrollbarX = new am4charts.XYChartScrollbar(); chart.scrollbarX.series.push(series); // Add cursor chart.cursor = new am4charts.XYCursor(); chart.cursor.xAxis = dateAxis; chart.cursor.snapToSeries = series; //不連続な間隔(日付)で投稿された値を表示する function generateChartData() { var chartData = []; for (var j =0; j< weights.length; j++){ for (var i = 0; i < termDate; i++) { var newDate = new Date(firstDate) newDate.setDate(newDate.getDate() + i); //初日からi日分たす if ((new Date(dates[j])) - (newDate)==0){ weight =weights[j] chartData.push({ date: newDate, weight: weight }); } } } return chartData; } }); // end am4core.ready() </script> <div id="chartdiv"></div>基本はDemo sourceをコピペしてください。
styleがビューファイルにありますが、本記事の趣旨とズレるのでそのままビューファイルに記載してます。
また、Demo sourceの"visits"は"weight"としてます。(単数形です..Demo sourceはなぜ複数形にしてるんだろう...?)
下記コピペ部分とは違うところの説明です。1.コントローラーの変数をJavascriptで使えるようにする
index.erb<script> //JSON形式で値を渡す const weights = <%== JSON.dump(@weights) %>; const dates = <%== JSON.dump(@dates) %>; </script>JSONでコントローラーの値をわたします。
こちらはrailsのcontrollerからjavascriptに対して変数を渡すを参考にさせていただきました。2.表示する期間を算出する
index.erb<script> //表示期間を計算 var firstDate = new Date(dates[0]) //一番最初 var lastDate = new Date(dates.slice(-1)[0]) //一番最後 var termDate= (lastDate - firstDate)/ 1000 / 60 / 60 / 24 + 1 //表示する期間 </script>コントローラー側で配列にしたとき、
diaries_controller.rb@diaries=Diary.all.order('created_at ASC')としてるので、昇順(日付が古い順)になってます。なので、容易に一番最初を最後を取得できますね。
termDateは期間を計算しました。日付の場合そのまま引き算をできないので、/ 1000 / 60 / 60 / 24としてます。
+1は、たとえば 3日から5日までの期間を出すために"5-3=2"では1日たりないので+1としてます。3.不連続な間隔(日付)で投稿された値を表示する
Demo sourceだと下記のように表現されているところです。Demo.erb<script> function generateChartData() { var chartData = []; var firstDate = new Date(); firstDate.setDate(firstDate.getDate() - 1000); var visits = 1200; for (var i = 0; i < 500; i++) { // we create date objects here. In your data, you can have date strings // and then set format of your dates using chart.dataDateFormat property, // however when possible, use date objects, as this will speed up chart rendering. var newDate = new Date(firstDate); newDate.setDate(newDate.getDate() + i); visits += Math.round((Math.random()<0.5?1:-1)*Math.random()*10); chartData.push({ date: newDate, visits: visits }); } return chartData; } </script>簡易的に説明するならば、初期値1200であとは、ランダムで数値をあれやこれやとかえて、
i=1から500まで表現してます。
これはこれで学習するところがあっておもしろかったです。
本記事ではコントローラーから取得した値を下記のようにしてます。index.erb<script> //不連続な間隔(日付)で投稿された値を表示する function generateChartData() { var chartData = []; for (var j =0; j< weights.length; j++){ for (var i = 0; i < termDate; i++) { var newDate = new Date(firstDate) newDate.setDate(newDate.getDate() + i); //初日からi日分たす if ((new Date(dates[j])) - (newDate)==0){ weight =weights[j] chartData.push({ date: newDate, weight: weight }); } } } return chartData; } </script>[1]変数をinteger型の変数を2つ用意してます(i,j)
[2]jはweights.lengthの数まで繰り返します。つまり体重のレコードの数までですね。
[3]iをjにネストしてます。こちらはtermDateの数だけ繰り返します。つまり、”2.表示する期間を算出する”で計算してあげた期間分繰り返します。
[4]表示するのは、あくまで、体重のレコードが存在する日付のみなので、jの日付(体重記録がある日付)とiの日付(newDate)の差が0のときのみ、日付と体重をハッシュの形にし、chartDataに入れてあげます。完成!!
gifでは"Jul 11"と"Jul 12"に記録がありません。
参考文献
amcharts 4 Demos を使ってグラフを作成
railsのcontrollerからjavascriptに対して変数を渡す最後に
この記事を書いた目的
・自分なりに工夫した点をアウトプットして、理解を深める。
・あわよくば有識者にフィードバックをもらいたい。
・私と同じ初学者からも奇譚のない意見をもらいたい。(自分だったらどうこうする的な)筆者について
TECH::EXPERTにて4月27日より52期夜間・休日コースでruby/railsを学習している未経験エンジニアです。
ご不備等ありましたら、ご指摘ください。ちなみに本記事が初投稿になります。
言わずもがなかもしれませんが、趣味はボディメイク・筋トレでございます。
余談ですが、120kg⇨66kgまで減量して大会出場した経験があり
ダイエットについての質問はなんでも答えられるかと思いますひとこと
最後までご覧いただきまして、ありがとうございました。
もし気に入っていただけたら、イイね・ストック・フォローご自由に!
- 投稿日:2019-07-28T11:53:36+09:00
JSの参照渡し、値渡し。PHPの参照渡し、値渡し。
JSの配列やobjectは参照渡し。
stirngやintegerは値渡し。PHPは通常、値渡し。
&をつけて&$hogeとすると参照渡し。JSが参照渡しになることを知らなくて、なんでだー、Vueのせいかとかいろいろやった挙げ句、バグの原因がこれだったので、あーとなった。
参考
JavaScript の配列やオブジェクトは参照渡しになる…バグを生む落とし穴
http://neos21.hatenablog.com/entry/2018/05/20/080000
- 投稿日:2019-07-28T11:40:54+09:00
JSで文字列(数値、アルファベット混在を)並び替えたい
JSで文字列(数値、アルファベット混在を)並び替える方法のメモです。
配列ならソートは簡単にできそうですが、文字列だと一発でできなくて、こうやるとできます。
もっとスマートな方法がありそうですが、見つかりませんでした。let string = "1ab234"; string.split('').sort(function (a, b) { if (a > b) { return 1; } if (a < b) { return -1; } return 0; }).join(',').replace(/,/g, ''); console.log(string) => "1234ab"参考サイト
goma
JS:配列の正しいソート方法
- 投稿日:2019-07-28T11:10:29+09:00
v-forで回している選択肢に、動的に色をつけたいときに簡単に書く
v-forで回している選択肢に、動的に色をつけたいときに簡単に書く。
v-for="(hoge, key) in hoges" :class="{'hoge-color': key === current_key}" <script> data() { return { current_key: 0, // 初期表示のとき } }
'hoge-color': key === current_key
ここのロジックをメソッド化してもいいけど、めんどいのでこう書いた。
- 投稿日:2019-07-28T11:01:49+09:00
can not set reatcive property on undefined,Vueで複数選択可のチェックボックスを実装するときのエラー
Vueで次のエラーを解消するための一つの方法。
ノートのメモなので動かしてないので、違ってたらすみません。Vueで複数選択可のチェックボックスを実装するときのエラーです。
結論を言うと、dataにあらかじめ定義してないよというエラーみたい。
dataにあらかじめ定義してなくてもよい場合と、定義してないといけない場合があるみたい。can not set reatcive property on undefined, null, or primitive.value<label v-for="(hoge, key) in hoges" :key = "key" :for = "key" > <input type="checkbox" v-model="hoges.hoge_id" :value="hoge.id" id="index" > {{ hoge.names }} <script> data: ~ 中略 // 定義しておく hoges: { hoge_id: [] }
- 投稿日:2019-07-28T09:20:07+09:00
【Programming News】Qiitaまとめ記事 Weekly July 4rd week, 2019 Vol.2
筆者が昨日2019/7/21(日)~7/27(土)に気になったQiitaの記事のまとめのまとめを作成しました。日々のまとめ記事のWeekly版です。
皆様が興味のある言語や思いもよらぬハック方法をこの中から見つけられたら幸いです。
Java
- Tips
- Spring
- Spring Boot
- Performance
- データ分析
Python
- Beginner
- Tips
- おじさん、Pythonでスクリプトを作成したい
- Pythonに組み込み関数「メガンテ」を追加する
- anaconda環境を壊さないようにpipを使う
- CSV形式をJSON形式に変換するPythonプログラム
- QRコード作成 pythonライブラリ python-qrcode を読む
- .NET (on Windows)からpythonを呼び出す
- Pythonの各種ライブラリ一覧
- python-cgiで超シンプルな画面遷移を書く
- Pythonでyaml形式の設定ファイルを読み込んでloggingを使ってみた
- 言語処理100本ノックをNLP屋がやってみる: 第5章 40~45
- Pythonで媒介変数表示された平面曲線の曲率を求める
- python3で切り下げ,切り上げ,四捨五入,だけのためにライブラリをimportしたくない
- 48日目。pandasで10万×1万のCSVをマージしたら簡単・早くて驚きました!
- python で近似球を作ってみる
- tensorflowで画像処理
- Pythonで表示したウィンドウにテキスト、インプットボックス、ボタンを挿入する
- PythonでAWS S3のフォルダ配下の最新ファイルを取得 & 履歴管理する方法
- pipelineとgridsearchcvを使って前処理から予測値出力の流れをシンプルに実装
- 画像認識で自作データのサイズ加工
- [n番煎じ] 言語処理100本ノック 2015 第3章 with Python
- Python 3のmultiprocessingでプロセス間で大量のデータを受け渡しつつnumpyで処理する
- 言語処理100本ノック第2章を解いてみた
- python ビッグデータを取り扱ったときのメモ
- from csv to json with Python
- Evernoteのサードパーティ用アクセストークンをOAuth認証で取得する
- pythonを使ったデータ探索と回帰【kaggle, EDA,randomforest】
- Qiitaコーパスを作る会 2️⃣前処理をする
- LINEbotのログインURLを出してみる
- OpenCV
- ルールベース
- Django
- Tools
- Apps
Ruby
- Tips
- RSpec
- Tools
Rails
- Beginner
- RailsチュートリアルのためにDockerの環境構築 for Mac…「Yay, you’re on Rails!」の画面が表示されるまで
- 【初学者向け】Railsアプリケーションにカスタムフォントを追加する方法
- <Windows>Ruby on Rails チュートリアルでのつまづきポイントまとめ
- Railsを支えるRESTについて
- WindowsでRuby on Railsの環境構築してみる
- Rails + Selenium + DockerでSystemSpecの環境構築
- Windows10環境にRubyとRailsをインストールしてみた
- Ruby on Rails開発環境を便利にしよう
- 【初心者向け】bundler、Gemfile、Gemfile.lockの関係性について図でまとめてみた
- Tips
- Ruby on Rails でレビュー機能を実装する
- Rails環境にUikitのデザインを適用する方法
- 初心者でもできる!Cloud RunでRailsアプリをデプロイ【Rails/GCP】
- Rails6 のちょい足しな新機能を試す57(Arel is_distinct_from編)
- Rails6 のちょい足しな新機能を試す58(ActiveRecord::Errors#of_kind?編)
- 既存のRuby on RailsプロジェクトにJenkins と Docker で CIを導入する
- Railsで複数DB接続する
- 【Rails 部分テンプレート(render partial)を用いたviewを別コントローラから呼び出す(render template)】
- Railsの日本語化、ヘルパーメソッドで呼べるようにする。
- 本番環境に合わせたアセットパイプライン
- Railsでダミーのユーザーデータをたくさん用意する方法
- RSpec
- Apps
C
Android
- Tips
- DataBinding
- Camera API
Swift
- Tips
- iOSアプリでSwifterを使ってTwitterログインする
- チュートリアルから一歩踏み出したSwiftUIのCustom Viewの作り方ーその2(PreferenceKey編)
- [swift] DateFormatterのインスタンス生成は遅い
- SwiftUIでViewのレイアウトを調整するStackについて調べて見ました
- Status Bar優良記事まとめ[Swift]
- iOSにデータベースを組み込んでみる。最新GRDBの使い方を解説
- 【Swift】AutoLayoutのPriorityを使ってアニメーション風のView制御をする
- Asset Catalogを使って色の管理 Swift
- 【iOS】iOS開発初心者の個人的に参考になったswift記事まとめ
- ブラウザで名前が定義されている140色をSwiftで定義する
Kotlin
- Tips
Flutter
Fulx
- Beginner
JavaScript
- Tips
- ESLint
- Turn.js
- Quark
React
- Beginner
- Tips
- Apps
Node.js
- Tips
- Tools
Vue.js
- Beginner
- Tips
- SentryとVueの組み合わせでイベントログを収集
- VueやNuxtでcodemirrorを使うときにはvue-codemirrorが便利
- Vuex-ORMとvuex-persistedstateの連携で半永続的データベースに!
- Vue.js + Vue.Draggable + CypressでDrag & Dropするテストを書く
- Vue.jsで作ったアプリをHerokuにデプロイ
- Vue.jsでカレンダーコンポーネント作ってみた
- Vue.js+Firebaseプロジェクト作成(Hosting、Authentication、Firestore、Storage、Functions)
- Vue + Quasarで環境を設定する
- Apps
Vuex
Nuxt.js
- Beginner
- Tips
- Apps
Nest.js
Angular
jQuery
- Beginner
TypeScript
- Beginner
- Tips
- Tools
- Apps
ReactNative
Laravel
- Beginner
- Tips
- 【Laravel】InnoDBで二番目以降のキーにAuto Incrementをマイグレーションで設定する
- 手軽なLaravelテストコード (3種類のモック手法)
- Laravelのお困りごと解決リスト
- Laravel 5.8 認証通知メールの多言語化
- Laravelで AWS の LB を通した環境でエンドユーザのIPを取得したい
- IntelliJ+Laravel+Vuejs+Docker+Mysqlの環境構築
- Laravelでいいね機能
- LaravelのControllerで生成・加工したデータをVue.jsのテンプレートファイルで利用する方法
- Laravelでエクセルを生成してS3に送信する方法
C
PHP
- Beginner
- Tips
- Tools
CakePHP
Rust
- Beginner
Go言語
- Beginner
- Tips
R言語
Scala
Unity
- Tips
- Unity ECS
A-Frame
- Beginner
PowerApp
Line
HTML
CSS
- Bulma
Sass
SQL
MySQL
- Beginner
- Tips
PostgreSQL
Oracle
MongoDB
SQL Server
ビッグデータ
Visual Studio Code
- Beginner
- Tips
IntelliJ IDEA
AI
IoC
Git
- Tips
AWS
- Beginner
- Tips
- EC2
- AWS Lambda
- AWS Athena
- AWS CDK
- Aurora
- Network
- ABAP
- AWS Systems Manager
- AWS Application Discovery Service
- Amazon Managed Blockchain
- AWS SageMaker
- AmazonLinux2
- AWS Chatbot
- AWS CodeStar
- AWS IoT
Azure
- Tips
- アーキテクチャ
- Azure AD B2C
- Azure DevOps
- Azure Heat Map
Oracle Cloud
IBM Clod
- Tips
- AutoAI
Active Directory
インフラ
ブロックチェーン
Ethereum
- OpenZeppelin
- ERC-721
セキュリティ
- SSL
機械学習
- Beginner
- Tips
Network
RPA
CI
Docker
- Beginner
- Tips
- Chromebook(ARM64)にLinux + Docker + PHPStormな開発環境を構築する
- JavaFXをDockerで実行するには
- AmazonLinux2のdockerイメージにpythonをインストールしてAWS ECRにアップする方法
- SwaggerをDockerで動かしてみる(環境構築)
- Dockerを使ってGAEにNuxt.jsをデプロイしたい
- Dockerコンテナ初回起動時にLaravelの環境をセットアップする方法を求めて
- Dockerで使っているコンテナを作り直す方法
- GHDLとVunitでVHDLのテスト環境を構築する[Docker版]
- docker expose プラグイン便利そうなので試す
Heroku
VirtualBox
kubernetes
OpenID
OAuth2.0
Elasticsearch
- Beginner
Linux
- Tips
Cent OS
Windows
Google API
Google Apps Script
- Tips
Google Cloud Platform
Google Colaboratory
Google Drive
Firebase
- Beginner
Server Side
CSS
BootStrap
WordPress
Develop
- Beginner
- Tips
- Tools
- Apps
PowerShell
Vim
awk
LaTex
Redmine
UML
- PlantUML
Raspberry
- Beginner
- Tips
- Raspberry PiでPythonファイルを自動起動させる
- QEMU4.0.0 + Raspbian Buster による RaspberryPi のエミュレーション環境構築
- Raspberry Pi Zero WにインストールしたRaspbian BusterにUSB経由でSSH接続する
- Raspberry Piで日本語入力をする
- QEMU4.0.0 + Raspbian Buster による RaspberryPi のエミュレーション環境構築(超シンプル手順バージョン)
- PC使ったRaspberry Pi開発手順
- Raspberry PiをHeadlessでセットアップする
- ラズパイからADと連携したRADIUS認証が必要な無線LANに接続する (WPA2-EAP)
- [Raspberry Piメモ] SSHでログインするまで
- Apps
RPA
- Beginner
IoT
Alexa
- Beginner
Line
SharePoint
VBA
ShellScript
Nim
Emacs
WPF
UI
Ansible
Arduino
Julia
- Tips
- Apps
Coral
- Tips
ionic
QRCode
OCR
資格
- Azure
- AWS認定ソリューションアーキテクト
- Associate Cloud Engineer
転職
更新情報
Kotlin
- Kotlin入門
Android
Java
IDE
- 投稿日:2019-07-28T09:19:53+09:00
【web開発 CSS】Inputのtextデザイン
はじめに
最近のwebサービスの開発では正しく機能しているかを重視してきたが、そろそろCSSの基本的なデザインは理解しとくべきだし、必ず役に立つ(と信じている)ため、今回はInputのtype="text"についてGoogleなどの大手サイトを参考にしながら作成してみた。また、基本的なアニメーションの勉強も兼ねた。今回はCSSデザインの勉強ということで、下に紹介する検索textボックスなどの検索ボタンや削除ボタンなどは機能は持たせていない。
1. 少し見栄えの良いtextボックス
ここでは3つの簡単なデザインを紹介。
1.1 枠線のみ変更
#text1{ width: 100%; /*親要素いっぱい広げる*/ padding: 10px 15px; /*ボックスを大きくする*/ font-size: 16px; border-radius: 3px; /*ボックス角の丸み*/ border: 2px solid #ddd; /*枠線*/ box-sizing: border-box; /*横幅の解釈をpadding, borderまでとする*/ }上のCSSではデフォルトの枠線をpaddingによって広げ色を変えただけのシンプルなもの。これだけでもデフォルトのtextボックスより見栄えは良い。
上のgif画像で見られるようにfocus時にはデフォルトの青い枠線が表示される。これをもうちょっと変えてみたのが下の二つ。box-sizing: border-box;
これを設定することで要素がpaddingとborderも含めてwidthと考えるようになる(デフォルトではwidthは要素本体のみ)ため親要素をはみ出ない。
1.2 focus時の枠線の色変更(border)
#text1:focus { border: 2px solid #ff9900; z-index: 10; outline: 0;1.3 focus時の枠線の色変更(shadow)
#text1:focus { box-shadow: 0 0 5px 0 rgba(255,153,0,1); border: 2px solid #FFF !important; outline: 0; }
こちらはshadowだけで色を表現。borderは背景と同じ白に設定しているため非表示になっている。2. 検索テキストボックス
こちらはGoogleの設定画面に設置されているものを参考に作成。
<div class="group"> <label for="text2">text2</label> <div class="search_bar"> <i class="fas fa-search search_icon"></i> <input id="text2" type="text" placeholder="キーワードを入力"> <i class="fas fa-times search_icon"></i> </div> </div>.search_bar{ display: flex; /*アイコン、テキストボックスを調整する*/ align-items: center; /*アイコン、テキストボックスを縦方向の中心に*/ justify-content: center; /*アイコン、テキストボックスを横方向の中心に*/ height: 50px; width: 100%; background-color: #ddd; } .search_icon{ /*アイコンに一定のスペースを設ける*/ height: 15px; width: 15px; padding: 5px 5px; } #text2{ font-size: 16px; width: 100%; /*flexの中で100%広げる*/ background-color: #ddd; border: none; /*枠線非表示*/ outline: none; /*フォーカス時の枠線非表示*/ box-sizing: border-box; /*横幅の解釈をpadding, borderまでとする*/ }flex
ここでのポイントは、虫眼鏡と×のアイコンとtextボックスを横並びにする際にinline-blockではなくflexを使っていること。flexを使うことで#text2のwidth: 100%;としても.search_bar要素をはみ出すことなく自動で調整してくれる。また縦のラインを合わせる際にinline-blockよりもflexのほうが簡単。
3. ちょっとしたアニメーションを付けたtextボックス
こちらはYoutubeのコメント投稿欄を参考に作成。擬似要素を使用した二つのデザインを紹介
3.1 中央からのアニメーション
<div class="group"> <label for="text3" id="l_text3">text3</label> <input id="text3" type="text" placeholder="コメントを入力する"> <div class="text_underline"></div> </div>#text3{ font-size: 16px; width: 100%; border: none; outline: none; padding-bottom: 8px; box-sizing: border-box; /*横幅の解釈をpadding, borderまでとする*/ } .text_underline{ position: relative; /*.text_underline::beforeの親要素*/ border-top: 1px solid #c2c2c2; /*text3の下線*/ } /*共通のstyle*/ .text_underline::before, .text_underline::after{ position: absolute; bottom: 0px; /*中央配置*/ width: 0px; /*アニメーションで0pxから50%に*/ height: 1px; /*高さ*/ content: ''; background-color: #3be5ae; /*アニメーションの色*/ transition: all 0.5s; -webkit-transition: all 0.5s; } /*中央から右へのアニメーション*/ .text_underline::before{ left: 50%; /*中央配置*/ } /*中央から左へのアニメーション*/ .text_underline::after{ right: 50%; /*中央配置*/ } #text3:focus + .text_underline::before, #text3:focus + .text_underline::after{ width: 50%; }擬似要素
今回::beforeや::afterというような擬似要素を使用することで無駄に要素を追加しないでアニメーションを適応してみた。上の例では::beforeと::afterをtextボックスの下線の中央に配置することで左右に伸びるアニメーションを実現した。
セレクタ
今回使用したセレクタは隣接セレクタ(+)。これは該当要素に隣接する兄弟関係の要素でありかつ該当要素直下しか適応されない。これが理由で#text3の直前に存在するlabelにはアニメーションをCSSで加えることはできない(もしあばご教授願います)。よって今回は一番簡単だと考えられるjQueryでの実装にした。
$('#text3').focus(function(){ $('#l_text3').animate({'color': '#3be5ae'}, 500); }).blur(function(){ $('#l_text3').animate({'color': 'black'}, 500); });こちらのanimateメソッドでは色指定にHTMLカラーコード(#から始まる色を表す英数字文字列)、色の名前およびRGB値のどれでも記述可能。またjQueryにはデフォルトではanimateメソッドで色を変える機能は付いていないぽいので以下のスクリプトが必要。
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-color/2.1.2/jquery.color.js"></script>アニメーション
transition: 対象 ミリ秒;でアニメーションを与えることができる。非常にシンプル。
左右からのアニメーション
/*左から右へのアニメーション*/ .text_underline::before{ position: absolute; left: 0px; /*左配置 ここright: 0px;にすると右から左のアニメーション*/ bottom: 0px; /*左配置*/ width: 0px; /*アニメーションで0pxから100%に*/ height: 1px; /*高さ*/ content: ''; background-color: #3be5ae; /*アニメーションの色*/ transition: all 0.5s; -webkit-transition: all 0.5s; } #text3:focus + .text_underline::before{ width: 100%; }
3.1のものとほとんど同じなので説明は簡単に。こちらでは左から右にアニメーションするだけなので伸びる方向が1つ。よって擬似要素も::beforeの1つで実現。ただ::beforeを左端に配置して右に伸ばしてるだけ。4. かっこいいアニメーションtextボックス
こちらもGoogleのログイン画面に設置されているものを参考に作成。
<div class="group"> <label for="text4">text4</label> <div class="password_box"> <div class="password_inner"> <input id="text4" type="password"> <div class="password_string">パスワードを入力</div> </div> <i class="fas fa-eye-slash"></i> </div> </div>.password_box{ display: flex; /*アイコン、テキストボックスを調整する*/ align-items: center; /*アイコン、テキストボックスを縦方向の中心に*/ justify-content: center; /*アイコン、テキストボックスを横方向の中心に*/ width: 100%; height: 50px; border-radius: 5px; border: 1px solid lightgray; } .password_inner{ width: 100%; height: 100%; background-color: transparent; /*.password_boxの枠線お角一部被るため透明に*/ position: relative; } #text4{ position: absolute; z-index: 1; /*.password_stringよりも上に配置*/ height: 100%; width: 100%; top: 0; left: 0; bottom: 0; right: 0; border: none; /*枠線非表示*/ outline: none; /*フォーカス時の枠線非表示*/ padding: 0 10px; font-size: 16px; background-color: transparent; /*後ろの.password_stringを見せるため*/ box-sizing: border-box; /*横幅の解釈をpadding, borderまでとする*/ } .password_string{ position: absolute; height: 100%; width: 140px; /*文字列分の長さ*/ top: 0; left: 0; bottom: 0; right: 0; padding-left: 10px; /*position: absolute;でのmarginは親要素はみ出す*/ font-size: 16px; line-height: 50px; /*文字列を縦方向にmiddleに見せるため*/ background-color: transparent; color: #80868b; box-sizing: border-box; /*横幅の解釈をpadding, borderまでとする*/ transition: all 0.2s; -webkit-transition: all 0.2s; } .fa-eye-slash{ /*アイコンに一定のスペースを設ける*/ height: 20px; width: 20px; padding: 5px 5px; } /*アニメーション*/ #text4:focus + .password_string{ color: #3be5ae; font-size: 10px; line-height: 10px; width: 85px; height: 10px; padding: 0 2px; background-color: white; transform:translate3d(5px, -6px, 0); }$('#text4').focus(function(){ $('.password_box').animate({borderTopColor: '#3be5ae', borderLeftColor: '#3be5ae', borderRightColor: '#3be5ae', borderBottomColor: '#3be5ae'}, 200); }).blur(function(){ $('.password_box').animate({borderTopColor: '#d3d3d3', borderLeftColor: '#d3d3d3', borderRightColor: '#d3d3d3', borderBottomColor: '#d3d3d3'}, 200); });
こちらでも.password_boxの枠線をCSSで変更する方がめんどくさそうだったのでjQueryで記述。またこの時、borderの色を変えるにはborderTopColor, borderLeftColor, borderRightColor, borderBottomColorの全てを指定する必要があり、色の名前(blackなど)は使えない。使えるのはHTMLカラーコードとRGB値のみ。position: absolute;でのpadding
少し躓いた。position: absolute;ではmarginを使うと親要素をはみ出してしまうため、paddingを使う。position: absolute;の設定下ではpadding分要素が小さくなり、親要素の中に収まる仕組みになっている。
translate3d
アニメーションで要素を動かす際translate()を使うと描画にCPUを使用し、translate3d()を使うとGPUを使用するようになる。当然GPUを使った方がスムーズだしこれを使うべき。
最後に
今回こうしていくつかデザインを作った目的は、これからのwebサービス作りの際の辞書代わりに使うため、CSSデザインの勉強だった。色々触ってるとアニメーションにもいくつもやり方があったりとかなり勉強になった。擬似要素を使うとかなり複雑なデザインも実現できるため習得できると強みになると思った。また、インターネット上の様々なwebサイトをchromeのdeveloperツールを使ってどのような構造になっているのかを見るのはとても勉強になる。
- 投稿日:2019-07-28T07:10:22+09:00
Chrome 77のDevToolsの新機能をざっくり確認してみた
こちらにChrome 77のdev toolの新機能について紹介があったため、私が勉強のために試してみたと言う投稿になります。
2019/07/28現在のchromeのバージョンは75.0.3770.142なので、
Chrome Canaryバージョン78.0.3866.0を使用して確認しています。要素のスタイルをコピー Copy element's styles
ElementsパネルのDOMを右クリックしてcssをクリップボードにコピーできるようになった!
レイアウトシフトを可視化 (※こちらはうまく動かせませんでした) Visualize layout shifts
参考ページにレイアウトシフトの説明があったので一部引用です。
It usually happens when images and ads finish loading. The page hasn't reserved any space for the images and ads, so the browser has to shift all the other content down to make room for them.
↓ google翻訳 ↓
通常、画像や広告の読み込みが完了したときに発生します。 ページは画像や広告のためのスペースを予約していないので、ブラウザはそれらのためのスペースを空けるために他のすべてのコンテンツを下にシフトしなければなりません。画像などの読み込み後に画面ががたついてしまったりする問題のことをレイアウトシフトと言うようです。
devtoolの新機能ではレイアウトのずれを検出することができるようなのですが、私の環境ではうまく動きませんでした。
手順通りにやってみたのですが、画面が真っ黒になってしまい、うまくいきません。
- devtoolを開く
- Shift + command + P でCommand Menuを開く
Rendering
と入力Show Rendering
を選択する- Renderingパネルの「Layout Shift Regions」にチェックをつける
画面が黒くなってしまう
また、コマンドからChrome Canaryを起動してみるとエラーが出ていました、
$ /Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canaryエラーログ$ /Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary tracing_service_impl.cc: Configured tracing, #sources:3, duration:0 ms, #buffers:1, total buffer size:25600 KB, total sessions:1 [3700:775:0726/005556.605442:ERROR:gles2_cmd_decoder.cc(6509)] [.DisplayCompositor]GL ERROR :GL_INVALID_OPERATION : glBindTexture: texture bound to more than 1 target. [3700:775:0726/005556.608870:ERROR:gles2_cmd_decoder.cc(10736)] [.DisplayCompositor]RENDER WARNING: there is no texture bound to the unit 0 [3700:775:0726/005556.623967:ERROR:gles2_cmd_decoder.cc(10736)] [.DisplayCompositor]RENDER WARNING: there is no texture bound to the unit 0 [3700:775:0726/005558.518672:ERROR:gles2_cmd_decoder.cc(10736)] [.DisplayCompositor]RENDER WARNING: there is no texture bound to the unit 0 [3700:775:0726/005558.530997:ERROR:gles2_cmd_decoder.cc(6509)] [.DisplayCompositor]GL ERROR :GL_INVALID_OPERATION : glBindTexture: texture bound to more than 1 target. [3700:775:0726/005558.555357:ERROR:gles2_cmd_decoder.cc(6509)] [.DisplayCompositor]GL ERROR :GL_INVALID_OPERATION : glBindTexture: texture bound to more than 1 target. [3700:775:0726/005558.571220:ERROR:gles2_cmd_decoder.cc(6509)] [.DisplayCompositor]GL ERROR :GL_INVALID_OPERATION : glBindTexture: texture bound to more than 1 target. [3700:775:0726/005558.823379:ERROR:gles2_cmd_decoder.cc(6509)] [.DisplayCompositor]GL ERROR :GL_INVALID_OPERATION : glBindTexture: texture bound to more than 1 target. [3700:775:0726/005558.823437:ERROR:gles2_cmd_decoder.cc(10736)] [.DisplayCompositor]RENDER WARNING: there is no texture bound to the unit 0 [3700:775:0726/005559.314042:ERROR:gles2_cmd_decoder.cc(10736)] [.DisplayCompositor]RENDER WARNING: there is no texture bound to the unit 0 [3700:775:0726/005559.321744:ERROR:gles2_cmd_decoder.cc(6509)] [.DisplayCompositor]GL ERROR :GL_INVALID_OPERATION : glBindTexture: texture bound to more than 1 target. [3700:775:0726/005601.189083:ERROR:gles2_cmd_decoder.cc(6509)] [.DisplayCompositor]GL ERROR :GL_INVALID_OPERATION : glBindTexture: texture bound to more than 1 target. [3700:775:0726/005601.204262:ERROR:gles2_cmd_decoder.cc(6509)] [.DisplayCompositor]GL ERROR :GL_INVALID_OPERATION : glBindTexture: texture bound to more than 1 target. ...うまく動かす方法を見つけたら追記します
AuditsパネルにLighthouse 5.1 Lighthouse 5.1 in the Audits panel
※「実際にはChrome76に同梱されていた」と注意書きあり
- Provides a valid apple-touch-icon. Checks that a PWA can be added to an iOS homescreen.
- Keep request counts and file sizes low. Reports the total number of network requests and file sizes for various categories, such as documents, scripts, stylesheets, images, and so on.
- Maximum Potential First Input Delay. Measures the maximum potential time between a user's first page interaction and the browser's response to that interaction. Note that this metric replaces the Estimated Input Latency metric. Maximum Potential First Input Delay does not factor into your Performance category score.
参考ページによると↑のように色々な機能が追加されるようなのですが、私がAuditsパネル、Lighthouse関して知識がなく、(^ ^;)
有効なapple-touch-icon
をチェックしてくれるようなった機能 だけみてみました
このように
apple-touch-icon
が有効かどうかの項目が増えたので、192pxの正方形のpngをhtmlで読み込んでみると
<link rel="apple-touch-icon" sizes="192x192" href="icon.png">有効と判定してくれました!
OSテーマと同期 OS theme syncing
OSをダークテーマにすると、設定から変更しなくてもdevtoolが自動でダークテーマになる
<-左 Chrome (バージョン 75) : Chrome Canary (バージョン 77) 右->
条件付きブレークポイントにショートカットキーが割り当てられた Keyboard shortcut for opening the Breakpoint Editor
ソースパネルのエディタにフォーカスがあるときにControl + Alt + B(Windows)またはCommand + Option + B(Mac)を押すと、Conditional Breakpointsを設定できるエディタを開ける(今まではショートカットはなく、ソースパネルのエディタの行番号を右クリックのメニューから設定できる)
Conditional Breakpointsとは
Conditional Breakpoints(条件付きブレークポイント)を使用すると、定義された式がtrueと評価されたときにコードブロック内でブレークできます。参考:https://blittle.github.io/chrome-dev-tools/sources/conditional-breakpoints.html
Networkパネルでprefechしたリソースがわかるように Prefetch cache in Network panel
prefetchで取得したリソースについては、Networkパネルで
(prefetch cache)
と表示してくれるようになった<link rel="prefetch" href="image.png">Prefetchとは
こちらの記事がprefetchを含むResource Hintsについて、とてもわかりやすくて私は毎回参考にしています。m(_ _)m参考:Resource Hintsでリソースを事前取得しよう! - Qiita
オブジェクトプレビューにクラスのプライベートプロパティ表示 Private properties when viewing objects
コンソールのオブジェクトプレビューにクラスのプライベートプロパティが表示されるようになりました
const human = new class { #name = "Jhon"; age = 20; } console.log(human)Applicationパネルで通知とpushメッセージを確認できるように Notifications and push messages in the Application panel
Applicationパネルで通知とpushメッセージを確認できるようになる
プッシュメッセージは、サーバーがサービスワーカーに情報を送信したときに発生
通知は、サービス担当者またはページのスクリプトがユーザーに情報を表示したときに発生試してみるとこんな感じで表示されるようでした。
最後まで見ていただいてありがとうございましたm(_ _)m
- 投稿日:2019-07-28T02:43:41+09:00
【Programming News】Qiitaまとめ記事 July 27, 2019 Vol.13
筆者が2019/7/27(土)に気になったQiitaの記事をまとめました。昨日のまとめ記事はこちら。
2019/7/15(月)~2019/7/20(土)のWeeklyのまとめのまとめ記事もこちらで公開しております。
Java
- Spring Boot
Python
- Tips
- Tools
Ruby
Rails
- Beginner
- Tips
- RSpec
JavaScript
Node.js
- Tips
Vue.js
- Tips
Android
Swift
PHP
- Tips
A-Frame
- Beginner
Line
MySQL
Azure
AWS
- EC2
- AWS Chatbot
Firebase
- Beginner
TypeScript
Google Apps Script
Go言語
Rust
- Beginner
Julia
ShellScript
Unity
Docker
Develop
- Tips
- Tools
- Apps
Raspberry
- Tips
Heroku
OAuth2.0
Visual Studio Code
IntelliJ IDEA
更新情報
Kotlin
- Kotlin入門
Android
Java
IDE
- 投稿日:2019-07-28T02:13:28+09:00
カメラモジュールOV7725の使い方(ESP32-WROVER-B)
概要
以前OV2640を使って画像を取得したので、今回はOV7725を使って画像を取得してみました。OV7725はjpegではなく、bmp形式で画像が出力されるので、少し制御方法が異なります。
今回はブラウザ上でOV7725のレジスタを変更できるようにしました!結論から申し上げると、30万画素ではあまりきれいな写真は撮れませんでした。初期の携帯に搭載されていたカメラと同性能らしいです…
きれいな写真を撮りたい場合はラズベリーパイを使って、OV5640で撮影したりした方がいいかと思います。OV2640ではESP32-WROOM-32を使用しましたが、OV7725の場合はbmpで、メモリ使用量が増えるため、PSRAM搭載のESP32-WROVER-Bを使用しました。
公式
https://github.com/espressif/esp32-cameraたぶん公式に採用される前
https://github.com/igrr/esp32-cam-demoピン配置
https://www.instructables.com/id/The-Incredible-ESP32-Wrover-From-Espressif/OV7725 データシート
https://cdn.sparkfun.com/datasheets/Sensors/LightImaging/OV7725.pdf設定
esp-idfのフォルダ内でsubmoduleをクローンします。
git submodule add https://github.com/espressif/esp32-camera.git components/esp32-camera mv esp-idf/components/esp32-camera/driver/private_include/sccb.h esp-idf/components/esp32-camera/driver/include/sccb.h mv esp-idf/components/esp32-camera/driver/private_include/sensor.h esp-idf/components/esp32-camera/driver/include/sensor.h注意点
- driver公式のページ通りにbuildすると、camera_init()が重複していてエラーになったので、中身をapp_main関数に移動しました。
- ピン配置は公式のものではなく、https://github.com/igrr/esp32-cam-demo を参考にしました。
- XCLK:20MHz→6MHzへ変更
コード
esp-idfのexampleにあるsimple serverを改良し、ブラウザで「http://<ローカルアドレス>/」へアクセスすると撮影画像が表示されます。ajaxで画像データ(base64で変換したデータ)を取得し、ブラウザに表示しています。
全コードは下記に載せています。
https://github.com/koki-iwaizumi/esp32-ov7725サイズはVGA、出力形式はYUV422にしています。wifi周りの設定は変更してください。
app_main.c#include <string.h> #include <esp_wifi.h> #include <esp_event_loop.h> #include <esp_log.h> #include <esp_system.h> #include <nvs_flash.h> #include <sys/param.h> #include <esp_http_server.h> #include <esp_camera.h> #include "mbedtls/base64.h" #include "cJSON.h" #include "sensor.h" #include "sccb.h" /********* io ***********/ #define CAM_PIN_PWDN 2 #define CAM_PIN_RESET 15 #define CAM_PIN_XCLK 27 #define CAM_PIN_SIOD 25 #define CAM_PIN_SIOC 23 #define CAM_PIN_VSYNC 22 #define CAM_PIN_HREF 26 #define CAM_PIN_PCLK 21 #define CAM_PIN_D9 19 #define CAM_PIN_D8 36 #define CAM_PIN_D7 18 #define CAM_PIN_D6 39 #define CAM_PIN_D5 5 #define CAM_PIN_D4 34 #define CAM_PIN_D3 32 #define CAM_PIN_D2 35 /********* wifi ***********/ #define EXAMPLE_WIFI_SSID "*" #define EXAMPLE_WIFI_PASS "*" static const char *TAG = "APP"; sensor_t* sensor = NULL; static camera_config_t camera_config = { .pin_pwdn = CAM_PIN_PWDN, .pin_reset = CAM_PIN_RESET, .pin_xclk = CAM_PIN_XCLK, .pin_sscb_sda = CAM_PIN_SIOD, .pin_sscb_scl = CAM_PIN_SIOC, .pin_d7 = CAM_PIN_D9, .pin_d6 = CAM_PIN_D8, .pin_d5 = CAM_PIN_D7, .pin_d4 = CAM_PIN_D6, .pin_d3 = CAM_PIN_D5, .pin_d2 = CAM_PIN_D4, .pin_d1 = CAM_PIN_D3, .pin_d0 = CAM_PIN_D2, .pin_vsync = CAM_PIN_VSYNC, .pin_href = CAM_PIN_HREF, .pin_pclk = CAM_PIN_PCLK, .xclk_freq_hz = 6000000, .ledc_timer = LEDC_TIMER_0, .ledc_channel = LEDC_CHANNEL_0, .pixel_format = PIXFORMAT_YUV422, .frame_size = FRAMESIZE_VGA, .jpeg_quality = 12, .fb_count = 1 }; esp_err_t index_get_handler(httpd_req_t *req) { extern const unsigned char index_start[] asm("_binary_index_html_start"); extern const unsigned char index_end[] asm("_binary_index_html_end"); const size_t index_size = (index_end - index_start); httpd_resp_send_chunk(req, (const char *)index_start, index_size); httpd_resp_sendstr_chunk(req, NULL); return ESP_OK; } esp_err_t image_post_handler(httpd_req_t *req) { int content_buf_size = 10 * 1000; char *content_buf = calloc(content_buf_size, sizeof(char)); if(content_buf == NULL){ ESP_LOGE(TAG, "Failed to allocate frame buffer - content_buf"); httpd_resp_send_500(req); return ESP_FAIL; } if(httpd_req_recv(req, content_buf, req->content_len) <= 0){ ESP_LOGE(TAG, "httpd_req_recv failed"); httpd_resp_send_500(req); return ESP_FAIL; } ESP_LOGE(TAG, "httpd_req_recv content=%s", content_buf); cJSON *content_json = cJSON_Parse(content_buf); for(int i = 0; i <= 172; i++){ char key[4]; itoa(i,key,10); int value = strtol(cJSON_GetObjectItem(content_json, key)->valuestring, NULL, 16); SCCB_Write(sensor->slv_addr, i, value); } cJSON_Delete(content_json); free(content_buf); camera_fb_t *fb = NULL; esp_err_t res = ESP_OK; int64_t fr_start = esp_timer_get_time(); fb = esp_camera_fb_get(); if( ! fb){ ESP_LOGE(TAG, "Camera capture failed"); httpd_resp_send_500(req); return ESP_FAIL; } uint8_t *buf = NULL; size_t buf_len = 0; bool converted = frame2bmp(fb, &buf, &buf_len); esp_camera_fb_return(fb); if( ! converted){ ESP_LOGE(TAG, "BMP conversion failed"); httpd_resp_send_500(req); return ESP_FAIL; } ESP_LOGI(TAG, "esp_get_free_heap_size (%d bytes)", esp_get_free_heap_size()); ESP_LOGI(TAG, "esp_get_minimum_free_heap_size (%d bytes)", esp_get_minimum_free_heap_size()); ESP_LOGI(TAG, "heap_caps_get_free_size(MALLOC_CAP_8BIT) (%d bytes)", heap_caps_get_free_size(MALLOC_CAP_8BIT)); ESP_LOGI(TAG, "heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT) (%d bytes)", heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT)); ESP_LOGI(TAG, "heap_caps_get_largest_free_block(MALLOC_CAP_8BIT) (%d bytes)", heap_caps_get_largest_free_block(MALLOC_CAP_8BIT)); int image_buf_size = 1300 * 1000; uint8_t *image_buf = calloc(image_buf_size, sizeof(char)); if(image_buf == NULL){ free(buf); ESP_LOGE(TAG, "Failed to allocate frame buffer - image_buf"); httpd_resp_send_500(req); return ESP_FAIL; } size_t olen = 0; int base64_err = mbedtls_base64_encode(image_buf, image_buf_size, &olen, buf, buf_len); free(buf); if (base64_err != 0) { ESP_LOGE(TAG, "error base64 encoding, error %d, buff size: %d", base64_err, olen); httpd_resp_send_500(req); return ESP_FAIL; } ESP_LOGI(TAG, "esp_get_free_heap_size (%d bytes)", esp_get_free_heap_size()); ESP_LOGI(TAG, "esp_get_minimum_free_heap_size (%d bytes)", esp_get_minimum_free_heap_size()); ESP_LOGI(TAG, "heap_caps_get_free_size(MALLOC_CAP_8BIT) (%d bytes)", heap_caps_get_free_size(MALLOC_CAP_8BIT)); ESP_LOGI(TAG, "heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT) (%d bytes)", heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT)); ESP_LOGI(TAG, "heap_caps_get_largest_free_block(MALLOC_CAP_8BIT) (%d bytes)", heap_caps_get_largest_free_block(MALLOC_CAP_8BIT)); uint8_t *image_json = calloc(image_buf_size, sizeof(char)); if(image_json == NULL){ free(image_buf); ESP_LOGE(TAG, "Failed to allocate frame buffer - image_json"); httpd_resp_send_500(req); return ESP_FAIL; } sprintf((char *)image_json, "{\"image\":\"%s\"}", (const char *)image_buf); free(image_buf); ESP_LOGI(TAG, "image_json length=%d", strlen((const char*)image_json)); res = httpd_resp_set_type(req, "application/json") || httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*") || httpd_resp_send(req, (const char *)image_json, strlen((const char*)image_json)); free(image_json); int64_t fr_end = esp_timer_get_time(); ESP_LOGI(TAG, "BMP: %uKB %ums", (uint32_t)(buf_len/1024), (uint32_t)((fr_end - fr_start)/1000)); return res; } esp_err_t config_get_handler(httpd_req_t *req) { int data_json_size = 10 * 1000; char *data_json = calloc(data_json_size, sizeof(char)); if(data_json == NULL){ ESP_LOGE(TAG, "Failed to allocate frame buffer - data_json"); httpd_resp_send_500(req); return ESP_FAIL; } strcat(data_json, "{"); for(int i = 0; i <= 172; i++){ char data_json_buf[255] = {'\0'}; sprintf(data_json_buf, "\"%d\":\"%d\"", i, SCCB_Read(sensor->slv_addr, i)); strcat(data_json, data_json_buf); if(i != 172) strcat(data_json, ","); } strcat(data_json, "}"); ESP_LOGI(TAG, "data_json=%s", data_json); ESP_LOGI(TAG, "data_json length=%d", strlen((const char*)data_json)); esp_err_t res = httpd_resp_set_type(req, "application/json") || httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*") || httpd_resp_send(req, (const char *)data_json, strlen((const char*)data_json)); free(data_json); return res; } httpd_uri_t index_uri = { .uri = "/", .method = HTTP_GET, .handler = index_get_handler, }; httpd_uri_t image_uri = { .uri = "/image", .method = HTTP_POST, .handler = image_post_handler, }; httpd_uri_t config_uri = { .uri = "/config", .method = HTTP_GET, .handler = config_get_handler, }; httpd_handle_t start_webserver(void) { httpd_handle_t server = NULL; httpd_config_t config = HTTPD_DEFAULT_CONFIG(); ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port); if(httpd_start(&server, &config) == ESP_OK){ ESP_LOGI(TAG, "Registering URI handlers"); httpd_register_uri_handler(server, &index_uri); httpd_register_uri_handler(server, &image_uri); httpd_register_uri_handler(server, &config_uri); return server; } ESP_LOGI(TAG, "Error starting server!"); return NULL; } void stop_webserver(httpd_handle_t server) { httpd_stop(server); } static esp_err_t event_handler(void *ctx, system_event_t *event) { httpd_handle_t *server = (httpd_handle_t *) ctx; switch(event->event_id){ case SYSTEM_EVENT_STA_START: ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START"); ESP_ERROR_CHECK(esp_wifi_connect()); break; case SYSTEM_EVENT_STA_GOT_IP: ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP"); ESP_LOGI(TAG, "Got IP: '%s'", ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); if(*server == NULL){ *server = start_webserver(); } break; case SYSTEM_EVENT_STA_DISCONNECTED: ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED"); ESP_ERROR_CHECK(esp_wifi_connect()); if(*server){ stop_webserver(*server); *server = NULL; } break; default: break; } return ESP_OK; } static void initialise_wifi(void *arg) { tcpip_adapter_init(); ESP_ERROR_CHECK(esp_event_loop_init(event_handler, arg)); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); wifi_config_t wifi_config = { .sta = { .ssid = EXAMPLE_WIFI_SSID, .password = EXAMPLE_WIFI_PASS, }, }; ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid); ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config)); ESP_ERROR_CHECK(esp_wifi_start()); } void app_main() { static httpd_handle_t server = NULL; ESP_ERROR_CHECK(nvs_flash_init()); esp_err_t err = esp_camera_init(&camera_config); if(err != ESP_OK){ ESP_LOGE(TAG, "Camera Init Failed"); return; } sensor = esp_camera_sensor_get(); initialise_wifi(&server); }HTML周り
index.html<!doctype html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.4/css/all.css"> <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"> <title>OV7725</title> <style> .alert{ display:none; } #overlay{ position: fixed; top: 0; z-index: 100; width: 100%; height:100%; display: none; background: rgba(0,0,0,0.6); } .cv-spinner{ height: 100%; display: flex; justify-content: center; align-items: center; } .spinner{ width: 40px; height: 40px; border: 4px #ddd solid; border-top: 4px #2e93e6 solid; border-radius: 50%; animation: sp-anime 0.8s infinite linear; } @keyframes sp-anime{ 0% { transform: rotate(0deg); } 100% { transform: rotate(359deg); } } .is-hide{ display:none; } .ov_a{ height:600px; overflow-y:auto; } </style> </head> <body> <div id="overlay"> <div class="cv-spinner"> <span class="spinner"></span> </div> </div> <div class="text-center mt-3 mb-3"> <h1>OV7725</h1> </div> <div class="container-fluid"> <div class="row"> <div class="col-lg-6 text-center mb-4"> <div class="row"> <div class="col-md-12"> <div class="alert alert-success" role="alert">success</div> <div class="alert alert-danger" role="alert">failed</div> </div> <div class="col-md-12"> <img id="img" class="mw-100"> </div> <div class="col-md-12 mt-4"> <button id="image_button" type="button" class="btn btn-success w-100">Take a Picture</button> </div> </div> </div> <div class="col-lg-6"> <div class="ov_a"> <form id="form"> <table id="table_regs" class="table table-striped"> <tr> <th>Address(Hex)</th> <th>Value(Hex)</th> </tr> </table> </form> </div> </div> </div> </div> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> <script type="text/javascript"> $(function(){ var parseJson = function(data) { var returnJson = {}; for(idx = 0; idx < data.length; idx++){ returnJson[data[idx].name] = data[idx].value } return returnJson; }; $(document).ready(function(){ read_config(); get_image(); }); $("#image_button").on("click", get_image); function get_image(){ $.ajax({ url: "./image", type: "POST", data: JSON.stringify(parseJson($("#form").serializeArray())), dataType: "json", beforeSend: function() { $(".alert").css("display", "none"); $("#overlay").fadeIn(300); } }) .done((data) => { $(".alert").css("display", "none"); $(".alert-success").css("display", "block"); $("#img").attr("src", "data:image/jpeg;base64," + data["image"]); }) .fail((data) => { $(".alert").css("display", "none"); $(".alert-danger").css("display", "block"); console.log(data); }) .always((data) => { setTimeout(function(){ $("#overlay").fadeOut(300); },500); }); } function read_config(){ $.ajax({ url: "./config", type: "GET", dataType: "json", async: false, }) .done((data) => { $.each(data, function(i, item){ $("#table_regs").append('<tr><td>0x' + Number(i).toString(16) + '</td><td><div class="input-group"><div class="input-group-prepend"><span class="input-group-text" id="text1a">0x</span></div><input name="' + i + '" value="' + Number(item).toString(16) + '" type="text" class="form-control"></div></td></tr>'); }); }) } }); </script> </body> </html>
- 投稿日:2019-07-28T01:38:02+09:00
【Programming News】Qiitaまとめ記事 July 26, 2019 Vol.12
遅くなりましたが筆者が2019/7/26(金)に気になったQiitaの記事をまとめました。昨日のまとめ記事はこちら。
2019/7/15(月)~2019/7/20(土)のWeeklyのまとめのまとめ記事もこちらで公開しております。
Python
- Beginner
- Tips
- Apps
JavaScript
Ruby
- RSpec
Android
Swift
Kotlin
Rails
- Beginner
- Tips
React
Nuxt.js
Laravel
Sass
PHP
- Tips
MySQL
Oracle
AWS
- AWS Lambda
- AWS CodeStar
- AWS IoT
- Beginner
Docker
Visual Studio
- Beginner
IBM Cloud
Unity
- Tips
TypeScript
- Tips
- Tools
Raspberry
- Tips
- Apps
Julia
Git
- Tips
Vim
VirtualBox
更新情報
Kotlin
- Kotlin入門
Android
Java
IDE
- 投稿日:2019-07-28T01:12:25+09:00
v-slotのサンプルコード(Vue.js初心者)
Vue.js公式サイトのスコープ付きスロットに、v-slotの解説とコード断片が載っていますが、 最初読んだ時、データの関係が私にはよく理解できず、試しにコード書いても変数のReferenceErrorで進まず。「サンプルコード全体(HTML+JavaScript)は一体どのように書けば動作するのか?」と躓いてしまいました。が、試行錯誤してやっとわかったのでここに書きます。
- 対象Version: Vue.js 2.6.10+
躓いた箇所
この解説文。
親コンポーネント内でスロットコンテンツとして user を使えるようにするためには、 要素の属性として user をバインドします:
HTML<span> <slot v-bind:user="user"> {{ user.lastName }} </slot> </span>v-bindの両辺に
user
が出てくるので、いざコードを書いていくと「自分が今ここに書いたuser
は上の解説文でいうどれのこと?」と、データの関係があやふやな状態になり頭が混乱していきます。わかった!
v-slotのコーディングを実演しているこの動画を観て、コツがわかりました!
Vue 2.6 First Look And V-Slot Tutorial!完成したサンプルコード
See the Pen [Vue.js basics] v-slot example 1 by Kazuhiro Hashimoto (@kaz_hashimoto) on CodePen.
理解を助けるためのポイント
先にJavaScript側から。
JavaScriptVue.component('current-user', { data: function() { return { xxuser: { firstName: 'Kazuhiro', lastName: 'Hashimoto' } }; }, template:` <span> <slot v-bind:content="xxuser"> {{ xxuser.lastName }} </slot> </span> ` }); new Vue({ el: '#app' });
- v-bind:○○○="▲▲▲" の左辺と右辺で別々の名前を付けて、見た目で区別できるようにしておく。上記サンプルでは、左辺○○○を
content
、右辺▲▲▲をあえてxxuser
とした。data
プロパティはVue.component
の引数に渡すオブジェクト内で定義し、その値は関数リテラル。で、その関数の戻り値は、xxuser
を要素に持つオブジェクト。template
に記述するslotのコンテンツ{{...}}
に入れるのは、v-bind:○○○="▲▲▲"の▲▲▲、つまりxxuser
経由でアクセスする式。HTML<div id="app"> <current-user> <template v-slot:default="slotProps"> {{ slotProps.content.firstName }} </template> </current-user> <hr> <current-user></current-user> </div>
- v-slotの右辺の変数
slotProps
でスロットプロパティを受け取る(名前は何でもよい)Vue.component
で指定したdata: function() {...}
の戻り値のオブジェクトが、slotProps
に相当するイメージ。slotProps
からxxuser
のメンバーにアクセスするには、v-bind:○○○="▲▲▲" に指定した○○○部分、つまりcontent
経由でアクセスする。(ココが最初わからなくてハマった)v-slotを使ったコーディングに慣れるまでは、自分にわかりやすい
v-bind:content="xx..."
形式でサンプルコードを書いて練習しようと思います。
- 投稿日:2019-07-28T00:57:07+09:00
「2019年にReact Nativeを使い始める:初めてのアプリケーションを構築する」をやってみた
今までReact Nativeをやってみたいと思っていたのですが、なかなか機会がなく、今回↓のチュートリアルがとてもわかりやすそうでしたのでやってみました。
Getting Started with React Native in 2019: Build Your First App
この投稿で書いてある内容は、素人の私が参考サイトの通りやってみただけの内容です。
参考サイトの方では詳しい解説などもあるため、私の投稿は読まずに参考サイトを見れば大丈夫です。
ですが一応やってみたので投稿させていただきました。バージョン
- macOS Mojave 10.14.5
- react 16.8.6
- react-native 0.60.4
- Xcode 10.2
- Android Studio 3.3.2
React Native CLI のインストール
$ yarn add -D react-native-cli $ yarn react-native --version yarn run v1.17.3 warning package.json: No license field $ /Users/kengookumura/dev/tmp/rn/node_modules/.bin/react-native --version react-native-cli: 2.0.1 # ←react-native-cliのバージョンを確認 react-native: n/a - not inside a React Native project directory ✨ Done in 0.19s.React Native Appを実行
yarn react-native init EmojiDictRN cd EmojiDictRN/
アプリを起動
yarn startiOSで起動
yarn start
で実行しているのとは別のターミナルのタブで↓を実行するyarn react-native run-iosiOSシミュレータを起動することができました
※最初Xcode 11.0 beta 3がインストールされている状態で行ったのですが、以下のようにエラーが出ました。
Xcode 10.2をインストールして動かせましたerror Could not find "iPhone X" simulator.↓からXcode 10.2をダウンロードしました
Androidで起動
yarn start
で実行しているのとは別のターミナルのタブで↓を実行するbashyarn react-native run-android #※私の環境ではエラーになりました
エラーになってしまったので、自分がやってみことです
まずこちらを参考にし、
ANDROID_HOME
のパスを設定しましたexport ANDROID_HOME=/Users/kengookumura/Library/Android/sdk私の環境では↑だけではうまくいかず、
↑こちらのリンクで、「アプリケーションを実行するには、Androidシミュレータを起動するか、デバイスを接続する必要がある」とあっため、↓を参考にエミュレータを起動した上で
yarn react-native run-android
を実行するとうまくいきましたApp.jsを変更してみる
参考サイトの通りに
App.js
を変更して、iOSシミュレータの場合はcommand + Rでリロードすると、App.jsimport React, { Component } from 'react'; import { Platform, StyleSheet, Text, View } from 'react-native'; export default class App extends Component { render() { return ( <View style={styles.container}> <Text style={styles.instructions}>Hello World!</Text> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF' }, instructions: { textAlign: 'center', color: '#333333', marginBottom: 5 } });表示を変更することができました
Hot Reloadする
iOSの場合ですと、iOSエミュレータのメニューで[Hardware] > [Shake Gesture]とするとメニューが表示されるので、
「Enable Hot Reloading」を選択するとHot Reloadしてくれました簡単にコンポーネントを作成する
src/components/EmojiDict.js
ファイルを作成するsrc/components/EmojiDict.jsimport React, { Component } from 'react'; import { View, Text, StyleSheet } from 'react-native'; class EmojiDict extends Component { state = { '?': '? Smiley', '?': '? Rocket', '⚛️': '⚛️ Atom Symbol' }; render() { return ( <View style={styles.container}> <Text>{this.state['?']}</Text> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center' } }); export default EmojiDict;App.jsimport React, { Component } from 'react'; import { Platform, StyleSheet, Text, View } from 'react-native'; import EmojiDict from './src/components/EmojiDict'; export default class App extends Component { render() { return <EmojiDict />; } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF' }, instructions: { textAlign: 'center', color: '#333333', marginBottom: 5 } });
emojiのリストを
FlatList
で表示するするsrc/components/EmojiDict.jsimport React, { Component } from 'react'; import { Text, StyleSheet, FlatList } from 'react-native'; class EmojiDict extends Component { state = { '?': '? Smiley', '?': '? Rocket', '⚛️': '⚛️ Atom Symbol' }; render() { return ( <FlatList contentContainerStyle={styles.container} data={[ { key: '?', value: '? Smiley' }, { key: '?', value: '? Rocket' }, { key: '⚛️', value: '⚛️ Atom Symbol' } ]} renderItem={({ item }) => <Text>{item.value}</Text>} /> ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center' } }); export default EmojiDict;
React Nativeについてはわからないことだらけですが、とりあえず動かすことができたとてもありがたかったです。
読んでいただいてありがとうございました。m(_ _)m