20200125のJavaScriptに関する記事は27件です。

【JavaScript】Qiita 週間いいね数ランキング【タグ別】

他のタグ

集計期間

01月18日 ~ 01月25日

いいね数ランキング

1位: もしものために,香川県からのアクセスを避ける

JavaScript Geolocation
711いいね
@ni__no13さん(01月22日 13時51分の投稿)

2位: 2020年流行するであろうwebデザインのトレンド

JavaScript HTML5 CSS3 Webデザイン
335いいね
@sugar_tkさん(01月20日 01時17分の投稿)

3位: useReducerの本質:良いパフォーマンスのためのロジックとコンポーネント設計

JavaScript React
202いいね
@uhyoさん(01月19日 08時05分の投稿)

4位: ブログカードを支える技術

JavaScript api OGP favicon ブログ
127いいね
@hinastoryさん(01月22日 21時46分の投稿)

5位: SvelteでWebサイトを作ってみて感じた魅力

JavaScript Svelte Sapper
52いいね
@nishinoshakeさん(01月20日 01時44分の投稿)

6位: 最近名前をよく見かけるsvelte/sapperを試してみた ~その2 構文編~

JavaScript TypeScript まとめ Svelte Sapper
24いいね
@inagackyさん(01月20日 07時34分の投稿)

7位: WEBエンジニアへの転職を目指す外資系新卒が、8週間の学習過程を晒してみる

JavaScript Rails 転職 React 未経験エンジニア
12いいね
@Ryo__Mさん(01月20日 07時55分の投稿)

8位: 【Vue.js】Vueコンストラクタ関数をWebコンソール上で見つける方法

JavaScript Chrome デバッグ Vue.js webpack
11いいね
@masciiさん(01月19日 07時08分の投稿)

9位: 鼓膜の画像を送り質問に返答すれば、自動で中耳炎の診断や治療方針が返されるLINE Botを作成(ヒーローズ・リーグ2019 LINEテーマ賞)

JavaScript Node.js VSCode LINEmessagingAPI CustomVisionService
8いいね
@doikatsuyukiさん(01月21日 02時57分の投稿)

10位: C/C++に組み込める軽量JavaScriptエンジン “QuickJS” を試す

C C++ JavaScript QuickJS
8いいね
@taskieさん(01月19日 08時32分の投稿)

11位: GraphQLが"グラフ"であることを利用してビジネスロジックを入れ込んでみる

JavaScript 入門 GraphQL apollo
7いいね
@tetsuya-zamaさん(01月25日 08時09分の投稿)

12位: Reactでの条件分岐 4つの方法のメモ

JavaScript 初心者向け React 新人プログラマ応援
7いいね
@penpentaさん(01月19日 06時43分の投稿)

13位: 1時間でChrome拡張作ってみた!

JavaScript chrome-extension
4いいね
@Michinosukeさん(01月23日 04時41分の投稿)

14位: [JS]配列を操るメソッドたち(基礎編)

JavaScript
4いいね
@okuxxbos20さん(01月22日 15時34分の投稿)

15位: JavaScript: 変数の初期化ではundefinednull のどちらを使うのがよい?

JavaScript Node.js NULL undefined
4いいね
@abetomoさん(01月22日 03時14分の投稿)

16位: 最近の要素・ノード操作型メソッド事情

JavaScript
4いいね
@amamamaouさん(01月21日 09時25分の投稿)

17位: 【初心者】JavaScriptで「コマ移動ゲーム(横移動のみ)」を作ってみた

HTML JavaScript 初心者 初心者向け
4いいね
@yuya_yuzenさん(01月21日 01時49分の投稿)

18位: JSDocの書き方・出力メモ

JavaScript JSDoc
4いいね
@zaburoさん(01月20日 22時37分の投稿)

19位: 2020年 ITカンファレンスまとめ

Ruby Python PHP JavaScript Conference
4いいね
@TaitoAjikiさん(01月20日 15時28分の投稿)

20位: ElectronでcontextBridgeによる安全なIPC通信

JavaScript Node.js xss Electron
4いいね
@pochmanさん(01月20日 15時04分の投稿)

21位: micro:bitとブラウザ間で文字列をやり取りするまでの手順

JavaScript WebBluetooth microbit WebUSB MakeCode
4いいね
@nakazawaken1さん(01月19日 10時04分の投稿)

22位: obniz-nobleでBLEスキャンをグラフ表示してみた

JavaScript Vue.js chart.js ESP32 obniz-noble
4いいね
@porurubaさん(01月19日 07時03分の投稿)

23位: Vue.js のディレクティブの記述を膨らませないために

JavaScript Vue.js
3いいね
@hidacheさん(01月23日 14時26分の投稿)

24位: Node.js、Web Speech APIを使って音声認識を出力

JavaScript Node.js Heroku WebSpeechAPI
3いいね
@harach19kさん(01月22日 23時56分の投稿)

25位: html2canvasを使ってVue.js のサイトを画像で切り取る

JavaScript Vue.js html2canvas
3いいね
@13sayuさん(01月22日 15時52分の投稿)

26位: HTML + JavaScript 非同期でIPアドレスを取得して表示する

HTML JavaScript IPアドレス
3いいね
@shimajiriさん(01月22日 09時34分の投稿)

27位: MDN ja の間違い探し

JavaScript MDN
3いいね
@standard-softwareさん(01月22日 07時47分の投稿)

28位: Node.js(axios)からDiscordに通知を送るメモ

JavaScript Node.js Webhook axios discord
3いいね
@n0bisukeさん(01月22日 04時32分の投稿)

29位: 【Nuxt.js】 middleware is 何?

JavaScript Vue.js middleware Vuex nuxt.js
3いいね
@ono-taさん(01月21日 10時05分の投稿)

30位: Zeitの最強ホスティングサービスnowのDNS設定にレコードを追加する方法

JavaScript Node.js dns NOW next.js
3いいね
@kanye__eastさん(01月21日 03時05分の投稿)

31位: 花粉症LINE Botのデータをnode.jsを使ってFirebaseに出し入れする(花粉カレンダー作成③)

JavaScript Node.js Firebase VSCode LINEmessagingAPI
3いいね
@doikatsuyukiさん(01月20日 22時11分の投稿)

32位: Svelteでテストがしたい

JavaScript Svelte
3いいね
@nishinoshakeさん(01月20日 19時48分の投稿)

33位: RxJS: map, concatMap, mergeMap, switchMap, exhaustMap の動作を理解する

JavaScript TypeScript RxJS
3いいね
@nori3tsuさん(01月20日 05時04分の投稿)

34位: Excelマクロから脱却しよう。HTMLとJavascriptでエクセルマクロで実現していた処理を実現する方法(JavaScriptで行追加、JavaScriptでCSVファイル読み込み、JavaScriptでCSVファイル出力)

JavaScript HTML5 Excel ExcelVBA CSV読込・保存
3いいね
@KOJI-YAMAMOTOさん(01月20日 03時42分の投稿)

35位: Auth0で簡単にユーザー認証を実装(花粉カレンダー作成②)

JavaScript Node.js Vue.js VSCode Auth0
3いいね
@doikatsuyukiさん(01月20日 03時01分の投稿)

36位: Vue.js×FullCallendarでWEBカレンダー作成(花粉カレンダー作成①)

JavaScript Node.js Vue.js FullCalendar VSCode
3いいね
@doikatsuyukiさん(01月19日 22時43分の投稿)

37位: Googleサイトでサーバーレスなカウントダウンサイトを作ってみた

HTML JavaScript google サーバーレス Googleサイト
3いいね
@t-chiさん(01月19日 14時04分の投稿)

38位: Playwrightも知らないで開発してる君たちへ

JavaScript スクレイピング テスト自動化 Playwright
2いいね
@cc822jpさん(01月24日 17時03分の投稿)

39位: Rails Javascript/Html: 投稿編集画面で写真の表示を確認したい!

JavaScript Rails HTML5
2いいね
@kokeshi1357さん(01月24日 11時45分の投稿)

40位: フッターのコピーライトとかで自動的に今の年を表示する方法

HTML JavaScript
2いいね
@mtoutsideさん(01月24日 07時02分の投稿)

41位: JavaScriptでテキストの差分を見るライブラリ

JavaScript
2いいね
@mima_itaさん(01月24日 06時00分の投稿)

42位: Reactコンポーネントのテスト設計と実装(後編)

JavaScript Jest React redux react-testing-library
2いいね
@takahoriさん(01月24日 05時24分の投稿)

43位: 【永久保存版】「||」とか「&&」ってなんやねん

JavaScript
2いいね
@suzukazu428さん(01月24日 00時31分の投稿)

44位: VueコンポーネントのfunctionをES6スタイルで書く

JavaScript es6 Vue.js nuxt.js
2いいね
@pisolinoさん(01月23日 17時59分の投稿)

45位: Vue.jsでイベントオブジェクトを取得する

JavaScript Vue.js
2いいね
@hidacheさん(01月23日 14時08分の投稿)

46位: LaravelでUncaught domexception: failed to execute '***' on 'element': ',' is not a valid attribute name.を吐き出したときに確認したこと

PHP JavaScript Laravel
2いいね
@yutaro704さん(01月23日 10時58分の投稿)

47位: npmとは yarnとは

JavaScript npm YARN
2いいね
@Hai-dozoさん(01月23日 06時42分の投稿)

48位: Nuxt.jsのbeforeDestroyed()でイベントリスナーを削除できなかった時の対処法。

JavaScript Vue.js nuxt.js
2いいね
@ddg171さん(01月23日 06時19分の投稿)

49位: JavaScript の dateObj.toISOString() が便利だった。

JavaScript 日付フォーマット
2いいね
@ohbayazyさん(01月22日 02時43分の投稿)

50位: JavaScript map()で上手く新しい配列が作れなかった話

JavaScript
2いいね
@Kohei-Sato-1221さん(01月22日 01時36分の投稿)

51位: デザイナがReactを始める前に押さえておきたいJavascriptの基本③(クラス編)

JavaScript クラス React 新人プログラマ応援
2いいね
@seiraさん(01月21日 15時10分の投稿)

52位: 【Nuxt.js】pagination実践編:$router.pushで簡単実装!

JavaScript Vue.js コンポーネント nuxt.js
2いいね
@aLizさん(01月21日 09時28分の投稿)

53位: 花粉症LINE BotからのデータをWEBカレンダーに表示する(花粉カレンダー作成④)

JavaScript Node.js Vue.js Firebase LINEmessagingAPI
2いいね
@doikatsuyukiさん(01月21日 03時08分の投稿)

54位: JavaScriptの正規表現

JavaScript 正規表現
2いいね
@oekaki-hoho-ronさん(01月20日 15時07分の投稿)

55位: NuxtのasyncDataとdataの使い分け方

JavaScript Vue.js nuxt.js
2いいね
@ToshioAkaneyaさん(01月20日 13時03分の投稿)

56位: Understand Promise in 5 minutes

JavaScript promise Web
2いいね
@kueiappさん(01月20日 03時14分の投稿)

57位: 【Rails×Ajax】いいね機能の実装で上手く出来ないあなたへの2つの注意喚起 #学習者向け

Ruby JavaScript Rails エラー対処
2いいね
@shoji621さん(01月20日 03時03分の投稿)

58位: 高卒プログラマーのアルゴリズム体操(n!は末尾何桁ゼロが並ぶか)

JavaScript アルゴリズム 算数 CodeWars
2いいね
@pppp403さん(01月20日 01時15分の投稿)

59位: webGLでVertex Animation Textureをやる

JavaScript WebGL Shader HOUDINI
2いいね
@machilda777さん(01月19日 13時53分の投稿)

60位: ページ遷移先でリロードしないと非同期通信(ajax)できない

Ruby JavaScript Rails
2いいね
@avicii2314さん(01月19日 05時18分の投稿)

61位: https(SSL)通信の環境下でjavascriptが動かなくなる場合の原因と解決方法 ( 本番環境(AWS)でjavascriptを読み込む方法 )

Ruby JavaScript Rails
2いいね
@avicii2314さん(01月19日 04時17分の投稿)

62位: Slack デスクトップアプリの最新版にカスタム CSS を当てる方法

CSS JavaScript Slack Electron
1いいね
@Siketyanさん(01月25日 13時05分の投稿)

63位: 【nuxt.js】headにmetaとかGAタグとか加える時のアレコレ

HTML JavaScript SEO nuxt.js
1いいね
@bandwagonさん(01月25日 07時26分の投稿)

64位: 【Vue.js】TOAST UI Image Editor を使って画像編集!

JavaScript canvas ライブラリ Vue.js 画像編集
1いいね
@r_pg10さん(01月25日 05時39分の投稿)

65位: ハッカソンでFPS視点ラジコンをデモしたら思った以上に好評だったので作り方を紹介します

JavaScript RaspberryPi Lego obniz mjpeg_stremer
1いいね
@zgw426さん(01月25日 02時18分の投稿)

66位: 【OSS貢献記録】emoji-martをReact 17に対応

JavaScript OSS React
1いいね
@h6akhさん(01月25日 01時24分の投稿)

67位: Sigfox Coverage MapをGoogle Maps APIで表示する

JavaScript GoogleMapsAPI IoT token Tilemap
1いいね
@ghibiさん(01月24日 22時49分の投稿)

68位: Svelte ハンズオン

JavaScript 勉強会 ハンズオン Svelte
1いいね
@oekazumaさん(01月24日 13時15分の投稿)

69位: CLI のテンプレートプロジェクト by node and TypeScript

JavaScript Node.js cli TypeScript
1いいね
@amay077さん(01月24日 13時03分の投稿)

70位: Vue.jsプロジェクトにESLintを設定する最もシンプルな方法

JavaScript npm ESLint lint-staged husky
1いいね
@tamonmonさん(01月24日 08時25分の投稿)

71位: CodePen - HTML+JavaScriptで色サンプル(RGBカラーのパレット)作ってみた

JavaScript codepen
1いいね
@kob58imさん(01月24日 07時33分の投稿)

72位: JSのグローバル変数について備忘録

JavaScript 初心者
1いいね
@shimamarさん(01月24日 06時04分の投稿)

73位: iOSを“区別”したモーダル背景のスクロール固定

CSS JavaScript jQuery Safari モーダル
1いいね
@m_shinadaさん(01月24日 05時01分の投稿)

74位: chrome拡張機能で抽選機を作った

JavaScript chrome-extension
1いいね
@engabesiさん(01月24日 00時38分の投稿)

75位: jsPDFでHTML上の日本語表記のテーブルをサクッとPDF化

JavaScript PDF 日本語化 jspdf autotable
1いいね
@oigus-kさん(01月23日 08時59分の投稿)

76位: 【Nuxt.js】TypeScript基礎編:Vue.extendでシンプルなコードを書こう

JavaScript Vue.js コンポーネント nuxt.js
1いいね
@aLizさん(01月23日 08時03分の投稿)

77位: ReactNative(Expo)でイベントトラッキング 〜expo-analyticsの使い方をざっくりまとめる〜

JavaScript GoogleAnalytics React reactnative expo
1いいね
@tsukueさん(01月23日 05時02分の投稿)

78位: Javascript アロー関数を簡単にまとめて学ぶ

HTML CSS JavaScript
1いいね
@ryomaDsakamotoさん(01月23日 04時08分の投稿)

79位: 俺的PWAの振り返り

JavaScript PWA
1いいね
@krohigewagmaさん(01月22日 15時46分の投稿)

80位: Slackに匿名で画像を投稿できるようにした

JavaScript Node.js Slack slackbot Botkit
1いいね
@BIG_LARGE_STONEさん(01月22日 14時23分の投稿)

81位: React.Componentのメンバ変数は、再レンダー時に更新されるとは限らない!

JavaScript フロントエンド React
1いいね
@ssint1120さん(01月22日 09時22分の投稿)

82位: Lambda + API GatewayでCORSを有効にしているのにCORSでエラーになる

JavaScript AWS CORS lambda APIGateway
1いいね
@scalewalletさん(01月22日 03時55分の投稿)

83位: Chart.js【javascriptで簡単にグラフが描ける!】

JavaScript 初心者 グラフ chart.js ブランク
1いいね
@satosuzu1108さん(01月22日 00時09分の投稿)

84位: Vue.js の基本的な機能を使ったサンプルを書く

JavaScript Vue.js
1いいね
@niwasawaさん(01月21日 14時12分の投稿)

85位: async(Promise)のthen()内の関数の引数の省略

JavaScript promise async then
1いいね
@nkn-msさん(01月21日 09時47分の投稿)

86位: 文章読み上げアプリをJavaScriptで作成してみました

JavaScript HTML5 CSS3
1いいね
@otake619さん(01月21日 05時19分の投稿)

87位: Javascriptのシンプルな構成でAWS Cognitoを理解する

JavaScript AWS JWT cognito
1いいね
@TKFM21さん(01月21日 03時02分の投稿)

88位: React hooksとclassコンポーネントにおけるsetStateの挙動の違いについて(動かせるコードあり)

HTML JavaScript 初心者 hooks React
1いいね
@yseki_さん(01月20日 11時44分の投稿)

89位: obnizのBLEでペリフェラルに接続する

JavaScript Node.js BLE IoT obniz
1いいね
@yukisato1987さん(01月20日 11時23分の投稿)

90位: Reactでの関数のバインド 4つの方法のメモ

JavaScript 初心者向け React 新人プログラマ応援
1いいね
@penpentaさん(01月20日 09時17分の投稿)

91位: 【初心者】JavaScriptで「おみくじゲーム」を作ってみた

HTML JavaScript 初心者 初心者向け
1いいね
@yuya_yuzenさん(01月20日 06時56分の投稿)

92位: padStart で日付・時刻を二桁表示する書き方

JavaScript date 日付
1いいね
@Shohei-Japanさん(01月20日 05時02分の投稿)

93位: ReduxにおけるImmutableの概念についてまとめてみた

JavaScript 初心者 React redux Immutable
1いいね
@Rui1009さん(01月20日 04時18分の投稿)

94位: ソート機能を実装した際に感じたfilterメソッドの魅力

JavaScript
1いいね
@shotashimuraさん(01月20日 02時18分の投稿)

95位: エンジニアのポートフォリオを作ってみた

CSS JavaScript Rails HTML5 jQuery
1いいね
@ssstttさん(01月19日 13時06分の投稿)

96位: Amazon Prime VideoやAbema TVなどを画面端の小さいウィンドウで再生する方法

JavaScript Chrome Safari ブックマークレット AbemaTV
1いいね
@AKKYMさん(01月19日 10時50分の投稿)

97位: 【React】useReducer をもっと自由に活用しよう

JavaScript React
1いいね
@iMasanariさん(01月19日 09時04分の投稿)

98位: Javascriptのspliceで削除すな

JavaScript 初心者ですぅ
1いいね
@Takuthonさん(01月19日 09時03分の投稿)

99位: RailsにおけるAjaxの実装(JavaScriptとjQueryのコード比較)

Ruby JavaScript Rails jQuery Ajax
1いいね
@t-yama-3さん(01月19日 08時19分の投稿)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】Qiita 週間いいね数ランキング【自動更新】

他のタグ

集計期間

01月19日 ~ 01月26日

いいね数ランキング

1位: もしものために,香川県からのアクセスを避ける

JavaScript Geolocation
727いいね
@ni__no13さん(01月22日 22時51分の投稿)

2位: 2020年流行するであろうwebデザインのトレンド

JavaScript HTML5 CSS3 Webデザイン
339いいね
@sugar_tkさん(01月20日 10時17分の投稿)

3位: ブログカードを支える技術

JavaScript api OGP favicon ブログ
133いいね
@hinastoryさん(01月23日 06時46分の投稿)

4位: SvelteでWebサイトを作ってみて感じた魅力

JavaScript Svelte Sapper
54いいね
@nishinoshakeさん(01月20日 10時44分の投稿)

5位: 最近名前をよく見かけるsvelte/sapperを試してみた ~その2 構文編~

JavaScript TypeScript まとめ Svelte Sapper
25いいね
@inagackyさん(01月20日 16時34分の投稿)

6位: GraphQLが"グラフ"であることを利用してビジネスロジックを入れ込んでみる

JavaScript 入門 GraphQL apollo
23いいね
@tetsuya-zamaさん(01月25日 17時09分の投稿)

7位: WEBエンジニアへの転職を目指す外資系新卒が、8週間の学習過程を晒してみる

JavaScript Rails 転職 React 未経験エンジニア
12いいね
@Ryo__Mさん(01月20日 16時55分の投稿)

8位: 鼓膜の画像を送り質問に返答すれば、自動で中耳炎の診断や治療方針が返されるLINE Botを作成(ヒーローズ・リーグ2019 LINEテーマ賞)

JavaScript Node.js VSCode LINEmessagingAPI CustomVisionService
8いいね
@doikatsuyukiさん(01月21日 11時57分の投稿)

9位: 1時間でChrome拡張作ってみた!

JavaScript chrome-extension
4いいね
@Michinosukeさん(01月23日 13時41分の投稿)

10位: [JS]配列を操るメソッドたち(基礎編)

JavaScript
4いいね
@okuxxbos20さん(01月23日 00時34分の投稿)

11位: HTML + JavaScript 非同期でIPアドレスを取得して表示する

HTML JavaScript IPアドレス
4いいね
@shimajiriさん(01月22日 18時34分の投稿)

12位: JavaScript: 変数の初期化ではundefinednull のどちらを使うのがよい?

JavaScript Node.js NULL undefined
4いいね
@abetomoさん(01月22日 12時14分の投稿)

13位: 最近の要素・ノード操作型メソッド事情

JavaScript
4いいね
@amamamaouさん(01月21日 18時25分の投稿)

14位: 【初心者】JavaScriptで「コマ移動ゲーム(横移動のみ)」を作ってみた

HTML JavaScript 初心者 初心者向け
4いいね
@yuya_yuzenさん(01月21日 10時49分の投稿)

15位: JSDocの書き方・出力メモ

JavaScript JSDoc
4いいね
@zaburoさん(01月21日 07時37分の投稿)

16位: 2020年 ITカンファレンスまとめ

Ruby Python PHP JavaScript Conference
4いいね
@TaitoAjikiさん(01月21日 00時28分の投稿)

17位: ElectronでcontextBridgeによる安全なIPC通信

JavaScript Node.js xss Electron
4いいね
@pochmanさん(01月21日 00時04分の投稿)

18位: Slack デスクトップアプリの最新版にカスタム CSS を当てる方法

CSS JavaScript Slack Electron
3いいね
@Siketyanさん(01月25日 22時05分の投稿)

19位: Vue.js のディレクティブの記述を膨らませないために

JavaScript Vue.js
3いいね
@hidacheさん(01月23日 23時26分の投稿)

20位: Node.js、Web Speech APIを使って音声認識を出力

JavaScript Node.js Heroku WebSpeechAPI
3いいね
@harach19kさん(01月23日 08時56分の投稿)

21位: html2canvasを使ってVue.js のサイトを画像で切り取る

JavaScript Vue.js html2canvas
3いいね
@13sayuさん(01月23日 00時52分の投稿)

22位: MDN ja の間違い探し

JavaScript MDN
3いいね
@standard-softwareさん(01月22日 16時47分の投稿)

23位: Node.js(axios)からDiscordに通知を送るメモ

JavaScript Node.js Webhook axios discord
3いいね
@n0bisukeさん(01月22日 13時32分の投稿)

24位: 【Nuxt.js】 middleware is 何?

JavaScript Vue.js middleware Vuex nuxt.js
3いいね
@ono-taさん(01月21日 19時05分の投稿)

25位: 【Nuxt.js】pagination実践編:$router.pushで簡単実装!

JavaScript Vue.js コンポーネント nuxt.js
3いいね
@aLizさん(01月21日 18時28分の投稿)

26位: Zeitの最強ホスティングサービスnowのDNS設定にレコードを追加する方法

JavaScript Node.js dns NOW next.js
3いいね
@kanye__eastさん(01月21日 12時05分の投稿)

27位: 花粉症LINE Botのデータをnode.jsを使ってFirebaseに出し入れする(花粉カレンダー作成③)

JavaScript Node.js Firebase VSCode LINEmessagingAPI
3いいね
@doikatsuyukiさん(01月21日 07時11分の投稿)

28位: Svelteでテストがしたい

JavaScript Svelte
3いいね
@nishinoshakeさん(01月21日 04時48分の投稿)

29位: RxJS: map, concatMap, mergeMap, switchMap, exhaustMap の動作を理解する

JavaScript TypeScript RxJS
3いいね
@nori3tsuさん(01月20日 14時04分の投稿)

30位: Excelマクロから脱却しよう。HTMLとJavascriptでエクセルマクロで実現していた処理を実現する方法(JavaScriptで行追加、JavaScriptでCSVファイル読み込み、JavaScriptでCSVファイル出力)

JavaScript HTML5 Excel ExcelVBA CSV読込・保存
3いいね
@KOJI-YAMAMOTOさん(01月20日 12時42分の投稿)

31位: Auth0で簡単にユーザー認証を実装(花粉カレンダー作成②)

JavaScript Node.js Vue.js VSCode Auth0
3いいね
@doikatsuyukiさん(01月20日 12時01分の投稿)

32位: [W.I.P.] 【実況】Vue の 50 倍の速度が出るらしい「Svelte」のライブラリ「Sapper」を使って人生初のポートフォリオを作ってみる

JavaScript github-pages ポートフォリオ Svelte Sapper
2いいね
@u-shoさん(01月26日 04時41分の投稿)

33位: 【JavaScript】Qiita 週間いいね数ランキング【自動更新】

JavaScript
2いいね
@kou_pg_0131さん(01月25日 23時01分の投稿)

34位: ハッカソンでFPS視点ラジコンをデモしたら思った以上に好評だったので作り方を紹介します

JavaScript RaspberryPi Lego obniz mjpeg_stremer
2いいね
@zgw426さん(01月25日 11時18分の投稿)

35位: Playwrightも知らないで開発してる君たちへ

JavaScript スクレイピング テスト自動化 Playwright
2いいね
@cc822jpさん(01月25日 02時03分の投稿)

36位: Svelte ハンズオン

JavaScript 勉強会 ハンズオン Svelte
2いいね
@oekazumaさん(01月24日 22時15分の投稿)

37位: Rails Javascript/Html: 投稿編集画面で写真の表示を確認したい!

JavaScript Rails HTML5
2いいね
@kokeshi1357さん(01月24日 20時45分の投稿)

38位: フッターのコピーライトとかで自動的に今の年を表示する方法

HTML JavaScript
2いいね
@mtoutsideさん(01月24日 16時02分の投稿)

39位: JavaScriptでテキストの差分を見るライブラリ

JavaScript
2いいね
@mima_itaさん(01月24日 15時00分の投稿)

40位: Reactコンポーネントのテスト設計と実装(後編)

JavaScript Jest React redux react-testing-library
2いいね
@takahoriさん(01月24日 14時24分の投稿)

41位: 【永久保存版】「||」とか「&&」ってなんやねん

JavaScript
2いいね
@suzukazu428さん(01月24日 09時31分の投稿)

42位: VueコンポーネントのfunctionをES6スタイルで書く

JavaScript es6 Vue.js nuxt.js
2いいね
@pisolinoさん(01月24日 02時59分の投稿)

43位: Vue.jsでイベントオブジェクトを取得する

JavaScript Vue.js
2いいね
@hidacheさん(01月23日 23時08分の投稿)

44位: LaravelでUncaught domexception: failed to execute '***' on 'element': ',' is not a valid attribute name.を吐き出したときに確認したこと

PHP JavaScript Laravel
2いいね
@yutaro704さん(01月23日 19時58分の投稿)

45位: npmとは yarnとは

JavaScript npm YARN
2いいね
@Hai-dozoさん(01月23日 15時42分の投稿)

46位: Nuxt.jsのbeforeDestroyed()でイベントリスナーを削除できなかった時の対処法。

JavaScript Vue.js nuxt.js
2いいね
@ddg171さん(01月23日 15時19分の投稿)

47位: JavaScript の dateObj.toISOString() が便利だった。

JavaScript 日付フォーマット
2いいね
@ohbayazyさん(01月22日 11時43分の投稿)

48位: JavaScript map()で上手く新しい配列が作れなかった話

JavaScript
2いいね
@Kohei-Sato-1221さん(01月22日 10時36分の投稿)

49位: デザイナがReactを始める前に押さえておきたいJavaScriptの基本③(クラス編)

JavaScript クラス React 新人プログラマ応援
2いいね
@seiraさん(01月22日 00時10分の投稿)

50位: 花粉症LINE BotからのデータをWEBカレンダーに表示する(花粉カレンダー作成④)

JavaScript Node.js Vue.js Firebase LINEmessagingAPI
2いいね
@doikatsuyukiさん(01月21日 12時08分の投稿)

51位: JavaScriptの正規表現

JavaScript 正規表現
2いいね
@oekaki-hoho-ronさん(01月21日 00時07分の投稿)

52位: NuxtのasyncDataとdataの使い分け方

JavaScript Vue.js nuxt.js
2いいね
@ToshioAkaneyaさん(01月20日 22時03分の投稿)

53位: Understand Promise in 5 minutes

JavaScript promise Web
2いいね
@kueiappさん(01月20日 12時14分の投稿)

54位: 【Rails×Ajax】いいね機能の実装で上手く出来ないあなたへの2つの注意喚起 #学習者向け

Ruby JavaScript Rails エラー対処
2いいね
@shoji621さん(01月20日 12時03分の投稿)

55位: 高卒プログラマーのアルゴリズム体操(n!は末尾何桁ゼロが並ぶか)

JavaScript アルゴリズム 算数 CodeWars
2いいね
@pppp403さん(01月20日 10時15分の投稿)

56位: コード内で環境名 (dev,stg,prod) を使用しないことのすすめ

JavaScript Node.js Docker サーバー 環境変数
1いいね
@ku6ryoさん(01月26日 17時26分の投稿)

57位: React+Hookで作るメニュー外クリックで閉じるドロップダウンメニュー

JavaScript React
1いいね
@Tonoooさん(01月26日 15時15分の投稿)

58位: SPA向けのCSS設計 - COCSS

JavaScript SPA frontend CSS設計 cocss
1いいね
@stophobiaさん(01月26日 14時39分の投稿)

59位: Obnizで戦車ラジコンを作ろう

JavaScript obniz Motor M5Camera
1いいね
@porurubaさん(01月26日 13時37分の投稿)

60位: [Chrome拡張機能]ローカルのフォルダ/ファイルを開く機能を作ってみた

JavaScript Chrome C# chrome-extension Registry
1いいね
@zarukishiさん(01月26日 00時22分の投稿)

61位: Vue.jsのイベント修飾子について理解する

JavaScript Vue.js
1いいね
@hidacheさん(01月25日 17時50分の投稿)

62位: Deep Learningアプリケーション開発 (8) TensorFlow.js

JavaScript 機械学習 DeepLearning TensorFlow TensorFlow.js
1いいね
@iwatake2222さん(01月25日 17時23分の投稿)

63位: 【nuxt.js】headにmetaとかGAタグとか加える時のアレコレ

HTML JavaScript SEO nuxt.js
1いいね
@bandwagonさん(01月25日 16時26分の投稿)

64位: 【Vue.js】TOAST UI Image Editor を使って画像編集!

JavaScript canvas ライブラリ Vue.js 画像編集
1いいね
@r_pg10さん(01月25日 14時39分の投稿)

65位: ライブラリのコードを読み解くNo.1

JavaScript frontend redux
1いいね
@takahoriさん(01月25日 11時03分の投稿)

66位: 【OSS貢献記録】emoji-martをReact 17に対応

JavaScript OSS React
1いいね
@h6akhさん(01月25日 10時24分の投稿)

67位: Sigfox Coverage MapをGoogle Maps APIで表示する

JavaScript GoogleMapsAPI IoT token Tilemap
1いいね
@ghibiさん(01月25日 07時49分の投稿)

68位: CLI のテンプレートプロジェクト by node and TypeScript

JavaScript Node.js cli TypeScript
1いいね
@amay077さん(01月24日 22時03分の投稿)

69位: ifの入れ子をできるだけ少なくするための工夫

JavaScript 初心者
1いいね
@shimamarさん(01月24日 19時40分の投稿)

70位: Vue.jsプロジェクトにESLintを設定する最もシンプルな方法

JavaScript npm ESLint lint-staged husky
1いいね
@tamonmonさん(01月24日 17時25分の投稿)

71位: CodePen - HTML+JavaScriptで色サンプル(RGBカラーのパレット)作ってみた

JavaScript codepen
1いいね
@kob58imさん(01月24日 16時33分の投稿)

72位: JSのグローバル変数について備忘録

JavaScript 初心者
1いいね
@shimamarさん(01月24日 15時04分の投稿)

73位: iOSを“区別”したモーダル背景のスクロール固定

CSS JavaScript jQuery Safari モーダル
1いいね
@m_shinadaさん(01月24日 14時01分の投稿)

74位: chrome拡張機能で抽選機を作った

JavaScript chrome-extension
1いいね
@engabesiさん(01月24日 09時38分の投稿)

75位: jsPDFでHTML上の日本語表記のテーブルをサクッとPDF化

JavaScript PDF 日本語化 jspdf autotable
1いいね
@oigus-kさん(01月23日 17時59分の投稿)

76位: 【Nuxt.js】TypeScript基礎編:Vue.extendでシンプルなコードを書こう

JavaScript Vue.js コンポーネント nuxt.js
1いいね
@aLizさん(01月23日 17時03分の投稿)

77位: ReactNative(Expo)でイベントトラッキング 〜expo-analyticsの使い方をざっくりまとめる〜

JavaScript GoogleAnalytics React reactnative expo
1いいね
@tsukueさん(01月23日 14時02分の投稿)

78位: Javascript アロー関数を簡単にまとめて学ぶ

HTML CSS JavaScript
1いいね
@ryomaDsakamotoさん(01月23日 13時08分の投稿)

79位: 俺的PWAの振り返り

JavaScript PWA
1いいね
@krohigewagmaさん(01月23日 00時46分の投稿)

80位: Slackに匿名で画像を投稿できるようにした

JavaScript Node.js Slack slackbot Botkit
1いいね
@BIG_LARGE_STONEさん(01月22日 23時23分の投稿)

81位: React.Componentのメンバ変数は、再レンダー時に更新されるとは限らない!

JavaScript フロントエンド React
1いいね
@ssint1120さん(01月22日 18時22分の投稿)

82位: Lambda + API GatewayでCORSを有効にしているのにCORSでエラーになる

JavaScript AWS CORS lambda APIGateway
1いいね
@scalewalletさん(01月22日 12時55分の投稿)

83位: Chart.js【javascriptで簡単にグラフが描ける!】

JavaScript 初心者 グラフ chart.js ブランク
1いいね
@satosuzu1108さん(01月22日 09時09分の投稿)

84位: Vue.js の基本的な機能を使ったサンプルを書く

JavaScript Vue.js
1いいね
@niwasawaさん(01月21日 23時12分の投稿)

85位: async(Promise)のthen()内の関数の引数の省略

JavaScript promise async then
1いいね
@nkn-msさん(01月21日 18時47分の投稿)

86位: 文章読み上げアプリをJavaScriptで作成してみました

JavaScript HTML5 CSS3
1いいね
@otake619さん(01月21日 14時19分の投稿)

87位: Javascriptのシンプルな構成でAWS Cognitoを理解する

JavaScript AWS JWT cognito
1いいね
@TKFM21さん(01月21日 12時02分の投稿)

88位: React hooksとclassコンポーネントにおけるsetStateの挙動の違いについて(動かせるコードあり)

HTML JavaScript 初心者 hooks React
1いいね
@yseki_さん(01月20日 20時44分の投稿)

89位: obnizのBLEでペリフェラルに接続する

JavaScript Node.js BLE IoT obniz
1いいね
@yukisato1987さん(01月20日 20時23分の投稿)

90位: Reactでの関数のバインド 4つの方法のメモ

JavaScript 初心者向け React 新人プログラマ応援
1いいね
@penpentaさん(01月20日 18時17分の投稿)

91位: 【初心者】JavaScriptで「おみくじゲーム」を作ってみた

HTML JavaScript 初心者 初心者向け
1いいね
@yuya_yuzenさん(01月20日 15時56分の投稿)

92位: padStart で日付・時刻を二桁表示する書き方

JavaScript date 日付
1いいね
@Shohei-Japanさん(01月20日 14時02分の投稿)

93位: ReduxにおけるImmutableの概念についてまとめてみた

JavaScript 初心者 React redux Immutable
1いいね
@Rui1009さん(01月20日 13時18分の投稿)

94位: ソート機能を実装した際に感じたfilterメソッドの魅力

JavaScript
1いいね
@shotashimuraさん(01月20日 11時18分の投稿)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

npm で全ての package をアップデートする

$ npx npm-check-updates -u

これで package.json のみ更新される。
あとは、いつも通り npm i

以下、オプション例。

# "devDependencies" のみアップデートしたい
npx npm-check-updates -u --dep dev

# 特定の package はアップデートしたくない
npx npm-check-updates -u --reject typescript,@types/node

Ref.

https://www.npmjs.com/package/npm-check-updates

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Slack デスクトップアプリの最新版にカスタム CSS を当てる方法

BetterDiscord のようにカスタム CSS の適用を Slack でもしたかったのですが,最新版の v4.3.2 で行う方法を探すのが難しかったので簡単にまとめました.

はじめに

この方法は Slack 公式の機能ではありません.
何らかのデータ破損等が発生しても一切責任は負いませんので,自己責任でお願いします.

方法

Slack がインストールされたディレクトリを開きます.

OS ディレクトリ
Windows %LOCALAPPDATA%\slack
macOS /Applications/Slack.app/Contents
Linux /usr/lib/slack

app-x.x.x のうち一番バージョンが高いものを開きます.
resources 内の app.asar を以下のコマンドで展開します.

$ npx asar extract app.asar app.asar.tmp

展開された app.asar 内の main-preload-entry-point.bundle.js の末尾に以下を追記します.

main-preload-entry-point.bundle.js
document.addEventListener('DOMContentLoaded', function() {
    document.head.appendChild(
        Object.assign(document.createElement('style'), {
            innerHTML: `
/* 適用したいカスタム CSS */
            `
        })
    );
});

再度 app.asar に戻します.

$ npx asar extract app.asar.tmp app.asar

Slack のアプリで メニュー → ヘルプ → トラブルシューティング → キャッシュを消去して再起動 をクリックします.
これで完了です.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【gon】jsでRails変数を取得する

Railsでjsライブラリを使うときに楽にRailsで定義した変数をJavaScriptに渡したい!楽したい!

プログラマ三大美徳

1.怠慢(Laziness)
2.短気(Impatience)
3.傲慢(Hubris)

まさにこれですね。

何を使うか?

RailsといえばGemですね。[gon]というgemです。
https://github.com/gazay/gon

インストール

Gemfileに記載

Gemfile
gem 'gon'

ターミナルからインストール

ターミナル
bundle install

読み込み設定

application.html.erb
<%= include_gon %>
<%= javascript_include_tag "application" %>

使ってみる

****.controller.rb
gon.user_name = 'my name is imaizumi'
****.js
console.log(gon.user_name)

スクリーンショット 2020-01-25 21.11.28.png

バッチリ変数を渡せました:yum:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

js if条件判定 早見表

// 値
var v;
var v = '';
var v = 0;
var v = '0';
var v = null;
var v = 'null';
var v = undefined;
var v = 'undefined';
var v = [];

// console
console.log('v:' + v);

条件が空文字

console if(v) if(v == '') if(v === '') if(!v) if(v != '') if(v !== '')
v value:undefined false false false true true true
v = '' value: false true true true false false
v = 0 value:0 false true false true false true
v = '0' value:0 true false false false true true
v = null value:null false false false true true true
v = 'null' value:null true false false false true true
v = undefined value:undefined false false false true true true
v = 'undefined' value:undefined true false false false true true
v = [] value: true true false false false true

条件がnull

console if(v == null) if(v === null) if(v != null) if(v !== null)
v value:undefined true false false true
v = '' value: false false true true
v = 0 value:0 false false true true
v = '0' value:0 false false true true
v = null value:null true true false false
v = 'null' value:null false false true true
v = undefined value:undefined true false false true
v = 'undefined' value:undefined false false true true
v = [] value: false false true true

条件がundefined

console if(v == undefined) if(v === undefined) if(v != undefined) if(v !== undefined)
v value:undefined true true false false
v = '' value: false false true true
v = 0 value:0 false false true true
v = '0' value:0 false false true true
v = null value:null true false false true
v = 'null' value:null false false true true
v = undefined value:undefined true true false false
v = 'undefined' value:undefined false false true true
v = [] value: false false true true
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SPA入門色々(Vue.js/Blazor/React)

概要

SPA(Single Page Application)という技術があることを知り、フロントエンドフレームワークを触ってみるいい機会になるのでないかと思い、Vue.js/Blazor/Reactをまとめて触ってみた。
SPAのルータのページ切り替えを見て従来のiframeや生のJavaScriptとできることがどのように違うのかということも疑問に思ったので同じようなこちらでも同じような感じで実装を試みた。

作ったSPAの仕様

  • 上部にメニューバー
  • Home/Counter/Todoの独立したWebアプリをコンポーネントとして所持。
    • Home
      • 別のコンポーネントを読み込んで文字を表示するだけなアプリ。
    • Counter
      • Blazorの実行サンプルに付いてきたカウンターアプリ。
    • Todo

成果物

Vue.js | Blazor | React | iframe | Vanilla-JS
ソースとか(GitHub)

感じたメリット・デメリット

SPA全体

メリット

  • 本来サーバーサイドで受け持っていたような大幅なhtmlの書き換えが簡単にできる。
  • htmlを分割して部品化できる。
  • 単一htmlとしてシームレスにページ切り替えられる。

デメリット

  • フレームワークありきな存在であること。
  • 読み込み時に時間がかかりがち。
  • サーバーありきな存在なので気軽に公開しにくい。

Vue.js

メリット

  • 本来のhtmlに近い書き方でhtmlを複数ファイルに分割できる(.vue)。
  • 変数をフォームとバインド(同期)できる。
  • v-for\v-ifでタグをループできる。
  • CSSをコンポーネントに閉じ込めることができる。
  • vue-cliがあれこれ揃えてくれるのでセットアップが簡単。
  • 地味にラップされてるWebStorage。

デメリット

  • ルーターの設定が少し煩雑。
  • exportするオブジェクトの構成が少し煩雑。

Blazor

メリット

  • C#だけでほぼ完結して処理を書くことができる。
  • 本来のhtmlに近い書き方でhtmlを複数ファイルに分割できる(.razor)。
  • 変数をフォームとバインド(同期)できる。
  • @for\@ifでタグをループできる。
  • ルーターの設定が容易。
  • イベントハンドラの呼び出しが素直。
  • 他のコンポーネントを呼びだすためのimportが不要。

デメリット

  • まだリリース前。
  • ページを開いた時の読み込みが遅い。
  • @がゲシュタルト崩壊する。
  • DOM由来のAPIを扱うのが面倒。
  • 変数バインドと@onchangeを同時に使うことができない。
  • CSSに対するサポートがない。
  • ライフサイクルが少し心もとない?

React

メリット

  • あくまでもJavaScriptが主体となっている。

デメリット

  • htmlじゃないhtml。
  • htmlとして書くとJSXの形式が直感的じゃない。
  • フォームと変数のバインドに癖がある。
  • stateの更新が手間。
  • 必要なプラグインは基本的物でも各自そろえる必要がある。
  • CSSの扱いに癖がある(スコープ化するのも手間っぽい)。
  • 専用のループ構文は存在しない。

iframe

メリット

  • フレームワークが不要(htmlとJavaScriptの知識だけで良い)。
  • いにしえの枯れた技術。
  • 適当なホームページスペースに上げても動く。

デメリット

  • そもそも、SPAではない。
  • htmlが独立しておりそれぞれの連携が面倒。
  • URLが項目ごとに変わらない。
  • 各フレームのサイズ調整が困難。
  • フォームと変数のバインドとかはない。

生JavaScript

メリット

  • あるかな?

デメリット

  • 生成した要素の状態管理が絶望的。
  • 再レンダリングが困難

まとめ

触り始めたばかりの素人の感覚としてはReactよりもVue.jsが触りやすいもののように思えた。
アニメーションを付けたりする段階まで行くとまた印象が変わってくるのかもしれない。
Blazorはwasmというものの可能性を感じるものだと感じた。
また、全体的な構成がVue.jsと似ており、触りやすいような印象を受けた。
ちなみにAngularは触ろうとしたけど面倒くさくなって投げた(情報量少なくないですか…?)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

サーバサイドレンダリングについて調べてみた

サーバサイドレンダリングの勉強メモ

サーバサイドレンダリングがいまいち分からなかったので自分なりに仮説を立ててみたり、疑問に思ったところを書き残してます
ググると色々出てきますが、一通り読んでもなんとなくしか理解できず、、、
是非間違いがあればご指摘頂きたいです
主にこちらを参考にさせて頂きました

サーバサイドレンダリングのメリット

  1. 初期ロード時間が短い
    => 最大の利点。ブラウザで実行されるjsをサーバで実行してレンダリングしたものをクライアントに返すので初期表示がとても早い

  2. SPAとは違い確実にクローリングされるのでSEOが良い
    => これは昔の話で、現在はjsでレンダリングされるページもクローラーは検知できるので大した利点ではない

  3. 柔軟性

  4. メンテナンス性
    => 重複したロジックを低減できる? MVCのViewをフロントエンドが一貫して担当することでMVCの独立性が増す? いまいちピンときてません

実際、ここ一年で私が関わった関わった3つのプロジェクトのうち、2つでSPAによる開発とし、さらにそのうちの1つをアイソモーフィックJavaScriptで開発しましたが、バックエンドはAPIを返すだけでよくなったためロジックやパフォーマンスチューンに集中することができ、フロントエンドはバックエンドの組み込みやテンプレートの準備などを待たずに開発ができるようになりました。

担当を分断できるのは効率がよくなりそうではあるよね、バックエンドを待つ必要があったというのがピンとこないけど

image.png

この画像分かりやすい!
jsファイルと画像ファイルのダウンロード時間は変わらないけど、jsファイルはパース,コンパイル,実行にかかる時間が半端ないですね
だからjsはサーバで実行させようって話か!

謎1
でもよく分からんのがブラウザで実行するからあのアニメーション的な動きが出せるわけではないのですか?
ブラウザでボタンぽちー => リクエスト送信 =>サーバでjs実行してHTML生成 => ブラウザで表示
これだとあのヌルヌルした楽しい動きがなくなるのではないのですか?

また、アイソモーフィックJavaScript(SPA)を使用する場合は、ほとんどの場合Vue.jsやReactのようなフレームワークを導入することとなるため、結果的にフロントエンドのコードがコンポーネント単位で独立し、長期的な保守性も向上しました。

コンポーネント単位で分割できると修正とか楽そうだよね、本当になんとなくだけども

サーバサイドレンダリングのデメリット

1.情報量 —日本語の情報が少ない
=>これは調べてみて実感しました、このサイトめっちゃわかる!ってのにまだ出会えてない...

2.堅牢性 — エラーハンドリングやテストなどが必須に

3.サーバー — Node.jsをサポートしたサーバー
=>これってそんな大変なのかな?

アイソモーフィックJavaScriptは、サーバーサイドの一部をフロントエンドが担当することになるため、一言で言えばフロントエンドのカバーする範囲が従来よりも広くなります。
従来のWebアプリケーションでは、仮にエラーハンドリングをしなかったとしてもそのセッション中のアプリケーションが停止するだけで済みましたが、アイソモーフィックJavaScriptでは、フロントエンドのエラーの影響がサーバー全体に及ぶ可能性があります。
例えばGETパラメーター( http://example.com/?param=fooの ?param=foo の部分)によって挙動を変えるアプリケーションがあった場合、パラメーターから受け取った値を適切に処理しなければ、サーバー全体に関わるセキュリティリスクになりえます。
エラーハンドリグやサニタイズ、単体テスト、セキュリティに関わる処理は別途用意したAPIサーバーを通じて行い、JavaScript側に機密情報を含む処理を実装しないようにするなど、今までよりも多くのことができるようになった分、より多くのことに気つけなくてはなりません。

要するに例外処理の手間がかかるってことですかね

そもそもSSRってなんで必要なんだっけ

シンプルに構成されたシングルページアプリケーションでは、サーバーはファイルを配信するだけで、画面を組み上げるための処理はブラウザ側で行われます。この方式におけるいくつかのデメリットを埋めるために考案されたのが、ある程度の画面構築をサーバー側で行う、サーバーサイドレンダリングです。

SPAの問題点:シングルページアプリケーション(SPA)の場合、サーバはHTML,js,cssなどのファイルを送信するだけでHTMLを組み立てる(レンダリングする)のはブラウザが行うのでjsの実行に時間がかかる
これを解決するために考案されたのがSSR!!

ある程度の画面構築をサーバ側で行う

仮説

これ見てピンと来た仮説がこちら
仮説1:謎1で話した画面が再構築される時のアニメーションはブラウザで実行する必要がある。
仮説2:実行に時間のかかるjsイベントはSSRで。アニメーションで動かしたいjsはブラウザで行う。

もしこの仮説が合ってるなら最初のこの文章も納得できる

Isomorpfic JavaScriptとは、クライアントとサーバで同じjavascriptコードが実行されることを意味している
つまりjsファイルはサーバにも置いておくし(SSR用)、最初のリクエストで全てのjsファイルをクライアントにも返す(SPA用)ってことかな?
SSRとSPAはどちらか一方しか使わないものだと思い込んでいたけど、もしかして併用とかするんかな?

とりあえず今日はここまで...

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptのデータ型や組み込みオブジェクトについて

はじめに

自分なりの解釈で書いています。
多分間違っているかもしれないので「あーこんなかんじなのかなー」程度に聞いてほしいです。
そしてこの記事が僕にとっての初投稿となる記事になります。なので温かい目で見てくれると幸いです。

データ型とは

JavaScriptは動的型付け言語に分類される言語であるため、静的型付け言語のような変数の型はありません。 しかし、文字列、数値、真偽値といった値の型は存在します。 これらの値の型のことをデータ型と呼びます。

jsprimer データ型とリテラル より  

このように変数を定義して値を変数に代入した時、値が文字列ならstring型となって数値ならnumber型となります。
他にもありますが省略します

組み込みオブジェクトについて

ビルトインオブジェクトともいうらしい。
標準で定義されているオブジェクトのプロパティやメソッドを使うことができるようだ

データ型と組み込みオブジェクトの関係

詳しくは説明できないが、それぞれのデータ型(string,number,boolean)には最初からオブジェクトが定義されていて(組み込みオブジェクト)、仮に変数がstring型になった場合、stringオブジェクト(組み込みオブジェクト)の中のプロパティやメソッドを使える。
そのプロパティやメソッドを使う(アクセス)の仕方は、オブジェクトを使う時と同じようにするだけだ。

main.js
let obj = {
    name: 'yuuki3',
    hello: function() {
        console.log('hello world');
    }
}
console.log(obj.name);//yuuki3
obj.hello();//hello world

これはごく普通のオブジェクトのアクセス方法で、実行すればコンソールのほうにyuuki3とhello world が出てくるはず。
次は組み込みオブジェクトを使う

index.js
let str = 'こんにちは';
console.log(str.length);//5
console.log(str.indexOf(''));//2

ちょっと詳しく説明してみる
lengthプロパティについて
何をしているのかというと、まず変数strに こんにちは という文字列を代入している。(シングルクォーテーションやダブルクォーテーションで囲めば文字列にできる)
次,console.log()を使いブラウザのコンソールにlog()というメソッドの()の中に入力したものを出力させる。( () とは引数という)
()引数の中にstr.lengthと書いたがこれをよく見ると、いつも使っているオブジェクトのアクセス方法に似ている。(main.jsのなかのconsole.log(obj.name)が似ていると思う。)
lengthは文字数を取得してくれるプロパティのことだ。
文字数は5文字あることがわかる
裏のどこかにstringオブジェクトとかを定義しているファイルがあって(尚且つデータ型が決まれば、そこからアクセスできる)、そのstringオブジェクトの中にあるlengthとして定義されているのを使ったんだと自分は解釈している。

次はindexOf()というメソッドについて。
stringオブジェクトの中に定義されているindexOfメソッドを使う。
indexOf()というメソッドは、対象となる物(ここでいうstr)の中から引数に入ってるものを最初から何番目にあるのかを知るため(検索するため)のメソッドである
今回は 「こんにちは」 という文字列の中に 「に」 は何番目にあるかを探しています
初めての方は3だと思いますが、それは違います。
javascriptなどの言語では1から数えるのではなくて0から数えていきます。
なので0から数えていくと引数に入れている文字は2番目にあることがわかります。

終わりに

javascriptのデータ型や標準でメソッドやプロパティが使える組み込みオブジェクト(ビルトインオブジェクト)について軽く自分なりの解釈で説明していきました。(説明下手だと思うけど.....)

まあmarkdownの書き方も練習できたのでいい経験になったんじゃないかな。と

終わり

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ES6の配列メソッドfilter,map 〜ポケモンとポケルスを使って考えてみる〜

cat_javascript.png

はじめに

ES6(ES2015)から便利なメソッドがたくさん増えました。その中で特によく使いそうな配列メソッドfilter,mapを、ポケモンを例えに説明します。

各メソッドの説明の後に、
『メンバー6体をそれぞれ10,000回対戦させて、ポケルスに感染したポケモンを選抜しよう』検証を通して、filter,mapを実践的に使います。

filter()とは

対象となる配列からデータを抽出するメソッド

filter()の例

// この御三家未だにかわいいと思う
const gosanke = [
  {'name': 'ヒトカゲ'   , 'type': 'ほのお'},
  {'name': 'ゼニガメ'   , 'type': 'みず'},
  {'name': 'フシギダネ' , 'type': 'くさ'},
];

// これでタケシとカスミを一瞬で倒してやるぜ!
let kiminikimeta = gosanke.filter(pokemon => {
  return pokemon.type === 'くさ';
});

console.log(kiminikimeta);
// {'name': 'フシギダネ' , 'type': 'くさ'}

御三家の中から、タイプが草のポケモン、つまり「フシギダネ」を選んだわけです。pokemonに配列の各要素は代入されていきます。returnを忘れないようにしましょう。

map()とは

配列から各要素を受け取って、新しい配列を作成するメソッド

map()の例

// 旅も終盤ですなー
const gosanke = [
  {'name': 'リザードン' , 'type': 'ほのお'},
  {'name': 'カメックス' , 'type': 'みず'},
  {'name': 'フシギバナ' , 'type': 'くさ'},
];

// メガ進化だ!!
let megasinka = gosanke.map(pokemon => {
  return `メガ${pokemon.name}`;
})

console.log(megasinka);
/* {'name': 'メガリザードン'},
   {'name': 'メガカメックス'},
   {'name': 'メガフシギバナ'} */

みんなメガ進化に成功しました!(そんなことあるのかな...笑)
ここでもreturnを忘れないようにしましょう。

では本題!

とその前に

ポケルスって

ポケルスとは感染すると得られる努力値が2倍になる病気。感染・発症条件は「戦闘終了後」だが、その確率 3/65536 。
https://dic.nicovideo.jp/a/ポケルス

普通に物語を楽しんでいるだけでは、まあ感染しない良い病気です。色違いのポケモンに出会す確率よりも低いんですって。ほえ〜。
ちなみにポケルスは自然感染以外にも感染しますが、今回はそれは置いておきます。

『メンバー6体をそれぞれ10,000回対戦させて、ポケルスに感染したポケモンを選抜しよう』

// ポケモンワールドチャンピオンシップス初代優勝パーティー
const Pokemons = [
  {'name': 'メタグロス' , 'status': ''},
  {'name': 'ボーマンダ' , 'status': ''},
  {'name': 'ルンパッパ' , 'status': ''},
  {'name': 'ドクロッグ' , 'status': ''},
  {'name': 'カビゴン'   , 'status': ''},
  {'name': 'エンペルト' , 'status': ''}
];

let myParty = Pokemons.map(Pokemon =>{
  // 10000回、バトルだ!
  for (let i = 1; i <= 10000; i++) {
    if (Math.random() < 3/65536) Pokemon.status = 'pokerus';
  }
  return Pokemon
});

// どれどれ...ポケルスを持っている子はいるかね
let myMemberPokerus = myParty.filter(myPokemon => {
  return myPokemon.status === 'pokerus'
})

// ポケルスを持ったポケモンは何体いたかな
console.log(myMemberPokerus);

mapもfilterも便利!!

※努力値振った後の初代メンバーにポケルスが感染させても意味ないんでしょうけどね(ポケモンマスターじゃないからあんまりわかってない)

さいごに

初めての投稿で分かりづらい部分もあったかと思いますので、コメントいただけるとありがたいです。これからも、JavaScript、React、Typescriptについての情報を発信していきます!
最後までご覧いただきありがとうございました。

参考

初めてのJavaScript 第3版

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsのイベント修飾子について理解する

概要

本記事では、Vue.jsで用意されているイベント修飾子について.stop.preventを例にしていまとめていきます。

.stop

処理内容はJavaScriptにおけるDOMのEventオブジェクトのevent.stopPropagation()と同じ。
下記の例では、mousemoveによりpタグの上にマウスを乗せるとイベントが発火するようになっています。
そのpタグの中にspanタグを入れていますが、このspanタグの上にマウスを乗せた時だけはイベントを発火させたくないという時に.stopを使用します。
イベント発火をさせたくないspanタグにv-on:mousemove.stopを書いてあげることで、"ここでは反応させたくない"の文字の上ではイベントが発火しないようになります。

index.html
<div id="app">
  <p id="sampleText" v-on:mousemove="mousePosition">ここにマウスを載せると下のX、Yの値が変わるよ
    <span v-on:mousemove.stop>ここでは反応させたくない</span>
  </p>
  <p id="result">X:{{x}}, Y:{{y}}</p>
</div>
index.js
new Vue({
  el: '#app',
  data: {
    x: 0,
    y: 0
  },
  methods: {
    mousePosition: function(event) {
      this.x = event.clientX;
      this.y = event.clientY;
    }
  }
})

.prevent

処理内容はJavaScriptにおけるDOMのEventオブジェクトのevent.preventDefault()と同じ。
下記の例では、aタグのリンク先としてGoogleを設定しています。
本来であればクリックした時にGoogleのサイトに遷移しますが、v-on:click.preventと記述するとaタグとしてのデフォルトの挙動を妨げることができます。

index.html
<div id="app">
  <a v-on:click.prevent href="https://google.com">リンク</a>
</div>
index.js
new Vue({
  el: '#app'
})

最後に

Vue.jsで用意されているイベント修飾子をうまく利用する事で、シンプルなコードを書く事ができるようになります。
他にも利用可能なイベント修飾子はたくさんあるので調べてみてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React向けチャートライブラリRechartsで円グラフ

React向けチャートライブラリRechartsで円グラフ

?グラフの種類

AreaChart:面グラフ。折れ線グラフに基づき、定量データを表示したグラフ。
BarChart:棒グラフ。四角い棒の長さで何らかの値を表現するグラフ。
LineChart:折れ線グラフ。散布図の一種であり、プロットされた点を直線でつないだグラフ。
ComposedChart:折れ線グラフ、面グラフ、棒グラフで構成されるグラフ。線のような単一のタイプのチャートを描画する場合は、LineChartを使う。
PieChart:円グラフ。丸い図形を扇形に分割し、何らかの構成比率を表したグラフ。
RadarChart:レーダーチャート。複数の項目の大きさを一見して比較することのできるグラフ。
RadialBarChart:円弧で表現する棒グラフ。
ScatterChart:散布図(分布図)。縦軸、横軸に2項目の量や大きさ等を対応させ、データを点でプロットしたグラフ。
FunnelChart:ファンネルチャート。販売プロセスの段階を表し、各段階の潜在的な収益額を示すためによく使用されるグラフ。
Treemap:ツリーマップ。ネストされた長方形のセットとして階層状のツリー構造のデータを表示したもの。

円グラフに関連するコンポーネント

<PieChart />コンポーネント

円グラフの大元となるコンポーネント。全体のサイズ等を定義します。

☘️プロパティ

  • width : チャートコンテナーの幅。
  • height : チャートコンテナーの高さ。
  • margin : コンテナの周りの空白のサイズ。例){ top: 5, right: 5, bottom: 5, left: 5 }

?イベント

  • onClick
  • onMouseEnter
  • onMouseLeave

?子コンポーネント

  • <PolarAngleAxis />:
  • <PolarRadiusAxis />:
  • <PolarGrid />:
  • <Legend />:
  • <Tooltip />:
  • <Pie />:
  • <Customized />:

<Pie />コンポーネント

グラフのセクター全体についてを定義します。

プロパティ

  • cx : 中心のx座標。パーセンテージを設定した場合、最終値はコンテナ幅のパーセンテージを乗算することにより取得されます。
  • cy : 中心のy座標。パーセンテージを設定すると、コンテナの高さのパーセンテージを乗算することで最終的な値が取得されます。
  • innerRadius : すべてのセクターの内半径。パーセンテージを設定した場合、最終値は、幅、高さ、cx、cyで計算されるmaxRadiusのパーセンテージを乗算することにより取得されます。
  • outerRadius : すべてのセクターの外半径。パーセンテージを設定した場合、最終値は、幅、高さ、cx、cyで計算されるmaxRadiusのパーセンテージを乗算することにより取得されます。
  • startAngle : 最初のセクターの開始角度。円の頂点から反時計回りで始めたい場合は、{startAngle:90, endAngle:450}、同じく円の頂点から時計回りで始めたい場合は、{startAngle:450, endAngle:90}を指定する。
  • endAngle : 最後のセクターの終了角度。
  • minAngle : 各非ゼロデータの最小角度。
  • paddingAngle : 2つのセクター間の角度。
  • nameKey : 各セクターの名前のキー。
  • dataKey : 各セクターの値のキー。
  • legendType : 凡例のアイコンのタイプ。'none'に設定すると、凡例項目はレンダリングされません。'line' 'square' 'rect 'circle' 'cross' 'diamond' 'square' 'star' 'triangle' 'wye' 'none'
  • label : trueの時、値がラベルとして表示されます。デフォルトはfalse
  • labelLine : trueの場合、セクターとラベルを結ぶ線を描画。
  • data : 各要素がオブジェクトであるソースデータ。
  • activeIndex : Pieのアクティブセクターのインデックス。このオプションは、マウスイベントハンドラーで変更できます。
  • activeSphape : アクティブなセクターの形状。
  • isAnimationActive : trueに設定すると、パイのアニメーションが有効化。
  • animationBegin : アニメーションをいつ開始するかを指定します。このオプションの単位はmsです。
  • animetionDuration : アニメーションの継続時間を指定します。このオプションの単位はmsです。
  • animationEasing : イージング関数のタイプ。'ease' 'ease-in' 'ease-out' 'ease-in-out' 'linear' Function

イベント

  • onAnimationStart
  • onAnimationEnd
  • onClick
  • onMouseDown
  • onMouseUp
  • onMouseMove
  • onMouseOver
  • onMouseOut
  • onMouseEnter
  • onMouseLeave

子コンポーネント
- <Cell />
- <LabelList />

<Cell />コンポーネント

各セクターの色や枠を定義します。

プロパティ
- fill : セクターの塗りつぶし色を指定。
- stroke : セクターの枠線の色を指定。
- strokeWidth : セクターの枠線の太さを数値で指定。

<LabelList />コンポーネント

各セクターに対応するラベルを定義します。

<Legend />コンポーネント

グラフの凡例を定義します。

プロパティ

  • width : 凡例の幅。
  • height : 凡例の高さ。
  • verticalAlign : 凡例の位置。
  • formatter : 凡例の項目表示をカスタマイズ。

<ToolTip />コンポーネント

セクターにマウスオーバーしたときのツールチップを定義。
プロパティ
- content : ツールチップをカスタマイズ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Deep Learningアプリケーション開発 (8) TensorFlow.js

この記事について

機械学習、Deep Learningの専門家ではない人が、Deep Learningを応用したアプリケーションを作れるようになるのが目的です。MNIST数字識別する簡単なアプリケーションを、色々な方法で作ってみます。特に、組み込み向けアプリケーション(Edge AI)を意識しています。
モデルそのものには言及しません。数学的な話も出てきません。Deep Learningモデルをどうやって使うか(エッジ推論)、ということに重点を置いています。

  1. Kerasで簡単にMNIST数字識別モデルを作り、Pythonで確認
  2. TensorFlowモデルに変換してPythonで使用してみる (Windows, Linux)
  3. TensorFlowモデルに変換してCで使用してみる (Windows, Linux)
  4. TensorFlow Liteモデルに変換してPythonで使用してみる (Windows, Linux)
  5. TensorFlow Liteモデルに変換してCで使用してみる (Linux)
  6. TensorFlow Liteモデルに変換してC++で使用してみる (Raspberry Pi)
  7. TensorFlow LiteモデルをEdge TPU上で動かしてみる (Raspberry Pi)
  8. TensorFlow.jsモデルに変換してブラウザ上で動かしてみる <--- 今回の内容

今回の内容

  • Kerasモデル(h5)を、TensorFlow.jsモデルに変換する
  • TensorFlow.jsモデルを使ってみる
  • カメラ入力から数字識別するWebアプリを作ってみる

ソースコード: https://github.com/iwatake2222/tfjs_study/tree/master/mnist

環境

  • Google colaboratory
    • Tensorflow 1.15
  • VisualStudio Code
  • ブラウザ(Chrome)

TensorFlow.jsとは

機械学習用 JavaScript ライブラリです。
TensorFlow.jsを使ってモデルを作り、推論するところまで出来ます。

本記事では以前Pythonで作成したモデルを変換して推論してみます。
TensorFlow.jsもチュートリアルやデモが充実しているのですが、推論に特化したシンプルなコードがなかったり、分かりやすい前処理コードが見つからなかったので、そこらへんを補完する意味で本記事を書いてみました。

Kerasモデル(h5)を、TensorFlow.jsモデルに変換する

Kerasモデルを作る

元になるモデルはKerasで簡単にMNIST数字識別モデルを作り、Pythonで確認で作成したconv_mnist.h5を使います。
これは、28x28グレースケール画像を入力して、数字の0~9を識別する簡単なMNIST用モデルです。

モデル作成コードは、https://github.com/iwatake2222/tfjs_study/blob/master/mnist/CreateMnistModel_tfjs.ipynb の通りです。

TensorFlow.jsモデルに変換する

変換のために、tensorflowjs パッケージが必要になるのでインストールします。その後、tensorflowjs_converter で変換します。
(https://www.tensorflow.org/js/tutorials/conversion/import_keras )

Google Colaboratoryだと、以下のようになります。
TensorFlow.jsモデルをconv_mnist_tfjs.tar.gzとしてローカルPCにダウンロードして保存しておきます。

TensorFlow.jsモデルに変換するスクリプト(colab)
!pip install tensorflowjs

!mkdir conv_mnist_tfjs
!tensorflowjs_converter --input_format keras \
                       conv_mnist.h5 \
                       ./conv_mnist_tfjs
!tar zcvf conv_mnist_tfjs.tar.gz conv_mnist_tfjs

# Download to local
from google.colab import files
files.download("./conv_mnist_tfjs.tar.gz")
files.download( "./conv_mnist.h5")

モデルの中身は、以下のようになっています。モデルサイズが小さいのでbinは1つだけですが、大きいモデルになると数が増えていくと思います。
model.jsonにモデル情報が格納されています。

TensorFlow.jsモデル
model.json
group1-shard1of1.bin

TensorFlow.jsモデルを使ってみる

(おまけ)JavaScript開発環境

開発環境の準備

VisualStudio Codeに以下のExtensionをインストールしました。

  • 必須
    • Debugger for Chrome
    • Live Server
  • 必要に応じて
    • ESLint
    • HTMLHint
    • IntelliSense for CSS class name in HTML

デバッグ方法

適当なhtmlファイルを右クリックして、「Open with Live Server」をクリックします。
すると、ローカルサーバが起動してブラウザ上で選択したhtmlが開かれます。

1.png

次に、これをデバッグできるようにします。
「F5」キーを押して、「Select Environment」から「Chrome」を選びます。その後、launch.json が開かれるので、url の項目を先ほど起動したローカルサーバ上のアドレスに変更します。

2.png

launch.json
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "chrome",
            "request": "launch",
            "name": "Launch Chrome against localhost",
            "url": "http://127.0.0.1:5500/mnist/index_image.html",
            "webRoot": "${workspaceFolder}"
        }
    ]
}

簡単なテストアプリでMNIST数字識別する

コードと実行結果

HTML5のimgタグの内容を読み込んで、数字識別してみます。

3.png

index_image.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Test TensorFlow.js</title>
  </head>

  <body>
    <img id="img" src="number.jpg"></img>
    <p id="result" style="font-size: 20pt;"></p>

    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js"></script>

    <script>
      "use strict";
      console.log(tf.version)
      tf.loadLayersModel("./conv_mnist_tfjs/model.json").then(model => {
      // tf.loadLayersModel("https://iwatake2222.github.io/tfjs_study/mnist/conv_mnist_tfjs/model.json").then(model => {
        console.log(model.input.shape);
        const MODEL_HEIGHT  = model.input.shape[1];
        const MODEL_WIDTH   = model.input.shape[2];
        const MODEL_CHANNEL = model.input.shape[3];

        /* Read image and convert into tensor */
        const img_org = document.getElementById('img');
        let inputTensor = tf.browser.fromPixels(img_org, 3);  // get rgb (without alpha)

        /* Resize to model input size (28x28) */
        inputTensor = inputTensor.resizeBilinear([MODEL_HEIGHT, MODEL_WIDTH])

        /* Convert to grayscale (keep dimension(HWC))*/
        inputTensor = inputTensor.mean(2, true);

        /* Reverse black and white */
        inputTensor = tf.sub(255, inputTensor);  

        /* 0.0 - 1.0 */
        inputTensor = inputTensor.cast("float32").div(tf.scalar(255));

        /* expand dimension (HWC ->  NHWC) */
        inputTensor = inputTensor.expandDims();

        /* Inference */
        // scores = model.execute(inputTensor, "output_scores");
        const scores = model.predict(inputTensor).dataSync();

        /* Post process */
        const maxScoreIndex = tf.argMax(scores).arraySync();

        /* Display result */
        console.log(scores);
        document.getElementById("result").innerHTML = "Number: " + maxScoreIndex + " (" + scores[maxScoreIndex].toFixed(3) + ")";

      });
    </script>
  </body>
</html>

コードの説明

TensorFlow.jsを使う準備

<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js"></script> によって、TensorFlow.jsを読み込んでいます。
Node.jsで実行する場合には、代わりにimport * as tf from '@tensorflow/tfjs'; するみたいです。

モデルのロード

tf.loadLayersModel("./conv_mnist_tfjs/model.json") によって、先ほど変換したTensorFlow.jsモデルを読み込みます。この関数はPromiseを返す非同期関数なので、モデル読み込み後の処理はthen の中に記載します。モデル読み込み後、まずはモデルのサイズ情報をログに出力してみました。

画像読み込み

tf.browser.fromPixels によって、imgタグの内容をTensorに変換しています。
imgタグは、RGBAの4チャネルの画像情報を持ちます。Tensorに変換する際にはアルファチャネルを除いた3チャネルだけを取得します。そのため、3(default)を指定しています。これで、R0,G0,B0,A0,R1,G1,B1,A1,R2,G2,B2,A0というピクセル画像データをR0,G0,B0,R1,G1,B1,R2,G2,B2というTensorにします。
https://js.tensorflow.org/api/latest/#browser.fromPixels

前処理(リサイズ)

tf.image.resizeBilinear によって、モデルの入力サイズ(28x28)にリサイズしています。
https://js.tensorflow.org/api/latest/#image.resizeBilinear

前処理(グレースケール化)

tf.mean によって、RGB3チャネルTensorをグレースケールの1チャネルTensorに変換します。axis = 2を指定することで、[R,G,B] の平均を取ります。また、keepDims=trueを指定することで、平均を取った後も、shapeの形を維持します(28x28ではなく、28x28x1のままにする)。
https://js.tensorflow.org/api/latest/#mean

イメージ
[[R,G,B],[R,G,B],[R,G,B],],
[[R,G,B],[R,G,B],[R,G,B],],
[[R,G,B],[R,G,B],[R,G,B],],
⇒
[[Y],[Y],[Y],],
[[Y],[Y],[Y],],
[[Y],[Y],[Y],],

前処理(白黒反転)

MNISTモデルの学習に使用したデータは黒字に白のため、白黒反転させます。
そのために、255とtf.sub で引き算しています。

前処理(値の正規化)

MNISTモデルの学習時に、ピクセル値0~255を、0.0~1.0に正規化しました。
これに揃えるために、tf.cast でfloat32にし、tf.div で255で割っています。

前処理(次元の拡張)

TensorFlowの入力TensorはNHWCを期待しています。
ここまで、通常の画像だったのでTensorの形はHWCの3次元でした。推論なので、N(バッチ数)が1になるようtf.expandDims で拡張します。
https://js.tensorflow.org/api/latest/#expandDims

推論

tf.LayersModel.predict によって、推論処理を実行します。この関数自体は、tf.Tensor を返却します。
https://js.tensorflow.org/api/latest/#tf.LayersModel.predict

tf.Tensor から結果のデータを取り出す必要があります。
そのために、tf.Tensor.datatf.Tensor.dataSync を使います。tf.Tensor.data だと非同期処理になります。サンプルコードだと大体こちらが使われているのですが、ここでは簡単にするためtf.Tensor.dataSync を使いました。
これによって、scoresという変数に結果が格納されます。
https://js.tensorflow.org/api/latest/#class:Tensor

後処理

画数時のスコアが格納されたscoresから、最もスコアの高いものを探します。
そのために、tf.argMax を使いました。この関数もtf.Tensor を返却すうるので、そこからデータを配列として取り出すために、今回はarraySync を使いました。

最後に結果を表示して完了です。

カメラ入力から数字識別するWebアプリを作ってみる

コードと実行結果

次に、ウェブカメラで撮影した画像から数字識別をするという、少し実用的なアプリケーションを作ってみます。
カメラから画像を読み込み、中央付近をクロップ/リサイズしてMNISTモデルに食わせています。
その際、簡易的な2値化処理もしています。

4.png

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Test TensorFlow.js</title>
  </head>

  <body>
    <video id="video"></video>
    <canvas id="canvas"></canvas>
    <span id="result" style="font-size: 48pt;"></span>
    <p id="time"></p>

    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js"></script>

    <script>
      "use strict";
      console.log(tf.version)

      // Parameters
      const CANVAS_SIZE = [300, 300];
      const TARGET_AREA = [0.25, 0.25, 0.75, 0.75]; // y1, x1, y2, x2
      const WEBCAM_CONFIG = {facingMode: "environment"};
      let   MODEL_SIZE = [-1, -1, -1, -1]; // NHWC. get from model

      async function initCam() {
        // const videoElement = document.createElement("video");
        try {
          const videoElement = document.getElementById("video")
          videoElement.width = CANVAS_SIZE[0];
          videoElement.height = CANVAS_SIZE[1];
          const cam = await tf.data.webcam(videoElement, WEBCAM_CONFIG);
          return cam;
        } catch (e) {
          alert("[initCam] failed");
          alert(e.message);
          return null;
        }
      }

      async function initModel() {
        try {
          let model = await tf.loadLayersModel("./conv_mnist_tfjs/model.json");
          MODEL_SIZE = model.input.shape;
          return model;
        } catch (e) {
          try {
            alert("[initModel] failed to open local model. try to load from server");
            let model = await tf.loadLayersModel("https://iwatake2222.github.io/tfjs_study/mnist/conv_mnist_tfjs/model.json");
            MODEL_SIZE = model.input.shape;
            return model;
          } catch (e) {
            alert("[initModel] failed");
            alert(e.message);
            return null;
          }
        } 
      }

      async function getImage(cam) {
        const imgCam = await cam.capture(); /* [300x300x3] tensor */

        const processedImg = tf.tidy(() => {
          /* Crop center and Resize to model input size (28x28) */
          /* need expandDims and squeeze to ficropAndResize */
          let img = tf.image.cropAndResize(imgCam.expandDims(), [TARGET_AREA], [0], [MODEL_SIZE[1], MODEL_SIZE[2]]).squeeze()

          /* Convert to grayscale (keep dimension(HWC))*/
          img = img.mean(2, true);

          /* Reverse black and white */
          img = tf.sub(tf.scalar(255), img);  

          // /* 0.0 - 1.0 */
          // img = img.cast("float32").div(tf.scalar(255));
          /* Rough binarization */
          img = img.cast("float32").div(tf.scalar(128));  /* 0.0 - 2.0 */
          img = img.clipByValue(0.5, 1.5).sub(0.5); /* 0.5 - 1.5 -> 0.0 - 1.0 */

          return img;
        });
        imgCam.dispose();

        tf.browser.toPixels(processedImg.resizeBilinear([128, 128]), document.getElementById("canvas"));

        /* expand dimension (HWC ->  NHWC) */
        return processedImg.expandDims();
      }

      (async function() {
        const cam = await initCam();
        const model = await initModel();
        if (cam == null || model == null) {
          document.getElementById("result").innerHTML = "init failed"
          return;
        }

        while(1) {
          /* Get image and pre process */
          const t0 = performance.now();
          const inputTensor = await getImage(cam);

          /* Inference */
          const t1 = performance.now();
          const scores = await model.predict(inputTensor).data();
          inputTensor.dispose();

          /* Post process */
          const t2 = performance.now();
          const maxScoreIndex = await tf.argMax(scores).array();

          /* Display result */
          const t3 = performance.now();
          console.log(scores);
          document.getElementById("result").innerHTML = "Num: " + maxScoreIndex + " (" + scores[maxScoreIndex].toFixed(3) + ")";

          const t4 = performance.now();
          document.getElementById("time").innerHTML = `Time[ms]: Total = ${(t4 - t0).toFixed(3)},
          PreProcess = ${(t1 - t0).toFixed(3)},
          Inference = ${(t2 - t1).toFixed(3)},
          PostProcess = ${(t3 - t2).toFixed(3)}`;
        }
      }());

    </script>
  </body>
</html>

コードの説明

先ほどのシンプルなコードを少し整理しました。
各処理を関数化しました。
上述した通り、モデルのロードやtf.Tensorからデータを取り出したりする処理は非同期となります。
今回は、await によって処理の完了を待っています。デモコードでもほとんどはこの方法が使われていました。
注意点として、awaitasync 関数の中でしか使うことはできません。トップレベルではworkaroundとして無名async関数の中で処理しています。

カメラ初期化

webカメラから画像を読み込むために、tf.data.webcam を使用しています。webcamVideoElement にHTML5のvideoエレメントを指定することで、自動的に取り込んだ画像を表示してくれます。また、スマホで使用することを考慮して、webcamConfigfacingMode にはenvironment (背面カメラ)を指定しました。

https://js.tensorflow.org/api/latest/#data.webcam

モデルのロード

やっていることは先ほどと同じですが、少し処理を変えています。
TensorFlow.jsではモデルロードのためにFetch APIを使用しているらしく、ファイルはサーバ上にあるものしかロードできません。
今回デバッグ時はローカルサーバを立てているので問題はないのですが、スマホなどにファイル一式をコピーして実行すると以下のようなエラーが出てしまいます。

エラー
platform_browser.ts:28 Fetch API cannot load file:///C:/Users/tak/Desktop/tfjs_study/mnist/conv_mnist_tfjs/model.json. URL scheme must be "http" or "https"

そのため、ローカルにあるモデルのロードに失敗したら、サーバ(GitHub) にあるモデルをロードするようにしました。

画像読み込み

cam.capture() によって画像(300x300のTensor)を取得します。

前処理

基本的にやっていることは先ほどのコードと同じです。
TensorFlow.jsでは、処理の最中に使われたTensorのメモリは自動的に開放されないようです。
そのため、処理全体をtf.tidyで囲って解放するようにします。
また、tf.tidy で囲った関数の戻り値は解放されないため、tf.dispose によって自分で解放しています。
上手くできていないと、GPUメモリ使用量がどんどん増加していくのですぐに気づくと思います(毎フレーム処理する場合)。

https://js.tensorflow.org/api/latest/#tidy
https://js.tensorflow.org/api/latest/#dispose

前処理(CropAndResize)

先ほどは画像全体を28x28にリサイズしたのですが、今回はカメラ入力なので、画面中央付近だけをクロップして使用するようにしました。
そのためにtf.image.cropAndResize を使用しました。
tf.image.cropAndResize は入力としてNHWCのTensorを期待しているので、わざわざexpandDims で次元拡張しています。
その他パラメータによって、クロップしたい領域([Y1,X1,Y2,X2] を画像サイズに対する比率で指定)、リサイズサイズを指定します。結果もNHWCで出力されるので、squeeze によってHWCに戻しています。

https://js.tensorflow.org/api/latest/#image.cropAndResize

前処理(2値化)

先ほどは、入力画像を単にグレースケール化して、0.0~1.0に正規化しました。
実際のカメラ画像だとノイズなどの影響で、白くても値が200くらいだったり、黒くても値が50くらいだったりします。本来はモデル学習時のAugmentation等でも工夫をすべきですが、ここでは推論時の入力画像を2値化することで対応します。

2値化用の関数は用意されていなかったので、自分で簡単に作ってみました。
まず、グレースケール化した画像を128で割って、0.0~2.0に正規化します。その後、tf.clipByValue によって0.5~1.5の範囲にクリップします。その後、0.5を引いて0.0~1.0の範囲に戻します。これによって0~64は0(0.0)に、192~255は255(1.0)になります。中途半端な明るさのものは2値化されずに戻ります。

https://js.tensorflow.org/api/latest/#clipByValue

モデルへの入力画像を表示する

tf.browser.toPixels によって、TensorをHTML5のcanvasエレメントに出力しています。
これによって、実際にどのような画像がモデルへ入力されたかを確認できます。

https://js.tensorflow.org/api/latest/#browser.toPixels

おわりに

いつも前処理や画像入力はOpenCVでやっていたため、JavaScriptではどうするのかなと思っていました。
一度Tensorに変換してしまえばTensorFlowによって提供される関数で大抵のことはできるようです。また、おそらくWebGLによってGPUなども使われるため、自分で下手に頑張るよりも速いはずです。

なお、本コードは分かりやすさとデバッグ性重視のため全部同期実行になっています。
非同期処理をうまく活用すればもう少し高速化するはずです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GraphQLが"グラフ"であることを利用してビジネスロジックを入れ込んでみる

動機

GraphQLを勉強しているとき、

  • GraphQLが"グラフ"を扱っているのはわかるけどそれによってどんないいことがあるんだろう?
  • バックエンドにGraphQLを選択した際、ビジネスロジックはどこに表現されるべきなんだろう?

という私の疑問にサクッと答えてくれる日本語の文献が少なくともネット上には見つからなかったので、書いてみることにしました。

作るもの

いわゆる"TODOリスト"を作ります。TodoistRememberTheMiik的なあれですね。

いきなり余談ではありますが、何か新しい言語やフレームワーク、DBなどをサクッと試したいときに作るものの題材として、"TODOリスト"は個人的に以下の観点からオススメです。

  1. 仕様がイメージしやすい
    • 大抵の方は何らかのTODOリストを使ったことありますよね?
  2. どんなアーキテクチャでも大抵1日以内に完成する
    • 慣れないアーキテクチャであまり壮大なアプリケーションに挑戦すると挫折しがちなので。。。
  3. データのCRUDが一通り抑えられる
    • 今回の例では便宜上R(読み取り)しか扱わないですが、CRUDを全部抑えられるような仕様をイチから考えるのは結構大変だったりします。

ということで、「○○(言語でもDBでも何でも)を勉強したいんだけど、何作ろうかなぁ。。。」と迷って始められないくらいであれば、"TODOリスト"をデフォルトの選択肢するのはいかがでしょうか?

環境準備

今回は、Apollo Serverを使用します。まあ、2020年1月時点でデファクトスダンダードといっても差し支えないですかね。尚、今回はGraphQLに焦点を絞りたいので、UIは作りません。また、DBについても特に使用せず、Apollo ServerのGet Startedのようにデータは内部メモリの上の配列に持つことにします。

ということで、Get Startedを参考に環境準備しましょう。前提として、Node.jsとnpmはよしなに最新Stableでも入れておいてください。

作業ディレクトリの準備
$ mkdir graphql-practice-todos
$ cd graphql-practice-todos
npmプロジェクトの初期化
$ npm init --yes
必要なライブラリのインストール
$ npm instlal --save apollo-server graphql
ソースファイルの作成
$ touch index.js

まずはシンプルに作ってみる

ビジネスロジックのことは一度置いておいて、まずはシンプルにTODOリストのタスクをQueryを通じて参照できるGraphQL Serverを作ってみましょう。Get Startedを見ながら真似れば何となくできると思います。

実装

index.js
const {ApolloServer, gql } = require('apollo-server');

/**
* GraphQLのSchema
*/
const typeDefs = gql`
  "タスク"
  type Task{
    "タスクのID"
    id: ID!
    "タスク名"
    name: String!
    "期限(YYYY-MM-DD hh:mm:ss)"
    expiresAt: String!
    "完了フラグ(完了していればtrue)"
    done: Boolean!
  }

  "クエリ"
  type Query{
    "すべてのタスク"
    allTasks: [Task!]!
    "完了済タスク"
    finishedTasks: [Task!]!
    "未完了タスク"
    unfinishedTasks: [Task!]!
    "IDからタスクを取得"
    taskByID(
      "取得したいタスクのID"
      id: ID!
    ): Task
  }
`;

/**
* タスクを管理する内部配列
*/
const tasks = [
  {
    id: 'd1947409-95b4-4a46-8e83-cc60b94d3dd4',
    name: '牛乳を買う',
    expiresAt: '2020-01-23 20:00:00',
    done: true
  },
  {
    id:'97dcafa7-53d3-47b0-b25f-8457577749c8',
    name: 'Qiitaに投稿する',
    expiresAt: '2020-01-25 22:00:00',
    done: false
  }
];

/**
* GraphQLのResolver
*/
const resolvers = {
  /**
  * "Query"ノードについてのResolver
  */
  Query: {
    /**
    * すべてのタスク
    */
    allTasks: () => tasks,
    /**
    * 完了済タスク
    */
    finishedTasks: ()=> tasks.filter(task => task.done),
    /**
    * 未完了タスク
    */
    unfinishedTasks: ()=> tasks.filter(task => !task.done),
    /**
    * IDからタスクを取得する
    * Schemaで定義したパラメータは第2引数のargsオブジェクトの中に格納される点に注意
    */
    taskByID: (_, args) => tasks.find(task => task.id === args.id)
  }
};

//定義したSchema(typeDefs)とRosolversを使用してApolloServerを作成
const server = new ApolloServer({typeDefs, resolvers});

//作成したApolloServerの起動(デフォルトポートは4000)
server.listen().then(({url}) => console.log(`Server ready at ${url}`));

動作確認

index.jsを実行
$ node index.js
Server ready at http://localhost:4000/

ブラウザでlocalhost:4000にアクセスすると、動作確認用のUIが表示されるはずです。
スクリーンショット 2020-01-25 13.24.41.png

例えば以下のようなクエリを左側に入力してください。

すべてのタスクを取得

クエリ

query{
  allTasks{
    id
    name
    expiresAt
    done
  }
}

結果

{
  "data": {
    "allTasks": [
      {
        "id": "d1947409-95b4-4a46-8e83-cc60b94d3dd4",
        "name": "牛乳を買う",
        "expiresAt": "2020-01-23 20:00:00",
        "done": true
      },
      {
        "id": "97dcafa7-53d3-47b0-b25f-8457577749c8",
        "name": "Qiitaに投稿する",
        "expiresAt": "2020-01-25 22:00:00",
        "done": false
      }
    ]
  }
}

IDからタスクを取得

クエリ

query{
  taskByID(
    id:"d1947409-95b4-4a46-8e83-cc60b94d3dd4"
  ){
    id
    name
    expiresAt
    done
  }
}

結果

{
  "data": {
    "taskByID": {
      "id": "d1947409-95b4-4a46-8e83-cc60b94d3dd4",
      "name": "牛乳を買う",
      "expiresAt": "2020-01-23 20:00:00",
      "done": true
    }
  }
}

完了済リストと未完了リストを同時に取得する

クエリ

query{
  # 完了済リスト
  # 完了しているので有効期限は不要なので要求しない
  finishedTasks{
    id
    name
  }
  # 未完了リスト
  unfinishedTasks{
    id
    name
    expiresAt
  }
}

結果

{
  "data": {
    "finishedTasks": [
      {
        "id": "d1947409-95b4-4a46-8e83-cc60b94d3dd4",
        "name": "牛乳を買う"
      }
    ],
    "unfinishedTasks": [
      {
        "id": "97dcafa7-53d3-47b0-b25f-8457577749c8",
        "name": "Qiitaに投稿する",
        "expiresAt": "2020-01-25 22:00:00"
      }
    ]
  }
}

スキーマを"グラフ"として理解する

さて、ここからが本題です。

GraphQLはその名の通りGraph(グラフ)を扱う問い合わせ言語(Query Language)です。
グラフ理論についての詳細な解説はこちらの素晴らしい記事にお任せするとして、ここでは単純に、

グラフ = 頂点(Node)と辺(Edge)でモデル化したもの

と捉えてください。

例えば、今回定義したスキーマはこのような有向グラフ(辺に方向があるグラフ)として表すことができます。

graphql_qiita_1.png

つまり、

  • Queryの子ノードとしてallTasks,finishedTasks,unfinisehdTasks,taskByIDの4ノードがある
  • 上記の4ノードはTaskノードを生成する
  • Taskの子ノードとしてid,name,expiresAt,doneの4ノードがある

というモデルであると捉えることができます。

リゾルバはノードに割り当てられる

さて、スキーマをグラフとして整理できると、今度はリゾルバとの関係も整理できるはずです。

index.jsより抜粋
/**
* GraphQLのResolver
*/
const resolvers = {
  /**
  * "Query"ノードについてのResolver
  */
  Query: {
    /**
    * すべてのタスク
    */
    allTasks: () => tasks,
    /**
    * 完了済タスク
    */
    finishedTasks: ()=> tasks.filter(task => task.done),
    /**
    * 未完了タスク
    */
    unfinishedTasks: ()=> tasks.filter(task => !task.done),
    /**
    * IDからタスクを取得する
    * Schemaで定義したパラメータは第2引数のargsオブジェクトの中に格納される点に注意
    */
    taskByID: (_, args) => tasks.find(task => task.id === args.id)
  }
};

形を見ればわかる通り、このコードで定義したリゾルバはQueryノードの子ノードであるallTasks,finsihedTasks,unfinishedTasks,taskByIDに割り当てられています。ここではまず、

リゾルバ(Resolver)はスキーマ(Schema)のノード(Node)に割り当てられる

ということだけ理解してください。

スキーマとリゾルバとクエリの関係

さて、スキーマとリゾルバの関係が整理できたところで、先ほど動作確認したようなクエリが投げられたとき、それらがどういう作用をするか考えていきましょう。先ほどのグラフ(図)を見ながら、下記のクエリと動作と結果の関係を追ってみてください。

すべてのタスクを取得する

クエリ
query{
  allTasks{
    id
    name
    expiresAt
    done
  }
}
  1. Queryノードの子ノードであるallTasksノードが要求される
  2. allTasksノードのリゾルバが呼ばれ、(0..n個の)Taskノードを生成する
  3. 生成されたTaskノードの子要素のうちクエリで指定されたid,name,expiresAt,doneが要求される
  4. それぞれのノードの値がクエリの結果として返却される
結果
{
  "data": {
    "allTasks": [
      {
        "id": "d1947409-95b4-4a46-8e83-cc60b94d3dd4",
        "name": "牛乳を買う",
        "expiresAt": "2020-01-23 20:00:00",
        "done": true
      },
      {
        "id": "97dcafa7-53d3-47b0-b25f-8457577749c8",
        "name": "Qiitaに投稿する",
        "expiresAt": "2020-01-25 22:00:00",
        "done": false
      }
    ]
  }
}

完了済リストと未完了リストを同時に取得する

クエリ
query{
  # 完了済リスト
  # 完了しているので有効期限は不要なので要求しない
  finishedTasks{
    id
    name
  }
  # 未完了リスト
  unfinishedTasks{
    id
    name
    expiresAt
  }
}
  1. Queryの子ノードであるfinishedTaskとunfinishedTaskが要求される
    1. finishedTaskのリゾルバが呼ばれ0..n個のTaskが生成される
      1. 生成されたTaskノードの子要素のうちクエリで指定されたid,nameが要求される
    2. unfinishedTaskのリゾルバが呼ばれ0..n個のTaskが生成される
      1. 生成されたTaskノードの子要素のうちクエリで指定されたid,name,expiredAtが要求される
  2. それぞれのノードの値がクエリの結果として返却される
結果
{
  "data": {
    "finishedTasks": [
      {
        "id": "d1947409-95b4-4a46-8e83-cc60b94d3dd4",
        "name": "牛乳を買う"
      }
    ],
    "unfinishedTasks": [
      {
        "id": "97dcafa7-53d3-47b0-b25f-8457577749c8",
        "name": "Qiitaに投稿する",
        "expiresAt": "2020-01-25 22:00:00"
      }
    ]
  }
}

ここまでのまとめ(スキーマとリゾルバとクエリの関係)

  1. スキーマはグラフである
  2. リゾルバはスキーマの各ノード(頂点)に設定される
  3. クエリが発行されると、その内容に応じてエッジ(辺)の方向にノードを参照していき、リゾルバがあればそれが実行される

では、ビジネスロジックを入れてみよう

それでは、GraphQLと"グラフ"の関係が整理できたところで、表題の目的に立ち返って簡単なビジネスロジックを入れてみましょう。

ビジネスロジック:urgent(緊急)タスク

以下のようなビジネスロジックを考えることにします。

  • 「未完了」かつ「期限が現在時刻から8時間以内」のタスクをurgent(緊急)タスクとする
  • 各TaskはurgentというBoolean型のプロパティを持ち、上記に定義されたurgentタスクであればtrueを返す

ということで、まずはスキーマにurgentプロパティを追加しましょう。

index.jsより抜粋
/**
* GraphQLのSchema
*/
const typeDefs = gql`
  "タスク"
  type Task{
    "タスクのID"
    id: ID!
    "タスク名"
    name: String!
    "期限(YYYY-MM-DD hh:mm:ss)"
    expiresAt: String!
    "完了フラグ(完了していればtrue)"
    done: Boolean!
    "緊急タスクかどうか"
    urgent: Boolean!
  }

  "クエリ"
  type Query{
    "すべてのタスク"
    allTasks: [Task!]!
    "完了済タスク"
    finishedTasks: [Task!]!
    "未完了タスク"
    unfinishedTasks: [Task!]!
    "IDからタスクを取得"
    taskByID(
      "取得したいタスクのID"
      id: ID!
    ): Task
  }
`;

スキーマを更新したので、グラフも更新しましょう。Taskの子ノードとしてurgentを追加します。

graphql_qiita_2.png

ついでに、今回のビジネスロジックは時間を扱う(現在時刻から8時間)ので、利便性を考えてmomentを入れておきましょう。

momentを追加インストール
$ npm install --save moment

GraphQLらしくない解決策

では、仮にGraphQLの特性を全く無視してこのビジネスロジックを実現しようとしたらどうなるでしょうか。少しJavaScriptに慣れた人であれば、このような解決策がパッと思いつくかも知れません。

index.jsより抜粋
const moment = require('moment');
/**
* taskがurgent(緊急)かどうか
* @param {Object} task タスク
* @return {Boolean} taskがurgentであればtrue
*/
const isUrgent = (task) => !task.done && moment().add(8,"hours").isAfter(task.expiresAt);
/**
* リスト内のtaskにurgentプロパティを追加する
* @param {Array} tasks タスクのリスト
* @return {Array} urgentプロパティが追加されたtaskのリスト
*/
const addUrgent = (tasks) => tasks.map(task => Object.assign({},task, {urgent: isUrgent(task)}));

/**
* GraphQLのResolver
*/
const resolvers = {
  /**
  * "Query"ノードについてのResolver
  */
  Query: {
    /**
    * すべてのタスク
    */
    allTasks: () => addUrgent(tasks),
    /**
    * 完了済タスク
    */
    finishedTasks: ()=> addUrgent(tasks).filter(task => task.done),
    /**
    * 未完了タスク
    */
    unfinishedTasks: ()=> addUrgent(tasks).filter(task => !task.done),
    /**
    * IDからタスクを取得する
    * Schemaで定義したパラメータは第2引数のargsオブジェクトの中に格納される点に注意
    */
    taskByID: (_, args) => addUrgent(tasks).find(task => task.id === args.id)
  }
};

なるほど、確かにjavascript的には全く問題なさそうですし、動作確認すればちゃんと動きます。

クエリ
query{
  allTasks{
    id
    name
    expiresAt
    done
    urgent
  }
}
結果
{
  "data": {
    "allTasks": [
      {
        "id": "d1947409-95b4-4a46-8e83-cc60b94d3dd4",
        "name": "牛乳を買う",
        "expiresAt": "2020-01-23 20:00:00",
        "done": true,
        "urgent": false
      },
      {
        "id": "97dcafa7-53d3-47b0-b25f-8457577749c8",
        "name": "Qiitaに投稿する",
        "expiresAt": "2020-01-25 22:00:00",
        "done": false,
        "urgent": true
      }
    ]
  }
}

ですが、この実装は以下の2点において不満です。

DRY(Don't Repeat Yourself)になっていない

この実装はすべてのリゾルバにおいて、tasksを参照する際に必ずaddUrgent(tasks)というようにaddUrgent関数の呼び出しを強要するものです。このルールは今後新たなQueryを定義する時にもMutation(本記事では扱っていませんが、データを作成・更新・削除する命令)を定義する時にも適用されます。

忘れない自信はありますか?1つくらいなら大丈夫かも知れません。では、Taskに関するビジネスルールが追加された場合は?

この実装は明らかにDRY原則に反していますね。このまま拡張を続けていけばすぐに保守性の問題にブチ当たりそうです。

クエリによっては実行コストが無駄になる

GraphQLの大きな特徴の1つに、「データは要求された分しか返さない」というものがあります。つまり、

すべてのクエリがurgentプロパティを要求するとは限らない

のです。

タスクの名前だけを要求するクエリ
query{
  allTasks{
    name
  }
}

先ほどの実装では、たとえクエリが上記のようなものであっても「そのTaskがurgentかどうか」を判定する演算(つまりisUrgent関数やaddUrgent関数の実行)をしてしまいます。

もちろん、この程度の演算で大したリソースは消費しません。仮にタスクが10,000件あっても大して問題にならないでしょう。

しかし、もしビジネスロジックがI/Oを伴うものであったら?バックエンドのDBに追加のクエリを発行する必要があるものであったら?ただタスクの名前の一覧が欲しいだけのクライアントに大して何秒待たせることになるのでしょう?

このような不必要な(不必要になるかも知れない)演算をしてしまう実装は、あまりGraphQLの特長を生かしているとは言えないのです。

いつからリゾルバはQueryノードにしか割り当てられないと勘違いしていた?

ということで、もう少しGraphQLらしい解決策を考えて見ましょう。

graphql_qiita_3.png

このようにTaskの子ノードを整理してみると、id,name,expiredAt,doneとurgentは性質が異なることが分かると思います。

urgentは"クエリに要求されたときだけ"計算したい

あれ?"クエリに要求された時"に動く処理って何かありませんでしたっけ?

そう、リゾルバですね。

リゾルバは"ノードに割り当てること"ができるのです。誰も"Queryノードに"と限定していませんよ?

urgentノードにリゾルバを割り当てる

ということで、urgentにリゾルバを割り当ててみましょう。

index.jsから抜粋
const moment = require('moment');

/**
* GraphQLのResolver
*/
const resolvers = {
  /**
  * "Query"ノードについてのResolver
  */
  Query: {
    /**
    * すべてのタスク
    */
    allTasks: () => tasks,
    /**
    * 完了済タスク
    */
    finishedTasks: ()=> tasks.filter(task => task.done),
    /**
    * 未完了タスク
    */
    unfinishedTasks: ()=> tasks.filter(task => !task.done),
    /**
    * IDからタスクを取得する
    * Schemaで定義したパラメータは第2引数のargsオブジェクトの中に格納される点に注意
    */
    taskByID: (_, args) => tasks.find(task => task.id === args.id)
  },
  /**
  * "Task"ノードについてのResolver
  */
  Task:{
    /**
    * taskがurgent(緊急)かどうか
    * urgentノードに割り当てられたResolver
    * @param {Object} task タスク(Resolverの第1引数には"親ノード"が渡される)
    * @return {Boolean} taskがurgentであればtrue
    */
    urgent: (task) => !task.done && moment().add(8,"hours").isAfter(task.expiresAt)
  }
};

先ほどのisUrgentやaddUrgentは削除してあります。
その代わりに、Taskノードの子ノードであるurgentノードに対するリゾルバを定義し、そこでurgentかどうかの演算をやっています。

動作確認

同じく、先ほどのグラフを見ながら処理の流れを追ってみてください。

クエリ
query{
  allTasks{
    id
    name
    expiresAt
    done
    urgent
  }
}
  1. Queryノードの子ノードであるallTasksノードが要求される
  2. allTasksノードのリゾルバが呼ばれ、(0..n個の)Taskノードを生成する
  3. 生成されたTaskノードの子要素のうちクエリで指定されたid,name,expiresAt,done,urgentが要求される
    1. id,name,expiresAt,doneについてはリゾルバが無いのでallTasksが返した値が割り当てられる
    2. urgentノードについてはリゾルバが呼ばれ、urgentかどうかの演算の結果が割り当てられる
  4. それぞれのノードの値がクエリの結果として返却される
結果
{
  "data": {
    "allTasks": [
      {
        "id": "d1947409-95b4-4a46-8e83-cc60b94d3dd4",
        "name": "牛乳を買う",
        "expiresAt": "2020-01-23 20:00:00",
        "done": true,
        "urgent": false
      },
      {
        "id": "97dcafa7-53d3-47b0-b25f-8457577749c8",
        "name": "Qiitaに投稿する",
        "expiresAt": "2020-01-25 22:00:00",
        "done": false,
        "urgent": true
      }
    ]
  }
}

上記は一例としてallTasksを呼んだ場合を例にしましたが、他のQueryやMutationを呼んだ場合でも3.以下は同じ挙動になります。

また逆に、クエリ内にurgentプロパティが指定されていない場合は、urgentのリゾルバは実行されません。

まとめ

いかがでしたでしょうか。
ここまでをまとめると、

  • GraphQLのスキーマは有向グラフである
  • リゾルバはグラフの頂点(ノード)に割り当てることができる
    • QueryやMutaion(今回取り扱ってないですが)だけでなく他のノードにも
  • クエリはスキーマで適宜された有向グラフの方向に沿って解決される
    • リゾルバが定義されているノードにたどり着いた時にリゾルバが実行される
  • 上記の性質を利用してビジネスロジックを表現することができる

ということになります。

今回の例はスキーマもビジネスロジックもごく単純なのであまりGraphQLの恩恵を実感しにくいかも知れませんが、GraphQLが本領を発揮するのは、

  • 複数のデータソース(RDBMS,NoSQL,ファイル,etc...)を統合して単一のAPIを提供する
  • さらに複数のAPI(REST,gRPC,他のGraphQL,etc...)を統合してBFF(Backend For FrontEnd)を提供する

といった局面かと思います。

こういった設計をする際に、今回紹介したようなGraphQLの性質が頭に入れておくと、パフォーマンス面やAPIの品質(分かりやすさなど)面でGraphQLの恩恵が受けやすいのでは無いかと思います。

補足

apolloの公式ドキュメントによると、リゾルバに与えられる引数は(parent, args, context, info)です。

つまり、QueryやMutationに対するリゾルバだけではなく、他のノードのリゾルバにおいても引数が取れます。たとえば今回のTaskノードで言えば

  "タスク"
  type Task{
    "タスクのID"
    id: ID!
    "タスク名"
    name: String!
    "期限(YYYY-MM-DD hh:mm:ss)"
    expiresAt: String!
    "完了フラグ(完了していればtrue)"
    done: Boolean!
    "緊急タスクかどうか"
    urgent: Boolean!
    "引数に与えられた時間に緊急かどうか"
    isUrgentAt(
       "調べたい時間(YYYY-MM-DD hh:mm:ss)"
       datetime:Strng!
    ):Boolean
  }

上記のisUrgentAt(datetime)のような引数をもつメソッドのようなものを定義することも可能です。

最後にフルソースを貼っておきますので、もしよろしければ思い思いに拡張して頂いて理解を深めてみてください。

フルソース

index
const {ApolloServer, gql } = require('apollo-server');

/**
* GraphQLのSchema
*/
const typeDefs = gql`
  "タスク"
  type Task{
    "タスクのID"
    id: ID!
    "タスク名"
    name: String!
    "期限(YYYY-MM-DD hh:mm:ss)"
    expiresAt: String!
    "完了フラグ(完了していればtrue)"
    done: Boolean!
    "緊急タスクかどうか"
    urgent: Boolean!
  }

  "クエリ"
  type Query{
    "すべてのタスク"
    allTasks: [Task!]!
    "完了済タスク"
    finishedTasks: [Task!]!
    "未完了タスク"
    unfinishedTasks: [Task!]!
    "IDからタスクを取得"
    taskByID(
      "取得したいタスクのID"
      id: ID!
    ): Task
  }
`;


/**
* タスクを管理する内部配列
*/
const tasks = [
  {
    id: 'd1947409-95b4-4a46-8e83-cc60b94d3dd4',
    name: '牛乳を買う',
    expiresAt: '2020-01-23 20:00:00',
    done: true
  },
  {
    id:'97dcafa7-53d3-47b0-b25f-8457577749c8',
    name: 'Qiitaに投稿する',
    expiresAt: '2020-01-25 22:00:00',
    done: false
  }
];

const moment = require('moment');

/**
* GraphQLのResolver
*/
const resolvers = {
  /**
  * "Query"ノードについてのResolver
  */
  Query: {
    /**
    * すべてのタスク
    */
    allTasks: () => tasks,
    /**
    * 完了済タスク
    */
    finishedTasks: ()=> tasks.filter(task => task.done),
    /**
    * 未完了タスク
    */
    unfinishedTasks: ()=> tasks.filter(task => !task.done),
    /**
    * IDからタスクを取得する
    * Schemaで定義したパラメータは第2引数のargsオブジェクトの中に格納される点に注意
    */
    taskByID: (_, args) => tasks.find(task => task.id === args.id)
  },
  /**
  * "Task"ノードについてのResolver
  */
  Task:{
    /**
    * taskがurgent(緊急)かどうか
    * urgentノードに割り当てられたResolver
    * @param {Object} task タスク(Resolverの第1引数には"親ノード"が渡される)
    * @return {Boolean} taskがurgentであればtrue
    */
    urgent: (task) => !task.done && moment().add(8,"hours").isAfter(task.expiresAt)
  }
};

//定義したSchema(typeDefs)とRosolversを使用してApolloServerを作成
const server = new ApolloServer({typeDefs, resolvers});

//作成したApolloServerの起動(デフォルトポートは4000)
server.listen().then(({url}) => console.log(`Server ready at ${url}`));
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【nuxt.js】headにmetaとかGAタグとか加える時のアレコレ

nuxt.js触っててheadの内容どこに書くのか分からなかったのでメモ

これさえ読めば以降読まなくてもokです

headに色々入れることができます。ページのdataとかも使ってページ個別に記載できます。
(公式)

nuxt.config.jsの中のheadプロパティを使えば、全ページ共通の要素は一発で記載できます。
(公式)

つかいかた 〜ページ個別の記載事項〜

各ページで記載内容を変えたい場合はこんな感じ。
こんな感じにhead()で書いていきます。

index.vue
  head () {
    return {
      title: 'ほげほげ',
      meta: [
        { hid: 'description', name: 'description', content: '説明' },
     //キーワードはお守りらしい。(消したい。。)
        { hid: 'keywords', name: 'keywords', content: 'キーワード' },
        { hid: 'og:type', property: 'og:type', content: 'website' },
        { hid: 'og:url', property: 'og:url', content: '〇〇.com' },
        { hid: 'og:title', property: 'og:title', content: 'ほげほげ' },
        { hid: 'og:description', property: 'og:description', content: '説明'},
        { hid: 'og:image', property: 'og:image', content: '/static/top_img.jpg' },
      ],
      script: [
     //タグマネージャー??関連のスクリプト
        //<script>innerHTMLの中身</script>
        { type: 'text/javascript', innerHTML: '/*<![CDATA[*〇〇〇〇*]]>*/' }
      ],
      // これが無いとscriptの中身がエスケープされる
      __dangerouslyDisableSanitizers: ['script'],
    }
  }

だいたい見て分かる気がしますが、1個だけ。

        { hid: 'description', name: 'description', content: '説明' },

Q:hidってなんや?

A:よく分かりません。

子コンポーネント利用されたときにメタ情報が重複してしまうことを避けるために hid キーを使ってユニーク識別子を meta 要素に設定してください。

と公式には書いてあります。
これが無いとnuxt.config.jsで決めた共通の要素の設定と共存する形になってしまったりします。
全ページ同じ識別子でも(今の所)支障無いです。
お詳しいかたぜひコメントください。。

つかいかた 〜全ページ共通の設定〜

nuxt.config.jsに書きます。
こんな感じに使っています

nuxt.config.js
  /*
  ** Headers of the page
  */
  head: {
    title: デフォルトのタイトル,
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: 'デフォルトdescription' },
      { hid: 'og:site_name', property: 'og:site_name', content: '〇〇' },
      { hid: 'fb:app_id', property: 'fb:app_id', content: 'fb_app_id' },
      { hid: 'twitter:card', property: 'twitter:card', content: 'summary_large_image' },
      { hid: 'twitter:site:id', property: 'twitter:site:id', content: '@ツイッターID' },
      { hid: 'twitter:creator', property: 'twitter:creator', content: '@ツイッターID' },
    ],
    link: [
      { rel: 'shortcut icon', href: '/favicon/favicon.ico', type: 'image/ico'},
      { rel: 'apple-touch-icon', href: '/favicon/apple-touch-icon16px.png', size: '16x16', type: 'image/png'},
      { rel: 'apple-touch-icon', href: '/favicon/apple-touch-icon32px.png', size: '32x32', type: 'image/png'},
      { rel: 'apple-touch-icon', href: '/favicon/apple-touch-icon64px.png', size: '64x64', type: 'image/png'},
      { rel: 'apple-touch-icon', href: '/favicon/apple-touch-icon180px.png', size: '180x180', type: 'image/png'}
    ]
  },

みたらだいたいわかるでしょう。fbのidとかはググったらたくさん出てきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javascriptでテキスト音声読み上げ

はじめに

javascriptでお手軽に音声読み上げできます。
Web Speech Synthesis API を使います。無料なのがうれしい(^^)

20200125_speechAPE_speak.jpg

自分用メモまで。お勉強中です

環境 

windows10
python3 (anaconda) 
(※動作確認まで ローカルホスト起動できれば、pythonでなくruby, apacheなどでもよいみたいです)

ブラウザ MicrosoftEdge (ver44?)
ブラウザ Chrome(ver74?)

ソースコード

下記HTMLを作成して、
C:\Users\自分のユーザーのフォルダ\sample.html として保存します。

sample.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>音声読み上げ</title>
</head>
<body>
<h1>音声合成</h1>
<input class="text" value="Speech API を使おう。" style="width: 80%; height: 40px; font-size: 40px;" />
<button onclick="speak()" style="height: 30px; width: 80px;">speak</button>

<!-- スクリプト部分 -->
<script>
function speak(){
  var speak   = new SpeechSynthesisUtterance();
  speak.text  = document.querySelector('.text').value;
  speak.rate  = 1; // 読み上げ速度 0.1-10 初期値:1 (倍速なら2, 半分の倍速なら0.5, )
  speak.pitch = 0; // 声の高さ 0-2 初期値:1(0で女性の声) 
  speak.lang  = 'ja-JP'; //(日本語:ja-JP, アメリカ英語:en-US, イギリス英語:en-GB, 中国語:zh-CN, 韓国語:ko-KR)

  sleep(2000);
  speechSynthesis.speak(speak);

}

function sleep(time){
  var date_1 = new Date().getTime();
  var date_2 = new Date().getTime();
  while (date_2 < date_1 + time){
    date_2 = new Date().getTime();
  }
  return;
};
</script>

<body>
<html>

#動作確認
ローカルWEBサーバー起動
※ webサーバー経由必須です。単体 sample.html だけでは機能しません。

python -m http.server 8080

ブラウザでアクセスします。
http://localhost:8080/sample.html

ボタンをクリックするとテキストボックスの中身を読み上げます。

これからしたいこと

・声の種類をいろいろ変更できるようにしたい
など

参考

参考 ソースコード: 【javascriptで実装!】Web Speech Synthesis APIを使ってブラウザで音声読み上げをしよう
https://shinmedia20.com/javascript-speech-synthesis

参考 APIの使い方: Webページでブラウザの音声合成機能を使おう - Web Speech API Speech Synthesis
https://qiita.com/hmmrjn/items/be29c62ba4e4a02d305c

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MakeCode Arcadeで遊んで見る

MakeCode Arcade

今年プログラミングが必修になるということで話題になっていたScratchとはどんなものだろうと思って見に行ってみました。
なるほどグラフィカルに簡単にプログラミングが出来るのか面白そうだなと思いましたが、もうちょい調べると似た感じのもので、MicroSoftのMakeCodeなるものがあることがわかりました。
しかもその中のMakeCode Arcadeは2Dゲームを作ることに特化しているということでちょっと遊んでみようと思ったわけです。
プログラミング経験のある人はパッと見で使い方がわかると思いますが、導入にはこちらの記事あたりが良いかも知れません。

で、何かをやってみようと思うと、こんにちはマイコン世代なので、まずはテニスゲームというかパドルとボールだけのスカッシュ的なものを作ってみてしまうのです。

こちら

カーソルキーか[A],[D]キーでパドルの左右移動です。Aボタンが[Z]か[スペース]、Bボタンが[X]です。
まあ単純で面白いというほどのものでもないですが、一応段々と玉が早くなるので限界が来ます。
これが数分~10分くらいで作れてしまうので楽しいです。

プログラムの説明

簡単にプログラムの説明をしてみます。
mc001a.png
これがパドルとボールの動きです。
on start
 動作開始時に呼ばれるブロックです
set dialog frame to タイプ
 ダイアログを出して[A]ボタンが押されるのを待ちます
show long text テキスト 位置
 位置をbottomにしてテキストを表示します
set score to 0
 スコアを0にリセットします
set "paddle" to sprite 絵 of kind "player"
 絵を指定してスプライトを"paddle"という名前、"player"という種類で登録します
set "paddle" to position to x 80 y 96
 "paddle"の位置を(80,96)にします
move "paddle" with buttons vx 150 vy 0
 "paddle"をボタンで動かします、X速度(横方向)は150、Y速度(縦方向)は0
 つまり横にだけ動かせるように指定します
set "paddle" stay in screen ON
 "paddle"は画面内に留まるように指定します
 これによって画面端まで動かしたときに止まります
set "ball" to projectile 絵 from "paddle" with vx 50 vy -50
 "ball"を発射体として絵を指定して、"paddle"から発射します
 X速度(横方向)50,Y速度(縦方向)-50ですから斜め右上に向かって飛んでいきます
set "ball" bopunce on wall ON
 "ball"は壁で跳ね返ると指定します

mc001b.png
これはパドルとボールの当たり判定です
on sprite of kind projectile overlaps otherSprite of kind "Player"
 発射体(ここではボール)が種類"Player"の他のスプライトと重なったらという条件でこのブロック内の処理をします
if "ball" vy(velocity y) > 0 then
 もし"ball"のY速度が0より大きかったら、つまり下向きだったら以下の処理をします
 上からボールが飛んできたときにだけパドルが跳ね返すようにします
 これがないとボールとパドルが重なったときに引っかかって変な動きをするときがあります
set "ball" velocity to vx "ball" vx(velocity x) vy "ball" vy(velocity y) x -1
 長いですが、"ball"のX速度はそのままでY速度はかける-1する(反転する)という処理です
 これによってボールがパドルに当たったときに跳ね返ります
change score by 1
 スコアに1点加えます
play tone at High C for 1/8 beat
 高いドの音を短く鳴らします

mc001c.png
ここはゲームオーバーの判定です
forever
 このブロックは常に動いています
if "ball" y > 112 then
 もし"ball"のY座標が112よりも大きかったら以下を処理します
 パドルに当てられず画面の下まで行ってしまったらということです
game over LOSE
 ゲームオーバー(負け)ということです
 この1文だけでゲームオーバーの処理をしてくれるのは楽です

mc001d.png
ここでは5秒毎にボールの速度を上げることでだんだんと難しくしています
forever
 このブロックは常に動いています(繰り返しています)
pause 50000ms
 5000ミリ秒(5秒)待ちます
 これで以下を5秒に1回処理することになります
以下の処理で横(X)、縦(Y)それぞれの速度を増します
if "ball" vx(velocity x) > 0 then
 もし"ball"の横方向速度が0より大きかったら
set "ball" vx(velocity X) to ball vx(velocity x) + 5
 X速度を5増やす(速くする)
else
 そうじゃなかったら("ball"の横方向速度が0以下だったら)
set "ball" vx(velocity X) to ball vx(velocity x) - 5
 X速度を5減らす(速くする)
同じことをY速度にも行います

という感じでプログラムを解説してみました。
需要があるとは全く思えないですけど楽しかったので(^_^;

MakeCode Arcade結構楽しいのでもう少し凝ったものも作ってみたいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】TOAST UI Image Editor を使って画像編集!

はじめに

切り取りとモノクロ化、輝度調整できることから、TOAST UI Image Editorを使っており、
Qiitaにあまり記事がなかったので、今回利用方法をまとめてみました。

TOAST UI Image Editorとは?

Canvasを利用した画像編集ライブラリで、主に以下の機能を提供しています。

  • 画像アップロード機能
  • 作成した画像のダウンロード
  • 画像の切り取り
  • 画像の反転、回転
  • 図形の挿入、描画
  • アイコン(アップロード可)、テキストの挿入
  • 背景のマスク機能
  • フィルタ機能(モノクロ、セピアなど)

参考情報

今回の記事のゴール

最小限の手数でTOAST UI Image Editorのフル機能を使えるようにするところをゴールとします。
作成していく中で必要なパラメータや詰まりやすい部分などは記事中に記載していきます。

画面収録 2020-01-25 14.31.08.mov.gif

環境

  • Vue.js 2.5.10
  • vue-cli 3.7.0
  • toast-ui.vue-image-editor
    ※Vue.js用に用意されたラッパーを使用して作成していきます。

実装(準備編)

  • 環境構築
    vue createコマンドでプロジェクトを作成していきます。
    プロジェクト名は任意で入れてください。
    セットアップにあたり聞かれる質問も任意で答えていただいて構いません。
vue create image-editor-app(プロジェクト名を入れてください)
cd image-editor-app
  • 必要なライブラリのインストール
    TOAST UI Image Editorの他に画像ダウンロード機能を使うため、
    file-saverをインストールします。
    ※tui-image-editorの依存関係パッケージに含まれているのになぜかインストールしないと使えませんでした。。。
    Issueで確認してますが、ご存知の方がもしいらっしゃればコメントください。。
yarn add @toast-ui/vue-image-editor
yarn add file-saver

※補足
TOAST UI Image Editorの他にパッケージをインストール(今回はfile-saver)した場合、
yarn serveコマンド実行時などコンパイル実行時に必要なパッケージが足りない旨
コンパイルエラーが発生する場合があります。

その際は、エラーの内容に従ってエラーに表示されたパッケージをyarn addコマンドで
追加してあげることで解消します。
ライブラリ側の既知の問題とのことです。

実装(コンポーネント作成)

TOAST UI Image Editorを使ったコンポーネントを作成していきます。
パラメータが多く、localeは一部だけ記載します。全部見たい方はGithubのソースをご覧ください。

要点と注意ポイントは以下の通りです。

  • useDefauleUIをtrueにしてPropsで渡すことでデフォルトのUIの適用し、カスタムする部分はoptionsに指定してPropsで値を渡す
  • コンポーネントにはライブラリの他に、スタイルを適用させるために、tui-image-editor.cssとアイコンをimportする
  • 各要素のスタイルは変数(下記ではtheme)に格納したうえでoptions>includeUIのthemeに指定すると適用される
  • ロケールも各要素に文言を指定した変数(下記ではlocale_ja)に格納したうえでoptions>includeUIのlocaleに指定する
imageEditor.vue
<template>
  <tui-image-editor ref="tuiImageEditor" :include-ui="useDefaultUI" :options="options"></tui-image-editor>
</template>

<script>
//ライブラリのインポート
import { ImageEditor } from "@toast-ui/vue-image-editor";

// アイコンのインポート
import icon_a from "tui-image-editor/dist/svg/icon-a.svg";
import icon_b from "tui-image-editor/dist/svg/icon-b.svg";
import icon_c from "tui-image-editor/dist/svg/icon-c.svg";
import icon_d from "tui-image-editor/dist/svg/icon-d.svg";

// ライブラリ側で用意しているスタイルの読み込み
import "tui-image-editor/dist/tui-image-editor.css";

// ダウンロード機能を使う際はインポートする
import "file-saver";

//ロケールの設定。自分で日本語に訳して入れていく
const locale_ja = {
  Load: "ファイル選択",
  Download: "ダウンロード",
  Apply: "適用",
  Arrow: "矢印",
  "Arrow-2": "矢印2",
  "Arrow-3": "矢印3"...
}
//表示される各要素のスタイルを指定
var theme = {
  //共通のスタイル
  "common.bi.image": "",
  "common.bisize.width": "0px",
  "common.bisize.height": "0px",
  "common.backgroundImage": "none",
  "common.backgroundColor": "#FFFFFF",
  "common.border": "0px",
  // ヘッダー
  "header.backgroundImage": "none",
  "header.backgroundColor": "transparent",
  "header.border": "0px",
  // ファイルアップロードボタンのスタイル
  "loadButton.backgroundColor": "#fff",
  "loadButton.border": "1px solid #ddd",
  "loadButton.color": "#222",
  "loadButton.fontFamily": "NotoSans, sans-serif",
  "loadButton.fontSize": "12px",
  // ダウンロードボタンのスタイル
  "downloadButton.backgroundColor": "#fdba3b",
  "downloadButton.border": "1px solid #fdba3b",
  "downloadButton.color": "#fff",
  "downloadButton.fontFamily": "NotoSans, sans-serif",
  "downloadButton.fontSize": "12px",
  // メインアイコン
  "menu.normalIcon.path": icon_d,
  "menu.activeIcon.path": icon_b,
  "menu.disabledIcon.path": icon_a,
  "menu.hoverIcon.path": icon_c,
  // サブメニューアイコン
  "submenu.normalIcon.path": icon_d,
  "submenu.activeIcon.path": icon_c,
  // submenu primary color
  "submenu.backgroundColor": "#ffffff",
  "submenu.partition.color": "#858585",
  // サブメニュー
  "submenu.normalLabel.color": "#858585",
  "submenu.normalLabel.fontWeight": "lighter",
  "submenu.activeLabel.color": "#000",
  "submenu.activeLabel.fontWeight": "lighter",
  // チェックボックス
  "checkbox.border": "1px solid #ccc",
  "checkbox.backgroundColor": "#fff",
  // 切り取り
  "range.pointer.color": "#000",
  "range.bar.color": "#666",
  "range.subbar.color": "#d1d1d1",
  "range.value.color": "#000",
  "range.value.fontWeight": "lighter",
  "range.value.fontSize": "11px",
  "range.value.border": "1px solid #353535",
  "range.value.backgroundColor": "#151515",
  "range.title.color": "#000",
  "range.title.fontWeight": "lighter",
  // colorpicker 
  "colorpicker.button.border": "1px solid #1e1e1e",
  "colorpicker.title.color": "#fff"
};

export default {
  name: "ImageEditor",
  components: {
    "tui-image-editor": ImageEditor
  },
  props: {},
  data() {
    return {
      useDefaultUI: true,
      options: {
        cssMaxWidth: document.documentElement.clientWidth,
        cssMaxHeight: document.documentElement.clientHeight![画面収録 2020-01-25 14.31.08.mov.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125532/bbc45a38-8cf5-25af-f098-1a9cb3ee72ca.gif)
,
        includeUI: {
          //表示メニューの設定。デフォルトでは全て表示される
          //menu: ["flip", "crop", "rotate", "filter"],
          //コンポーネント生成時に選択されるメニューの指定
          initMenu: "filter",
          //メニューバーの位置の指定
          menuBarPosition: "buttom"
          uiSize: {
            width: "100%",
            height: "650px"
          },
          theme: theme,
          locale: locale_ja,
        }
      }
    };
  }
};
</script>

注意点

モバイルのUIも用意されているのですが、レスポンシブ対応ができない?模様です。
できたら追記しようと考えています。

最後に

あまり知られていないのか、Qiita含めて記事も少なく、いくつかつまづいてしまったのですが
少しでもお役に立てれば嬉しいです。

機能は最低限網羅していますが、↑で紹介した以外にもデザインテーマをライブラリ側で用意していたり、
パラメータもあるのでぜひお試しください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

小中学生向けのプログラミング言語4つまとめ

最近は、小学生からプログラミングを始める時代になってきているそうです。
物事を考える力がつくから~ とかで、義務教育に入るそうですね。
そんな小学生・中学生の人におすすめなプログラミング言語)を紹介します。

Scratch

早速言語じゃないやん!ってね。
サイトはこちら:https://scratch.mit.edu/

GUIで、プログラミングをできるサイトです。
ブロックをつなげて、キャラクターを動かしたりできます。

特徴

他のものと違い、コードを打たないで、ブロックをつなげて作品を作ります。
そのため、始めやすいですが、他の言語に移るのは大変です。

私はあまり好きではありません。

JavaScript

一番おすすめです。

3日くらい勉強サイトを見ていれば、(若いうちは)すぐ頭に入ってくるくらい単純な言語です。

特徴

動かすのが簡単です。

Chromeを使っているなら、「右クリック→検証」か「F12」を押すとデバッグツール が出てきます。
その、「Console」と書いてあるところにコードを入力すると動かせます。

Googleで、「使っているブラウザの名前 デバッガーツール」と検索すれば、デバッガーツールの出し方が書いてあると思うので調べてみてください。

ただし、IEはおすすめしませんので。

HTML

これはプログラミング言語かな?

とりあえず、Webサイトを作るためのマークアップ言語です。
先程書いた「デバッガーツール」を開いてElementsをみると、

<html>
  <body>
    <a></a>
    <p></p>
  </body>
</html>

みたいなコードが出てくると思います。
これがHTMLです。

Webサイトはほぼこれで作られています。

プログラミング言語ではないので、動くものを作るのは大変だと思います。

JavaScriptと組み合わせると、ブラウザゲームも作ることができます。

PHP

これは少し難易度が高いです。
PHPを動かすためには、PHPが動くサーバーが必要です(Windows上でもXamppというソフトを使えば動きます)。

そして、phpファイルにコードを書くと動くのですが、JavaScriptよりも構文(書きかた)が難しいです。

すこし時間を掛けて勉強する必要があります。

特徴

Qiitaのようなサービスを作るにはPHPが必要不可欠です。QiitaはRubyでできています。ご指摘くださった方、ありがとうございます。
マニュアルが日本語でわかりやすいので、すぐに調べることができます。
また、人気プログラミング言語ランキングでも上位に入っているので、仕事の件数も多いです。

おすすめは?

最初はJavaScriptがおすすめです。

人気な言語なので、知恵袋などでもいろいろ答えが転がっています。
安定のJSから始めてはいかがでしょうか?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ハッカソンでFPS視点ラジコンをデモしたら思った以上に好評だったので作り方を紹介します

はじめに

前回紹介したラジコンを改良して、ハッカソンで RPGのデモ したら、好評だったので作り方を紹介します。前回はFPS視点とコントローラが別々でしたが、今回はそれらをくっつけてFPS視点コントローラにしました。

※とあるイベントで登壇したときに嬉しいお言葉頂きました。

デモ

FPS視点コントローラの操作デモです。ライブ配信画面をタップしてラジコンを操作します。画面から指を離すとラジコンが止まります。

こちらは、コントローラの画面です。スワイプでラジコンを操作します。タップした位置が原点となり、スワイプした方向でラジコンの進む方向が決まります。上にスワイプすると前進し、左にスワイプすると左旋回します。

ラジコン本体はこんな感じ

ラジコン本体の構造について概要を紹介します。

このようにIoTデバイス(ラズパイ、obniz)と、給電用のバッテリーを2つ搭載しています。

※車体はLEGOテクニックで作っています。LEGOパーツと作り方はこちら→ ラジコン車体編

FPS視点コントローラのソースはこちら

ソースはgithubに置きました → controller2.html

controller2.html内の {{ラズパイのIP}}obniz-ID を環境に合わせて書き換えます。

obniz-ID ラジコンにのせるobnizのID
{{ラズパイのIP}} ラジコンにのせるラズパイの(ローカルの)IPアドレス

ーーー

では、順に作っていきます

1.モーターを制御しよう

ラジコンの動力となるモーターをobnizで制御します。obnizは電子回路がわからない私でも気軽に使えるのでありがたいIoTデバイスです。インターネット経由で制御できるようになっており、ラジコンみたいに遠隔操作したいものを作るのにも便利です。

モーターとobnizを接続しよう

後輪にはそれぞれモーターがあり、それらモーター制御にobnizを使用します。

左車輪のモーターをobnizのメス端子0,1に、右車輪のモーターをobnizのメス端子10,11に接続します。赤と黒の配線を図のように接続します。obnizのメス端子の番号と、モータの配線の色を間違えないように注意します。また、左車輪のモーターはコードが上になるように、右車輪のモーターはコードが下になるようにします。

※今回使用したモーターはこちらです → 赤い歯車モーター

制御の概要はこんな感じ

Windows上にNodejsでWebサーバーを用意し controler2.html をサイトからアクセスできるよう(http://localhost/controler2.htmlみたいにアクセスできる)にします。

この時点で、モーター制御できるコントローラーまでが完成しました。次にFPS視点の機能を作っていきます。

2.ライブ配信しよう

ラズパイをライブ配信サーバにします。そして、ラジコンにカメラ付きのラズパイを搭載します。これで、ラズパイは『動くライブ配信サーバ』になります。このライブ配信サーバからの映像をコントローラと合わせることでFPS視点のコントローラーになります。

※ラズパイはRaspberry Pi 4 Model B 4 GBを使いましたが、次に紹介する MJPEG-StreamerはRaspberry Pi 4 Model B, Raspberry Pi 4 Model B+ でも動作しました。

MJPEG-Streamerをインストールしよう

ラズパイをライブ配信サーバにするため、MJPEG-Streamerをインストールします。

$ sudo apt-get install build-essential libjpeg8-dev imagemagick libv4l-dev cmake -y
$ git clone https://github.com/jacksonliam/mjpg-streamer.git
$ cd mjpg-streamer/mjpg-streamer-experimental
  • CMakeLists.txtを編集します。
  • ⾏頭に#を追加してコメントアウトします。(これすると、make実行時のエラー発生を対策できるみたいです)
#add_subdirectory(plugins/input_opencv)
$ make
$ sudo make install
  • ※わかってないのですが ↓ をするといいらしいです
~/mjpg-streamer/mjpg-streamer-experimental $ cp input_raspicam.so ../

インストールが終われば、ストリーミングを起動してライブ配信ができるか確認します。(ここではオプションの説明は省きます)

# ストリーミング起動
$ /usr/local/bin/mjpg_streamer -i "input_raspicam.so -x 640 -y 480 -fps 30 -q 80" -o "output_http.so -p 8090 -w /usr/local/share/mjpg-streamer/www" 

配信を開始したら、ブラウザで http://{{ラズパイのIP}}:8090/にアクセスして、↓のようなサイトが表示されれば成功です。

※参考※ ストリーミングを開始するShellを作ると便利です。

/usr/local/bin/start_mjpeg.sh
#!/bin/sh
/usr/local/bin/mjpg_streamer -i "input_raspicam.so -x 640 -y 480 -fps 30 -q 80" -o "output_http.so -p 8090 -w /usr/local/share/mjpg-streamer/www"

作成したシェルstart_mjpeg.shに実行権限を付与します。

$ chmod +x start_mjpeg.sh

以下のようにシェルを実行するとストリーミングが開始します。

$ ./start_mjpeg.sh

これでFPS視点のコントローラが完成しました。
ライブ配信のオプションを変更したときは、controler2.htmlの修正が必要か確認してください。

controler2.htmlにライブ配信のURLhttp://{{ラズパイのIP}}:8090/?action=snapshot・・・が2か所あります。

コントローラの操作方法を知っとこう

タップした位置を原点に移動した指の位置と原点との距離・角度で左右のモードの回転方向と出力を変更します。指を話すと止まります。


あとは、おまけです。

【補足情報】

ライブ配信のJavaScriptについて

ライブ配信に使用するJavaScriptは、MJPEG-StreamerのWebサイトhttp://{ラズパイのIP}:8090/javascript_simple.htmlから取得しました。このサイトは↓このように映像配信のみのシンプルなサイトになっています。(カメラの前のカーテンが映っています)

javascript_simple.htmlのソースを見ると、videoタグではなくimgタグを使っており、JavaScriptで画像を更新していました。パラパラ漫画なんですね。

javascript_simple.html
<div id="webcam"><noscript><img src="./?action=snapshot" /></noscript></div>

FPS視点のコントローラの構造

ライブ配信のimgタグとコントローラのcanvasタグが重なるようCSSでスタイルを記述しました。重ね合わせのサンプルを紹介します。

sample.html
<!-- img,canvasタグを重ねる -->
<html>
    <head>
        <style>
            #container {
                position: relative;
            }
            #webcam {
                width: 640px;
                height: 480px;
                position: absolute;
                z-index: 1;
            }
            #hoge {
                position: absolute;
                z-index: 2;
            }
        </style>
    </head>
    <body>
        <div id="container">
            <canvas id="hoge" width="600" height="600"></canvas>
            <div id="webcam"><img id="testImage" src="test.jpg" width="600" height="600"></div>
        </div>
    </body>
</html>

次は・・・

FPS視点をさらに拡張したいと思っていて、"首振り"の機能を追加しようと試行錯誤中です。そのための部品は揃えたので、あとは作るだけ! のはず

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ライブラリのコードを読み解くNo.1 <Redux createStore編>

はじめに

ライブラリのコードを読み解いて、より深くライブラリについて理解するという記事です。

今回は状態管理のためのライブラリであるReduxのcreateStoreについて記載します。

この記事で説明すること

  • ReduxのcreateStoreのソースコードの説明

この記事で説明しないこと

  • Reduxのコンセプトや概要の説明
  • createStoreの使用方法やサンプルコードの解説

Reduxの概要を説明した記事はいろいろあるかと思いますので、公式サイトやいろいろな解説記事を見ていただくのが良いと思います。

個人的には、Reduxのco-maintainerであるMark Erikson氏によって書かれたこちらのスライドがわかりやすかったです。

対象読者

  • Reduxのコンセプト説明などのページを見て概要はなんとなくはわかっている人
  • ReduxのAPIを使用したことがある人

ライブラリバージョン

  • Redux 4.0.5 (2020年1月25日時点での最新バージョン)

APIの説明

APIの定義

Reduxの公式サイトからの引用となりますが、APIの定義は以下のようになります。

createStore(reducer, [preloadedState], [enhancer])

Creates a Redux store that holds the complete state tree of your app. There should only be a single store in your app.

createStoreのAPIリファレンス

APIの使用方法

APIの使用方法としては以下のようになります。

(ReduxはReactと組み合わせて使われることも多いですが、公式サイトにも書かれている通りReactにしか使えないというものではありません。そのため、下記のコードにはReactのコードは入っていません)

index.js
import { createStore } from 'redux'
import todoApp from './reducers'
import {
  addTodo,
  toggleTodo,
} from './actions'

const store = createStore(todoApp)

console.log(store.getState())

const unsubscribe = store.subscribe(() => console.log(store.getState()))

store.dispatch(addTodo('Learn about actions'))
store.dispatch(toggleTodo(0))

unsubscribe()
actions.js
export const ADD_TODO = 'ADD_TODO'
export const TOGGLE_TODO = 'TOGGLE_TODO'

export function addTodo(text) {
  return { type: ADD_TODO, text }
}

export function toggleTodo(index) {
  return { type: TOGGLE_TODO, index }
}
reducers.js
import {
  ADD_TODO,
  TOGGLE_TODO,
} from './actions'

function reducer(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

export default reducer

createReducerのソースコードと説明

createReducerのコードを順番に見ていきます。

コード内に注目する点をコメントとして記述しましたので、読み解く際の参考にしていただければと思います。

1 createStore
createStore.ts
export default function createStore {
  ...
  let currentReducer = reducer
  // (1)createStoreの引数で受け取ったpreloadedStateをStateの初期値にセットする
  let currentState = preloadedState as S
  let currentListeners: (() => void)[] | null = []
  let nextListeners = currentListeners
  let isDispatching = false
  ...
  // (2)storeが保持する関数を定義する
  function getState(): S {
    ...
  }

  ...
  function subscribe(listener: () => void) {
    ...
  }

  ...
  function dispatch(action: A) {
    ...
  }

  // (3)reducerを実行してreducer内で初期値をセットしてreturnする
  // When a store is created, an "INIT" action is dispatched so that every
  // reducer returns their initial state. This effectively populates
  // the initial state tree.
  dispatch({ type: ActionTypes.INIT } as A)

  // (4)dispatch, subscribe, getState等の関数をreturnする
  const store = ({
    dispatch: dispatch as Dispatch<A>,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  } as unknown) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
  return store
}

(1)と(3)でStateの初期化が2度行われています。これは以下のような動きになります。

  • createStoreの引数にpreloadedStateが与えられたとき

    • Stateの初期値はpreloadedStateになる
  • createStoreの引数にpreloadedStateが与えられなかったとき

    • Stateの初期値はreducer内で初期化した値になる

reducerではES6の構文を使用してよくfunction myReducer(state = someDefaultValue, action)という形でstateに初期値が与えられます。createStoreにpreloadedStateが与えられなかったときには、myReducerの引数に渡されるstateがundefinedの状態になりますので、reducerはstateの初期値にsomeDefaultValueを入れて、この値をreturnすることになります。

より詳しい解説はReduxの公式サイトの Initializing Stateをご覧ください。

シーケンスは以下のようになります。
createStoreシーケンス図.png

2 store.subscribe
createStore.ts
function subscribe(listener: () => void) {
  ...
  let isSubscribed = true

  ensureCanMutateNextListeners()
  // (1)storeオブジェクト内のlistenrを保持している配列にlistenrを追加する
  nextListeners.push(listener)

  // (2)unsubscribeをするための関数をreturnする
  return function unsubscribe() {
    ...
    isSubscribed = false

    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    // (3)storeオブジェクト内のlistenrを保持している配列からlistenrを削除する
    nextListeners.splice(index, 1)
    currentListeners = null
  }
}

storeオブジェクト内に保持している配列にlistenerを追加したり削除したりしているだけですね。

3 store.dispatch
createStore.ts
function dispatch(action: A) {
  ...
  try {
    isDispatching = true
    // (1)reducerを実行して、返り値として新しいstateを受け取る
    currentState = currentReducer(currentState, action)
  } finally {
    isDispatching = false
  }

  const listeners = (currentListeners = nextListeners)
  // (2) subscribeによってよって登録されたlistenerを呼び出し、stateが更新されたことを通知する
  for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i]
    listener()
  }

  return action
}

シーケンスは以下のようになります。
dispatchシーケンス図.png

Storeからreducerを実行して更新されたStateを受け取ったら、listenerに登録されている関数を順番に呼び出すだけです。このとき、更新されたstateはlistenerには渡されません。listener内でstateを取得したい場合はgetState()を呼び出す必要があります。

※ ReduxをReactと組み合わせて使用する場合には、Listenerの部分は基本的にはUIコンポーネントになります。

4 store.getState
createStore.ts
function getState(): S {
  if (isDispatching) {
    throw new Error(
      'You may not call store.getState() while the reducer is executing. ' +
        'The reducer has already received the state as an argument. ' +
        'Pass it down from the top reducer instead of reading it from the store.'
    )
  }

  // (1)storeオブジェクト内に保持しているcurrentStateをそのまま返す
  return currentState as S
}

currentStateを返しているだけですね。

参考情報

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

英語のみでプログラミングを学習(途中経過)

英語のみでプログラミングを学習(途中経過)

 現在、都内で英語のみの環境でプログライングを学習しているko5ukeです。今回は私が英語でプログラミングを学習を始めた経緯と学習して感じたことを共有したいと思います。ちなみに、現在、私は六本木にキャンパスを構えいてるコードクリサリスのプログラミングブートキャンプに参加しています。

コードクリサリス(Code Chrysalis)とは

 現在、コードクリサリスのブートキャンプに参加していますが、コードクリサリスには主に2つのコースがあります。12週間フルタイムでプログラミングを学習するイマーシブコースと5週間パートタイムでプログラミングを学習するファンデーションズコースがあり、私はファンデーションズコースに参加しています。
 現在、都内には様々プログラミングを学べる環境はありますが、コードクリサリスでは英語でプログラミングを学べる環境を提供していて、ただ単にプログラミングを学ぶのではなく、ソフトスキルを取得することも大切にしています。詳しくはコードクリサリスのホームページをご覧ください。(Code Chrysalis)
 そこで、まずはなぜ私が英語でプログラミングを学ぼうと思ったのか共有したいと思います。

英語でプログラミングを学ぼうと思った経緯

 コードクリサリスで教えるプログライング言語はJavaScriptですが、実を言うと、私はRailsエンジニアで、Rubyを主言語としています。それでも、なぜわざわざわ英語でプログラミングを学ぶのかと言うと主に3つの理由があります。

  1. 開発で使用する技術の99%は海外で開発されている
  2. 国内の多くの企業で外国人を雇っている
  3. エンジニアは世界的な職種

1. 開発で使用する技術の99%は海外で開発されている

 極めて当たり前のことを書いていますが、まずは「1. 開発で使用する技術の99%は海外で開発されている」、エンジニアとして働かれている方であればわかると思いますが、開発で使用する技術の多くが海外性です。つまり、海外で開発されていると言うことは英語で仕様書が作られています。日本語訳がないわけではないですが、和訳のニュアンスが微妙に違ったり、和訳された物が出たときには若干時代遅れだったり、、、現代では流行り廃りのサイクルは早いもので、英語でダイレクトに情報が取得できることのメリットは大きい。だから私は英語でプログラミングを学習しています。

2. 国内の多くの企業で外国人を雇っている

 次に「2. 国内の多くの企業で外国人を雇っている」、昨年都内に引っ越してきて感じたことですが、東京には外国人が多い!!正直予想以上に外国人が多いです。また、エンジニア界隈に限って言えば、更に外国人の割合は増えるでしょう。現在IT人材不足から、日系企業でも多くの外国人を採用しているように思えます。
 エンジニアにとって、技術力はもちろんですが、私は技術以外の部分、コミュニケーション能力や提案力などもエンジニアには必要だと思います。特にコミュニケーション能力はどこの職場でも必要になると思っていて、開発はチームで行われるものなので、開発の進捗具合、サービスの良し悪しもチームの総合力が問われるわけです。今後更に日本国内のIT人材の外国人の割合は多くなると思いますので、英語がコミュニケーションを取れて損はないと思います。

3. エンジニアは世界的な職種

 そして、「3. エンジニアは世界的な職種」、先ほどの内容と被るところがありますが、英語ができることで自分の可能性が広がると考えています。日本国内だけでなく、世界的に見てもIT人材は不足しているので、それなりの技術力と英語力があれば、海外で活躍することも出来ます。私自身今すぐ海外で活躍したいとは考えていませんが、チャンスがあれば挑戦することも視野に入れています。そのためにも日頃から英語のある環境に身を置くことは重要だと思います。
 少し長くなりましたが、続いて英語でプログラミングを学んだ(途中経過)感想を書いていきます。

英語でプログラミングを学んだ感想

 ここからが今回のメインになります。現在、英語でプログラミングを学び始めて2週間を終えようとしているところですが、率直な感想を書きます。

1. 英語に対する抵抗感が少なくなった

 まず、はじめに感じたことですが、英語に対する抵抗感が少なくなったように思います。元々、少しずつ英語学習を続けていた私ですが、それでも英語で技術書を読むとなると、以前は辛い、グーグル翻訳に頼る、また途中で読むことをやめることもありました。しかし、最近は英語で技術書を読むことに対する抵抗感が薄れてきました。元々英語で学習していた内容がプログラミング向けのものではなかったこともあり、最近はプログラミングで使用される単語にも慣れてきました。まだ2週間しか経過していませんが、既に英語でプログラミングを学んで良かったと思っています。

2. 英語でコミュケーションをとることが楽しい

 これはブートキャンプに通っているメリットになりますが、コードクリサリスの学習ではソフトスキルも大切にしているため、レッスンの中で、隣の人と意見を交わし、どのようにコーディングするか話し合う機会があります。もちろん、英語でのレッスンのため、自分の意見も英語で伝えなければなりません。正直、初日の私の英語は酷かったと思います。普段から英語で会話をしていればいいのですが、開発に関わることを英語で話すことがほとんどないので、まず単語が出てこない。しかし、回数を重ねるごとに自分が話せる単語数も増え、英語で物事を伝えることが楽しくなりました。
 ただし、今でも苦労はします。単語もすぐ出てきませんし、わからない単語もまだまだ多いです。特に、相手が話している内容を理解するためには、相手が話している内容を自分で話せるレベルにならなければ聞き取れないと思っているので、ここは毎日トレーニングするしかないと思っています。そのため、私は毎日英語でプログラムを伝える練習をしています。簡単な英作文を書いて、それを読み返しています。

3. コードを読む速度が早くなる

 単純なことですが、プログラミング言語は英語ベースで作られているので、コードを読むスピードが若干ですが早くなったように思います。今のところ大きなメリットだと感じることはないですが、確かにコードを読むスピードは早くなったと思います。

4. 英語力の無さを実感

 私のクラスは英語で行われるため、クラスメイトの多くが外国人です。もちろん私以外にも日本人はいますが、皆さん英語が上手です。おそらく私が一番英語力がないでしょう。そのため、コードクリサリスに通うたびに英語力の無さを実感します。資料を読む際はマイペースに進めることが出来ますし、学習内容も復習しているため、授業内容がわからないと言うことはありません。
 ただし、コーディングについて質問した際は別で、相手が何を話しているか予想が立たないため、100%内容を理解しているかと言うとそうではありません。そのため、ここは慣れていくしかないように思えます。調べられることは調べて、わからないことは素直に質問する。これにつきますね。

以上で、今回の記事は終了にしますが、5週間経過後の感想も投稿したいと思います。それではまた。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【OSS貢献記録】emoji-martをReact 17に対応

前回のOSS貢献記録はこちら

はじめに

emoji-martは、下の画像のように絵文字選択フォームを実現するReactコンポーネントです。
スクリーンショット 2020-01-25 9.12.47.png

このコンポーネントを実際に利用していたら、次のような警告文がJavaScriptのコンソールに表示されました。

Warning: componentWillReceiveProps has been renamed, and is not recommended for use. See https://fb.me/react-unsafe-component-lifecycles for details.

* Move data fetching code or side effects to componentDidUpdate.
* If you're updating state whenever props change, refactor your code to use memoization techniques or move it to static getDerivedStateFromProps. Learn more at: https://fb.me/react-derived-state
* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress this warning in non-strict mode. In React 17.x, only the UNSAFE_ name will work. 
To rename all deprecated lifecycles to their new names, you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.

Please update the following components: NimblePicker

NimblePickerコンポーネントでReact 17で廃止予定のメソッドcomponentWillReceivePropsが使われていて、このままだとemoji-martはReact 17で使用できなくなります。 詳細はReact公式ブログの記事に記載されています。

自分がこの問題に気付いたときはReact初心者でした。なので、とりあえずissueを作って、これを見た誰かに直してもらおうと思いました。しかし、2ヶ月が経っても直される気配がありませんした。

これはまずいと思って、自分で改修できないと決意しました。2ヶ月間Reactを触り続けてReactの理解度が上がっていたので、自分で改修できるのではないかという自信が生まれていました。

改修内容

実際に出したPRはこちらです。

Qiitaの記事『React.js のライフサイクルメソッド componentWillReceiveProps の廃止対応』を参考しながら、改修方法を考えました。Nimble PickercomponentWillReceivePropsではpropsから新しいstateを作っています。なので、今回は記事中の2番目の方法「getDerivedStateFromProps に置き換える」方法をで採用しました。

今気付いたこと

getDerivedStateFromPropsではstateの変更箇所だけを返せばいいので、

static getDerivedStateFromProps(props, state) {
  if (props.skin) {
    return { skin: props.skin }
  } else if (props.defaultSkin && !store.get('skin')) {
    return { skin: props.defaultSkin }
  }
  return null
}

の方が適切でしたね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Sigfox Coverage MapをGoogle Maps APIで表示する

Sigfox Coverage Mapは、タイルレイヤーですので、簡単にGoogle Maps APIにも埋め込むことができます。

Sigfox Coverage Mapとは

Sigfox社が、地図アプリケーションやWebサイトに埋め込むことを目的にAPI提供しているカバレッジマップです。
下記のURLでタイル画像を取得できます。
https://tiles.sigfox.com/{TOKEN}/{z}/{x}/{y}.png

TOKENの取得

地図を取得するためのトークンです。有効期限は72時間ですので、適時更新する必要があります。
トークンを取得する方法は、下記URLでGETリクエストをします。
https://backend.sigfox.com/api/v2/tiles/public-coverage
応答は、下記のようなJSONになっていますので、

tiles-public-coverage.json
{
 "baseImgUrl":"https://tiles.sigfox.com/***",
 "tmsTemplateUrl":"https://tiles.sigfox.com/***/{z}/{x}/{y}.png",
 "bounds":{"sw":{"lat":-80.0,"lng":-180.0},"ne":{"lat":80.0,"lng":180.0}}
}

この***部分がトークンになります。

タイル画像の読み込み

トークンを取得すれば、先ほど説明したSigfox TilesサーバURLの{TOKEN}部分に取得したトークンを貼り付け、画像タイルに対応した地図アプリケーションから呼び出すだけとなります。

Google Maps APIでタイル画像レイヤを追加する

まずは、基本的なGoogle Maps Javascript APIで、地図をWebブラウザ上に表示してみます。
サンプルコードは、Google社提供のチュートリアル(こちら)をそのまま使ってみます。念のためソースコードは下記の通りです。

google-map.html
<!DOCTYPE html>
<html>
  <head>
    <title>Simple Map</title>
    <meta name="viewport" content="initial-scale=1.0">
    <meta charset="utf-8">
    <style>
      /* Always set the map height explicitly to define the size of the div
       * element that contains the map. */
      #map {
        height: 100%;
      }
      /* Optional: Makes the sample page fill the window. */
      html, body {
        height: 100%;
        margin: 0;
        padding: 0;
      }
    </style>
  </head>
  <body>
    <div id="map"></div>
    <script>
      var map;
      function initMap() {
        map = new google.maps.Map(document.getElementById('map'), {
          center: {lat: 35.6384, lng: 139.7343},
          zoom: 8
        });
      }
    </script>
    <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"
    async defer></script>
  </body>
</html>

Sigfox Coverage Tileレイヤを追加する

上記サンプルコードのinitMap()内に下記コードを追加してください

Sigfox-Tile.js
var tileLayer = new google.maps.ImageMapType({
  name: 'SigfoxCoverage',
  getTileUrl: function(coord, zoom) {
    var url = 'https://tiles.sigfox.com/{TOKEN}/{z}/{x}/{y}.png'
              .replace('{x}', coord.x)
              .replace('{y}', coord.y)
              .replace('{z}', zoom);
    return url;
  },
  tileSize: new google.maps.Size(256, 256),
  opacity: 0.7
});
map.overlayMapTypes.insertAt(0, tileLayer);

opacityは追加する画像タイルレイヤーの透過度です。これを設定しないと、背景地図が見えなくなるので注意してください。

Sigfox Coverage Mapを確認する

無事成功すると、下記のようにGoogle Map上にカバレッジマップが表示されます。
image.png

表示範囲を制限したい場合

もし、地図の表示範囲を制限したい場合は、google.maps.MapRestrictionインターフェースを使って下記のように緯度経度(四角形)指定で表示制限を付けることも可能です。

restriction.js
restriction: {
  latLngBounds: {
    north: 45.60,
    south: 20.42,
    west: 122.93,
    east: 153.99
  },
  strictBounds: false
}

緯度経度は、適当に日本の東西南北端点の座標を書いています。

ただ、もし、より正確にやろうとした場合、例えば、四角形定義(Bounds定義)では物足りない場合、dragイベントのリスナーを使って、ポリゴンの内外判定による表示制限をした方が良いかもしれませんね。

Sigfox Japan KCCS

Tweets by ghibi

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

videojsでendedイベントが発火しない場合の対処法

発生していた問題

  • videojsを利用して動画再生をしている
  • 動画再生が最後に達したら次のリストの動画を自動再生
  • ほとんどの動画で問題なく最後に達して次の動画が自動で再生ができるが、一部の動画だけが最後に達しても次の動画に遷移しない。

問題が発生していたJSコード一部

var player = videojs("#videojs_element");
player.on("ended", function() {
   // ここで再生が終了したら次の動画へ遷移する処理を行っている
});

問題の分析

endedが発火しているのか確認

以下のコードでendedが発火するか確認したら、問題が発生する動画では、consoleにended startというログが出ない。

var player = videojs("#videojs_element");
player.on("ended", function() {
   console.log('ended start');
   // ここで再生が終了したら次の動画へ遷移する処理を行う。
});

timeupdateを入れて再生位置と終了位置をリアルタイムで監視

問題が発生する動画ではendedが発火しないので、videojsのtimeupdateというイベントを使って現在の再生位置と終了位置をリアルタイムで監視してみた。

var player = videojs("#videojs_element");
// ended部分はコメントアウト
//player.on("ended", function() {
//   // ここで再生が終了したら次の動画へ遷移する処理を行う。
//});
// 以下を追加
player.on("timeupdate", function(){
    var self = this;
    console.log(self.currentTime()+'='+self.duration());
});

問題が発生する動画ではコンソールログの結果を見るとcurrentTime()がduration()の値まで達していない。。。

251.9711=254.034
252.220862=254.034
252.470623=254.034
252.721052=254.034
252.970452=254.034
253.220424=254.034
253.470793=254.034
253.722037=254.034
253.941767=254.034

問題が発生しない動画のコンソールログの結果を見ると、currentTime()がduration()の値まで達している。

142.565163=144.203763
142.813788=144.203763
143.063869=144.203763
143.313899=144.203763
143.564323=144.203763
143.814652=144.203763
144.06434=144.203763
144.203763=144.203763

問題が発生する動画は、duration()の値の小数点以下の桁数が3桁までになっています。ちなみに、問題が発生する他の動画も小数点以下の桁数が3桁まででした。

対象方法

根本的な解決ではないかもしれませんが、endedイベントで終了処理を行うのではなく、timeupdate内で終了処理を呼び出すことにしました。
小数点以下の値が怪しいので、小数点以下は削って比較するようにしました。

var player = videojs("#videojs_element");
// ended部分はコメントアウト
//player.on("ended", function() {
//   // ここで再生が終了したら次の動画へ遷移する処理を行う。
//});
// 以下を追加
player.on("timeupdate", function(){
    var self = this;
    if (Math.round(self.currentTime())==Math.round(self.duration())) {
        // ここで再生が終了したら次の動画へ遷移する処理を行う。
    }
});

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

webpack-dev-serverでdisableHostCheckを有効にするのはもうやめよう

この記事のコピペです(本人です): https://dev.to/origamium/webpack-dev-server-disablehostcheck-4am

ハローこんにちは。disableHostCheck: trueを使うのをやめよう。

disableHostCheckを使うのにはだいたい理由があって、たとえばiPhoneなどの別端末からPC上のlocalhostで動いているサイトを見たいという理由でdisableHostChecktrueにしたりしてアクセスしたりします。

6bg9lxvyh6ua2vstr2f7.png

[マシン名].local:[ポート番号]でアクセスするのはiPhoneを使った開発ではよく見る光景ですね。このときdisableHostCheckはだいたいInvalid Host headerを回避するために使います。

しかし、disableHostCheckを有効にするとDNS rebinding attack脆弱性を抱えてしまいますし、実際に webpack-dev-serverのdocumentationでも非推奨 の方法となっています。
代わりに今の所、useLocalIP: trueを使えば解決することができます。

config.devServer = {
    host: "0.0.0.0",
    useLocalIp: true,
}

host: "0.0.0.0"はないと動かないので注意。
この方法を使えばdisableHostCheckを回避できます。自分のローカルIPアドレスを使ってやればアクセスできるので、ネットワークによってURLは変更されますが。

tyvnlesm5s4oh3vm3w7j.png

また、最近のブラウザはモバイル端末とページを同期できる(送信できる)ようにもなっているので、URLがちょっと複雑になったぐらいなんともありません。

screenshot.136.jpg

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Playwrightも知らないで開発してる君たちへ

thumb.png

Playwrightとは

Microsoft から Playwright というツールが公開されました。
それはGitHubのトレンドにもなりインパクトのあるニュースでした。

Playwrightとは、ざっくり言うと
めっちゃ簡単にChrome、Safari、Firefoxをコマンドライン上で実行できるNodeのライブラリです。

公式: https://github.com/microsoft/playwright

使い方

Puppeteerとほとんど変わりません。
npm isntall して 数行のJavaScriptコードを記述するだけ でスクリーンショットが取れます。

npm i playwright
const pw = require('playwright');

(async () => {
  const browser = await pw.webkit.launch(); // or 'chromium', 'firefox'
  const context = await browser.newContext();
  const page = await context.newPage();

  await page.goto('https://www.example.com/');
  await page.screenshot({ path: 'example.png' });

  await browser.close();
})();

ポイント

const browser = await pw.webkit.launch();

重要なのはここで、 pw.webkit.launch() とするか pw.chromium.launch() とするかで
起動するブラウザが変わるだけです。

デバイスの設定も簡単

pw.devices に主な端末のviewport, UAの情報がまとめられています。

デバイスのリスト:
https://github.com/microsoft/playwright/blob/master/src/deviceDescriptors.ts

const pw = require('playwright');
// iPhone11のデバイス情報を取得
const iPhone11 = pw.devices['iPhone 11 Pro'];

(async () => {
  const browser = await pw.webkit.launch();
  const context = await browser.newContext({
    viewport: iPhone11.viewport,
    userAgent: iPhone11.userAgent,
    geolocation: { longitude: 12.492507, latitude: 41.889938 },
    permissions: { 'https://www.google.com': ['geolocation'] }
  });

  const page = await context.newPage('https://maps.google.com');
  await page.click('text="Your location"');
  await page.waitForRequest(/.*preview\/pwa/);
  await page.screenshot({ path: 'colosseum-iphone.png' });  
  await browser.close();
})();

WebDriver (Selenium) との違い

自動テスト化に関して、Seleniumの貢献は計り知れないものがあります。
が、実案件で使おうとなると、セットアップが大変だったり、クセが強くて手を焼いた方も少なくないと思います。

Playwrightの公式ドキュメントでは、WebDriverとの違いについて以下の通り述べています。

[capabilities] With Playwright, we aim at providing a more capable driver, including support for mobile viewports, touch, web & service workers, geolocation, csp, cookie policies, permissions, accessibility, etc.
[ergonomics] We continue the trend set with Puppeteer and provide ergonomically-sound APIs for frames, workers, handles, etc.
[reliability] With Playwright, we encourage setTimeout-free automation. The notion of the wall time is incompatible with the operation in the cloud / CI. It is a major source of flakiness and pain and we would like to provide an alternative. With that, Playwright aims at providing sufficient amount of events based on the browser instrumentation to make it possible.
引用: https://github.com/microsoft/playwright#faq

[機能] capabilities

Playwrightでは、モバイルビューポート、ジオロケーションなどのサポートを含む、より有能なドライバーの提供を目指しています。

[人間工学] ergonomics

Puppeteerのトレンドセットを継続し、人間工学的に健全なAPIを提供します。

[信頼性] reliability

Playwrightでは、setTimeoutのない自動化を推奨しています。それを可能にするために、十分な量のイベントを提供することを目指しています。

**

私なりに解釈すると、
テストに必要な機能 を揃えて、 使いやすいAPI も用意して、
「とりあえず sleep で待つ」みたいなテストコードを書かなくて済むように たくさんイベントを用意 する。
ということだと思います。

Puppeteerとの違い

Playwrightは Puppeteerの後継 という認識でよいです。
公式にも「私たちはPuppeteerを開発したチームと同じです」と記載されていました。

We are the same team that built Puppeteer.
引用: https://github.com/microsoft/playwright#faq

比較一覧

スクリーンショット 2020-01-25 1.25.30.png

何が嬉しいか

レンダリングエンジンやJavaScriptのエンジンが違うことで
挙動が変わってしまう問題は現代でも少なからずあります。

また、昨今はCookieポリシーの変更が激しく、
「PCではうまくいくのに、iPhoneだけcookieが取れてなかった」というような
インパクトの大きい事故が発生することもあります。

そういったブラウザ依存の問題が発生していないか
自動テストによって担保できるようになります。

でも銀の弾丸ではない

ハードウェアに依存する部分の問題は検知できませんし、
テストコードを作るコストについても別の課題としてあります。

また、WebKitとFirefoxに関してはかなり変更を加えているようなので
それによる影響も気になります。
Q: What browser versions does Playwright use? を参照ください

あと個人的にはnpm installがやたら長かったことも気になりました。。
firefoxとsafariのインストールに時間がかかってました。

まだ正式リリースではない

v1.0.0までに大幅にAPIを変更する可能性があるとのこと。
進捗については Is Playwright Ready? をチェックしてくれとのことでした。

まとめ

Playwrightという素敵なツールが公開されて、
自動テストで担保できる範囲が広がったというお話しでした。

「土曜日のフロントエンドエンジニア」では
実際にサンプルを実行してますので見てくれると飛んで喜びます☺️

動画解説版: https://youtu.be/ed4znUNMzEo

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む