- 投稿日:2019-08-30T23:20:18+09:00
Expoのアカウントを作ってReact Nativeの開発環境を整える
この記事は、「【連載】初めてのReact Native + Expo開発環境構築入門」の子記事です。環境などの条件は、親記事をご覧ください。
React Native を開発するにあたって、モジュールのバージョン管理やらデバッグ環境準備やら公開作業やらから解放されたい時には、Expoが便利です。今回は、Expoを使う前に必要な準備を整えていきます。Windows 10で実施しますが、MacやLinuxでも基本は同じです。
アカウント作成
Expoを使うには、アカウントを作成する必要があります。
まず、Expo.ioを開いて、「Create an account」をクリックします。
次に、Email、希望するユーザーID、パスワード2回を入れて、「Create your account」をクリック。
Projectsタブの中は空っぽですが、プロジェクトを追加するようなボタンはここにはありません。その代わりに「Learn how to get started」というリンクがありますね。このリンク先の説明に従って、これから開発するプロジェクトを作ることになりますが、ここではそのうちの不要ないくつかをスキップしながら進めます。
少し待ったらExpoからEmailの確認が届くので、Email本文内のリンクをクリックして確認を済ませてください。
Node.js を準備する
Expoを使った開発を行うには、まず自分のローカルPCにNode.jsをインストールする必要があります。Node.jsのバージョンは最新を使え、と書いてありますので、まだNode.jsをローカルPCにインストールしてない場合はNode.jsのサイトからLTS版をインストールしましょう。
Expo-CLI をインストールする
では、Node.jsのインストールで一緒にインストールされたnpmを使って、Expo-CLI(Expoのプロジェクトを作ったりパブリッシュしたりするためのコマンドライン機能)をローカルPCにインストールしましょう。
PowerShellを立ち上げて、
以下のコマンドを実行します。
npm install expo-cli --globalWARNはいくつかでますが、ERRORが出てなければ問題ないはずです。
Expoプロジェクトを格納するフォルダを作る
今後たくさんのExpoプロジェクトを作ることになるので、プロジェクト用フォルダを作っておきましょう。例えば
C:\ExpoProjects
など。PowerShellで、以下のようにします。> cd / > mkdir ExpoProjectsExpoプロジェクトを作成する
それではテストで1つ、HelloWorldプロジェクトを作りましょう。PowerShellで先ほど作ったプロジェクト用フォルダに入り、プロジェクトを作ります。
> cd /ExpoProjects > expo init hello-world新規プロジェクト作成では、テンプレートが選択できます。ここでは
blank
を選びます(Enterキー)。
また、プロジェクトの表示名を聞かれるので、キーボードで入力してEnterキーで確定します。ここでは「Hello World!」
またもやWARNはいくつかでますが、ERRORが出てなければ問題ないはずです。
では、Hello Worldプロジェクトを実行してみましょう。> cd hello-world > expo startファイアーウォールの警告が出た場合は、通信を許可してください。
うまくいったら以下のようにブラウザが開いて、QRコードが表示されます。Expo Clientアプリで実行を確認
実行結果の確認は、iPhoneまたはAndroidの実機にExpo Clientというアプリをインストールして、そのアプリでQRコードを読むことで実施します。(今回はiPhoneで試します)
手元のスマホにExpo Clientをインストールし、自分のExpoアカウントでログインしたのち、カメラアプリから先ほど表示したQRコードを読み込んでみてください。
Expo Clientがうまくいかない場合
Expoは、ローカルPC上で
expo start
することで、Expoサーバーとして動作します。モバイル(iPhoneやAndroid)上のExpo Clientは、そのサーバーにプロジェクトのアプリを取得しに行って、取得が終わったら実行することになります。つまり、モバイルとローカルPCが直接通信できる状態になければExpo Clientが動作しないことになります。うまくいかない場合、以下を確認しましょう。
- ローカルPCのファイアーウォールで19000や19001のポートが拒否されていないか。
- ローカルPCの参加するサブネットと、モバイルの参加するサブネットが同じか。
- ローカルネットワーク上で端末同士の通信が許可されているか。
- 仮想PC上で
expo start
したなら、仮想PCとモバイルと同じサブネットのIPアドレスに見えているか。ほかに、初回ビルドが遅すぎてClient側が待てなくてタイムアウトすることもあります。。。
いろいろ試してダメな場合は、ConnectionでTunnelを選んでやり直してみてください。初回画面
うまくいくと、Expo Clientの初期画面が表示されます。
上に表示されているのが初回だけ表示される内容で、意訳すると「デバイスを振るとこのメニューに戻れるからねー」です。Got itでとじましょう。閉じた後に実際の実行結果が表示されます。
真っ白な画面に、「App.jsを開けて開発を始めよう!」と出るだけですが、このメッセージはReactのコードから出力されています!簡単!
- 投稿日:2019-08-30T23:18:59+09:00
【連載】初めてのReact Native + Expo開発環境構築入門(Windowsベース)
この記事では、Windows 10にExpoを使ったReact Nativeの開発環境を1から構築し、サンプルアプリを作成するところまでをゴールとします。
1つの記事ですべてカバーできないので、いくつかの記事に分けて少しずつ投稿していく連載にしていきます。連載は2019年9月から10月の期間を予定しています。
なお、筆者はアメリカで開発業務を行っているため、選択するトレンドなどはアメリカ主体となります。環境
開発環境は、2018年11月時点最新の以下のソフトウェアで構築します。
- Chrome 最新 (76.0.3809.100 (64-bit))
- Visual Studio Code 最新 (1.37.1)
- Node.js + npm 最新 (10.16.3)
なぜExpo?
モバイルアプリ開発のクロスプラットフォームには、Cordova (ionicやMonaca含む)、Xamarin、最近ではFlutterなどいくつもの選択肢がありますが、その中で以下のメリットが強いときにReact Native + Expoを使うのがよいかと思います。
- とにかく開発工数を小さくしたい。
- 業務用アプリなどで、細部にはあまりこだわらない
- Web開発の知識をなるべくそのまま応用したい。
- JavaScriptの知識をメインにして作りたい
- Reactの知識がすでにある
- デバイスに依存した機能は使わないプロジェクト、またはモックアップ用
- 実機でデバッグを簡単に実施したい
- 簡単に配布したい
同じような条件では、Monacaが競合に入ってきます。実際私も過去にMonacaを使って開発しましたが、Reactのメリットを感じない場合はMonacaでもいいかなと思います。ただ、最近よくReactを使うから今さらjQueryはあまり積極的に使いたくないとか、データ取り扱いでやっぱりReactにメリットを感じるといった場合は、Expoサイコーでよいと思います。
なお、React Native (=react-native-cli)じゃなくてReact Native + Expoな理由は、この辺りの記事が参考になります。
記事一覧
連載内容の予定です。途中で大幅に変更する可能性があります。
- Expoのアカウントを作ってReact Nativeの開発環境を整える
- React Native Expoの編集環境をVisual Studio Codeで準備する
- [実機でデバッグする]
- [エミュレータでデバッグする]
- [画面構成の計画を立てる]
- [画面デザインを作成する]
- [画面間を移動する]
- [サーバーからデータを取得する]
- [QRコードからURLを取得する]
- [データの永続化を実現する]
- [社内配布方法]
- 投稿日:2019-08-30T23:09:24+09:00
Java Script(条件分岐、オブジェクト、for文、関数の定義方法)について
コンソールに記入/表示する
JavaScriptはブラウザ上で動く言語であるため検証ツールからコンソールを開く事でコードが書く事ができる。変数宣言の際に定義する必要がある
let 後で置き換える事のできる変数宣言
conset 後で置き換える事ができない変数宣言条件分岐の仕方
条件式は()で囲む
処理内容は{}で囲む
複数条件の際はelse ifとするlet num = 60; if (num % 15 == 0) { console.log(num + 'は3と5の倍数です'); } else if (num % 3 == 0) { console.log(num + 'は3の倍数です'); } else if (num % 5 == 0) { console.log(num + 'は5の倍数です'); } else { console.log(num + 'は3の倍数でも、5の倍数でもありません'); }オブジェクト
データのまとまりのこと。配列は順番での管理だが、オブジェクトはデータを名前と値をセットで管理できる。このセットをプロパティという。
作成方法には{}を使う例1)
let a = {};例2)
let a = {name: 'imada'}; プロパティ名 値値の取得、変更方法
名前を取り出したい場合
let a = {name: 'imada', age: '25'}; console.log(a.name);名前をtanakaに変更したい場合
let a = {name: 'imada', age: '25'}; a.name = 'tanaka'; console.log(a.name);for文
繰り返し処理はfor文を使う
例
num = 1 for (let i = 0; i < 100; i += 1){ console.log(num + '回目') num += 1 }関数の定義方法
function文
function 関数名(引数){ 処理内容 }※注意)引数が無くても記入しなくてはならない
return文について
Rubyでは関数において最後の戻り値が関数の戻り値として返すがJavaScriptではreturnを明示する必要がある関数の定義種類
1.関数宣言
2.関数式(無名関数)関数宣言 function hello(){ console.log('hello'); } 関数式(無名関数) let hello = function(){ console.log('hello'); }役割に大きな違いはないが、関数式にする事で代入や他の関数に渡しやすくなる
- 投稿日:2019-08-30T22:05:15+09:00
WYSIWYGなMarkdownエディターを目指してContentEditableおよびexecCommandと真っ向勝負してみる(part 1)
今更ながらマークダウン記法について学び、とても感動しています。
最近はメモや簡単な文章作成にはマークダウンを使っています。そんな時、自分の理想的なマークダウンエディタがブラウザ上で動けばもっと便利になると思い、少し試してみました。
修正点やもっとこうした方がいいなどございましたらご教授いただけると幸いです。m(_ _)m
どんなエディター?
僕がイメージしているエディターは、よくある画面が二分割されてエディター部分とプレビュー部分に分かれているタイプでは無く、書いた部分がそのままHTMLに変換されるものをいいます。
有名なソフトだと、Typora
などがあるでしょうか。ざっと調べてもいろいろな実現方法があるみたいですが、今回は一番単純なようで実は大変そうな
contentEditable
とexecCommand
を用いた実装について試行錯誤しながら試してみました。contentEditableの仕様
実装に当たってまずは
contentEditable
の仕様を調べてみます。
以下のファイルでテストをしてみました。contenteditable-test.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Test</title> </head> <body> <div contenteditable='true'> <h1>h1</h1> <h2>h2</h2> <div>div</div> <ul> <li>1</li> <li>2</li> <li>3</li> </ul> <blockquote>block quote</blockquote> </div> </body> </html>マークダウン記法でいうと、文頭に所定のマークを入れる要素たちです。
とりあえずここでこのページの簡単な挙動を調べることができます。実際にお試しいただけるとわかりやすいかと思いますが、特徴をまとめるとこんな感じです。
なお、僕の環境では最新版のChromeをMac上で使っています。良さそうな点
Shift+Enter
では同じエレメント内で改行、Enter
のみだと今のエレメントの次にdiv
が挿入される- リストを
Enter
のみで改行すると次の行にもリストが自動で挿入される。二度Enter
を押すと通常のdiv
が挿入される少し気になった点
blockquote
ではEnter
のみを押しても、div
の代わりにblockquote
が挿入され抜け出せない。こんな感じでしょうか。
意外と標準機能でマークダウンエディターっぽい動きができている気がします。
あとはマークダウン記法を読み取って、その都度DOMを置き換えればいいような気がしていました。この時までは...
とりあえず作ってみた
マークダウン記法の判定
さて、先述の特徴を踏まえて、文頭数文字で判断できる系の以下の四つを実装してみます。
- 見出し:#, ##, ###, ...など
- リスト:-
- 番号付きリスト:1.
- 引用文:>
キャレットのある位置行の文字は以下のコードで取得できます
window.getSelection().getRangeAt(0).endContainer.data
keyup
イベントが起こるたびに現在の行の文字列を取得し、マークダウン記法になっているかを判定します。const element = document.getElementById('markdown') element.addEventListener('keyup', function (event) { const currentLine = window.getSelection().getRangeAt(0).endContainer.data if(currentLine.match(/^#{1}\xA0$/)){ // 見出し // '# 'を<h1>に置き換える } // // 見出し2〜6は{}内の数字を変えるだけ // else if (currentLine.match(/^>\xA0$/)){ // 引用 // '> 'を<blockquote>に置き換える } else if (currentLine.match(/^\d+\.\xA0$/)) { // 順序付きリスト // '- 'を<ul>に置き換える } else if (currentLine.match(/^[\-+*]\xA0+$/)) { // リスト // '1. 'を<ol>に置き換える } })はい、これで場合分けができました。
マークダウン記法の変換
contentEditable
ではEnter
を押すと改行されてdiv
が挿入されます。
このdiv
をマークダウン記法に対応するDOMに変更するためにexecCommand
を用います。以下は<h1>に変換する例です。
document.execCommand('formatblock', false, 'h1')第一引数の
formatblock
はMDN web docsによると、formatBlock
現在の選択範囲を含む行の前後に HTML ブロックレベル要素を追加し、すでに存在する場合は、その行を含むブロック要素に置き換えます (Firefox では <blockquote> は例外です。 — これはブロック要素を囲みます)。引数としてタグ名の文字列が必要です。実質的にすべてのブロックレベル要素を利用することができます。
(Internet Explorer および Edge は見出しタグ H1–H6, ADDRESS, PRE のみに対応しており、 "<H1>" のように山かっこで囲む必要があります。)とのことです。
要するに、今キャレットがあるDOMを指定の要素に変換するってことだと思います。
(詳しくはわからないので、ご教授いただけると幸いです。)なお、リストと順序付きリストは別のコマンドを用います。
それぞれ以下のようになります。// リスト document.execCommand('insertUnorderedList') // 順序付きリスト document.execCommand('insertOrderedList')マークダウン記法の削除
さて、前述までの方法でマークダウン記法を判定して、HTML要素の変換がすることができましたが、文字として打ち込んだマークダウン記法は残ったままです。
これを削除します。要素内のテキストを変更するといえば、
innerText
が思いつきます。
キャレットがある要素のinnerText
を取得することでうまくいきそうです。先述した
window.getSelection().getRangeAt(0).endContainer
では現在のテキスト部分、つまり#text
が取得されます。
この親要素が現在の要素になります。
すなわち、マークダウン記法を削除するには以下のコードを用います。window.getSelection().getRangeAt(0).endContainer.parentNode.innerText = ''とりあえずできた
以上のことをまとめると以下のコードになりました。
const element = document.getElementById('markdown') element.addEventListener('keyup', function (event) { const currentLine = window.getSelection().getRangeAt(0).endContainer.data if(currentLine.match(/^#{1}\xA0$/)){ // 見出し document.execCommand('formatblock', false, 'h1') clearCurrentLine() } // // 見出し2〜6は{}内の数字を変えるだけ // else if (currentLine.match(/^>\xA0$/)){ // 引用 document.execCommand('formatblock', false, 'blockquote') clearCurrentLine() } else if (currentLine.match(/^\d+\.\xA0$/)) { // 順序付きリスト document.execCommand('insertOrderedList') clearCurrentLine() } else if (currentLine.match(/^[\-+*]\xA0+$/)) { // リスト document.execCommand('insertUnorderedList') clearCurrentLine() } }) const clearCurrentLine = () => { window.getSelection().getRangeAt(0).endContainer.parentNode.innerText = '' }デモはこちらから
次回に向けての修正点
さて、デモをお触りいただくとわかると思いますが、このコードChromeではうまく動きませんorz
(Safariでは動きました。他のブラウザは未検討)
要素は挿入されますが、キャレットが直前の要素に飛んでしまい、しかも挿入された要素にはどうやってもキャレットを合わせることができません。これを踏まえた以下が次回に向けての修正点です。
- 挿入された要素にキャレットを合わせることができない
blockquote
はEnter
を押しても抜け出すことができない次回はこれらの問題点を修正するところから始めます。
アドバイスなどございましたら、コメントください。m(_ _)m
- 投稿日:2019-08-30T21:40:36+09:00
Put a semicolon there
備忘録です。
'use strict'; let range = { from: 1, to: 5, [Symbol.asyncIterator]() { return { current: this.from, last: this.to, async next() { await new Promise(resolve => setTimeout(resolve, 1000)); if (this.current <= this.last) return { done: false, value: this.current++ }; else return { done: true }; } }; } }; // here (async () => { for await (const value of range) console.log(value); })();here のセミコロンを忘れると、
TypeError: {(intermediate value)(intermediate value)(intermediate value)} is not a function
というエラーになります。javascript の行解釈のヤなところorz
- 元ネタ: 非同期イテレーションとジェネレータ
参考にしたページ
- 投稿日:2019-08-30T18:30:52+09:00
Jestで非同期処理を順番に評価したい場合の対処法
対象
下記のような非同期処理(Vueのmethodsの中、signInアクションはPromiseを返す)を順番(例えば、dispatch評価後にrouterが呼ばれている事を確認したいなど)に評価したい場合、どうしたら良いかわからない方向け
this.$store.dispatch('signIn', { loginId, password }) .then(() => { this.$router.push('/') }) .catch((error) => { this.error = error.message } )問題点
下記のテストコードだとdipatch実行後,then後の処理に移るまでに後者が評価されてしまいテストが失敗する。
expect($store.dispatch).toHaveBeenCalledWith('signIn', testUserInfo) expect($router.push).toHaveBeenCalledWith('/')解消方法
前者のPromiseが評価された後に後者が評価されるようにすれば良い。
await expect($store.dispatch).toHaveBeenCalledWith('signIn', testUserInfo) expect($router.push).toHaveBeenCalledWith('/')他、
もう少しスマートで可読性に優れた方法があれば教えて頂けると嬉しいです。
- 投稿日:2019-08-30T17:19:35+09:00
Object等の呼び出しでkeyに動的な変数を使う [TypeScript]
やりたいこと
object等の呼び出しで
Object[key]
のようにするとき、key
に動的な変数を入れたい。問題・エラー
何も考えずに下記のように書くと、
const object = { aaa: 'aaaa', bbb: 'bbb', }; // keyには動的に生成された値 const key: string = receivedStringValue; const value = object[key];下記のエラー
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ aaa: string; bbb: string; }'.解決策
object
に対して型を定義するよくない例
このエラーは、
key
に規則性がない(any型)、であるためにでているエラーなので、下記のような型定義ではエラーは治らない。interface AB { aaa: string; bbb: string; } const data: AB = { aaa: 'aaaa', bbb: 'bbb', }; const key: string = receivedStringValue; const value = data[key];結論
key
の型がわかるようなobject
の型を定義するinterface StringKeyObject { // 今回はstring [key: string]: any; } const object: StringKeyObject = { aaa: 'aaaa', bbb: 'bbb', }; // keyには動的に生成された値 const key: string = receivedStringValue; const value = object[key];(
StringKeyObject
... もっと良い命名とかあれば教えてください…)参考
Typescriptで、Object[key]とすると出るIndex signature of object type implicitly has an 'any' type.を正しく回避する
- 投稿日:2019-08-30T17:13:59+09:00
マウスカーソルを目で追うGopherくんのサイトをDockerで開発してGitHub Pagesで公開した
完成品
https://cajonito.github.io/gopher/
大変可愛らしい。ソースコードはGitHubで公開されています。
https://github.com/cajonito/gopherこの記事
フロントの練習のために上記サイトを作成する過程を紹介します。
Webフロントエンドのプログラミングの練習のために作りましたが記事の内容は環境構築の話がメインです。
登場するものは以下の通りです。
- GitHub Pagesでサイトを公開
- Dockerで開発環境を構築
- クリエイティブ・コモンズ・ライセンス
- HTML + CSS + Javascript + jQuery でGopherくんを作成
動機と構想
HTMLとCSSの練習がしたい。何かサイトを作ろう。
Gopherくんがマウスカーソルを目で追いかける様を見たいから作ってみよう。どこかで公開したいけどお金はかけたくない。
出来るだけストレス無く開発したい。
絵描けないけどGopherくんの画像をどうやって用意しよう?GitHub Pagesで公開
静的なサイトなら無料で公開できる環境は多くありますが、個人的にはGitHub Pagesが一番楽だと思います。
Hosted directly from your GitHub repository. Just edit, push, and your changes are live.
ということで、GitHubのリポジトリを作っただけでページが公開されて、pushするだけでデプロイされます。すごく楽です。
一応細かい制限はあるみたいです。
https://help.github.com/ja/articles/what-is-github-pages無料プランの場合、GitHub Pagesはパブリックリポジトリのみで利用出来るようです。
Hello Wolrd
今回は任意のリポジトリ名でdocsディレクトリ以下を公開するという設定にします。
(他の公開形式もありますが今回は割愛します。)今回はgopherというパブリックリポジトリを作成しました。
$ git clone https://github.com/(ユーザー名)/(リポジトリ名).git $ cd (リポジトリ名) $ mkdir docs $ echo 'hello world' > docs/index.htmlツリー構造はこんな感じ
$ tree . ├── README.md └── docs └── index.html 1 directory, 2 files作成したファイルをmasterブランチにpushしておきます。
次にGitHubのリポジトリの設定からmasterブランチのdocsディレクトリ以下を公開するよう設定します。
docsディレクトリが存在しないとこの選択肢は選べないので注意です。これで以下のURLにアクセスするとdocs以下に配置したindex.htmlが表示されるはずです。
https://(ユーザー名).github.io/(リポジトリ名)/
あとはdocs/以下を編集してmasterブランチにpushするだけで上記ページは更新されていきます。Dockerで開発環境を構築
何か編集するたびにpushして確認するのも大変なのでローカルで開発環境を作ります。
Dockerを使うとさくっとサーバーを建てて、必要なくなったらさくっと消せるので便利です。DockerとDockerComposeをインストール
公式サイトより入手してください。
https://www.docker.com/設定ファイルを作成
今回はNginxコンテナをローカルで建てて利用します。
docker-compose.ymlとnginx.confを以下の位置に作成します。$ tree . ├── README.md ├── docker-compose.yml ├── docs │ └── index.html └── nginx.conf 1 directory, 4 filesまずnginx.confを作ります。
僕は詳しくないのですが、ローカルで自分で使うだけなので最低限必要そうなものを設定します。nginx.confserver { listen 80; server_name _; root /var/www/html; index index.html; charset utf-8; }次にdocker-compose.ymlを作成します。
DockerComposeは本来複数のコンテナを管理するためのものだった気がしますが、使うコンテナが1つでも簡単に構築、削除が出来るので今回みたいにサーバーを建てる用途の時はよく利用します。docker-compose.ymlversion: '3' services: nginx: image: nginx:latest ports: - 8080:80 volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf - ./docs:/var/www/html
- image
- nginxコンテナの最新バージョンを使います。
- バージョンを指定することもできます。
- ports
- ホストのポート8080をコンテナの80に関連付けます。
- ホストで使いたいポートとnginx.confで指定したポートに応じて変更してください。
- volumes:
- ホストのnginx.confをコンテナ内の所定の位置にマウントします
- ホストのdocs/以下をnginx.confで指定したrootディレクトリにマウントします
ポイントはGitHubPagesで公開されるdocs/以下をNginxのルートディレクトリにマウントしていることです。
こうすることでローカルではNginxコンテナで表示を確認し、そのままGitHubにpushして公開する事が出来ます。これらの設定ファイルも同じリポジトリでバージョン管理すれば楽ちんです。
Dockerコンテナを起動
docker-compose.ymlが配置されたディレクトリで以下のコマンドを実行して起動。
$ docker-compose up -d以下にアクセスするとGitHub Pagesと同じ表示がされるはずです。
http://localhost:8080終了する時は同じくdocker-compose.ymlが配置されたディレクトリで以下のコマンドです。
$ docker-compose downあとはdocs/以下を編集して、上記URLを開いて確認を繰り返して開発していきます。
公開したくなったらmasterブランチにpushしましょう。マウスカーソルを目で追うGopherくんを作る
お待ちかねです。
画面に大きくGopherくんを表示して、マウスカーソルを目で追いかける感じにしたいです。画像の用意
https://github.com/golang-samples/gopher-vector
こちらにCC BY 3.0ライセンスで公開されたGopherくんの画像がありました。クリエイティブ・コモンズ・ライセンスは以下のサイトが非常にわかりやすかったです。
https://creativecommons.jp/licenses/クリエイティブ・コモンズは、クリエイティブ・コモンズ・ライセンス(CCライセンス)を提供している国際的非営利組織とそのプロジェクトの総称です。
CCライセンスとはインターネット時代のための新しい著作権ルールで、作品を公開する作者が「この条件を守れば私の作品を自由に使って構いません。」という意思表示をするためのツールです。「CC BY」は
原作者のクレジット(氏名、作品タイトルなど)を表示することを主な条件とし、改変はもちろん、営利目的での二次利用も許可される最も自由度の高いCCライセンス。
とのことなので、サイト上にクレジットを表示して利用させて頂く事にしました。
目を動かすために改変をしますがそれもOKのようです。画像の加工
元の画像のSVGをテキストファイルで編集して目とそれ以外で2ファイル用意しました。
要素を削除したりサイズをいじるだけなのでフィーリングでなんとかなりました。ページの作成
最終的な構成は以下のようになりました。
$ tree . ├── README.md ├── docker-compose.yml ├── docs │ ├── css │ │ └── style.css │ ├── image │ │ ├── gopher.svg │ │ └── gopher_eye.svg │ ├── index.html │ └── javascript │ └── main.js └── nginx.conf 4 directories, 8 filesソースコードは悪戦苦闘した結果以下のようになりました。
色々セオリーを外してるかもしれませんがとりあえず動きました。
マウスカーソルのx, y座標を入力したら目の位置を計算するコードを書いてjQueryで制御するのが簡単かなと思いました。
index.html
index.html<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <link rel="stylesheet" href="./css/style.css" /> <title>Gopher Eye Tracking</title> </head> <body> <div id="page"> <header> <div id="header_inner"> <p> Gopherくんは <a href="http://reneefrench.blogspot.com/" target="_blank">Renee French</a> さんがデザインしました。 </p> <p> 画像は <a href="https://twitter.com/tenntenn" target="_blank">Takuya Ueda</a> さんが作成したものを元に加工したものです。 </p> </div> </header> <main> <div id="gopher_image"> <div id="gopher"> <img src="./image/gopher.svg" alt="gohperくん" /> <div id="gopher_right_eye"> <img src="./image/gopher_eye.svg" alt="gohperくんの右目" /> </div> <div id="gopher_left_eye"> <img src="./image/gopher_eye.svg" alt="gohperくんの左目" /> </div> </div> </div> </main> <footer></footer> </div> <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha256-pasqAKBDmFT4eHoN2ndd6lN370kFiGUFyTiUHWhU7k8=" crossorigin="anonymous" ></script> <script src="./javascript/main.js"></script> <script> var gopher = new Gopher( "#gopher", "#gopher_right_eye", "#gopher_left_eye" ); $(window).on("load mousemove", function(e) { var cX = e.clientX; var cY = e.clientY; if (!cX) { cX = $(window).width() / 2; cY = $(window).height() / 2; } gopher.update(cX, cY).render(); }); </script> </body> </html>
css/style.css
style.csshtml, body, div, span, iframe, h1, h2, h3, h4, h5, h6, p, a, img, ul, li, table, tr, th, td, tbody, footer, header, main, nav, section, article { margin: 0; padding: 0; border: 0; font-weight: normal; list-style: none; text-decoration: none; } body { font-family: "ヒラギノ角ゴ Pro W3", "Hiragino Kaku Gothic Pro", "メイリオ", Meiryo, Osaka, "MS Pゴシック", "MS PGothic", sans-serif; width: 100%; } #header_inner { padding: 10px; font-size: 10px; position: fixed; top: 0%; right: 0%; z-index: 100; } #gopher_image { overflow: hidden; position: fixed; bottom: 0%; width: 100%; height: 70%; } #gopher { position: absolute; left: 50%; transform: translateX(-50%); width: 1000px; z-index: 1; } #gopher_right_eye { position: absolute; text-align: center; left: calc(50% - 17%); top: 120px; width: 120px; z-index: 2; } #gopher_left_eye { position: absolute; text-align: center; left: calc(50% + 14%); top: 120px; width: 120px; z-index: 2; } img { width: 100%; }
javascript/main.js
main.jsclass Gopher { constructor(gopherSel, rightPupilSel, leftPupilSel) { this._dom = $(gopherSel); var width = this._dom.width(); var height = this._dom.height(); this.x = width / 2; this.y = height / 2; var range = width * 0.07; this.rightEye = new Eye( rightPupilSel, this.x - width * 0.17, this.y - height * 0.33, range ); this.leftEye = new Eye( leftPupilSel, this.x + width * 0.14, this.y - height * 0.34, range ); } update(x, y) { var offset = this._dom.offset(); var baseX = offset.left; var baseY = offset.top; this.rightEye.update(x - baseX, y - baseY); this.leftEye.update(x - baseX, y - baseY); return this; } render() { this.rightEye.render(); this.leftEye.render(); return this; } } class Eye { constructor(selector, x, y, range) { this._pupil = new Pupil(selector, x, y); this.x = x; this.y = y; this.range = range; } get pupil() { return this._pupil; } update(x, y) { const distanceRatio = 0.3; var distanceX = x - this.x; var distanceY = y - this.y; var distance = Math.min( Math.sqrt(distanceX ** 2 + distanceY ** 2) * distanceRatio, this.range ); var rad = Math.atan2(distanceY, distanceX); var newX = this.x + Math.cos(rad) * distance; var newY = this.y + Math.sin(rad) * distance; this.pupil.update(newX, newY); return this; } render() { this.pupil.render(); return this; } } class Pupil { constructor(selection, x, y) { this._dom = $(selection); this.update(x, y); } update(x, y) { this.x = x - this._dom.width() / 2; this.y = y - this._dom.height() / 2; return this; } render() { this._dom.css({ top: this.y + "px", left: this.x + "px" }); return this; } }感想
フロントエンドの練習をして公開するならDockerとGitHubPagesは手軽で強力だと思いました。
やっぱり手を動かすと得られるものは多いと思うので、手を気持ちよく動かせる環境は大事です。
- 投稿日:2019-08-30T17:04:12+09:00
【LT資料】Node.jsとは?
自己紹介
松田尚也(24)
(一応)二年目エンジニア最近やったこと・やっていること
- 金融系のREST APIの開発
- スマホアプリ設計 / 開発
得意・好き
- JavaScript
- Node.js
- CI/CD (Azure)
OAuth2.0/(Open ID Connect)
♡ビール♡
(最近サッポロビールからアサヒ派になりました)
What Node.js?
What Node.js
- だいたい ServerSide JavaScript 実行環境
- だいたい イベントループ
- だいたい ノンブロッキングI/O と 非同期処理
だいたい ServerSide JavaScript 実行環境
ServerSide JavaScript 実行環境
従来ブラウザで動いていたJavaScriptをServerでも動かせるようにした
WebServer + JavaScript の実行環境がセットになった感じ
node
コマンドを使用できる
サーバーサイドというよりは、実行環境の提供
node hoge.js
これだけで対象ファイルのJavaScriptコードが実行されるnpm(Node Package Manager)
非常にかんたんにライブラリ管理を行える
だいたいイベントループ
イベントループ と 背景
Node.jsの開発が開始された2009年
C(クライアント)10K(1万台)問題
クライアントの同時接続数が1万件を超えると、CPU等のリソースは余裕があるにも関わらず、
同時接続のオーバーヘッドにより、速度が遅くなってしまう問題のこと
同時実行(並行処理)の限界
Web及びフロントの発展
↓
Serverに対するリクエスト数が増える
↓
1リクエストに対して1スレッドの処理方式だと、メモリの消費が激しい
↓
結果
1万クライアントを超えると処理が遅くなる
イベントループ
- 一つのスレッドでイベント
- イベントキューには Node.jsより更に下のOS等がやってくれる処理を登録
イベントループは常にイベントキューに処理がないか監視し、ある場合は実行する
あれ。。。
遅いんじゃない???
ノンブロッキングI/O と 非同期処理
ノンブロッキングI/O と 非同期処理
同期的なイベントループ方式だと、、、
I/Oが発生するたびに処理がブロック(何もできない状態)されてしまう。
ノンブロッキングI/O と 非同期処理
- I/Oが発生する処理を行うと直ちに処理が返ってくる
- でも実際には、イベントキューの中で実行待ち状態
- 実際の処理が終わると、予め処理が終了した際に行う
call back
処理が呼び出される
What Node.js
- ServerSide JavaScript 実行環境
- イベントループでシングルプロセス
- ノンブロッキングI/O で 非同期処理
- 投稿日:2019-08-30T16:50:03+09:00
【画像を】読み込みが遅い画像をエミュレートする【重くする】
ローカルディスクでは読み込みが速すぎる画像を遅くする
画像の遅延読み込みのコードをを書く際に、ローカルで確認するとき、ディスクから読み込むとあまりにも速すぎて動作の確認が難しいことがあります。そのような際に使えるサービスを見つけました。
Deelay.me
- HTTPの応答に遅延を発生させるProxy
302 Moved Temporarily
で転送- 遅延を指定可能(ミリ秒単位)
- セルフホスティングも可能(npmパッケージ)
- 別に画像じゃなくても使える
使い方
遅くする画像を
http://deelay.me/[遅延(ミリ秒)]/[画像URL]
として参照させるサンプル(5秒)
- 投稿日:2019-08-30T16:13:16+09:00
【GAS×Vue.js Webアプリ】google.script.run で async/await を使う
はじめに
某デジタルメディアでGASを使ってを社内ツールを開発している情報系学部4年生です。
WebアプリケーションをVue.jsで書き、バックエンドや各種APIコールなどをGoogle Apps Scriptで処理し、データベースにスプレッドシートを使用することでインフラコスト0で運用できる。Webアプリケーション側からGAS内のコードを走らせてその戻り値を受け取るために
google.script.run を使用すると楽に実装できるが、サーバサイドのコードを走らせているため、非同期処理となり、複数のスプレッドシートを読み込んで実装する場合はコールバック地獄が発生する。その問題を解決できたので、簡単にメモ
参考にしたサイトコード
フロントのVue.jsの処理を以下のように実装する。
hoge.jslet db = new Vue({ el: '#app', data: { sheet_a: null, sheet_b: null, }, methods: { loadData: async function() { //ここに書くと順番に実行される const sheet_a = await this.getSheetA(); console.log('成功!取得したデータ: ' + JSON.stringify(sheet_a)); this.sheet_a = sheet_a; const sheet_b = await this.getSheetB(); console.log('成功!取得したデータ: ' + JSON.stringify(sheet_b)); this.sheet_b = sheet_b; //全部のデータを取得した後に実行したい処理を下に記述 }, getSheetA: function() { //コード.js内のgetSheetA()を実行 return new Promise((resolve, reject) => { google.script.run .withSuccessHandler((result) => resolve(result)) .withFailureHandler((error) => resolve(error)) .getSheetA(); }); }, getSheetB: function() { //コード.js内のgetSheetB()を実行 return new Promise((resolve, reject) => { google.script.run .withSuccessHandler((result) => resolve(result)) .withFailureHandler((error) => resolve(error)) .getSheetB(); }); } }, //マウント時 mounted: function() { //マウント時にシートを取得 this.loadData(); }, });一応GAS側で実行されるコードも記述しておきます。
※本来はdoGet()
関数内にHTMLファイルを返すためのコードを書かないといけません。
GAS×Vueで簡単なWebアプリを作る方法はこちらコード.gsfunction getSheetA() { var sheet = SpreadsheetApp.getActive().getSheetByName('シート1'); var rows = sheet.getDataRange().getValues(); var keys = rows.splice(0, 1)[0]; return rows.map(function(row) { var obj = {}; row.map(function(item, index) { obj[String(keys[index])] = String(item); }); return obj; }); } function getSheetB() { var sheet = SpreadsheetApp.getActive().getSheetByName('シート2'); var rows = sheet.getDataRange().getValues(); var keys = rows.splice(0, 1)[0]; return rows.map(function(row) { var obj = {}; row.map(function(item, index) { obj[String(keys[index])] = String(item); }); return obj; }); }
- 投稿日:2019-08-30T16:03:11+09:00
レコード詳細画面でボタンを置いて特定のフィールドをmdファイルとしてダウンロード
kintoneの詳細画面とかにボタンを置いて、押すと任意のフィールドをmdファイルとしてダウンロード。
ダウンロードするファイル名はYYYY-MM-DD形式のフィールド(今回はルックアップ)の値を取得して、いい感じにトリミングして「YYYYMMDD.md」としてダウンロード
memo_markdown
この部分を任意のコピーしたいフィールドのフィールドコードに変更。- ファイル名につかう日付フィールド(今回はルックアップ)を
release_date
というフィールドコードで配置- 「スペース」フィールドの配置が必要。今回は
md_link_sapce
というフィールドコードで配置。- jQueryは読み込み必要。
sample.js(function($) { 'use strict'; let events = [ 'app.record.detail.show' ] kintone.events.on(events, function(event){ let el = kintone.app.record.getSpaceElement('md_link_sapce'); $('<input />', { 'type': 'button', 'name': 'c_button', 'value': 'Download', 'class': 'create_button', }).on('click', function (resp) { var kin_stream = event.record.memo_markdown.value; var r_date = event.record.release_date.value; r_date = r_date.replace( /-/g , "" ); SaveToFile(r_date + ".md",kin_stream); }).appendTo(el); }); function SaveToFile(FileName,Stream) { if (window.navigator.msSaveBlob) { window.navigator.msSaveBlob(new Blob([Stream], { type: "text/markdown" }), FileName); } else { var a = document.createElement("a"); a.href = URL.createObjectURL(new Blob([Stream], { type: "text/markdown" })); //a.target = '_blank'; a.download = FileName; document.body.appendChild(a) // FireFox specification a.click(); document.body.removeChild(a) // FireFox specification } } })(jQuery);
- 投稿日:2019-08-30T16:03:11+09:00
kintoneの特定のフォールドをmdファイルとしてダウンロード
kintoneの詳細画面とかにボタンを置いて、押すと任意のフィールドをmdファイルとしてダウンロード。
ダウンロードするファイル名はYYYY-MM-DD形式のフィールド(今回はルックアップ)の値を取得して、いい感じにトリミングして「YYYYMMDD.md」としてダウンロード
memo_markdown
この部分を任意のコピーしたいフィールドのフィールドコードに変更。- ファイル名につかう日付フィールド(今回はルックアップ)を
release_date
というフィールドコードで配置- 「スペース」フィールドの配置が必要。今回は
md_link_sapce
というフィールドコードで配置。- jQueryは読み込み必要。
sample.js(function($) { 'use strict'; let events = [ 'app.record.detail.show' ] kintone.events.on(events, function(event){ let el = kintone.app.record.getSpaceElement('md_link_sapce'); $('<input />', { 'type': 'button', 'name': 'c_button', 'value': 'Download', 'class': 'create_button', }).on('click', function (resp) { var kin_stream = event.record.memo_markdown.value; var r_date = event.record.release_date.value; r_date = r_date.replace( /-/g , "" ); SaveToFile(r_date + ".md",kin_stream); }).appendTo(el); }); function SaveToFile(FileName,Stream) { if (window.navigator.msSaveBlob) { window.navigator.msSaveBlob(new Blob([Stream], { type: "text/markdown" }), FileName); } else { var a = document.createElement("a"); a.href = URL.createObjectURL(new Blob([Stream], { type: "text/markdown" })); //a.target = '_blank'; a.download = FileName; document.body.appendChild(a) // FireFox specification a.click(); document.body.removeChild(a) // FireFox specification } } })(jQuery);
- 投稿日:2019-08-30T16:00:46+09:00
ドットインストールさんの動画をいくつか視聴して、オリジナルで作ってみた「ラストババ抜き」
はじめに
こんにちは。私は今夏休みなので、たくさん時間を確保できる状況にあります。
そこで、HTML、CSS、JavaScriptをしっかり勉強しようと考えました。
私が勉強する教材として利用したのがドットインストールです。そして今回、ある程度、理解することができ、オリジナルでゲームを作ることができたので、記事にしました。
久しぶりの投稿になりますが、よろしくお願いします。
視聴したレッスン
- 【旧版】はじめてのHTML
- 【旧版】はじめてのCSS
- 【旧版】JavaScript基礎文法徹底マスター
- 【旧版】JavaScriptでおみくじを作ろう
- 実践!ウェブサイトを作ろう
- 実践!スマートフォンサイトを作ろう
- 詳解HTML 基礎文法編
- 詳解CSS 基礎文法編
- はじめてのJavaScript
- 詳解JavaScript DOM編
- JavaScriptでスライドショーを作ろう
- JavaScriptでスロットマシンを作ろう
- JavaScriptでハイアンドローゲームを作ろう
- JavaScriptでカウントダウンタイマーを作ろう
- JavaScriptで5秒当てゲームを作ろう
- JavaScriptで作る誕生日診断
- JavaScriptで作る王様ゲーム
上記に書いてあるレッスンを視聴しました。
それぞれの文法については、2~3周ほどしました。
JavaScriptに関しては、私は他の言語も触っていましたので、スムーズに理解することができました。環境
- Windows 10 home
- Google Chrome
「ラストババ抜き」の制作
このゲームはトランプの「ババ抜き」の最後の部分を少し改良して再現したゲームになります。相手のカード枚数が残り2枚で、そこから「JOKER」のカードではなく、「あたり」のカードを選ぶものです。
こちらから遊ぶことができます。→ ラストババ抜き
ソースコード
Githubにもあげてありますが、こちらにもそれぞれ載せておきます。
index.html<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>ラストババ抜き</title> <link rel="stylesheet" href="styles.css"> </head> <body> <div class="container"> <h1>dealer</h1> <div class="dealer"> <ul> <li>?</li> <li>?</li> </ul> </div> <div id="resetButton">もう一度!</div> <div id="result"></div> <h1>player</h1> <div class="player">あたりを引け!</div> </div> <script src="main.js"></script> </body> </html>styles.cssbody{ background-color:#088A4B; } .container{ width:500px; margin:100px auto; font-weight:bold; font-size: 24px; text-align:center; } h1{ border:5px solid #fff; margin:0 0 30px; } ul{ display:flex; justify-content:space-between; list-style:none; padding:0; margin:0; } li,.player{ background-color:skyblue; width:100px; height:160px; border:5px solid #fff; border-radius:5px; line-height:160px; user-select:none; } li:hover{ opacity:0.8; cursor:pointer; user-select:none; } .player{ margin:0 auto; font-size: 12px; } #result{ margin:50px 0; height:70px; font-size: 48px; line-height:70px; } #resetButton{ width:100px; height:100px; line-height:100px; font-size:18px; border-radius:50%; background-color:purple; border:3px solid #fff; float:right; position:relative; top:30px; right:60px; } #resetButton:hover{ opacity:0.8; cursor:pointer; user-select:none; } .hidden{ display:none; }main.js"use strict"; { const d_li=document.querySelectorAll(".dealer li"); const resetButton=document.getElementById("resetButton"); const result=document.getElementById("result"); window.onload=function(){ init(); } for(let i=0;i<d_li.length;i++){ d_li[i].addEventListener("click",()=>{ if(!resetButton.classList.contains("hidden")){ return; } let rdNum=Math.floor(Math.random()*d_li.length) if(i === rdNum){ gameClear(i);//あたり }else{ gameOver(i);//はずれ } resetButton.classList.remove("hidden"); }); } resetButton.addEventListener("click",()=>{ init(); }); function init(){ //初期化 for(let i=0;i<d_li.length;i++){ d_li[i].textContent="?"; } resetButton.classList.add("hidden"); result.textContent=""; } function gameClear(i){ //ゲームクリア d_li[i%2].textContent="あたり"; d_li[(i+1)%2].textContent="JOKER"; result.textContent="あたり!" } function gameOver(i){ //ゲームオーバ― d_li[i%2].textContent="JOKER"; d_li[(i+1)%2].textContent="あたり"; result.textContent="ざんねん!" } }以上がソースコードになります。
もしなにかあればコメントをください。できる限り対応します。終わりに
以上で「ラストババ抜き」の制作は終了です。
時間はかかりましたが、やっとオリジナルで作れたので、嬉しいです。知識を入れていく際なのですが、ドットインストールでは手を動かしながらできるので、効率よく勉強できると思います。
個人的に、CSSの学習は大変でした。次々に新しいことが出てくるので驚きの連続です。今後も、HTML、CSS、JavaScriptを使ってオリジナルのものを作っていく予定なので、よろしくお願いします。
ここまで読んでいただき、ありがとうございました。
- 投稿日:2019-08-30T15:46:25+09:00
Vue.jsによるモーダルウィンドウのサンプル (スタンダードなUIからVueに慣れる③)
Vue.jsでモーダルウィンドウを実装してみた
Vue.jsによるタブ切り替えの実装、Vue.jsによるカルーセルスライダーの実装に引継ぎスタンダードなUIに
モーダルウィンドウがありますが、それもVue.jsでは簡単に実装する事ができます。
今回は「vue-js-modal」を使ってモーダルウィンドウを実装してみます。「vue-js-modal」とは
Vue.jsで簡単にモーダルウィンドウを導入できるプラグインです。
導入は、npmコマンドで導入する方法とCDNより読み込む方法があります。
今回はCDNで簡易的に導入します。See the Pen GRKvKME by YusukeIkeda (@YusukeIkeda) on CodePen.
npmで導入する場合は下記コマンドを実行
npm install vue-js-modal --saveCDNで導入する場合は下記JSを読み込む
https://cdn.jsdelivr.net/npm/vue-js-modal@1.3.28/dist/index.min.jsHTML
HTML<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>vue-js-modal</title> <style> .modal-area-inner{ padding: 10px; box-sizing: border-box; position: relative; } .modal-area-inner .hide{ position: absolute; top: 10px; right: 10px; font-size: 12px; } </style> </head> <body> <div id="app"> <button class="button" v-on:click="show">モーダルウィンドウを表示</button> <modal name="modal-area"> <div class="modal-area-inner"> <p>モーダルウィンドウが表示されました。</p> <span class="hide" v-on:click="hide">閉じる[close]</span> </div> </modal> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/vue-js-modal@1.3.28/dist/index.min.js"></script> <script src="modal.js"></script> </body> </html>JavaScript
vue-js-modalプラグインの取り込み
modal.jsVue.use(window["vue-js-modal"].default);メソッドの用意
modalテンプレートを呼び出すためのメソッドを用意します。
modal.jsvar app = new Vue({ el: '#app', methods: { show: function() { this.$modal.show('modal-area'); }, hide: function() { this.$modal.hide('modal-area'); }, } });よく使いそうなオプション
ウィンドウの幅指定
モーダルウィンドウの幅を指定したい場合は
:width="300"
を付与するだけでOKです。
デフォルトの幅600になっています。モーダルウィンドウのサイズを可変にする
スライドの速度を指定する場合はは
:resizable="true"
を指定します。
デフォルト値はfalse
です。モーダルウィンドウを遅らせて表示させる。
モーダルウィンドウを遅延させて表示させたい場合は
:delay="1000"
を付与します。
ページを表示させた後一定時間後に表示させたい場合などに便利です。オプション指定例<modal name="modal-area" :width="300" :height="300" :resizable="true" :delay="5000"> <div class="modal-area-inner"> <p>モーダルウィンドウが表示されました。</p> <span class="hide" v-on:click="hide">閉じる[close]</span> </div> </modal>上記以外にも以下の表のように様々なオプションが用意されています。
name 必須 型 初期値 name true [String, Number] delay false Number 0 resizable false Boolean false adaptive false Boolean false draggable false [Boolean, String] false scrollable false Boolean false reset false Boolean false clickToClose false Boolean true transition false String overlayTransition false String 'overlay-fade' classes false [String, Array] 'v--modal' width false [String, Number] 600 height false [String, Number] 300 minWidth false Number (px) 0 minHeight false Number (px) 0 maxWidth false Number (px) Infinity maxHeight false Number (px) Infinity pivotX false Number (0 - 1.0) 0.5 pivotY false Number (0 - 1.0) 0.5 root false Vue instance null まとめ
Vue.jsではモーダルウィンドウもプラグインを使うことで簡単に実装する事ができました。
標準なUIについてはほぼプラグインが用意されているので、Vue.jsそのもの仕様を理解してオリジナルで作成してみようと思います。
・参考 https://www.npmjs.com/package/vue-js-modal
- 投稿日:2019-08-30T15:28:00+09:00
既存のVueプロジェクトにTypeScriptを導入する
TL;DR
- 既存のVueプロジェクトにTypeScriptを導入したい際にやったことをまとめた
vue-cli
やnuxt
を利用したプロジェクトではない- extendではなくデコレータ方式で記述する
バージョン
- webpack: 4.33.0
- typescript: 3.5.3
- vue-property-decorator: 8.2.1
- ts-loader: 6.0.4
- @typescript-eslint/parser: 1.12.0
- @vue/eslint-config-typescript: 4.0.0
- vue-eslint-parser: 6.0.4
- ts-jest: 24.0.2
実際に導入する
Vue
関連モジュール
$ npm i -D typescript vue-property-decorator型定義ファイル(
d.ts
)を作成する。index.d.tsdeclare module "*.vue" { import Vue from "vue"; export default Vue; }Webpack
関連モジュールのインストール
$ npm i -D ts-loaderwebpack.config.jsの設定
webpack.config.js... { test: /\.ts$/, loader: "ts-loader", exclude: "/node_modules/", options: { appendTsSuffixTo: [/\.vue$/] } } ...ESLint
関連モジュールのインストール
$ npm i -D @typescript-eslint/parser @vue/eslint-config-typescript vue-eslint-parser.eslintrc.jsの設定
eslintrc.jsmodule.exports = { ... extends: [ 'plugin:vue/recommended', 'eslint:recommended', '@vue/typescript' ], parser: 'vue-eslint-parser', parserOptions: { parser: '@typescript-eslint/parser', ecmaFeatures: { "legacyDecotators": true } }, ... }Jest
関連モジュールのインストール
$ npm i -D ts-jestjest.config.jsの設定
jest.config.jsmodule.exports = { moduleFileExtensions: ["js", "jsx", "json", "vue", "ts"], transform: { "^.+\\.vue$": "vue-jest", "^.+\\.(ts|tsx)$": "ts-jest" }, transformIgnorePatterns: ["node_modules/"], moduleNameMapper: { "^@/(.*)$": "<rootDir>/src/js/$1" }, snapshotSerializers: ["jest-serializer-vue"], testMatch: ["**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"], testURL: "http://localhost/", setupFiles: [ "<rootDir>/tests/unit/jest-env.js" ], globals: { "ts-jest": { "tsConfigFile": "tsconfig.json" } }, watchPlugins: [ "jest-watch-typeahead/filename", "jest-watch-typeahead/testname" ], "collectCoverage": true, "collectCoverageFrom": ["src/js/**/*.{js,vue}"] };型定義ファイル
使ってるライブラリの型定義ファイル(
@types/〇〇
)をインストールしておく。TypeScriptの設定ファイル
tsconfig.json
を作成する。tsconfig.json{ "compilerOptions": { "target": "es5", "module": "es2015", "strict": false, "importHelpers": true, "moduleResolution": "node", "experimentalDecorators": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "sourceMap": true, "baseUrl": ".", "paths": { "@/*": [ "src/*" ] }, "lib": [ "es2019", "esnext", "dom" ], "resolveJsonModule": true } }
- 投稿日:2019-08-30T14:31:26+09:00
PWAでオフラインキャッシュ駆動しようとしたらされなかった話とGAのお話
ドキュメントは小説より奇なり
備忘録もかねて書きます。
オフラインの話
https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/offline-for-pwa?hl=ja
上記ページを見るとiOS Safariは最大50MBキャッシュ出来るよ!とありますが、
初回インストール時のキャッシュ容量はリクエスト数に紐づくっぽいです。厳密に数値を研究出来てないので曖昧なことはかけませんが、画像のSVG化、デコードなどでリクエスト数を減らしてページの総容量をあげたところ120MBまでキャッシュ出来た場合がありました。
セーフゾーンが50MBなのでしょうね。GAの話
https://developers.google.com/web/tools/workbox/modules/workbox-google-analytics
ChromeでとれるけどSafariで取れないよね、そうだBackground-SyncがiOS対応してないんだった。
というお話。気をつけましょう。
- 投稿日:2019-08-30T14:24:22+09:00
Promiseシンプル構文
非同期処理で使われるPromise関数には
new Promise
とPromise.resolve
で行う方法があります。
良く紹介されている方法はnew Promise
で行う方法が多いですが、シンプルに書きたいときはPromise.resolve
で行うとよいです。よくある構文
var result = new Promise(function(resolve) { resolve('Hello1') }) result .then(function(data){ console.log(data) return new Promise(function(resolve){ resolve('Hello2') }) }) .then(function(data){ console.log(data) return new Promise(function(resolve){ resolve('Hello3') }) }).then(function(data){ console.log(data) })シンプルな構文
Promise.resolve('Hello1') .then(function(data){ console.log(data) return Promise.resolve('Hello2') }) .then(function(data){ console.log(data) return Promise.resolve('Hello3') }) .then(function(data){ console.log(data) })
- 投稿日:2019-08-30T14:07:02+09:00
57日目 Javascriptでファイルは読めるが書き出せなさそう。
Javascriptで作ったサイトにCSVを読み書きする機能を追加したいと思ったらどハマりしてしまいました。
まずは読み込み
function getCsv(url){ //CSVファイルを文字列で取得。 var txt = new XMLHttpRequest(); txt.open('get', url, false); txt.send(); //改行ごとに配列化 var arr = txt.responseText.split('\n'); //1次元配列を2次元配列に変換 var res = []; for(var i = 0; i < arr.length; i++){ //空白行が出てきた時点で終了 if(arr[i] == '') break; //","ごとに配列化 res[i] = arr[i].split(','); } return res; } getCsv("test.csv");これでイケると思ったら全然読まない。コンソールにはこんなエラーが出ていました。
from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.セキュリティ上の理由でローカルファイルへのアクセスをブロックしているようです。
このブロックを許可するオプション付きでChromeを起動して読ませるとできるらしい。Start Google Chrome on Mac with command line switches
sudo /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --args -allow-file-access-from-filesできました!
てすが、いつもこのオプション付きでChromeを立ち上げてくださいっていうの、あんまり現実的ではない感じです。書き込み
これが大変でした。
Javascriptからファイルを書き出す方法がみつからず。①ダウンロード
できそう。だけどできればバックグラウンドで動かしたい。②Node.jsで書き込む
サーバー側で上げられればありかも。③FilesystemAPI
任意のファイルの編集は無理。サンドボックス内のみ可能。いろいろ調べたのですがJavascriptだけでファイルの書き込みは難しそうです。
(所要時間2時間)
- 投稿日:2019-08-30T13:35:01+09:00
SafariでjQueryのAjaxでファイルをUploadできない。
概要
Ajax経由でファイルをUploadしている部分が、MacのSafariブラウザでだけうまくUploadできない現象があった。
safariのバージョンは2019年8月現在の最新版で、確認に使ったバージョンは12.1.1でした。結論
SafariでForm要素をclone()で複製した際に、Safariではファイルオブジェクトは空要素としてCloneされるため、挙動が異なります。その結果Safariではファイル要素がアップロードされているようでいて、中身が空になってしまいます。
詳細
不具合が起きたソースの概略は下記の通り。フォームでファイルを選択すると、Submitボタンを押すことなく、Ajaxで送信し、プレビューに表示するような動作となっています。アップロード中を示す動作や、サーバからのエラー処理などは省いています。
HTML部分
<img src="/imgages/default.png" id="preview" alt="アップロード画像"> <form action="/fileupload.php" id="fileupload"> <input type="file" name="upload" id="upload"> </form>JS部分
$('#upload').on('change', function(e){ e.preventDefault(); // 下記の部分でコピーされるデータがSafariと他のブラウザで異なります。 let cloneFormData = $('#fileupload').clone(); let fd = new FormData(cloneFormData.get(0)); let url = '/uploader.php'; $.ajax({ url: url, type: 'post', contentType: false, processData: false, cache: false, data: fd }).done(function (e) { $('#preview').attr('src', e.file.uploaded_url); // プレビュー用の画像を表示 }); });jQueryオブジェクトをcloneしている箇所は下記の部分です。
let cloneFormData = $('#form_input_file').clone();
「ネットワーク」タブから送信しているPOSTデータを見てみると、clone()をした場合、下記のようにfilename が取れていません。
clone()しないで、フォームオブジェクトを直接POSTした場合は下記のようにfilenameが取れています。
(データ本体はSafariでは見えないようで)
あとは、HTMLプロトコルのヘッダ部分Content-lengthを見ても、何らか大きめのデータをアップしていることが確認できます。
ちなみにデバッガで送信するフォームデータを見るとcloneしなかった場合でもした場合でも、下記のように見えるので中身がよくわかりません。
ということで、Safariで ファイルオブジェクトがあるform を clone した場合、
ファイルオブジェクトは空要素としてCloneされるようです。
- 投稿日:2019-08-30T12:52:03+09:00
初心者でも出来るHTML,JavaScript入門5
1.事前知識
事前知識として、上記リンクの内容が必要です。
2.文字列と数値
文字列と数値<script type="text/javascript"> alert("文字列:Hello!"); alert("数値:"+100); </script>
JavaScript
では、文字列
を扱う際「' '(シングルクォート)」または「" "(ダブルクォート)」で囲む必要がある。JavaScript
では、全ての数値
は、整数
も浮動小数点数
も内部的に区別されない。JavaScript
において、数値
は全て浮動小数点数
として扱われる。3.テキストの出力
文字列と数値<script type="text/javascript"> document.open();//ドキュメントの出力を開始 document.write('Hello');//ドキュメントに文字列を書き出す document.close();//ドキュメントの出力を終了 </script>
document.write()
は、ドキュメント
に文字列
を書き出す。文字列
として書き出すため、タグ等も書き出しが可能。4.記述例
js1_1.html<!DOCTYPE html> <html> <head> <!-- 付加情報 --> <!-- タイトル --> <title>JavaScriptの説明</title> <!-- 文字コードの指定 --> <meta charset="utf-8" /> </head> <body> <!-- コンテンツ --> <!-- インラインスクリプト --> <script type="text/javascript"> document.open();//ドキュメントの出力を開始 document.write('<h1>h1</h1>');//ドキュメントに文字列を書き出す document.write('<h2>h2</h2>');//ドキュメントに文字列を書き出す document.write('<h3>h3</h3>');//ドキュメントに文字列を書き出す document.close();//ドキュメントの出力を終了 </script> </body> </html>中身の文をコピーして、文字コードは
UTF-8
を指定し、ファイル名をjs1_1.html
でデスクトップに保存するとブラウザではこうなります↓↓画像のようになれば成功です。
- 投稿日:2019-08-30T12:39:52+09:00
ES6のimportでFirebase Admin, Cloud Storageを書く
JavaScript -> TypeScript の変換をしていく中で、なかなか見つからなかったのでメモがてら
import * as admin from 'firebase-admin'; import * as serviceAccount from './serviceAccountKey.json'; import { Storage } from '@google-cloud/storage'; const params = { type: serviceAccount.type, projectId: serviceAccount.project_id, privateKeyId: serviceAccount.private_key_id, privateKey: serviceAccount.private_key, clientEmail: serviceAccount.client_email, clientId: serviceAccount.client_id, authUri: serviceAccount.auth_uri, tokenUri: serviceAccount.token_uri, authProviderX509CertUrl: serviceAccount.auth_provider_x509_cert_url, clientC509CertUrl: serviceAccount.client_x509_cert_url }; // firebase admin admin.initializeApp({ credential: admin.credential.cert(params) }); // firestore const db = admin.firestore(); db.settings({ timestampsInSnapshots: true }); // cloud storage const storage = new Storage({ credentials: { client_email: serviceAccount.client_email, private_key: serviceAccount.private_key } });以上です
参考
- 投稿日:2019-08-30T11:50:01+09:00
【Markdown】9.9秒で3x3の表を作るChrome拡張を作った。
【Markdown】9.9秒で3x3の表を作るChrome拡張を作った。
時間測定2: 9971.44091796875msボルト並みの速度ですね。
作った表。
ちゃんとした表
時間測定: 40985.581787109375ms40.9秒なので、
400m ウェイド・バンニーキルク
さんくらいの記録でしょうか。
緊急度(高) 緊急度(低) 重要度(高) 1 2 重要度(低) 3 4 拡張へのリンク
ちょっとした技術解説
GitHub https://github.com/ykhirao/TableToMd
Vue create
で作った普通のVue.jsアプリbuild
でdist/
というディレクトリにバンドルしていますcp:contents
で、その時に必須のファイルをコピーしてます(webpackに任せる方法でやっても可だと思います)zip
でChrome拡張配布ようにZIPファイルに変換していますpackage.json"scripts": { "serve": "vue-cli-service serve", "lint": "vue-cli-service lint", "build": "vue-cli-service build && npm run cp:contents", "zip": "bestzip TableToMd.zip dist/*", "cp:contents": "cpx src/manifest.json dist/ && cpx src/icon-128.png dist/" },開発してちょっと詰まったところ
manifest.json{ "manifest_version": 2, "version": "0.1", "name": "Table To Markdown Creater", "short_name": "TableToMd", "description": "Chrome and Firefox exetension", "browser_action": { "default_icon": { "19": "icon-128.png" }, "default_popup": "index.html", "default_title": "TableToMd" }, "homepage_url": "https://github.com/ykhirao/TableToMd", "icons": { "128": "icon-128.png" }, "permissions": [ "activeTab" ] }
permissions
はhttp://*
とかにすると審査が伸びるみたいな雰囲気でした。途中でactiveTab
に変更して、3日で審査追えました。
manifest_version
は2
固定みたいですね。ちゃんと公式を確認してから開発始めたほうがいいと思います。
Gsuite使っている人向け
Chrome拡張の開発と公開は普通5ドルの課金が必要ですが、同じGsuite内だけの配布なら課金なしで始めれるので、社内ツールとか作るのはとてもよいと思いました。
ゆる募
- スタイルあててくれる人
- アイコン作ってくれる人
最後に
読んでいただきありがとうございました!!
- 投稿日:2019-08-30T11:28:09+09:00
Vue.jsによるカルーセルスライダーのサンプル (スタンダードなUIからVueに慣れる②)
Vue.jsでカルーセルスライダー実装してみた
以前Vue.jsによるタブ切り替えの実装を紹介しましたが、Vue.jsではカルーセルスライダーも簡単に実装する事ができます。
今回は「Vue Carousel」を使ってカルーセルスライダーを実装してみます。「Vue Carousel」とは
jQueryのslick.jsのように簡単にカルーセルスライダーを導入できるプラグインです。
導入は、npmコマンドで導入する方法とCDNより読み込む方法があります。
今回はCDNで簡易的に導入します。See the Pen VwZWJQx by YusukeIkeda (@YusukeIkeda) on CodePen.
CSS
Vue Carouselを導入してブラウザのコンソールよりで要素を確認すると
.VueCarousel-slide
というクラスが生成されているのが分かります。今回はそのclassにCSSを適用します。CSS.VueCarousel-slide { font-size:12px; display: flex; align-items: center; justify-content: center; height:100px; background:#ccc; border-right:1px solid #FFF; box-sizing:border-box; }上記CSSを適用したクラスについてはコンソールにて確認する事ができます。
動的に生成されたHTML要素<div class="VueCarousel"> <div class="VueCarousel-wrapper"> <div class="VueCarousel-inner" style="transform: translate(0px, 0px); transition: transform 0.5s ease 0s; flex-basis: 514.5px; visibility: visible; height: auto;"> <!--VueCarousel-slideというクラスが動的に生成されている事が確認できる--> <div tabindex="-1" aria-hidden="true" role="tabpanel" class="VueCarousel-slide">Slide 1</div> <div tabindex="-1" aria-hidden="true" role="tabpanel" class="VueCarousel-slide">Slide 2</div><div tabindex="-1" aria-hidden="true" role="tabpanel" class="VueCarousel-slide">Slide 3</div> </div> </div> <div data-v-438fd353="" class="VueCarousel-pagination" style=""> <div data-v-438fd353="" role="tablist" class="VueCarousel-dot-container" style="margin-top: 20px;"> <button data-v-438fd353="" aria-hidden="false" role="tab" title="Item 0" value="Item 0" aria-label="Item 0" aria-selected="true" class="VueCarousel-dot VueCarousel-dot--active" style="margin-top: 20px; padding: 10px; width: 10px; height: 10px; background-color: rgb(0, 0, 0);"> </button> <button data-v-438fd353="" aria-hidden="false" role="tab" title="Item 1" value="Item 1" aria-label="Item 1" aria-selected="false" class="VueCarousel-dot" style="margin-top: 20px; padding: 10px; width: 10px; height: 10px; background-color: rgb(239, 239, 239);"> </button> </div> </div> </div>HTML
HTML<!DOCTYPE html> <html> <head> <title>vue-carousel</title> <link rel="stylesheet" type="text/css" href="slide.css"> </head> <body> <div id="app"> <carousel> <slide>Slide 1</slide> <slide>Slide 2</slide> <slide>Slide 3</slide> </carousel> </div> <!-- Vue.jsをCDNで読み込む --> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script> <!-- vue-carousel.jsをCDNで読み込む --> <script src="https://cdn.jsdelivr.net/npm/vue-carousel@0.18.0/dist/vue-carousel.min.js"></script> <script src="slide.js"></script> </body> </html>JavaScript
下記は特にオプションを指定しない場合の設定です。
JavaScriptvar app=new Vue({ el: "#app", components: { 'carousel': VueCarousel.Carousel, 'slide': VueCarousel.Slide }, });よく使いそうなオプション
自動再生
カルーセルスライダーを自動再生させたい場合は
autoplay="true"
を付与するだけでOKです。
次のスライドに移るタイミングは:autoplayTimeout="1000"
で設定します。
デフォルト値は2000
となっており、値を小さくするほど次のスライドへ移る時間が短くなります。ループ再生
カルーセルスライダーをループ再生させたい場合は
:loop="true"
を指定します。
デフォルト値はfalse
となっております。スライドのスピード
スライドの速度を指定する場合はは
:speed="500"
を指定します。
デフォルト値は500
となっており、値を小さくするほどスピードは速くなります。オプション指定例<carousel :autoplay="true" :autoplayTimeout="1000" :loop="true" :speed="500"> <slide>Slide 1</slide> <slide>Slide 2</slide> <slide>Slide 3</slide> </carousel>まとめ
CDNを読み込んでから数十分でカルーセルスライダーを作ることができました。
Vueに移行した際もこういったアニメーション要素のあるUIは心配なさそうです。
また他にも色々なオプションがgitHubで確認することができるので、ほとんどの案件で対応できるかと思います。
- 投稿日:2019-08-30T01:36:21+09:00
toggleメソッドで表示→非表示のイベント実装
こんばんは!今日はjs/jQueryで実装する部分があり、その中でtoggleがめちゃ便利だったので備忘録として。
toggleメソッドとは?
toggleメソッドは表示・非表示を切り替える事ができるjQueryの便利なメソッド。hideとshowが合体したメソッド。
js/jQueryでclickイベントを実装したい
実装したい事
①クリックしたら画像部分が表示される
②もう一度クリックしたら画像部分が非表示になる
※ちなみgyazo gifをqittaで表示する方法は![demo](http://gyazo.com/xxxxxxxxxx/raw)
でいけますGyazoでアップロードしたGifアニメーションを簡単に表示する方法。(URLのみでOK)https://qiita.com/Kobutorina_hato/items/d28d4cc90096e058566d
準備
gem 'jquery-rails'
を記述
bunde install
しますapplication.js//= require activestorage //= require turbolinks //= require_tree . //= require jquery #追加 //= require jquery_ujs #追加jQueryのライブラリを読み込ませましょう。JavaScriptの前に記述しないとエラーします
app/views/layouts/application.html.erb<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>これを実行する事でスクリプトファイルの読み込むためにスクリプトファイルを自動的に生成してくれます
うまくいかない
sample.html.haml.new-member-registration-form-content__group.whatnumber-text-right .signup-seqcode-text %i.fas.fa-question-circle{ style: "color:royalblue;" } カード裏面の番号とは? .signup-seqcode-infoclassの
signup-seqcode-info
をクリックしたらイベント発火させたいsample.js$(function() { let btn = document.querySelector(".signup-seqcode-text"); let image = `<div class="signup-seqcode-info is-show"> カードの裏面をご残照下さい。 <img src="//www ://表示したい画像のURL" alt="" width="240"> </div>` btn.addEventListener("click", function() { $(".signup-seqcode-info").html(image); }); });指定のクラス名
.signup-seqcode-text
をクリックすると変数image
が.signup-seqcode-info
要素の下に表示されるように実装できた。しかし表示はできるがどうすれば非表示にできるか分からない。off()やremove()使ってみたが、動的に出現したhtmlを消す方法がうまくいかず。toggleメソッドで簡単に実装できた
今日のメインテーマのtoggle登場。色々調べて書いてみて動かなくてを繰り返していたら、toggleメソッドだったら簡単にいけるのではないかと。
sample.haml.new-member-registration-form-content__group.whatnumber-text-right .signup-seqcode-text %i.fas.fa-question-circle{ style: "color:royalblue;" } カード裏面の番号とは? .signup-seqcode-info .signup-seqcode-info.is-show カードの裏面をご参照下さい = image_tag "//www://表示したい画像.png?3312594182", alt: "", width: "240"表示したい文字
カードの裏面をご参照下さい
と画像部分=image_tag
はhamlに書いておく
.signup-seqcode-info.is-show
はクリックして動的に追加したDOM要素に与えるクラスsample.scss.signup-seqcode-info.is-show { width: 300px; display: none; position: absolute; top: 6px; z-index: 1; padding: 16px; border-radius: 6px; background: #eee; text-align: center; color: #333; line-height: 1.5; img { padding-top: 10px; } }cssで
display:none;
にしてデフォルト時は非表示にする
デフォルト時は見えないですが、クリックして出現する時にどこに表示したいかをcssで決めておきましょ。ポイントはz-index: 1;
を忘れると他の要素の下に潜り込みますのでご注意を。sample.js$(function() { $('.signup-seqcode-text').click(function() { $('.is-show').toggle(); }) });指定したクラス名
.signup-seqcode-text
をクリックするとイベント発火
toggleはクリック1回目showイベント発火で.is-show部分が表示される
クリック2回目hideイベントで.is-show部分が非表示になる最初は二回clickイベントを作る必要があると思っていたがtoggleメソッド使えば超短い記述で実装できた
toggleは引数も指定できる
【 対象要素.toggle( ミリ秒 ) 】
◯ミリ秒かけて表示・非表示が行われる
【 対象要素.toggle( ミリ秒, 関数 ) 】
◯ミリ秒かけて表示・非表示をした後に関数を実行する事もできます(これ実装してないので詳しくは割愛)toggleはアニメーションも設定できる
- 【 fast 】:素早く非表示にする
- 【 slow 】:ゆっくり非表示にする
- 【 swing 】:デフォルトの非表示スタイル
- 【 linear 】:一定の速度で非表示にする
toggle兄弟いろいろ
詳しい説明は省略しますが、
「fadeToggle()」
,「toggleClass()」
,「slideToggle()」
はよく使われるいたいですfadetoggle()はじわーっと表示・非表示にしてくれます
toggleClass()は対象となる要素のclass属性値を追加したり削除したりを繰り返すことが可能なメソッド
slideToggle()は特定の要素を縦方向にアニメーションしながら表示・非表示する事ができます
この辺りはフロント実装で使えそうなので下記サイトを参考に使ってみたいなぁと思います
30分で理解!jQueryのtoggle()と3種のメソッド活用術!
https://www.sejuku.net/blog/40705まとめ
そういえば以前にもtoggleってなんだろ?ってちょろっと調べた事ありました。ただ実際に動かす機会がないと分かりませんね。js/jQuery苦手なんですけど(他が得意なわけではない笑)、やっぱり見た目の変化が分かりやすいので意図した動きになった時は楽しいです。
終わり
- 投稿日:2019-08-30T01:24:37+09:00
オブジェクトの配列から値を取り出す
準備
オブジェクトの配列に以下を用います。
この配列から"hoge1"を取り出す方法を書いていきます。var arry = [ { id: 0, name: "hoge0" }, { id: 1, name: "hoge1" }, { id: 2, name: "hoge2" }, { id: 3, name: "hoge3" }, { id: 4, name: "hoge4" } ];① 配列の二番目を直接指定する
これはmapメソッドを利用するときに、使えるかと思います。
console.log(arry[1]["name"]); // hoge1 console.log(arry[1].name); // hoge1② idから指定する
これは、①の発展で、特定の値から配列の中のオブジェクトを指定します。
例えば、引数の値を参照にする場合に使うかと思います。var num = 1; var idx = arry.findIndex(({ id }) => id === num); // idx: 1 /* 以下、①と同様 */ console.log(arry[idx].name); // hoge1最後に
僕は、①の値の指定の仕方が二種類あることに驚きました。配列は、数値を指定するもの!っていう余計な先入観がありました...もっと勉強しないとですね...
- 投稿日:2019-08-30T00:08:42+09:00
D3.jsでグリッドを作る
grid.html<!DOCTYPE html> <html lang="jp"> <head> <meta charset="utf-8" /> <script src="https://d3js.org/d3.v5.js"></script> <script src="./js/grid.js"></script> </head> <body> <div id="grid"></div> <span id="data" style="display:none;"> [ [ {"x":1,"y":1,"width":100,"height":100,"selected":false}, {"x":101,"y":1,"width":250,"height":50,"selected":false} ], [ {"x":101,"y":51,"width":100,"height":50,"selected":false}, {"x":201,"y":51,"width":150,"height":50,"selected":false} ], [ {"x":1,"y":101,"width":50,"height":300,"selected":false}, {"x":51,"y":101,"width":50,"height":150,"selected":false}, {"x":101,"y":101,"width":100,"height":150,"selected":false}, {"x":201,"y":101,"width":150,"height":150,"selected":false} ], [ {"x":51,"y":251,"width":50,"height":150,"selected":false}, {"x":101,"y":251,"width":100,"height":150,"selected":false}, {"x":201,"y":251,"width":150,"height":150,"selected":false} ] ] </span> </body> </html>grid.jsdocument.addEventListener("DOMContentLoaded", function () { // html内のJson文字列をデシリアイズします。 const gridData = JSON.parse(document.querySelector("#data").textContent); const grid = d3 .select("#grid") .append("svg") .attr("width", "510px") .attr("height", "510px"); const row = grid .selectAll() .data(gridData) .enter() .append("g") .attr("class", "row"); const noSelectedColor = "#ffffff"; const selectedColor = "#2C93E8"; const column = row .selectAll() .data(d) { return d; }) .enter() .append("rect") .attr("class", "square") .attr("x", function (d) { return d.x; }) .attr("y", function (d) { return d.y; }) .attr("width", function (d) { return d.width; }) .attr("height", function (d) { return d.height; }) .style("fill", noSelectedColor) .style("stroke", "#222") .on('click', function (d) { d.selected = !d.selected; let cell = d3.select(this); if (d.selected) { cell.style("fill", selectedColor); } else { cell.style("fill", noSelectedColor); } }); });解説
上から順番に解説します。
const grid = d3 .select("#grid") .append("svg") .attr("width", "510px") .attr("height", "510px");<!-- 実行後 --> <div id="grid"> <svg width="510px" height="510px"> </svg></div>selectメソッドのセレクターはjsやcssなどと同じです。
取得した d3.selection オブジェクトに append メソッドで svg タグが挿入されます。append をすることでタグが実際に生成されます。append の戻り値は d3.selection オブジェクトです。
attr メソッドで属性を追加します。const row = grid .selectAll() .data(gridData) .enter() .append("g") .attr("class", "row");<div id="grid"> <svg width="510px" height="510px"> <g class="row"></g> <g class="row"></g> <g class="row"></g> <g class="row"></g> </svg> </div>data メソッドでグリッドのオブジェクトを割り当てます。サンプルでは Json 文字列からオブジェクトを生成しています。
enter メソッドで data メソッドで割り当てられたオブジェクトがd3.selectionオブジェクトにセットされます。
append("g")でオブジェクトの各配列要素に g 要素 をバインドし、タグを生成します。
(よくあるサンプルだと始めの selectAll で selectAll("row") などして、空の selection オブジェクトを取得するのを見かけます。ですが、引数は空でいいと思うのですが、わざわざ"row"などして空振りする効果がよくわかりません。)d3.jsのセレクタとタグ操作
D3.jsで<g>要素を活用するconst noSelectedColor = "#ffffff"; const selectedColor = "#2C93E8"; const gridStrokeColor = "#222222"; const column = row .selectAll() .data(d => d) .enter() .append("rect") .attr("class", "square") .attr("x", d => d.x) .attr("y", d => d.y) .attr("width", d => d.width) .attr("height", d => d.height) .style("fill", noSelectedColor) .style("stroke", gridStrokeColor) .on('click', function (d) { d.selected = !d.selected; let cell = d3.select(this); if (d.selected) { cell.style("fill", selectedColor); } else { cell.style("fill", noSelectedColor); } });<div id="grid"> <svg width="510px" height="510px"> <g class="row"> <rect class="square" x="1" y="1" width="100" height="100" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);"> </rect> <rect class="square" x="101" y="1" width="250" height="50" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);"> </rect> </g> <g class="row"> <rect class="square" x="101" y="51" width="100" height="50" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);"> </rect> <rect class="square" x="201" y="51" width="150" height="50" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);"> </rect> </g> <g class="row"> <rect class="square" x="1" y="101" width="50" height="300" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);"> </rect> <rect class="square" x="51" y="101" width="50" height="150" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);"> </rect> <rect class="square" x="101" y="101" width="100" height="150" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);"> </rect> <rect class="square" x="201" y="101" width="150" height="150" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);"> </rect> </g> <g class="row"> <rect class="square" x="51" y="251" width="50" height="150" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);"> </rect> <rect class="square" x="101" y="251" width="100" height="150" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);"> </rect> <rect class="square" x="201" y="251" width="150" height="150" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);"> </rect> </g> </svg> </div>ここでは、先程 g 要素にバインドした配列の行オブジェクトの要素から rect を生成します。x や y、width、height、fill、stroke は SVG の属性です。
jQuery のように、on メソッドでイベントを付与できます。ここでは、クリックすると背景色を切り替えるイベントを付与しています。
(ただ、ここでもう一度 data(d => d).enter() しないといけないのがよくわからない。)完成形
- 投稿日:2019-08-30T00:06:02+09:00
TypeScriptで書いてwebpackでビルドしたjsファイルのエラーをわかりやすくする
Why
webpackでバンドルしてるのでスタックトレースを見てもtsファイルでのエラー箇所がわからない・・
What
node-source-map-supportをインストールして、tsconfig.json, webpack.config.jsを修正します
node-source-map-supportのインストール
$ npm install source-map-supporttsファイルの修正
webpackのエントリポイントにしているtsファイルに以下を追加します。
app.tsimport sourceMapSupport from 'source-map-support' sourceMapSupport.install()設定ファイルの修正
webpack, TypeScriptの設定ファイルを修正します。
webpack.config.jsmodule.exports = { mode: 'development', target: 'node', + devtool: 'inline-source-map', ... }↑ちなみに、
devtool: 'inline-source-map'
でも動きました。tsconfig.js{ "compilerOptions": { + "sourceMap": true ... } }これで、webpackでビルドし直すと、エラー時にtsファイルの行数も教えてくれるようになりました
こんなかんじ (app.tsの73行目でエラーになってることがわかる)
at Object.<anonymous> (/path/to/project/dist/webpack:/src/app.ts:73:4)以上です
参考
- evanw/node-source-map-support: Adds source map support to node.js (for stack traces)
- (TypeScriptで書かれてWebpackでビルドされた) CloudFunctionsのエラー通知をわかりやすくしてみた - selmertsxの素振り日記
- webpack, babel, node環境で例外時のスタックトレースに元ソースの場所を表示させる - ncaq
- NBM2 - TypeScript + WebPack でSourceMapを有効にする
- javascript - Is there source map support for typescript in node / nodemon? - Stack Overflow
- err.stackに含まれるコードの位置情報をsource mapで元に戻す - Qiita