20201117のJavaScriptに関する記事は28件です。

画面透過について

bodyに背景画像や背景色を挿入したものの、containerなどの要素の中も透過して画像や色を反映させたいと思った事はありませんか?

そんな時はrgba(255,255,255,0)と設定しましょう。
以下のようになります。
スクリーンショット 2020-11-17 20.30.36のコピー.png

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

PHPの勉強でポートフォリオ投稿サイトを作ってみた

はじめに

新卒でウェブ制作会社に就職し、コードの量産に嫌気が差し営業に転職。
趣味で作りたいもののために勉強しているウェブエンジニアです、こんにちは。

今まではWordpressを使用しウェブサイトを作ってきましたが、ウェブサービスを作りたく思いバックエンドの勉強を開始しました。
その時、悩んだこと、作ったサービスを簡単に書こうと思います。

「PHP」を選択するか、あるいは「Laravel」選択するか

結果的にタイトルにあるようにPHPを選択しましたが、すごい悩みました。
というのもPHPはあまりいい噂を聞きません。
「やれ、古い」「やれ、汚い」挙句の果てに「オワコン」なんて言葉も見つけました。

それに比べてLaravelはかっこいい。
なんかモダンな香りがしますよね。
PHP勉強してるより、Laravel勉強してる」の方がかっこいい。

形から入るタイプの私はすごい悩みました。
通勤電車中、仕事中、食事中、ずっと考えました。
考えて考えて考え抜いた結果、「とりあえず手を動かそう、早く作ろう」という結論に至りました。

そうすると環境構築するのも面倒くさいので、もうPHPでいいやと吹っ切れました。

結果的にPHPのことが良く知れたので正しい選択をしたと思っています。
たぶんLaravelの勉強の役にもたったんじゃないかと思い込むようにしています。

勉強開始~制作完了まで

言語選択に時間をかけ過ぎました。
勉強を始めてみるとPHPは確かに学習コストがそこまで高くなく、いい感じで進むので楽しく作成することができました。
学習期間は、ほとんど作りながらの学習だったので、2020年8月20日~2020年10月20日。
丸2か月かかりました。
結構かかりましたが、データベースとPHPをまるっと学べることができたので、有意義な時間でした。
作っているものが形になっていく過程は相変わらず、面白いですね。

ポートフォリオ投稿サイト

作ったのはポートフォリオ投稿サイトです。
機能は見ていただけるとわかりますが簡単にリスト化すると

  • ログイン
  • 投稿
  • イイネ
  • コメント
  • 投稿削除
  • ログアウト

こんな具合。
まあ、多くの投稿サイトと同じ機能を持っていると思います。

サイトは下記URLになりますので、よろしければ投稿してみてください。
またアドバイスもお待ちしております。ぜひとも、何卒。

ポートフォリオ投稿サイト

作ってみた感想と今後

とにかく物を作りたいという欲求を満たすことができました。
学習コストの低いPHPそういった入り口として非常にいい言語なのかもしれません。

ただ実力不足でサービスとしては物足りない部分も多く、例えばセキュリティー対策も最低限しかできていないですし、イイネ機能の非同期通信も結局、実装せずに満足してしまいました。投稿された画像の軽量化とかもしたかった。

そしてにより、作っただけのサービスになってしまいましたので、そこも反省材料として血肉にしていこうと思います。

次はLaravelを使用し、サービスとして確立できるものを作りたいと思います。

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

5歳娘「パパ、型はドキュメントだよ?」

とある休日

娘(5歳)「パパ、今日はお休みだから一緒にゲームしよ?」

ワイ「ええで!なんのゲームする?」
ワイ「スーパー正男ブラザーズでもやろか?」

娘「ううん」
娘「コードジャンケンしよ!」

ワイ「なんや、その恐ろしそうな名前のゲームは。。。」

娘「なんか仕様を決めて、どっちが読みやすいコードで実装できるか勝負するの!」

ワイ「おお、ええで」
ワイ「流石に5歳児には負けへんで!」
ワイ「6歳児だと危ういけどな!」

娘「じゃあ、ママ」
娘「何か仕様をちょうだい!」

よめ太郎「ええで」

仕様: 数値を文字列に変換する

よめ太郎「仕様、考えたで」
よめ太郎「数値文字列に変換する、っちゅう内容や!」

【いま考えた仕様書】

  • とあるAPIから0〜2の数値が返ってくるとする
  • 0〜2の数値は、ユーザーの申込進捗状況を表しているものとする
  • 0〜2の数値は、それぞれ以下の状態を表している
    • 0 → 申込書到着
    • 1 → 申込手続中
    • 2 → 申込完了
  • 画面上では、数値ではなく文字列として表示する必要がある

上記の数値を、画面表示用の文字列に変換する処理を実装せよ。

ワイ「ほう・・・ユーザーの申込進捗状況ねぇ」
ワイ「要するに・・・」
ワイ「APIから0が返ってきたら、画面には'申込書到着'って表示する」
ワイ「APIから1が返ってきたら、画面には'申込手続中'って表示する」
ワイ「APIから2が返ってきたら、画面には'申込完了'って表示する」
ワイ「そのための、数値から文字列への変換処理を書けってことやな?」

よめ太郎「そういうことや」

ワイ「クッソ簡単やん」
ワイ「こんなん、誰が書いても同じやろ」

娘「言語はTypeScriptでいい?」

ワイ「ええで!」
ワイ「ほな勝負や!」

ワイのコード

ワイ「書けたで!」

wai.ts
const userStatuses: Array<string> = [
    '申込書到着',
    '申込手続中',
    '申込完了'
]

ワイ「↑こうや!」

よめ太郎「なるほどな」
よめ太郎「数値は012な訳やから」
よめ太郎「配列を用意しておいて、その数値でアクセスすればいいって訳か」

ワイ「せや」

wai.ts
console.log(userStatuses[0])
// -> '申込書到着'

console.log(userStatuses[1])
// -> '申込手続中'

console.log(userStatuses[2])
// -> '申込完了'

ワイ「↑こんな感じや!」

よめ太郎「なるほどな、シンプルやな」

5歳娘ちゃんのコード

娘「私も書けたよ!」

musume.ts
type ApiValue = 0 | 1 | 2
type DisplayText = string

const userStatusTextMap: Map<ApiValue, DisplayText> =
    new Map([
        [0, '申込書到着'],
        [1, '申込手続中'],
        [2, '申込完了']
    ])

娘「↑こんな感じ!」

よめ太郎「ほうほう」
よめ太郎「まずは・・・」

musume.ts
type ApiValue = 0 | 1 | 2

よめ太郎「なるほど」

型「APIから返ってくる値は、012のどれかやで!」

よめ太郎「↑ってことを表しているんやな」

娘「うん!」

よめ太郎「説明的でええやないか」
よめ太郎「ほんで、次の行は・・・」

musume.ts
type DisplayText = string

よめ太郎「string型に、別名を付けてあげてる訳か」

娘「うん!」

型「ここでのstringは、画面に表示するための文字列ですよ!」

娘「ってことを表現してみたの」

よめ太郎「ああー」
よめ太郎「単にstringだと、なんのための文字列なのかってことまでは分からへんもんな」
よめ太郎「せやから、もう少し意味のある型名を付けてあげた訳やな」
よめ太郎「素敵やん」
よめ太郎「そんで、その次は・・・」

musume.ts
const userStatusTextMap: Map<ApiValue, DisplayText> =
    new Map([
        [0, '申込書到着'],
        [1, '申込手続中'],
        [2, '申込完了']
    ])

よめ太郎「なるほどな」
よめ太郎「この・・・」

musume.ts
Map<ApiValue, DisplayText>

よめ太郎「っていう型がエエ感じやな」

型「APIから取得した値表示用テキスト対応表ですよ!」

よめ太郎「↑こんな感じが滲み出てるな」

ワイ「ああ〜、そうか」
ワイ「Mapって対応表って意味やもんな」

娘「うん」
娘「それで、こんな感じで使うの」

musume.ts
console.log(userStatusTextMap.get(0))
// -> '申込書到着'

console.log(userStatusTextMap.get(1))
// -> '申込手続中'

console.log(userStatusTextMap.get(2))
// -> '申込完了'

console.log(userStatusTextMap.get(3))
// -> コンパイルエラー!!!

よめ太郎「おお〜」
よめ太郎「ApiValueの値を012に制限してあるから」
よめ太郎「3のぶんを取得しようとすると、ちゃんとコンパイルエラーが起きるんやな」
よめ太郎「ええやん」

娘「てへへ」

よめ太郎「こういうのも、型の旨みやもんな」

勝敗判定中

よめ太郎「娘ちゃんのコードは、全体的に説明的で良いな」
よめ太郎「コードを読んだだけでも・・・」

「こんな仕様書だったんやろな〜」

よめ太郎「っていうことが、なんとなく想像できるわ」
よめ太郎「このコードを後から保守する人も・・・」

保守担当「むむ?」
保守担当「Map<ApiValue, DisplayText>とな?」
保守担当「なるほど、これは・・・」
保守担当「APIから取得した値表示用テキスト対応表ってことか」

よめ太郎「って感じで、型の意図を読み取りやすそうやな」
よめ太郎「修正フェーズ保守フェーズで価値を発揮しそうなコードやな」

娘「うん、そこは意識してみたよ」
娘「この部分のコードだけ読んでも、できるだけ意味や意図が分かるように、って」

よめ太郎「なるほどな」

娘「小さなプロジェクトなら、仕様書を全部読んで、コードも型も全部読んで」
娘「全部理解してから修正すればいいけど」
娘「大規模な案件だと、全ての仕様を理解して、全体のコードを読んでから修正するとか」
娘「頭がパンクしちゃうもん」

よめ太郎「まあ、できれば全体を把握した方がええんやろうけど」
よめ太郎「一部分だけを見ても分かりやすい、説明的な型やコードが書かれてると」
よめ太郎「あとから読む人は、すごく助かるよな!」

娘「そうだね!」
娘「型は、ドキュメントの役割もするからね!」

ワイ「(型はドキュメント・・・?)」
ワイ「(何を言うてんの・・・?)」
ワイ「(でも・・・)」
ワイ「せやな!型はドキュメントや!
ワイ「(一応言うとこ・・・!)」

よめ太郎「かたや、やめ太郎のコードは・・・」

wai.ts
const userStatuses: Array<string> = [
    '申込書到着',
    '申込手続中',
    '申込完了'
]

ワイ「う〜ん、まさに」

型「APIから取得した値表示用テキスト対応表ですよ!」

ワイ「って、型が語りかけてきてる感じがするな!」

よめ太郎「いやどこがやねん
よめ太郎「どこから感じたねん」
よめ太郎「エスパーか」

ワイ「ぐぬぬ・・・」

よめ太郎「Array<string>という型から分かるんは」

型「文字列が入った配列やで」

よめ太郎「それだけやろ」

ワイ「確かに・・・」

結果発表

よめ太郎「ほな、結果発表するで」

ワイ「ワクワク・・・!」

よめ太郎「ようワクワクできるな」

ワイ「ぐぬぬ」

よめ太郎「勝者は・・・」
よめ太郎「娘ちゃんや!

ワイ「まぁ、知ってたわ・・・」

よめ太郎「せやろな」
よめ太郎「勝因としては・・・」

  • 型や命名で、できる限り仕様を説明していた

よめ太郎「ってとこやな」

ワイ「なるほどな・・・」
ワイ「型はドキュメント・・・」
ワイ「そういうことか・・・」

よめ太郎「しかも、型で書いた通りに実装せんと」
よめ太郎「エラーが出て前に進めない・・・」
よめ太郎「強制力のあるドキュメントやな」

ワイ「確かに」
ワイ「コメントはワンチャン間違っとるかもしれんけど」
ワイ「型は、間違っとったらコンパイルエラーで進めへんもんな」

よめ太郎「せや」
よめ太郎「テストコードと並んで、一番信頼できるドキュメントと言えるかもしれん」

まとめ

  • 型の付け方で仕様を説明できると素敵やね
  • 元々ある型に別名を付けて説明してあげるのもええね
  • 型はドキュメント的な役割も果たすんやね

ワイ「↑こういうことやな!」
ワイ「これで、さらにコメントもちゃんと書けば」
ワイ「だいぶリーダブルなコードになりそうやな」

よめ太郎「せやな」
よめ太郎「あ!でも一点だけ・・・」

musume.ts
Map<ApiValue, DisplayText>

よめ太郎「↑ここは」

musume.ts
ReadonlyMap<ApiValue, DisplayText>

よめ太郎「↑こうの方がええかもな」

娘「あ、そっか」
娘「ReadonlyMap型のほうがよかったね」

よめ太郎「ReadonlyMapにしておけば、上書きできひんから・・・」

musume.ts
userStatusTextMap.set(2, '死亡')

console.log(userStatusTextMap.get(2))
// -> '死亡'

よめ太郎「↑こういう事故が防げるもんな」

娘「そうだね」
娘「それに・・・」

型「上書きする必要のない、読み取り専用の対応表やで!」

娘「ってことも伝わるしね」
娘「あー、そうすればよかったなぁ」

ワイ「そうかぁ」
ワイ「ってことは・・・ワイの勝ちかな?」

よめ太郎「いやちゃうやろ

〜おしまい〜

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

子育てサポートWebARアプリケーションを作成中!プログラム初心者パパの挑戦

「イヤイヤ期」を解決する子育てサポートWebARアプリケーション

 現在、イヤイヤ期を迎えたこどもを持つパパ/ママ向けの子育てサポートWebアプリケーションを作成中です。

 試作の状態のため、「アプリケーションを作ろうと思った背景」や、「プロダクトの全体像」、「解決しようとしている課題とその狙い」などの詳細説明は、本記事では割愛しますが、制作の状況や、完成品はQiitaにも都度アップデート出来ればと思っていますので、ご興味あれば引き続き記事のチェックお願いします!

 ざっくり言うと、こどもの『ごはんのイヤイヤ(最後まで食べずに遊びたい)と、その後のお風呂のイヤイヤ(お風呂入りたくない)を、お風呂場にあるおもちゃのARコンテンツで解決したい』と思っています!

 現時点で出来ている下記2点までで、娘に使ってもらうテストをしてみましたので、その様子と、見えてきた改善点などを記事にしてみたいと思います。
・ARマーカーを読み取りマーカー上にモデルを出現させる
・モデルを画面に出現させたタイミングでmp3音源を再生する

娘に使ってもらい、その様子を観察した

1.使用感
 1才11か月の娘にはARの世界観を理解してもらえるか(怖がったりしないか)、反応が読めませんでしたが、スマホをかざすと、現実には存在しないペンギンが現れることにかなり興味を示し、楽しんでる様子でした!スマホの中を覗き込んだり、スマホの外からマーカーを直接見たりしながら、「現実にあるようで無い」世界観をニヤニヤしながら楽しんでいました。
 一方で、よっぽど楽しいのか何度も何度も「スマホをかずす」→「やめる」の繰り返しとなり、終わりのない感じになってしまいました。この点は、アプリケーションとしての「終わり」を準備してあげる必要性を感じました。
IMG_9587.PNG
2.効果
 ペンギンのARが出現したタイミングで、アプリケーション内で流れる「●●ちゃん、お風呂に入ろう!」という音声については、「・・・お風呂」と小さな声で呟いていたので、「お風呂」というキーワードは認識してくれたと思います。さらに妻が「ペンペン(←我が家のペンギンの愛称)がお風呂で待ってるってよ~」と促すと、いつもよりスムーズにお風呂場に向かってくれました。そして、お風呂場にいるペンペンに対面したときは「あーペンペンだー!」と言って、嬉しそうにペンギンのおもちゃを手にしていましたので、お風呂に入りたくないという「イヤイヤ」の改善効果は、ある程度見込めるのではないかと思いました!
 ただし、初めてだったので、目新しもあり、良い反応を示したということもあると思いましたので、継続して利用してもらえるように、ペンギンだけでなく「今日は誰がお風呂で待ってくれてるだろう感」が必要な気がしました。
IMG_9588.PNG

3.妻へのヒアリング
 娘の反応は結構いいね!という同じ評価をもらえましたが、下記3点の指摘ももらいました。
・ARモデルのペンペン(ペンギン)がリアルすぎてお風呂にいるおもちゃとギャップある。
・娘の操作だと、スマホが固定できずARマーカーの読み取りが途中で切れてしまうことが何度かあった。音声が途中で途切れてしまうと、「お風呂に入ろう!」というキーワードまで辿りつかない。
・うちの娘以外の反応も見てみた方が、色んな観点で評価できるのではないか。

ということで、今後考えたい対策

見えた課題 対策
ARが楽しくて終わりがない 音声再生が終わったらペンペン(←我が家のペンギンの愛称)が自ら立ち去るようにしたい
継続して利用してもらえるかわからない ペンギンだけでないARモデルも準備したい
ARモデルのペンペンがリアルすぎてお風呂にいるおもちゃとギャップある もう少しデフォルメされたペンギンのARモデルを探す
娘の操作だと、スマホが固定できずARマーカーの読み取りが途中で切れて、音声が途切れてしまう 対策検討中(持ちやすそうなカメラ型のスマホケース買ってみたので次はそれでテスト予定)
我が子以外のこどもの反応が悪い可能性がある 検証に協力してくれる人を探して、色んなフィードバックをもらう

今回は以上。引き続き頑張ります!

 とりあえずはベースとなる機能と簡単な検証は成功したとと思うので、今回見えた課題の改善に取り組み、より良いモノを引き続き目指していきたいです!!

続く。

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

自作PC特化だけど、リモート起動や自動起動を好きにカスタムできるWebアプリをobnizで試作してみた

自作PCの自動起動を好きにカスタムできる良い感じのアプリが無いので、自分で製作を考えている。試しにobnizを利用してWebアプリから電源ONを試作。 ※今回は電源ONの代わりにLED点灯を試す。
ちょっと分かり辛いが、電源ONクリックするとLEDが1秒点灯して消える。

Webアプリ画面

曜日別に起動時間を設定したいので、曜日別時間設定画面を設けた。データベース保存はまだ実装出来ていない。CodePenはコチラから
image.png

コード

.html
<!DOCTYPE html>
<html lang="jp" >
<head>
  <meta charset="UTF-8">
  <title>HPC-accede</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css'>
</head>

<body>
<!-- 全体をVue.js有効にする -->
<div id="app" class="container text-white bg-dark p-1">

  <!-- タイトル -->
  <div class="row text-center">
    <div class="col-sm-6 mx-auto"><h1>HPC-accede</h1></div>
  </div>
  <!-- 設定 -->
  <div class="form-group my-3 mx-4">
    <div class="border-bottom col-sm-12"><h5>Obniz ID</h5></div>
  </div>
  <div class="form-group form-inline my-3 mx-5">
    <input v-model:value="ObnizID[0]" class="form-control" type="text" maxlength="4" style="width:80px;">
    <label class="control-label mx-2">-</label>
    <input v-model:value="ObnizID[1]" class="form-control" type="text" maxlength="4" style="width:80px;">  
  </div>

  <!-- 即時電源ON -->
  <div class="form-group my-3 mx-4">
    <div class="border-bottom col-sm-12"><h5>即時起動</h5></div>
  </div>
  <div class="form-group my-3 mx-5">
    <button v-on:click="PowerON" class="btn btn-success">電源ON</button>
  </div>

  <!-- 曜日指定 -->
  <div class="form-group my-3 mx-4">
    <div class="border-bottom col-sm-12"><h5>曜日別時間設定</h5></div>
  </div>

  <div class="form-group form-inline col-sm-12">
    <!-- 日曜 -->
    <div class="form-inline col-sm-4 mb-2">
      <label class="control-label mx-3">日曜:</label>
      <input v-model:value="WeekTime['Sun']" class="form-control" type="time" step="1" style="width:130px;">
    </div>
    <!-- 月曜 -->
    <div class="form-inline col-sm-4 mb-2">
      <label class="control-label mx-3">月曜:</label>
      <input v-model:value="WeekTime['Mon']" class="form-control" type="time" step="1" style="width:130px;">
    </div>
    <!-- 火曜 -->
    <div class="form-inline col-sm-4 mb-2">
      <label class="control-label mx-3">火曜:</label>
      <input v-model:value="WeekTime['Tue']" class="form-control" type="time" step="1" style="width:130px;">
    </div>
    <!-- 水曜 -->
    <div class="form-inline col-sm-4 mb-2">
      <label class="control-label mx-3">水曜:</label>
      <input v-model:value="WeekTime['Wed']" class="form-control" type="time" step="1" style="width:130px;">
    </div>
    <!-- 木曜 -->
    <div class="form-inline col-sm-4 mb-2">
      <label class="control-label mx-3">木曜:</label>
      <input v-model:value="WeekTime['Thu']" class="form-control" type="time" step="1" style="width:130px;">
    </div>
    <!-- 金曜 -->
    <div class="form-inline col-sm-4 mb-2">
      <label class="control-label mx-3">金曜:</label>
      <input v-model:value="WeekTime['Fri']" class="form-control" type="time" step="1" style="width:130px;">
    </div>
    <!-- 土曜 -->
    <div class="form-inline col-sm-4 mb-2">
      <label class="control-label mx-3">土曜:</label>
      <input v-model:value="WeekTime['Sat']" class="form-control" type="time" step="1" style="width:130px;">
    </div>
  </div>
</div>

<!-- CDN -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.3/js/bootstrap.min.js'></script>
<script src='https://unpkg.com/obniz@3.9.0/obniz.js'>
</script><script  src="./script.js"></script>

</body>
</html>
script.js
// 任意の秒数待つことができる関数
const sleep = (msec) => new Promise(res => setTimeout(res, msec));
// Obniz関数
let obniz;

// Obniz呼び出し関数
const connect = function(func, ob){
  console.log(ob.connectionState);
  // Obnizへの接続を確認
  if (ob.connectionState === "connected") {
    func();
  } else {
    ob.on('connect', () => {
      func();
    })
  }
}

const app = new Vue({
  el: '#app', // Vueが管理する一番外側のDOM要素
  data: {
    // Vue内部で利用する変数定義
    ObnizID: ['0000', '0000'],
    WeekTime: [
      {'Sun':'00:00:00'},
      {'Mon':'00:00:00'},
      {'Tue':'00:00:00'},
      {'Wed':'00:00:00'},
      {'Thu':'00:00:00'},
      {'Fri':'00:00:00'},
      {'Sat':'00:00:00'},
    ],
  },

  methods: {
    // 関数はココに記述
    PowerON: function() {
      // LED ON
      // Obniz ID 指定
      let obnizid = `${this.ObnizID[0]}-${this.ObnizID[1]}`;
      console.log(obnizid);
      this.obniz = new Obniz(obnizid);

      let me = this; // thisを関数内で使えないので変数に代入
      // connect関数を呼んで、connect関数内で以下のFunctionを実行
      connect(async function() {
        const led = me.obniz.wired('LED', { anode: 0, cathode: 1 });
        me.obniz.display.clear();
        me.obniz.display.print('ON');
        led.on(); await sleep(1000); led.off();   // LED点灯 1s
        me.obniz.display.clear();
      }, this.obniz);
    },
  },
});

弟に使い勝手とかを聞いてみた

今回はLED点灯だが、完成後はPCが起動するようになるイメージを持ってもらったうえで使ってもらった。

操作面

  • 初回実行時のLED点灯の動作がちょっとおかしい。 ※コーディングミスってるかも。
  • 初期設定(ObnizID)は別メニューの方が良いと思う。
  • 電源ON を押したらすぐ起動しちゃうの?間違えて起動とか考慮したら?
  • 毎日を同じ時間にしたい場合、一つずつ設定するのは面倒では?
  • 電源OFF は出来ないんだよね?(聞いてみただけみたい。)
  • この日だけ臨時で時間設定とか出来たら良いのでは?
  • プリセット登録(パターンを予め設定できる)とかどう?

企画面

  • 自作PCじゃないから使わないな。(自作PCでしか出来ない?)
  • 同じ仕組みで遠隔起動出来ると良いかもしれないけど、思いつかないね。(私も思いつかない。)

なぜ自動起動したいか(補足)

PCでTV録画しているからって理由。毎日同じ時間で起動はBIOSで設定できるけど、もう少し起動時間をフレキシブルに変更したいっていう私のわがまま。

想定イメージ

今回はWebアプリとObnizの連携部分のみの実装。LED部分をリレーと置き換え、自作PCのPowerSWと置き換えてショートさせれば電源ON出来る想定。(たぶん。)

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

【JavaScript】スコープについてまとめ

スコープってなに?

demo

プログラミングにおけるScope(スコープ)とはある変数や関数が特定の名前で参照される範囲のことを言う

要は今実行中のコードからその変数や関数が見えるか見えないか(参照できるかできないか)ということ

実際に見てみる

↓このコードを実行すると

main.js
{
  let a = 0;
  const b = function () {}
  debugger; //実行中のコード
}

demo
変数a関数bが参照できているのがわかる


↓次にこのコードを実行すると

main.js
{
  let a = 0;
  const b = function () {}
}
debugger; //実行中のコード

demo

aとbが参照できない・・・

つまり実行中のコード(今回はdebugger;)から変数や関数が見える範囲をスコープという

スコープの種類

JavaScriptには5種類のスコープが存在する。

  • グローバルスコープ

  • スクリプトスコープ

  • 関数スコープ

  • ブロックスコープ

  • モジュールスコープ

グローバルスコープ

スクリプトファイル直下varfunctionで定義するとここに入る

main.js
var a = 0;
function b() {}
debugger; //実行中のコード

スクリーンショット 2020-11-13 6.43.45.png

スクリプトスコープ

スクリプトファイル直下letconstで定義するとここに入る

main.js
let a = 0;
const b = function() {}
debugger; //実行中のコード

スクリーンショット 2020-11-13 6.58.33.png

グローバルスコープスクリプトスコープはそのファイル内であればどこからでも参照可能。

関数スコープ

名前の通り、関数の中のスコープ。

その関数内であれば変数bが参照できることがわかります。
スクリーンショット 2020-11-13 7.27.53.png

一方関数の外では参照できません。

スクリーンショット 2020-11-13 7.27.14.png

ブロックスコープ

ブロックって?
↓これです
スクリーンショット 2020-11-13 7.34.02.png
この{}の中をブロックスコープと言います。
記事の一番最初の例がブロックスコープの例となります。

if文for文の中で定義された変数もブロックスコープとなります。
スクリーンショット 2020-11-13 7.38.53.png
スクリーンショット 2020-11-13 7.40.50.png

もちろんこれらもブロック外からは変数を参照できません。

モジュールスコープ

モジュール(ESM)を使用した際にスクリプトスコープモジュールスコープに切り替わる。
inport / export を使用しないと別ファイルの変数や関数を参照できない。

【JavaScript】基本的なモジュール(ESM)の使い方についてまとめ

レキシカルスコープ

これはどのようにしてスコープが決定するかの仕様のことです。

例として、まず関数f1を定義します。
その中に変数aを定義しています。

ここで1つの関数スコープができました。

main.js
const f1 = () => {
  let a = 0;
}
f1();

さらに関数f1の中に関数f2を定義します。

ここで関数スコープの中にもう1つの関数スコープができました

変数aを出力させ、実行します。
結果として、変数aの値を取得し出力されます。

main.js
const f1 = () => {
  let a = 0;
  const f2 = () => {
    console.log(a); // => 0
  }
  f2();
}
f1();

この時実行中の関数f2外側にあるスコープから変数を参照しています。
この外側にあるスコープを外部スコープ(レキシカルスコープ)と言います。

スクリーンショット 2020-11-17 8.34.35.png

また、このようにスコープが複数階層になっている状態をスコープチェーンと言います。

スコープチェーン

スコープが複数階層で、連なっている状態のこと。

スコープチェーンでは一番内側のスコープから順番に変数を取得していく仕様です。

main.js
let a = 0;
const f1 = () => {
  let a = 1;
  const f2 = () => {
    let a = 2; //     <-これ
    console.log(a); // => 2
  }
  f2();
}
f1();
main.js
let a = 0;
const f1 = () => {
  let a = 1; //      <-これ
  const f2 = () => {
    // let a = 2;
    console.log(a); // => 1
  }
  f2();
}
f1();
main.js
let a = 0; //        <-これ
const f1 = () => {
  // let a = 1;
  const f2 = () => {
    // let a = 2;
    console.log(a); // => 0
  }
  f2();
}
f1();


以上、スコープについてでした!
ここまで見ていただきありがとうございました。

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

【Firebase】TimeStampクラスの変数をsessionStorageに入れて出すとただのObjectになる

はじめに

タイトルが全てです.
JavaScriptを使った開発中,FirebaseのFireStoreから受け取った値をsessionStorageに保存し,再利用するコードを書きました.
その際,エラーが出てかなり苦戦したので,備忘録を兼ねて記事を書きました.

出たエラー

コンソールには以下のようなエラー文が表示されました.

.toDate is not a function.

原因

sessionStorageに入れるためにTimeStampクラスのものをJSON形式にすると,出てきたものはただのObjectになるみたい.

toDate()メソッドはTimeStamp型にしか使えないので,エラーが出ていたみたいです.

対処法

原因が分かれば,対処法は色々あると思います.
自分の場合はsessionStorageに入れる前にtoDate()メソッドを用いて,JSのdate型に直してからsessionStorageに入れました.

終わりに

JavaScriptは変数の型を指定しないのでこういったエラーがよく起こりそう.気を付けねば……
とりあえず,

not a function

って出てきたときは,変数の型をチェックするのがよさそう.

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

React フックを勉強した

以下の公式ドキュメントに沿って勉強しました
https://ja.reactjs.org/docs/hooks-intro.html

フックとは

登場当初のReactはコンポーネントをclassとして書いて、値をclassのsteteやpropとして管理するというお作法でした。しかしながら、javascriptのthisの挙動が他の言語と違っていたり、ステートフルなコンポーネントの使いまわしがやりにくかったり、その結果としてコードが冗長になったり多層に積み重なってとにかく辛い、という事になっていたのを解消するべく、関数として書けるようにしたのがフックだそうです。
https://ja.reactjs.org/docs/hooks-intro.html

元々のコンポーネントやprops, state, コンテクスト, ref, ライフサイクルについてはそのまま使えるように設計されていて変わるのは書き方だけであり、フックを実装済みのコンポーネントについてはclassで書いていたものと同じものをフックでも作れるし、その方が良いよという事のようです。
https://ja.reactjs.org/docs/hooks-faq.html

1. プロジェクトの作成

npx create-react-app my-appで作った新規プロジェクトのApp.jsを書き換えてやってみます。

npx create-react-appについては以下に書いたので省略します
https://qiita.com/studio_haneya/items/539adda6df7b7c909da6

2. ステートフック

コンポーネントのstatesを使えるようにするフックです

classで書いた場合

App.jsを以下のように書き換えます。constructorでthis.stateを定義して、button onClickでthis.state.countを1つずつ増加していくという書き方でした。

my-app/src/App.js
import React from 'react';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

export default App;

カウントアップするアプリになりました
image.png

フックで書く場合

上記をフックで書き換えると以下のようになります。useState()がconstructorの代わりをしてくれる関数で、const [count, setCount] = useState(0)と書くと、countという名前のstateの初期値を0として、setCount関数により更新しますという宣言になります。この初期値の代入はApp()が最初に呼ばれた時にしか実行されず、2回目以降では既に定義済みのcountというstateの値を参照するだけで、これをuseState()が上手くやってくれるわけです。

my-app/src/App.js
import React, { useState } from 'react';

function App() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

export default App;

image.png

2. 副作用(side-effect)フック

コンポーネント内部から外部データを取得する動作をReactでは副作用(side-effect)または短く作用(effect)と呼んでいるようです。

2-1. クリーンアップが不要な場合

例えば先程の例のようにbutton onClickで増えていくカウントを、本文中だけでなくサイトタイトルにも書き出したい場合、render()される度にdocument.titleを更新したくなります。
image.png

クラスで書く場合

これをクラスで書く場合、render()の度に呼ばれるクラスコンポーネントは存在しない為、以下のようにcomponentDidMount()とcomponentDidUpdate()に同じ内容を書くことになります。

my-app/src/App.js
import React from 'react';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }
  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

export default App;

副作用フックで書く場合

react.useEffect()内に書くことで副作用を仕込むことができます。useEffect()はカスタマイズしない限り初回のrender()時と更新がかかって再render()するときに実行されるので、更新がかかったらついでに何かをしたいときにうってつけのやり方になります。

my-app/src/App.js
import React, { useState, useEffect } from 'react';

function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

export default App;

2-2. クリーンナップが必要な場合

外部データを読みに行っているような場合はメモリリークが起こらない安全なやり方としてクリーンアップするやり方が推奨されています。これをフックで書く場合はクリーンアップする為の関数をuseEffect()の返り値にすると自動的に処理してくれます。

以下ではChatAPIという外部データ取得用のクラスが既に書かれている場合に、ChatAPI.subscribeToFriendStatus()で取得した値をChatAPI.unsubscribeFromFriendStatus()によりクリーンアップしています。

my-app/src/App.js
import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

副作用が実行される度にクリーンアップも実行される為、render()される度に実行されることになり、パフォーマンス上の問題が生じる可能性があります。その場合には、更新した場合に副作用の実行が必要な値を監視し、値の変化がない場合は副作用を実行しない、という形で負荷を低減することが出来ます。
https://ja.reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

以下ではcountが更新されていない場合に副作用が実行されないようにしています

my-app/src/App.js
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

3. その他フックAPI

useState、useEffect以外にもいろいろなフックAPIが用意されています
https://ja.reactjs.org/docs/hooks-reference.html

基本のフック

  • useState
  • useEffect
  • useContext

追加のフック

  • useReducer
  • useCallback
  • useMemo
  • useRef
  • useImperativeHandle
  • useLayoutEffect
  • useDebugValue

4. フックのルール

Reactフックが正常に機能する為には以下の2つのルールがあります
https://ja.reactjs.org/docs/hooks-rules.html

4-1. Reactの関数のトップレベルから呼ぶ

ループや条件分岐、あるいはネストされた関数内で呼び出してはいけない

4-2. Reactの関数から呼ぶ

通常のJavascript関数から呼んではいけない
(Reactの関数から呼び出すか、カスタムフックから呼ぶ)

eslint

上記をチェックできるようにしたeslintプラグインが公開されていて、create-react-appすると自動で適用されるようになっているようです。
https://ja.reactjs.org/docs/hooks-rules.html

5. カスタムフック

2-2ではAPIからユーザー情報を取得してOnlineなのかOfflineを返す関数を書いていましたが、これを1つのフックとしておいて使い回すことが出来ます。

import { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

フックだと分かるようにuseStateやuseEffectのように、useから始まるカスタムフック名を設定するように推奨されています。
https://ja.reactjs.org/docs/hooks-custom.html

以下のように書けば同じロジックを複数のコンポーネントから使うことが出来ます。

function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

6. まとめ

React用のライブラリのスニペットがフックで書かれていたり、フックに慣れてないとReactを使っていくのに支障が出るようになりつつあるようですので頑張って覚えていきましょう。

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

湯婆婆言語作ってみた

湯婆婆言語作ってみた

Qiitaで湯婆婆のセリフをいかに短く実装するかブームらしいので自作言語を作って
実装した話です
ソースコードはここにのってます
https://github.com/riya81/yuba

うぷ主の環境

  • macOS Big Sur 11.0.1
  • Node 12.16.3

手順
1. 自作言語の構文を考える
2. トランスパイル先の言語を決める
3. ファイルを読みこむ
4. トランスパイラを作る
5. トランスパイラをビルドする

手順は僕の他の記事に詳しく書かれているので省きます
この記事のソースコードを少し変えただけです
https://qiita.com/riya81/items/b1d5261205d13e4740cb

コマンド

y "名前";

これだけで湯婆婆を実装できます
この機能しかないです

まとめ

まとめることなんかほぼ無いうっすい記事ですが以上
湯婆婆言語を作った話でした

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

でかいデータのAjaxリクエストを圧縮して軽くしてPHPで受け取る

数MBくらいあるデータをリクエストボディに載せたいけど、少しは軽くしたいのでゴニョゴニョ。

JS側

import pako from 'pako'
import axios from 'axios'

const bigObject = { /* 超でかいObject */ }
const json = JSON.stringify(bigObject)
const gzipped = pako.gzip(json, { to: 'string' })
const base64Encoded = btoa(gzipped)
axios.post('/api/foo', { compressed: base64Encoded })
  • JSONにする
  • pako を使ってgzip圧縮する
  • btoa() を使ってBase64エンコードする
  • 文字列としてリクエストする

PHP側

$gzipBinary = base64_decode($_POST['compressed']);
$json = gzdecode($gzipBinary);
$data = json_decode($json, true);

余計な処理は入るけど、リクエストボディが 1.5MB から 250KB くらいまでは小さくなったので満足。

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

【3分でわかる】Reactの強みを初学者目線で解説

Reactってなんだよ

javascriptの基礎を勉強し終えた「駆け出しエンジニア」がよく目にするjsフレームワークとして
"react","vue","Angular"...etc
などあるとおもいます。

でも結局のところ、「react,vue,Angular」って何ができるの?って思いますよね
(僕も今回の記事を書くまで人に説明できないレベルでした、、)

今回はそのjsフレームワークの中のReactについて説明したいと思います。

Reactの特徴3つ

1.リアクティブ!
「リアクティブ」は日本語に直すと「反応的な」という意味合いがあります。
その通りで、Reactで値を変化するとすぐにそれが画面上に表示されます。
今までは、

①エンジニアが値を変更する。→②表示の内容を変更させなければならない。
といった手順が必要でした。

そのReactによってその手間が省けます。
このことを仮想DOMなどと言われたりしています。

一応仮想DOMって何?っていう人は下記の記事をご覧ください。



Reactの仮想DOMとは何か?【仕組みをわかりやすく説明します】

2.大規模なアプリケーション開発に向いている

大規模のアプリケーションを運用するにあたって最も大事なところは運用です。

ページ数が膨大になればなるほど、機能の共通化を図らなければ、膨大な時間をテストや検証にあてなければなりません。

・管理しやすい設計
・カスタマイズしやすい
・複雑な機能に対応できる

という点で大規模開発に向いているといえます。
なのでより大きな会社で働きたいエンジニアにはReactはもってこいだと思います。

3.めちゃくちゃお得(一度かければ汎用性が高いので)
react.jsは一度習得すれば、Webアプリケーションだけではなく

・ネイティブアプリ(Android & iOSが一度にかける)
・VR(React VR)

といった
他のアプリケーションにReact.jsで学んだ事を転移できるので自分の領域が広がります。

ちなみに、僕のQiita処女作です

最後まで読んでいただいて本当にありがとうございます。

現在ぼくは関西の私立大学の文理融合学部に在籍しています。
Qiitaをやり始めたきっかけは、好奇心です。
アウトプットしたらエンジニアとして成長できるのかな?という疑問符の元はじめました。

「自分のアウトプットがが誰かのためになればなあ」ときれいごとながら思っています(笑)

また、自分が学んだことをアウトプットしていく中で
関西地方の大学生エンジニアや社会人エンジニアと仲良くなったりしていきたいなと思っています。

よろしければコメントよろしくお願いします!

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

【ES6】変数,定数,関数の基礎をさらう。

参考

この記事はトラハックさんのYouTube動画(https://www.youtube.com/channel/UC-bOAxx-YOsviSmqh8COR0w)のアウトプット記事です。

とても参考になるので、大変助かっています?‍♂️

本題

ES6は何が起きたのか

ES6では多くの機能が追加されました。 下記はES6で追加された機能,構文の例です

  • let,const
  • アロー関数
  • 分割代入
  • class構文

などなど。簡単に言うとES6では便利な機能や構文が追加されたので、保守性が高く、可読性の高いコードを表現することができます。

今回は「変数」「定数」「関数」に絞って解説していきます!

変数と定数

これまでは変数を宣言するにはvarを使っていました。しかし、ES6以後でletという新たな変数宣言ができるようになりました。letを使うことで、varよりも保守性が高まります。

letを使うことで、無駄にスコープを汚染することなく宣言,代入することができます。下記はサンプルコードです。

const funcScope = (scope) => {
  if (scope === 'function'){
    var hoge = 'varで宣言しました'
  } else if (scope === 'block') {
    let foo = 'letで宣言しました'
  }
  console.log(hoge)
}

funcScope('function');
// => "varで宣言しました"

上記のようにvarで宣言した場合は、ブロックを超えて参照することができます。ではletの場合を見てみます。

const funcScope = (scope) => {
  if (scope === 'function'){
    var hoge = 'varで宣言しました'
  } else if (scope === 'block') {
    let foo = 'letで宣言しました'
  }
  console.log(foo)
}

funcScope('block');
// Uncaught ReferenceError: foo is not defined 

エラーが出てしまいましたね。

letはブロック({})内でしか参照することができないという特徴があります。ではでは、次にconstです。

constは定数を宣言するのに使われます。下記にサンプルコートを書きます。

const hoge = '定数です。'

再代入しようとしてみます。

const hoge = '定数です。'
hoge = '代入しようとしてみる'
// => Uncaught TypeError: Assignment to constant variable.

ちゃんと再代入できませんね。しかし、注意点が一つありまして、「配列」や「オブジェクト」の中身は変更することができます。下記のサンプルコード書きます。

const ary = [1,2,3,4]
ary.push(5)
console.log(ary)
// => [1, 2, 3, 4, 5]

ちゃんと配列に追加できていますね。次は、オブジェクトの場合です。

const obj = {
  name: 'おにかん',
  age: 20
}

obj.height = 170

console.log(obj)
// Object {
//  age: 20,
//  height: 170,
//  name: "おにかん"
// }

ちゃんと変更できていますね!

アロー関数の登場

アロー関数というのが登場しました。サンプルコードを下記に書いておきます。

const funcArrow = (text) => {
  return text
}

単純に受けとった値を返すアロー関数です。上記ように一行なら{}を省略して式を書くことができます。式なので return は書きません。

const funcArrow = (text) => text

さらに、引数がひとつであれば引数の括弧も省略できます。

const funcArrow = text => text

以上、簡単にですがまとめてみました。

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

Material UIでラジオボタンをクリックしても選択されない

問題

{JSON.WorkSheets.map((v, i) => (
   <FormControlLabel
     key={v.ID}
     //type int
     value={v.ID}
     control={<Radio />}
     label={v.Text}
   />
))}

JSON内のWorkSheetsという配列をmapで回して動的にラジオボタンを作っている感じですね。
しかし、これだとラジオボタンを選択しても実際に選択されることはありません。

解決方法

{JSON.WorkSheets.map((v, i) => (
   <FormControlLabel
     key={v.ID}
     //int to string
     value={v.ID.toString()}
     control={<Radio />}
     label={v.Text}
   />
))}

stringでないといけませんね。

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

【JavaScriptの超基本】ファイルのインポートやエクスポートについて簡単に解説

概要

この記事では、JavaScriptのファイルのインポートやエクスポートについて、超基本的な知識をメモ的にまとめています。
自分用の備忘録なのであしからず。

今回使うコードの詳細はこの記事では、説明しません。
コードの詳細は以下の記事で解説しているので、興味があればご覧ください。

【JavaScriptの超基本】クラスの定義やメソッド・継承について簡単に解説

目次

ファイルの分割

コードが増えてくると1つのファイルで管理するのが大変になるため、複数のファイルに分けて管理します。
ファイルを分けて管理することでバグを見つけやすくなるというメリットもあります。

今回は、こちらの記事で作成したコードを分割して管理するようにしていきます。

まず、それぞれのクラスを定義するファイルとメインのプログラムを実行するファイルに分けます。

script.js
const forward = new Forward('Messi', 10);
forward.introduce();
console.log(forward);
barcelona.js
class Barcelona {
    constructor(name, uniformNumber) {
        this.name = name;
        this.uniformNumber = uniformNumber;
    }
    introduce() {
        console.log(`FC Barcelonaの背番号${this.uniformNumber}${this.name}です`);
    }
}
forward.js
class Forward extends Barcelona {
    constructor(name, uniformNumber) {
        super(name, uniformNumber);
        this.position = 'FW'
    }
    introduce() {
        console.log(`FC Barcelonaの背番号${this.uniformNumber}${this.name}です`);
        this.introducePosition();
    }
    introducePosition() {
        console.log(`ポジションは${this.position}です`);
    }
}

このような感じでファイルを分割しました。
ファイルを分割しただけだとエラーが起きてしまうので、それぞれのファイルを関連づけるためにファイルのエクスポートとインポートをしていきます。

※注意
ファイルをインポートしたりエクスポートしたりするには、ファイルをモジュール化する必要があります。今回詳しくは解説しませんが、JavaScriptを書いているファイルがあるフォルダ内に次のファイルを追加してくだい。

package.json
{
    "type": "module"
}

エクスポート

クラスや関数などを他のファイルで使うためには、まずそれらを定義した後に、そのファイルからエクスポート(出力)しないといけません。

エクスポートは、大きく「名前付き」と「デフォルト」の2種類に分類されます。

それぞれ見ていきましょう!

名前付きエクスポート

ファイル内で定義してあるクラスや関数、定数などはexport { 〇〇 }とすることでエクスポートすることができます。これを「名前付きエクスポート」と言います。

名前付きエクスポートでは、複数の値のエクスポートが可能です。
その場合、export { 〇〇, ×× }のように,で区切って書きましょう。

example.js
//定義
const 定数名 = ... ;
let 変数名 =  ... ;
function 関数名() { ... };
class クラス名 { ... };

//事前に定義された機能をエクスポート
export { 定数名 };

//複数の機能をエクスポート
export { 変数名, 関数名, クラス名 };

また、それぞれの機能は定義する際に個別に定義することもできます。

example.js
//定義と同時にエクスポート
export const 定数名 = ... ;
export function 関数名() { ... }

デフォルトエクスポート

もう一つのエクスポートの方法が「デフォルトエクスポート」です。
クラスや関数を定義した後にexport default 〇〇と書くことで、他のファイルでも使えるようにエクスポートできます。

example.js
//事前に定義された定数をデフォルトエクスポート
export default 定数名;

//事前に定義された関数をデフォルトエクスポート
export default 関数名;

デフォルトエクスポートは、名前付きエクスポートと同様に定義と同時にエクスポートすることも可能です。

example.js
//定義と同時にエクスポート
export default const 定数名 = ... ;

では、Barcelonaクラスをデフォルトエクスポートしてみます。

barcelona.js
//クラスを定義
class Barcelona {
    :
    :
}

//Barcelonaクラスをエクスポート
export default Barcelona;

エクスポートすることでBarcelonaクラスを他のファイルに渡す準備が整いました。

名前付きエクスポートとデフォルトエクスポートの違い

名前付きエクスポートは、エクスポートできるファイルの数に制限はありませんが、デフォルトエクスポートは、1ファイルにつき1つの値のみ使えます。

これはデフォルトエクスポートの性質に関連しています。
デフォルトエスクポートされたファイルをインポートするとexport default 〇〇と書いた〇〇の値が自動的にインポートされるという性質を持っています。

この性質から、エクスポート時の値とインポート時の値は、名前が異なっていても問題ありません

インポート

エクスポートされた値をインポートするにはimport { 〇〇 } form “ファイルへのパス”と書きます。

複数の値をインポートする際は、エクスポート時と同様に{ }の中の値を,で区切って書きます。

example.js
//値のインポート
import {定数名} from "ファイルへのパス";

//複数の値のインポート
import {変数名, 関数名, クラス名} from "ファイルへのパス";

名前付きでエクスポートされた値は、このようにしてインポートします。

ファイル名は、基本的に相対パスで書きます。相対パスについては後ほど解説します。

別名インポート

エクスポートした値を別名(エイリアス)でインポートすることもできます。

その場合には、以下のように書きます。

example.js
//別名インポート
import { 〇〇 as エイリアス } form ファイルへのパス

別名でインポートした値を使いたい場合は、エイリアスを用いて使うことができます。

また、*を用いると任意のファイルでエクスポートした全ての値をインポートすることができます。
その場合は、as モジュール名としてインポートします。

構文は以下の通りです。

example.js
import { * as モジュール名 } form ファイルへのパス

インポートした値を使いたい場合は、モジュール名.〇〇のように書くことでインポートしたファイルでも使うことができます。

デフォルトエクスポートされた値のインポート

デフォルトエクスポートされた値を読み込む際は、import 〇〇 from “./ファイル名”とします。名前付きインポートとは異なり{ }が必要ありません。

先ほど説明したように、デフォルトエクスポートされた値の場合は、エクスポート時の名前とインポート時の名前は異なっていても問題ありません。(例えば、エクスポートしたファイルでは〇〇 という名前の値だが、インポートするファイルでは△△という名前にする)

先ほど分割したファイルに、エクスポートとインポートのコードを追加します。Barcelonaクラスはデフォルトエクスポート、Forwardクラスは名前付きエクスポートし、それぞれインポートします。

BarcelonaクラスはBarcaという名前でインポートします。その際、Barcelonaというクラス名はインポートしたファイルでは使えないので、クラス名をBarcaに書き換えます。

script.js
//Forwardクラスをインポート
import {Forward} from "./forward.js";

const forward = new Forward('Messi', 10);
forward.introduce();
console.log(forward);
barcelona.js
class Barcelona {
    constructor(name, uniformNumber) {
        this.name = name;
        this.uniformNumber = uniformNumber;
    }
    introduce() {
        console.log(`FC Barcelonaの背番号${this.uniformNumber}${this.name}です`);
    }
}

//Barcelonaクラスをデフォルトエクスポート
export default Barcelona;
forward.js
//BarcelonaクラスをBarcaという名前でインポート
import Barca from "./barcelona.js";

//クラス名をBarcaに書き換える
class Forward extends Barca {
    constructor(name, uniformNumber) {
        super(name, uniformNumber);
        this.position = 'FW'
    }
    introduce() {
        console.log(`FC Barcelonaの背番号${this.uniformNumber}${this.name}です`);
        this.introducePosition();
    }
    introducePosition() {
        console.log(`ポジションは${this.position}です`);
    }
}

//Forwardクラスを名前付きエクスポート
export {Forward};

この状態で「script.js」ファイルを実行すると正常に処理が実行されます。

相対パス

ファイル名の記述には、通常「相対パス」を用います。
相対パスでは、記述されたファイルから見た位置関係でファイル名を示します。

これまで書いてきたファイル名の前についている./は相対パスが書かれているファイルと同じディレクトリを意味します。

以下のようなフォルダ階層になっているとします。

test
├── script.js
└── class
    ├── barcelona.js
    └── forward.js

script.jsbarcelona.jsを読み込みたいときは./class/barcelona.jsと書きます。

一つ上のディレクトリに移動するには../と書くことで指定することができます。
barcelona.jsscript.jsを読み込みたいときは../script.jsと書きます。

まとめ

ファイルの分割は、実際にコードを書く際には非常に大事なのでエクスポートやインポートをしっかり理解しましょう。

最後まで読んでいただいてありがとうございます。では。

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

IE11でも動くワンライナー湯婆婆を作り、IE11対応のつらさを再認識する

何番煎じかわからないネタをやります。

Q.なぜこんなことを?

流行りの湯婆婆にのっかりたかったんです。

Q.JSはみんなやってるよね?

はい、しかもすげーLTGMも稼いでるのでもう勝てないなって。
なのでIE11でも動くのをやってみようと思いました。

Q.IE11って需要あるの?

未だにIE対応が必要だと言われることがあるので、あと数年は需要があるかもしれない。
でも個人的には早く滅びてほしいと思っていますね(笑)

というわけでコード

JSのコード自体はワンライナーの体を保っている・・・はず。

湯婆婆.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>湯婆場</title>
    <script>
    (function(f){alert((function(f,n){return "フン。"+f+"というのかい。贅沢な名だねぇ。今からお前の名前は"+n+"だ。いいかい、"+n+"だよ。分かったら返事をするんだ、"+n+"!!"})(f,f[Date.now()%f.length]))})(window.prompt("契約書だよ。そこに名前を書きな。",""))
    </script>
</head>
</html>

ポイント

まず、一行で書いてしまったJSコードを一旦整形しましょう。

(function (f) { 
  alert(
    (function (f, n) { 
       return "フン。" + f + "というのかい。贅沢な名だねぇ。今からお前の名前は"
              + n + "だ。いいかい、" + n + "だよ。分かったら返事をするんだ、" + n + "!!" 
    })(f, f[Date.now() % f.length])
  )
})(window.prompt("契約書だよ。そこに名前を書きな。", ""))

真面目な書き方をするとこうなる

ワンライナーにするためにクソコードにしてしまった。
そしてこっちのほうが文字数が少ないとかいう問題にも目をつむってほしい。

// プロンプトで文字入力
const f = window.prompt("契約書だよ。そこに名前を書きな。", "");
// 時間使って疑似ランダム
const n = f[Date.now() % f.length];
// 結果表示
alert("フン。" + f + "というのかい。贅沢な名だねぇ。今からお前の名前は"
              + n + "だ。いいかい、" + n + "だよ。分かったら返事をするんだ、" + n + "!!" );

関数を作ってそれを即時実行

JSの関数を作って実行する場合には、だいたい以下のようにします。

function multiple(n) {
  return n * n;
}
const nn = multiple(8);

それを、以下のように書くことで即時に実行することができます。

const nn = (function(n) {
  return n * n;
})(8);

// ES2015だとここまで短縮はできる
const nn = ((n)=>n*n))(n);

正直、普通に書いたらいいじゃん、と思うでしょう。
本来はjQueryプラグインなんかを作るために多用していたテクニックです。

(function($){
  $.fn.myPlugin = function() {
    // ここにプラグインの中身を記載
  }
})(jQuyery);

こうすることで、window.onloadよりも前のタイミングでjQueryプラグインのロードを済ませつつ、
カプセル化を(一応)実現できるようになるのです。

要はclass構文がなかった頃のレガシーコードなので、使ってドヤらないように注意しましょう。

IE11では使えないJSの機能たち

JavaScriptにはES2015という規格があり、いろいろと新機能が追加されました。
しかしながら、IE11は中途半端に対応しているため動かない書き方が多数存在します。

アロー関数

アロー関数とは、関数定義を簡略化して記載するための記法です。
簡易的な計算を記載したり、コールバック関数としての記載をする場合によく使用します。

function(n) {
  return n * n;
}
// 下のように書ける
(n) => n * n;

なお、厳密にはthisの扱いが変わったりしますので、
なんでもかんでもアロー関数にしたらいいというわけではないので注意してください。

テンプレート文字列

テンプレート文字列は、CやJavaでのString.formatに相当する機能で、
文字列の中に変数を定義してテンプレートとして定義できます。

const name = "太郎";

let message = "こんにちは、" + name + "さん";

// これを以下のように書ける
let message = `こんにちは、${name}さん`;

ちなみに、アロー関数とテンプレート文字列を使うだけでも、ここまですっきりします。
(え、すっきりしない?)

((f) => alert(((f,n)=>`フン。${f}というのかい。贅沢な名だねぇ。今からお前の名前は${n}だ。いいかい、${n}だよ。分かったら返事をするんだ、${n}!!`)(f,f[Date.now()%f.length])))(window.prompt("契約書だよ。そこに名前を書きな。",""))

さいごに

短さや可読性は先人の方のほうが上ですので、真面目にJSの勉強をしたい方はそちらを参照しましょう。

IE11の対応を予定していない皆様はこの記事は幻のエンディングだと思って忘れるとよいでしょう。

PS追記

こんなんあったんか・・・
先走ってしまったので皆さんはきをつけてね。
https://qiita.com/advent-calendar/2020/yubaba

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

Vue・Reactを超える!? 近年話題の Svelte に入門してみよう!

はじめに

今回は最近ちょいちょい使われているフロントエンドフレームワーク Svelte.jsについて解説します1
最近だと、ロイターの大統領選挙ページで使われてましたね。

Svelte.jsは速くて軽く、簡単に実装できるフレームワークです。

どうやら、React, Vue, Angular, Emberよりも速いらしいです!
https://svelte.dev/blog/frameworks-without-the-framework#Introducing_Svelte

Tutorialも充実しており、サイト上で実際に書きながら学ぶこともできます。

本記事はTutorialを触る前に、ざっと概要を学びたい方向けに書いてます!

YouTube動画

動画で確認したい方はこちらもどうぞ。
【YouTube動画】 VueやReactを超える!? 次世代のフロントエンドフレームワーク Svelte.jsをご紹介!!
VueやReactを超える!? 次世代のフロントエンドフレームワーク Svelte.jsをご紹介!!

基本の書き方

それでは基本の書き方をみていきます。
Tutorialを始める前に、さらっと読んでおくと理解が捗ると思います!

変数の使い方

svelte.jsでは、scriptタグ内でletを定義するだけで変数として利用できます。

<!-- svelte.js -->
<script>
  let name = 'World';
</script>

<h1> Hello, { name } !! </h1>

Vue.jsで同じように出力しようとすると、以下のようになります。
svelteと比較して、記述量が多めです。

<!-- vue.js -->
<template>
  <h1> Hello, {{ name }} !! </h1>
</template>

<script>
export default {
  data() {
    return {
      name: "World"
    }
  } 
}
</script>

属性の使い方

属性の書き方も簡単です!
vueで画像を表示しようとすると次のようになります。

<!-- vue.js -->
<template>
  <img :src="image" alt='yassun youtube' />
</template>

<script>
export default {
  data() {
    return {
      image: "https://img.youtube.com/vi/rbhMST4A1Hs/0.jpg",
    };
  },
};
</script>

svelteだともっと簡単に書くことができます。
ちなみに、altを省略すると、A11y (Accessibility) の警告が出てきます。

<!-- svelte.js -->
<script>
  let src="https://img.youtube.com/vi/rbhMST4A1Hs/0.jpg"
</script>


<main>
  <img {src} alt='yassun youtube'>
</main>

styleの書き方

styleに直接記述するだけで、スタイルを当てることができます。
また、デフォルトでスコープがコンポーネント内になるため、クラス名の競合を意識しなくて大丈夫です。

<!-- svelte.js -->
<style>
  p {
    color: purple;
  }
</style>

<p>テスト!</p>

他コンポーネントの使い方

インポートするだけで使えます。

<script>
  import Nested from './Nested.svelte'
</script>

<Nested />

Reactive

on:イベントでイベント発火条件を書き、scriptタグ内のメソッドを呼び出すだけで、メソッドが使えます。
vueのようにmethodsを定義する必要がありません。

<script>
  let count = 0;

  function handleClick() {
    count += 1;
  }
</script>

<button on:click={handleClick}>{ count }</button>

preventDefaultやstopPropagationの追加もパイプで繋ぐだけで、簡単にできます。

<script>
  function handleForm() {
    console.log('handleForm');
  }
</script>

<form on:submit|preventDefault={handleForm}>
  <button>ボタン</button>
</form>

ある変数と連動して別のコードが動いて欲しい場合には、$:を使います。
例えば、scriptタグに以下のように書くと、countが変化した時にコンソールにログが流れます。

let count = 0;

$: console.log(`count: ${count}`);

変化するコードをグルーピングしたい場合は{}で括ります。

let count = 0;

$: {
  console.log('hello');
  console.log(`count: ${count}`);
}

ifで別途、コードが連動する条件を指定することもできます。
以下の場合は、countが2以上になったら、動きます。

let count = 0;

$: if (count >= 2) {
  console.log(`count: ${count}`);
}

props

propsはscript内でexportするだけで、別のコンポーネントで使えるようになります。
exportする際にデフォルト値を指定することもできます。

<!-- Child.svelte -->
<script>
 export let answer;
</script>

<p>{ answer }</p>
<script>
  import Child from './Child.svelte'
</script>

<Child answer={10} />

slot

slotはこんな感じで入れます。

Comp.svelte

<div>
  <slot></slot>
</div>

App.svelte

<script>
  improt Comp from './Comp.svelte'
</script>

<Comp>
  <div>スロット</div>
</Comp>

if

if文はこのように書きます。
/ifでif文の終わりを決めます。

{#if count > 10 }
  <p>Big!!</p>
{:else if count > 5 }
  <p>Medium</p>
{:else}
  <p>Small</p>
{/if}

await

awaitはif文と似たような感じで書けます。

{#await promise}
  <p>waiting...</p>
{:then number}
  <p>number is {number}</p>
{:catch error}
  <p>{error.message}</p>
{/await}

loop (each)

for文に慣れていると間違えそうですが、cats as catというように、各要素はasの右側に書きます。

{#each cats as cat, i}
  <div>{i}-th cat is {cat}</div>
{/each}

bind

bindはvueっぽく書けます。

<input bind:value={name}>

Lifecycle

ライフサイクルのメソッドは以下のように、svelteからインポートして使います。
onMount, onDestroy, beforeUpdate, afterUpdate, tickなどが用意されています。

<script>
  import { onMount } from 'svelte'

  let test = '';

  onMount(async () => {
    test = await somePromise();
  })
</script>

<p>{ test }</p>

Store

storeもsvelteに入っているため、以下のようにして使うことができます。
writable(0)をセットすることで、値のupdate, set, subscribeが使えるようになります。

他にもsvelte/storeには、読み込み専用のreadableや別のstoreを使って新しくstoreを作るderivedなどがあります。

import { writable } from 'svelte/store';

export const test = writable(0);

test.update(n => n + 1)
test.set(x)
test.subscribe(value => {})

{ $test }

以下のようにメソッドを制限したカスタムstoreを作成することもできます。
ここら辺の詳しい話もTutorialに詳細があるので、ぜひ確認してみてください。

function createCount() {
  // writable(0)をセットし、メソッドを呼び出す
  const { subscribe, set, update } = writable(0);

  return {
    subscribe,
    // 値を1だけ増やすメソッドを設定
    increment: () => update(n => n + 1),
    // 値を1だけ減らすメソッドを設定
    decrement: () => update(n => n - 1),
    reset: () => set(0)
  };
}

おわりに

Svelte.jsを試してみたくなりましたか?
簡単に始めることができるので、ぜひ試してみてください!

Tutorialはこちら
https://svelte.dev/tutorial/basics

自由に触りたい方は以下の方法もオススメです!
Svelteの公式ページでREPLを試す。
https://svelte.dev/repl

CodeSandboxのSvelte.jsのテンプレートを試す。
https://codesandbox.io/dashboard/home

npxでローカルにインストールして試す。
https://svelte.dev/blog/the-easiest-way-to-get-started


  1. 公式サイトでもフレームワークと名乗っているので、フレームワークという語を使います。https://svelte.dev/blog/frameworks-without-the-framework#Introducing_Svelte 

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

【JavaScript】Mapオブジェクト(値の更新)

Mapオブジェクトを初めて使ってみたので、備忘録として簡単に残します。

Mapオブジェクトとは

ES2015(ES6)から導入された、キーと値の組み合わせを保持することができるオブジェクト。

今回のコード

KillerQueen.js
const sutandoTsukai = ["Jyosuke", "Okuyasu"]
const fromKira = ["Jyosuke 2", "Okuyasu 6", "Okuyasu 3", "Jyosuke 4"]
const dict = new Map()

for (let i = 0; i < sutandoTsukai.length; i++) {
    dict.set(sutandoTsukai[i], 0)
}

for (let j = 0; j < fromKira.length ; j++) {
    const target = fromKira[j].split(" ")[0]
    const damage = Number(fromKira[j].split(" ")[1])

    if (dict.get(target)) {
      dict.set(target, dict.get(target) + damage)
    } else {
      dict.set(target, damage)
    }
}

console.log(dict.get(sutandoTsukai[0]))
console.log(dict.get(sutandoTsukai[1]))

ジョジョみあふれるコードです。

これは吉良吉影 VS 東方仗助&虹村億泰戦の際、四部ボス・吉良に受けたダメージについて
人ごと(スタンド使いごと)にデータを持たせて値を更新⇒最後に表示って流れですね。

配列fromKiraの各値は"名前、ダメージ量"を表しています。

流れを簡単に説明

Mapオブジェクトdictを作成(中身はまだ空)

const dict = new Map()

dictの値として配列sutandoTsukaiの各値(=各キャラの名前)、ダメージの初期値0をセット

for (let i = 0; i < sutandoTsukai.length; i++) {
    dict.set(sutandoTsukai[i], 0)
}

配列fromKiraについて、まずはキャラの名前・ダメージ量を変数に振り分ける

  • 文字列.split(" ")により、スペースを区切り文字として文字列の配列に分割する
  • キャラの名前:target/ダメージ量:damageにそれぞれ格納
    (damageは計算するため数値として扱う=Number()を忘れずに)
for (let j = 0; j < fromKira.length ; j++) {
    const target = fromKira[j].split(" ")[0]
    const damage = Number(fromKira[j].split(" ")[1])

    if (//下記で説明
    }
}

fromKiraの各値を元にdictの値を更新
(吉良の攻撃ごとに、攻撃されたスタンド使いのダメージを増やすッ!)

    if (dict.get(target)) {
      dict.set(target, dict.get(target) + damage)
    } else {
      dict.set(target, damage)
    }

① まずdictオブジェクト内のtargetの値を確認
 存在する場合はTrue、0の場合はFalse
⇒ 最初はJyosukeOkuyasuも初期値0なのでfalseになる

② Falseの場合:elseに入りtargetの値を今回のdamageに更新(set)
⇒ {Jyosuke=> 0}だったのが{Jyosuke=> 2}に変わる

③ Trueの場合:targetの値について、元の値+今回のdamageを足したものへ更新(set)
⇒ {Jyosuke=> 2}にdamage4をプラスして{Jyosuke=> 6}に変わる

※ dict.set内のdict.get(target)にて、元の値を取得(get)しています。

console.log(dict.get(sutandoTsukai[0]))
console.log(dict.get(sutandoTsukai[1]))

あとは、各スタンド使いごとの更新後のダメージ値を表示するだけ。

どうでしょう
なんとなくイメージできたのでは?:blush:


将来的にはこのコードの中に、「クレイジーダイヤモンドが億泰の傷を治す」アクションとか入れたいッスね。
スタンド使いを増やして遊んでみてもいいかも。(康一くんとか、露伴先生とか)

(ただクレイジーダイヤモンドは本人(仗助くん)の傷は治せないんだ・・・なんて優しいかっこいい・・・。ロジックはちとややこしくなる!?w)

おまけ情報

配列に変換

Mapオブジェクトを配列に変換したい場合は、Array.fromで簡単にできるようです。
sortしたい時とかよさそう。

const arr = Array.from(dict)
console.log(arr)
// [[Jyosuke, 6], [Okuyasu, 9]]

forEach()を使う時

Mapオブジェクトに対してforEachで繰り返し処理を行う時、
引数の順は(key, value)ではなく(value, key)とするようです。
(完全にkey,valueの順だと思っててつまづいた・・・)

その他色々やり方があるみたいなので、詳しくは下記を。

MDN - Map.prototype.forEach()


※ ちなみに、Mapオブジェクトはそれ自体がイテレータなので
 for of 構文でループさせることができるようです。

JavaScriptで連想配列を利用する際にObjectではなくMapを使うメリット

あとがき

mapオブジェクト、グレートですよこいつはァ!
ジョジョ四部の仗助くん&億泰コンビが好きすぎてつい書いてしまいました。

ネタが全く分からなかった人はジョジョ四部原作、もしくはアニメをお勧めいたします。
今回も読んでいただき、ありがとうございましたァン!:laughing:

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

[VUE]SPAウェブフロントエンド開発ためのまとめ

原本:KO-DEV-JP, [VUE]SPAウェブフロントエンド開発ためのまとめ

目標

本ポスティングではSPASingle Page Application開発に先立ってSPAについての理解およびJavaScriptのフレームワーク「Vue」に対する基本的な理解を目指したいと思います。MPAウェブサービスやMVCパターンに対する基本的な理解を元に作成することになりました。



目次

  1. JavaScript Frameworkの使用背景
  2. MPAからSPAまで
  3. SPAルーティングに対して
    ①Link方式
    ②Ajax方式
    ③Hash方式
  4. JavaScript Frameworkの主な活用の要素
  5. VUE
  6. 主要ライブラリ
    ①VUE Router
    ②VUE CLI
    ③Vuex
     (1)Stateとは
     (2)State管理の必要性
     (3)構成要素
     (4)State、Getters、Actions、Mutaitions
     (5)レンダリングパフォーマンスについて
  7. まとめ




1. JavaScript Frameworkの使用背景



JavaScriptのフレームワークが登場する前には、vanilla JSやjQuery*1をフロントロジックで活用して具現しました。JavaScriptフレームワークはフロントエンドの開発が複雑化によって登場することになりました。

JavaScriptフレームワークを活用すると、SPA(Single Page Application)*2など高級ウェブアプリケーションを開発するのにもっと容易します。

*1 jQuery:簡単にDOMを操作するためのライブラリ
*2SPA:単一ページのアプリケーション(Single Page Application)の形でウェブを開発することを意味、詳しい内容は2番を参考



2. MPAからSPAまで



既存のMPA(multiple-page application)ウェブサービスは、クライアントのリクエストrequestがある際ごとにサーバからリソースresourceとデータを受け取って、画面に新たにレンダリングrenderingをするやり方でありました。 リンク<a href="#">をクリックすると、該当ページに移動することになり、明示されている資源をサーバーに要請して回答を受けてきます。
このように、MPA方式は各ページごとにサーバ側でhtml文書を要請するために重複されるデータを受け取るしかありませんでした。

一方、SPAウェブサービスは、上記のような既存のウェブサービスの限界を補完します。 最初のロード時、全体ページをロードした後からは、既存のページと比較して更新が必要な特定の部分だけをjsonの形でデータを持ってきてバインディングbindingします。
したがって、SPA方式はAjax(3番参照)にデータを持って来てから必要な部分だけ更新するようにJavascriptを作成します。



3. SPAルーティングに対して



*ルーティング(Routing)とは。
データを送ることが最適の経路を選択する過程で、与えられたデータを最適化された形でやり取りできる経路を選択する過程です。

①リンク方式(伝統的方式)

リンク方式は下記の過程を含んでいます。

(1) link tag(<a href="#">)のクリック
(2) URLのpathにhref属性attribute値であるリソースの経路が追加
(3) ブラウザのアドレスバーにpath値を表示
(4) 該当リソースをサーバに要請

クライアント側で(1)~(4)を経ることになると、サーバ側からでは完全なリソースをhtmlの形で回答します。 また、ブラウザ側ではこのhtmlを受けてレンダリングします。 前のページから新たに受けたページまで転換するやり方は、全体のページを再びレンダリングする方式であります。それで、リロードreloadが発生してヒストリーhistoryが残り、前のページに戻ることができます。 この時、クライアントのリクエストごとに重複されたHTMLとJavaScript、CSSについてももらってこなければならないので、パフォーマンスにあって損害がある可能性があります。




出典:ASP.NET–Single-Page Applications / 伝統的なページ寿命周期



②AJAX方式

これを補完するため、JavaScriptで非同期Asynchronousにサーバとブラウザがデータを交換できる通信方式を活用することになり、この通信方式をAJAXAsynchronous JavaScript and XMLと言います。

ページでアップデートが必要な部分だけをロードした後に更新して、リロードしなく不必要なリソースの重複要請を防ぐするので、パフォーマンスにあってもっと優れています。データ部分が空白であるhtmlをサーバーから受け取って、ページロード時JavaScriptでデータを持ってきて満たして入れます。




出典:ASP.NET–Single-Page Applications /SPA寿命周期



この時、AJAXはURLを変更させないので、アドレスバーの住所が変更されません。 これはブラウザの履歴history理することができないし、前のぺーじでもどることもできないことを意味します。 (このほかにもSEOに対してもよくないです。)



③HASH方式



上のAJAXを補完するため、登場したクライアントルーティング方法がHashです。

Hash方式は、Anchor Tagを使う方式で、link tag(<a href="#hash">など)のhref属性にhash(#)を使用します。 クリックする際に文書で#の後に付けているidを持った要素に移動します。

同一のURLでHashが変更された場合
ブラウザ側ではこれをURLが変わったものと認識するが、実際にクライアント側でサーバ側にリクエストを送ったりはしません。 Anchorでウェブページの内部での移動を向けたものだからです。要請をしないため、ページの更新はありませんが、ページがそれぞれの固有のURLを持つので、ヒストリーhistoryを管理することができます

上記の3つの方式のほか、PJAX(html5 pushState+ajax)もあります。



4.Javascript Frameworkからの主な活用点



  • ルーティング
    :公式的なルーティングライブラリが提供されています。
  • テンプレート
    :レンダリングされたDOMの上に基本インスタンスのデータを宣言的にバインディングがすることができるHTML基盤テンプレート言語を使えます。
    (この部分はdJangoとも似ていたと思います)
  • コンポーネントの再利用
    WebコンポーネントはWebページとWebアプリケーションの間でカプセル化された再使用ができるHTMLタグを生成することができます。

5.VUE

仮想DOMを活用できるし、DOMを使って全ての要素をリアルタイムでの反応型コンポーネントで製作することができるJavascriptフレームワークです。 通常、プロジェクトの規模が大きくなるほど、様々な形のコンポーネントが生成されるために、これを管理するのに役立つルーティング管理およびグローバル状態を管理するライブラリなどを提供します。

6.主要ライブラリ



①VUE Router

Vueを利用してSPAウェブを構築する際には、ルーティング制御を向けた公式プラグインです。

  • VueRouterを利用してURL・ヒストリーを管理することができます。
  • ブラウザで뒤로 가기及び앞으로 가기動作をできるようにしており、これは使用性を向上させることができます。

[ルーティングを定義する形式]

//views/Home.vue(テンプレートの構成要素)<template>
  <div class="home">
    <h1>Home</h1>
  </div>
</template>
//src/router.js(ルーティング定義)import  Vue  from  ' vue ' 
import  Router  from  ' vue-router ' 
import  Home  from  ' ./views/ '

Vue . use ( Router )

export  default  new  Router ({ 
  mode :  ' history ' , 
  routes :  [ 
    { 
      path :  ' / ' , 
      name :  ' home ' , 
      component :  Home} 
  ] 
})

以後、リンクと定義する際には、テンプレートの下記のような構文を挿入して使用することができます。

<router-link to="/">Home</router-link>



②VUE CLICommand Line Interface

Vue-cliにコマンドを実行すると、cliが自動的に最適化された、プロジェクトの基本骨格を生成します。 だけでなく、下記の設定が可能です。

  • Vueプロジェクト生成
  • Vueライブラリ管理
  • Vueの配布ファイルの設定:最適化されたWebpack*1形の結果物を生成します。
  • Vue GUI提供



*1Webpack:JS、CSS、イメージを一つのJSファイルで縛りつけられるモジュールです。 開発者が作成したJSとプロジェクト内の必要なライブラリをロードするJS、作成したCSSファイルとイメージファイルまで整理することができます。 イメージファイルの場合、DataURI(Base64形式)に変換してJSファイルに整理します。

*dJangoでプロジェクトを生成して、ライブラリを管理して、配布時に静的ファイル管理を提供したものと類似した感じ


③Vuex

Vueコンポーネントで活用することがデータを管理することを目的とする、ステートState管理ライブラリです。

1)Stateとは

*公式文書でstateデータ管理を状態のパターンに翻訳をしておいて、多くの部分がこんがらがったところ(デザインパターン中にステートパターンを融合させたと)で、mutationも各翻訳によって言葉が異なりました。開発をある程度進行した後、もっと確実に定義することができました。

すべてのクライアント側のコンポーネントで共通的に使えるように選び出したクライアント用データです。 一般的にクライアント側にデータを渡すためには、サーバーからデータを受け取ってこなければならないという煩わしさがあります。 Stateを使う場合、サーバから最初持ってきたデータをStateに保管します。 以降、必要な部分に対するデータだけ取り出して使うことになります。

2)State管理の必要性

MVCパターンの複雑なデータフロー問題は、大規模なアプリケーション開発の際MVCパターンで発生する構造的間違いを引き起こします。 複雑な画面とデータの構成が必要したときControllerに多数のModelとViewが縺れてからです。 したがって、既存のMVC間の依存性を除去することが難しくなって、これは機能追加やアップデート時に起きる問題点を解決する複雑にします。

Vuexは、コンポーネント間のデータ伝達をViewActionStateに明示的に表現して、よりデータの流れを単純に維持します。

3)構成要素

Vuex構成要素は View, Actions, Stateになります。




[例示]


出典:https://vuex.vuejs.org/(vuex公式文書)



(1)ユーザが View(Template)でボタンをクリック
(2)Actions(Method)でメソッド呼び出して動作
(3)Actions実行してからState(data)を変更

Viewは、クライアントの方に表示されるTemplateを意味します。 クライアント側でViewを通じてデータ保存などの動作を要請すると、要請によってActionsではバックエンド側と要請や応答を交換するようになります。

Stateの場合、クライアントの操作に必要なデータを共有します。Actionsを通じてバックエンドAPIを呼び出して要請に即したデータをもらって来たら、コンポーネント間で当該データをすべて共有できるようにStateに保存します。

各コンポーネントではStateにプロジェクトで共通に共有するデータを保存しておくことができ、最初の保存だけでどのコンポーネントでも当該データを照会することも、修正することもできます。

(4)State、Getters、Actions、Mutations

  • State
    すべてのコンポーネントで同一のデータを使用できるように共通的な変数を宣言します。
  • Getters
    コンポーネント内でStateに特定処理を進めた後、コンポーネントにバインディングする場合、Gettersに該当ロジックを定義した後、各コンポーネントで呼び出すことができます。
    (例)Stateに含まれるすべてのデータのうち、特定のデータだけを持ってくる場合
  • Actions
    回答を受け取ってくる部分のタームが一定しない場合などの大気が必要でない非同期ロジックが入っている処理を作成します。
  • Mutations
    Stateに存在する変更するロジックを作成し、Stateの値を変更させることはMutations中でのみ変更されなければなりません。

    しかし、開発者がMutation乙直接呼び出す場合はなく、Stateを変更するcommit背中のメソッドを使用するなどの方法で間接的に使用することになります。

    Mutationの場合、各コンポーネントでStateを変更した履歴を確認することができます。 各変更件について一つ一つ同期的に処理し、全ての履歴を残すので、お互いに違うコンポーネントでStateを変更しても衝突がありません。


(5)レンダリングパフォーマンスについて


Vueのgettersの場合、使用するたびに、毎回演算処理が入っている方式であるため、多くの量のデータを処理する場合には性能が急激に低下する現象がありました。 コンポーネントから呼び出すデータの数(row基準)が3000件ほどになる時、普段、秒当たり60フレームの性能を見せてくれましたが、たくさんの量のデータを毎回gettersから読んでくると、毎秒2フレームになって最後にはブラウザーが止まってしまいました。




フレームの低下現象が持続される区間



このような現象を改善するため、一番目にはstateに読んでくることをなくして、サーバー側のapiを利用して必要な部分だけを直接読んでくる形で作成しました。stateを通じず、サーバーから直接持ってくるのでサーバー側と連動に間する時間だけかかり、コンポーネントをレンダリングするのは、多くの時間がかかりませんでした。

二番目には、該当コンポーネントにページネーションを追加をしました。 最初のデータはすべて読んできた後、クライアント側でページングを処理する形だったためにフレームの低下には大きな役に満たなかったが、ブラウザーが実行中断になってしまう現象が消えました。



同じページを二回呼んだときの現象。確かに以前よりフレームの低下区間が短くなったし、落ちる幅も減少した



7.まとめ


VUEはSPA開発向けのJavascriptフレームワークの一つであります。

  • SPA
    SPAウェブは、最初のロード時、全体ページをロードした後からは、既存のページと比較して更新が必要な部分だけをjsonの形でデータを持ってきてバインディングbindingします。


  • Vue.jsでウェブを開発する際には、プロジェクトの構築及びその他の作業に役に立つVue-Cli、ルーティングを担当するVue Router、コンポーネント間のState管理を担当するVuexを使います。


  • Routing
    SPAウェブの使用性限界を補完するためには、ルーティングに関するライブラリを利用してヒストリーを管理する必要があります。


  • State管理
    各コンポーネントではStateにプロジェクトで共通に共有するデータを保存しておくことができ、最初の保存だけでどのコンポーネントでも当該データを照会することも、修正することもできます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]カテゴリー機能

はじめに

アプリ開発において、ancestryというgemを用いてカテゴリー機能を加えたのでまとめました。

目次

  1. カテゴリー選択・保存
  2. カテゴリー検索表示
  3. カテゴリー検索結果表示

1. カテゴリー選択・保存

カテゴリー選択・保存

categoriesテーブルの作成

ancestryをインストールします。

gemfile
gem 'ancestry'

次にcategoryモデルを作成します。

ターミナル
rails g model category

has_ancestryを記述します。

app/models/category.rb
class Category < ApplicationRecord
  has_many :posts
  has_ancestry
end

以下のようにマイグレーションファイルに記述します。
indexについてはこちら

db/migrate/20XXXXXXXXXXXX_create_categories.rb
class CreateCategories < ActiveRecord::Migration[6.0]
  def change
    create_table :categories do |t|
      t.string :name,     index: true, null: false
      t.string :ancestry, index: true
      t.timestamps
    end
  end
end

googleスプレッドシートにカテゴリーを記述していきます。
Aの列がid、Bの列がname(カテゴリー名)、Cの列がancestry(親子孫を見分ける数値)となります。
データの保存方法は、ファイル → ダウンロード → カンマ区切りの値(.csv 現在のシート) の手順で保存できます。

カテゴリー記述例
カテゴリー記述例

ダウンロードしたcsvファイルはdbフォルダに配置します。

seeds.rbファイル内へ以下の通り記述します。

db/seeds.rb 
require "csv"

CSV.foreach('db/category.csv') do |row|
  Category.create(:id => row[0], :name => row[1], :ancestry => row[2])
end 

ターミナルでrails db:seedコマンドを実行するとcsvファイルを読み込み自動でDBのレコードが生成されます。
foreachの後に読み込みたいファイルの指定を行います。
その下の記述については、モデル名.create(カラム名 => 読み込みたい列)となります。
row[0] → Aの列がid
row[1] → Bの列がname(カテゴリー名)
row[2] → Cの列がancestry(親子孫を見分ける数値)

ルーティング

子、孫カテゴリーをjson形式でルーティングを設定します。

config/routes.rb
Rails.application.routes.draw do
  ~~
  resources :posts do
    collection do
      get 'top'
      get 'get_category_children', defaults: { format: 'json' }
      get 'get_category_grandchildren', defaults: { format: 'json' }
      get 'name_search'
    end
  ~~
end

コントローラー

postsコントローラーに親カテゴリーを定義します。
複数箇所で使用するためbefore_actionを使って定義します。

app/controllers/posts_controller.rb
def set_parents
  @parents = Category.where(ancestry: nil)
end

postsコントローラーに子、孫カテゴリーのメソッドを定義します。

app/controllers/posts_controller.rb
def get_category_children
  @category_children = Category.find("#{params[:parent_id]}").children
end

def get_category_grandchildren
  @category_grandchildren = Category.find("#{params[:child_id]}").children
end

json.jbuilderファイルを作成し、jsonデータへ変換します。

app/views/posts/get_category_children.json.jbuilder 
json.array! @category_children do |child|
  json.id child.id
  json.name child.name
end
app/views/posts/get_category_grandchildren.json.jbuilder 
json.array! @category_grandchildren do |grandchild|
  json.id grandchild.id
  json.name grandchild.name
end

ビュー

javascriptでカテゴリー選択時の動作を設定します。

:app/javascript/category_post.js
$(function(){
  function appendOption(category){
    var html = `<option value="${category.id}">${category.name}</option>`;
    return html;
  }
  function appendChildrenBox(insertHTML){
    var childSelectHtml = "";
    childSelectHtml = `<div class="category__child" id="children_wrapper">
                        <select id="child__category" name="post[category_id]" class="serect_field">
                          <option value="">---</option>
                          ${insertHTML}
                        </select>
                      </div>`;
    $('.append__category').append(childSelectHtml);
  }
  function appendGrandchildrenBox(insertHTML){
    var grandchildSelectHtml = "";
    grandchildSelectHtml = `<div class="category__child" id="grandchildren_wrapper">
                              <select id="grandchild__category" name="post[category_id]" class="serect_field">
                                <option value="">---</option>
                                ${insertHTML}
                                </select>
                            </div>`;
    $('.append__category').append(grandchildSelectHtml);
  }

  $('#item_category_id').on('change',function(){
    var parentId = document.getElementById('item_category_id').value;
    if (parentId != ""){
      $.ajax({
        url: '/posts/get_category_children/',
        type: 'GET',
        data: { parent_id: parentId },
        dataType: 'json'
      })
      .done(function(children){
        $('#children_wrapper').remove();
        $('#grandchildren_wrapper').remove();
        var insertHTML = '';
        children.forEach(function(child){
          insertHTML += appendOption(child);
        });
        appendChildrenBox(insertHTML);
        if (insertHTML == "") {
          $('#children_wrapper').remove();
        }
      })
      .fail(function(){
        alert('カテゴリー取得に失敗しました');
      })
    }else{
      $('#children_wrapper').remove();
      $('#grandchildren_wrapper').remove();
    }
  });
  $('.append__category').on('change','#child__category',function(){
    var childId = document.getElementById('child__category').value;
    if(childId != ""){
      $.ajax({
        url: '/posts/get_category_grandchildren',
        type: 'GET',
        data: { child_id: childId },
        dataType: 'json'
      })
      .done(function(grandchildren){
        $('#grandchildren_wrapper').remove();
        var insertHTML = '';
        grandchildren.forEach(function(grandchild){
          insertHTML += appendOption(grandchild);
        });
        appendGrandchildrenBox(insertHTML);
        if (insertHTML == "") {
          $('#grandchildren_wrapper').remove();
        }
      })
      .fail(function(){
        alert('カテゴリー取得に失敗しました');
      })
    }else{
      $('#grandchildren_wrapper').remove();
    }
  })
});

新規投稿ページにカテゴリーセレクトボックスを表示させます。

app/views/posts/new.html.erb
<div class="append__category">
  <div class="category">
    <div class="form__label">
      <div class="weight-bold-text lavel__name ">
        カテゴリー
      </div>
      <div class="lavel__Required">
        <%= f.collection_select :category_id, @parents, :id, :name,{ include_blank: "選択してください"},class:"serect_field", id:"item_category_id" %>
      </div>
    </div>
  </div>
</div>

2. カテゴリー検索表示

カテゴリー検索表示

コントローラー

app/controllers/posts_controller.rb
def top
  respond_to do |format|
    format.html
    format.json do
      if params[:parent_id]
        @childrens = Category.find(params[:parent_id]).children
      elsif params[:children_id]
        @grandChilds = Category.find(params[:children_id]).children
      elsif params[:gcchildren_id]
        @parents = Category.where(id: params[:gcchildren_id])
      end
    end
  end
end

ビュー

javascriptでどの親カテゴリーにの上にマウスがいるのか、それに属する子カテゴリーや孫カテゴリーを取得しています。

:app/javascript/category.js
$(document).ready(function () {
  // 親カテゴリーを表示
  $('#categoBtn').hover(function (e) {
    e.preventDefault();
    e.stopPropagation();
    $('#tree_menu').show();
    $('.categoryTree').show();
  }, function () {
    // あえて何も記述しない
  });

  // 非同期にてヘッダーのカテゴリーを表示
  function childBuild(children) {
    let child_category = `
                        <li class="category_child">
                          <a href="/posts/${children.id}/search"><input class="child_btn" type="button" value="${children.name}" name= "${children.id}">
                          </a>
                        </li>
                        `
    return child_category;
  }

  function gcBuild(children) {
    let gc_category = `
                        <li class="category_grandchild">
                          <a href="/posts/${children.id}/search"><input class="gc_btn" type="button" value="${children.name}" name= "${children.id}">
                          </a>
                        </li>
                        `
    return gc_category;
  }

  // 親カテゴリーを表示
  $('#categoBtn').hover(function (e) {
    e.preventDefault();
    e.stopPropagation();
    timeOut = setTimeout(function () {
      $('#tree_menu').show();
      $('.categoryTree').show();
    }, 500)
  }, function () {
    clearTimeout(timeOut)
  });

  // 子カテゴリーを表示
  $('.parent_btn').hover(function () {
    $('.parent_btn').css('color', '');
    $('.parent_btn').css('background-color', '');
    let categoryParent = $(this).attr('name');
    timeParent = setTimeout(function () {
      $.ajax({
          url: '/posts/top',
          type: 'GET',
          data: {
            parent_id: categoryParent
          },
          dataType: 'json'
        })
        .done(function (data) {
          $(".categoryTree-grandchild").hide();
          $(".category_child").remove();
          $(".category_grandchild").remove();
          $('.categoryTree-child').show();
          data.forEach(function (child) {
            let child_html = childBuild(child)
            $(".categoryTree-child").append(child_html);
          });
          $('#tree_menu').css('max-height', '490px');
        })
        .fail(function () {
          alert("カテゴリーを選択してください");
        });
    }, 400)
  }, function () {
    clearTimeout(timeParent);
  });

  // 孫カテゴリーを表示
  $(document).on({
    mouseenter: function () {
      $('.child_btn').css('color', '');
      $('.child_btn').css('background-color', '');
      let categoryChild = $(this).attr('name');
      timeChild = setTimeout(function () {
        $.ajax({
            url: '/posts/top',
            type: 'GET',
            data: {
              children_id: categoryChild
            },
            dataType: 'json'
          })
          .done(function (gc_data) {
            $(".category_grandchild").remove();
            $('.categoryTree-grandchild').show();
            gc_data.forEach(function (gc) {
              let gc_html = gcBuild(gc)
              $(".categoryTree-grandchild").append(gc_html);
              let parcol = $('.categoryTree').find(`input[name="${gc.root}"]`);
              $(parcol).css('color', 'white');
              $(parcol).css('background-color', '#b1e9eb');
            });
            $('#tree_menu').css('max-height', '490px');
          })
          .fail(function () {
            alert("カテゴリーを選択してください");
          });
      }, 400)
    },
    mouseleave: function () {
      clearTimeout(timeChild);
    }
  }, '.child_btn');

  // 孫カテゴリーを選択時
  $(document).on({
    mouseenter: function () {
      let categoryGc = $(this).attr('name');
      timeGc = setTimeout(function () {
        $.ajax({
            url: '/posts/top',
            type: 'GET',
            data: {
              gcchildren_id: categoryGc
            },
            dataType: 'json'
          })
          .done(function (gc_result) {
            let childcol = $('.categoryTree-child').find(`input[name="${gc_result[0].parent}"]`);
            $(childcol).css('color', 'white');
            $(childcol).css('background-color', '#b1e9eb');
            $('#tree_menu').css('max-height', '490px');
          })
          .fail(function () {
            alert("カテゴリーを選択してください");
          });
      }, 400)
    },
    mouseleave: function () {
      clearTimeout(timeGc);
    }
  }, '.gc_btn');


  // カテゴリー一覧ページのボタン
  $('#all_btn').hover(function (e) {
    e.preventDefault();
    e.stopPropagation();
    $(".categoryTree-grandchild").hide();
    $(".categoryTree-child").hide();
    $(".category_grandchild").remove();
    $(".category_child").remove();
  }, function () {
    // あえて何も記述しないことで親要素に外れた際のアクションだけを伝搬する
  });

  // カテゴリーを非表示(カテゴリーメニュから0.8秒以上カーソルを外したら消える)
  $(document).on({
    mouseleave: function (e) {
      e.stopPropagation();
      e.preventDefault();
      timeChosed = setTimeout(function () {
        $(".categoryTree-grandchild").hide();
        $(".categoryTree-child").hide();
        $(".categoryTree").hide();
        $(this).hide();
        $('.parent_btn').css('color', '');
        $('.parent_btn').css('background-color', '');
        $(".category_child").remove();
        $(".category_grandchild").remove();
      }, 800);
    },
    mouseenter: function () {
      timeChosed = setTimeout(function () {
        $(".categoryTree-grandchild").hide();
        $(".categoryTree-child").hide();
        $(".categoryTree").hide();
        $(this).hide();
        $('.parent_btn').css('color', '');
        $('.parent_btn').css('background-color', '');
        $(".category_child").remove();
        $(".category_grandchild").remove();
      }, 800);
      clearTimeout(timeChosed);
    }
  }, '#tree_menu');

  // カテゴリーボタンの処理
  $(document).on({
    mouseenter: function (e) {
      e.stopPropagation();
      e.preventDefault();
      timeOpened = setTimeout(function () {
        $('#tree_menu').show();
        $('.categoryTree').show();
      }, 500);
    },
    mouseleave: function (e) {
      e.stopPropagation();
      e.preventDefault();
      clearTimeout(timeOpened);
      $(".categoryTree-grandchild").hide();
      $(".categoryTree-child").hide();
      $(".categoryTree").hide();
      $("#tree_menu").hide();
      $(".category_child").remove();
      $(".category_grandchild").remove();
    }
  }, '.header__headerInner__nav__listsLeft__item');
});

トップ画面にカテゴリー選択ウィンドウをセットします。

app/views/posts/top.html.erb
  <div class="item-categories">
    <h2>
      カテゴリー一覧
    </h2>
    <%= link_to  posts_path, class: "category-button", id: 'categoBtn' do %>
      カテゴリーから探す
    <% end %>
    <div id="tree_menu">
      <ul class="categoryTree">
        <% @parents.each do |parent| %>
          <li class="category_parent">
            <%= link_to search_post_path(parent) do %>
              <input type="button" value="<%= parent.name %>" name="<%= parent.id %>" class="parent_btn">
            <% end %>
          </li>
        <% end %>
      </ul>
      <ul class="categoryTree-child">
      </ul>
      <ul class="categoryTree-grandchild">
      </ul>
    </div>
  </div>

3. カテゴリー検索結果表示

カテゴリいー検索結果表示

ルーティング

カテゴリーをidで区別するため、memberを用いてsearchアクションを定義しています。

config/routes.rb
resources :posts do
    ~~
    member do
      get 'search'
    end
   ~~
end

コントローラー

クリックしたカテゴリーが、親カテゴリー、子カテゴリー、孫カテゴリーのどれなのかで条件分岐しています。

app/controllers/posts_controller.rb
  def search
    @category = Category.find_by(id: params[:id])

    if @category.ancestry == nil
      category = Category.find_by(id: params[:id]).indirect_ids
      if category.empty?
        @posts = Post.where(category_id: @category.id).order(created_at: :desc)
      else
        @posts = []
        find_item(category)
      end

    elsif @category.ancestry.include?("/")
      @posts = Post.where(category_id: params[:id]).order(created_at: :desc)

    else
      category = Category.find_by(id: params[:id]).child_ids
      @posts = []
      find_item(category)
    end
  end

  def find_item(category)
    category.each do |id|
      post_array = Post.where(category_id: id).order(created_at: :desc)
      if post_array.present?
        post_array.each do |post|
          if post.present?
            @posts.push(post)
          end
        end
      end
    end
  end

ビュー

app/views/posts/search.html.erb
  <div class="item-categories">
    <h2>
      カテゴリー一覧
    </h2>
    <%= link_to  posts_path, class: "category-button", id: 'categoBtn' do %>
      カテゴリーから探す
    <% end %>
    <div id="tree_menu">
      <ul class="categoryTree">
        <% @parents.each do |parent| %>
          <li class="category_parent">
            <%= link_to search_post_path(parent) do %>
              <input type="button" value="<%= parent.name %>" name="<%= parent.id %>" class="parent_btn">
            <% end %>
          </li>
        <% end %>
      </ul>
      <ul class="categoryTree-child">
      </ul>
      <ul class="categoryTree-grandchild">
      </ul>
    </div>
  </div>

参考リンク

https://qiita.com/k_suke_ja/items/aee192b5174402b6e8ca
https://qiita.com/Sobue-Yuki/items/9c1b05a66ce6020ff8c1
https://qiita.com/dr_tensyo/items/88e8ddf0f5ce37040dc8
https://qiita.com/ATORA1992/items/bd824f5097caeee09678
https://qiita.com/misioro_missie/items/175af1f1678e76e59dea
https://qiita.com/Rubyist_SOTA/items/49383aa7f60c42141871

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

HTML.CSS.JavaScript.Ruby.Rails概要

HTML

 HTML(Hyper Text Markup Language)とはWEBページを作成する際に使用されるマークアップ言語である。
 WEBページのほとんどにHTMLが使用されている。
 HTMLはタグを使いコンピューターに命令を出す事により見出しを付けたり段落を付けたりと、WEBページのレイアウト、構成を形作ることができる。

CSS

CSS(Cascading Style Sheets)は先ほどのHTMLと組み合わせて使用する言語である。
CSSは文字の色やサイズ、レイアウトを変えたり、WEBページを装飾する言語である。
HTMLでもWEBページの装飾をすることは出来るが、CSSの役割なので分けて使う必要がある。

JavaScript

WEBサイトに動きをつけるためのプログラミング言語である。
具体的には文章や画像を拡大表示したり、より動的なWEBサイトを作ることができる。
サーバーを介さずにブラウザ上で動かすことができる。またこのようなプログラムをクライアントサイド・スクリプトという

Ruby

Rubyとは日本人であるまつもとゆきひろ氏によって作成されたオブジェクト指向スクリプト言語である。
WEBサイトやECサイトなどの製作、SNS開発など様々なことができる。有名なサイトではぐるなび食べログなど
Rubyは他の言語に比べシンプルなコードのため開発スピードが早く読みやすい

Rails

Ruby on RailsとはRubyを使用したフレームワークである。
フレームワークとは雛形のことで、一からプログラミングをしなくても枠組みが用意されているため開発時間を大幅に削減することができる。他にもSinatraやHANAMIなどがある

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

【Jest超基礎】導入篇

はじめに

この記事は実務1ヶ月弱の新人エンジニアがアウトプットを目的として書いている記事になります。
携わらせていただいている案件で
「Jestを使うぞ!」と言われたので、
「Jestってなんぞや?」となり、学習を始めました!!
間違っている箇所等ございましたらご指摘ください。

Jestってなに?

Jest はシンプルさを重視した、快適な JavaScript テスティングフレームワークです。
(Jestドキュメント引用)
つまり、テストコードを簡単にJavaScriptで書けるってことですね!

Babel, TypeScript, Node, React, Angular, Vue等、様々なフレームワークにも使用できます。

↓テストコードについてはこちら
【初心者向け】テストコードの方針を考える(何をテストすべきか?どんなテストを書くべきか?)

導入

早速始めていきましょう!
まずはテストを取り入れたいご自分のプロジェクト配下で以下を実行します。
yarnの場合

yarn add --dev jest

npmの場合

npm install --save-dev jest

続いてsum,jsファイルを作成します。

sum.js
function sum(a, b) {
  return a + b;
}
module.exports = sum;

次にsum.test.jsを作成します。

sum.test.js
const sum = require('./sum');

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

そして、package.jsonを確認します。

package.json
{
  "scripts": {
    "test": "jest"
  }
}

このようになっていたらOKです!
私はこの部分が少し違っていてエラーが起こっていたので、ちゃんと確認しないといけませんね。。。

ここまでできたらあとは実行するだけ!!

yarnの場合

yarn test

npmの場合

npm test

結果

PASS  ./sum.test.js
   ✓ adds 1 + 2 to equal 3 (2 ms)
 Test Suites: 1 passed, 1 total
 Tests:       1 passed, 1 total
 Snapshots:   0 total
 Time:        1.332 s, estimated 2 s
 Ran all test suites.

こんな感じに表示されていたら成功です!

参考文献

Jestドキュメント
ドキュメント導入部分

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

Vue.js 表示文字列にURLが含まれていたときにリンク化する

目的

  • Vue.jsにおいて表示する文字列の中にURLが含まれていた場合リンク化して表示する方法を簡単にまとめる

詳細

  1. 下記のPタグの中に変数textの内容を格納する。

    vue.js
    <p v-html="text"></p>
    
  2. 変数testにURLが含まれている場合、リンクとして表示したい。下記のようなメソッドを定義する。

    vue.js
    /**
     * @param {String} text
     */
    autoLink(text) {
        return _.isString(text) ? text.replace(/(https?:\/\/[^\s]*)/g, "<a href='$1'>$1</a>: '';
    },
    
  3. 下記のようにPタグ部分で定義したメソッドを呼ぶ。

    vue.js
    <p v-html="authLink(text)"></p>
    

メソッド部分の簡単な解説

  • 下記にメソッドの処理部分を記載する。

    vue.js
    return _.isString(text) ? text.replace(/(https?:\/\/[^\s]*)/g, "<a href='$1'>$1</a>": '';
    
  • isString(text)? : ;

    • 参考演算子を用いて引数に文字列が格納されているかチェックしている。
    • isString(引数文字列)? isStringが真のときの処理 : isStringが偽のときの処理のようになる。
  • text.replace(/(https?:\/\/[^\s]*)/g, "<a href='$1'>$1</a>"

    • replaceメソッドを使用して文字列の置換を行っている。
    • replace(検索条件の文字列, 置換後の文字列)として指定している。
  • /(https?:\/\/[^\s]*)/g(検索条件文字列の正規表現)

    • 正規表現部分を細かくまとめてみる。
      • 最初と最後の/はデリミタ(区切り文字)といって「この部分が正規表現ですよ」ということを表している。
      • 最後の/のあとのgは直前の正規表現を繰り返し検索する修飾子である。
      • デリミタの最初から最後部分までの( )は正規表現部分が一つの文字列であることを表している。
      • 「https」の後ろの?は直前の文字があるかないかでヒットとなる。なのでhttpでもhttpsでもヒットするように正規表現を用いて記述している。
      • :\/\/は「://」という文字列をエスケープを使用して記載しているだけである。
      • [ ]は内部に記載された内容いずれかにヒットする。
      • ^は後述する内容以外にヒットする。
      • \sは空白文字、半角スペース、タブ、改行文字のいずれかにヒットする。
      • *は直前文字0回以上繰り返すときにヒットする。(URLのあとに半角スペース、全角スペース、改行が入るところで区切る)
    • 上記に記載した内容を日本語にしてまとめてみる。

      /(httpかhttpsにマッチ://空白文字、半角スペース、タブ、改行文字以外のいずれかにマッチする。直前の文字列が0回以上マッチする。)/
      
  • <a href='$1'>$1</a>

    • 置換後の文字列であり$1にはマッチした文字列が格納されている。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

webpack v5がリリースされた現状まとめ

プロダクションで使うのはまだ早い(11月時点

webpack v5のリリースは行われましたが、まだバグが多くありwebpackで使用するloaderやpluginは対応が全然追いついていません。

webpack v5系のIssues を眺めると多くの問題があることがわかります。

プライベートでwebpack v5にアップデートを試みたところ全部壊れました。大変危険です、webpack v5 を使用するのは 数ヶ月ほど待ったほうが良いと思います。

周辺ツールの現状

webpackを使用するにあたって特に重要な webpack-dev-server と webpack-cli の現状は以下のとおりです。

webpack-dev-server

webpack-dev-server は webpack v5対応がまだリリースされてないので、動かないケースが何点かあります。

webpack-cli

webpack v5 対応のためv4のリリースはされていますが、上記でも書いている通りwebpack-dev-serverと組み合わせるとバグります。

webpack-cli v4からwebpack-dev-server の起動をwebpack serveコマンドに統合するようになっています。

変更内容

主に下記のような変更があります。

  • 永続的なキャッシング
  • TypeScriptの対応で @types/webpack が不要になり、import { WebpackOptionsNormalized } from 'webpack'; で型を import できるようになった。(ファイル名を webpack.config.ts にする必要がある
  • Tree Shaking の最適化が入りバンドルサイズ縮小に期待できる
  • CommonJs の Tree Shaking 対応
  • css の chunk が可能になった( MiniCssExtractPlugin 使用時にできる)

破壊的変更

特に大きな破壊的変更を2つ取り上げます。

Node.js の polyfill を自動で挿入しなくなった

今まではwebpackを使用すればNode.js のコードをクライアントサイドで使用し、自動でpolyfillを挿入してくれていました。

今後 webpackはwebで動作するコードに焦点を当てていくため、Node.jsのpolyfill がバンドルに含まれ、結果的にバンドルサイズがデカくなくることを望まないようになり、
自動で polyfill を挿入しなくなりました。

polyfill を挿入したい場合は webpack/node-libs-browser を参照して、自前で挿入する必要があります。
また、クライアントサイドで Node.js に依存したパッケージを使用している場合は、パッケージの対応を待つか、フロントエンド互換のあるパッケージに変更する必要があります。

global, __filename, __dirnameもwebpackのデフォルト設定でfalseに変更されたので、使用したい場合は明示的に変更する必要があります。

デフォルトランタイムが一部 ES2015 になった

webpackの生成するコードが一部 ES2015 になったので、明示的に es5 への対応が必要となりました。

browserslistnのサポートが含まれたので、browserslistの設定またはwebpack の設定を変える、2つの選択肢があります。

  • webpack の設定

webpack.conf.js

module.exports = {
  target: ['web','es5']
};
  • browserslist の設定

.browserslistrc

last 1 version
ie >= 11
  • package.json
 "browserslist": [
    "last 1 version",
    "> 1%",
    "ie >= 11"
  ]

参考文献

webpack@5の主な変更点まとめ

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

オブジェクトに何かを代入している処理を探す正規表現

訳あって、ツクールMVのプラグインの中から循環参照を探すことになった。
この場合、オブジェクトに代入処理をしているすべての処理を探り、その中から犯人を捜すことになる。
代入処理をすべて検索するには以下の記述を使う。

this\.(.*)( *)=( *)([^true|false|\d| new (.*)].+)

これは最適化もしてないし、否定条件は使いながら書き換えている。
もちろん他の代入パターンも考えられるが、基本的にはこれで問題ないはずである。

テストに使用したデータは以下の通り。

this.aaa =baa
this.bbb = baa
this.ccc=ccc

this.boo()
this.xxx = true
this.yyy = false
this.zzz =19
this.nnn = new hoge()
this.nnn = new hage(xxxx)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

子供用の学習アプリケーションを作る(2) アニメーション編

はじめに

以前作成したアプリの続きをしていきます。
参考: 子供用の学習アプリケーションを作る(1)

今回は、コンテンツの選択画面にアニメーションを導入し、選択後の詳細画面を作成したので、その実装について記事にしていこうと思います。

動作

まずは、動作を見ていただければと思います。

test3.gif

実装

構成

構成は以下のようになっています。

❯ pwd        
/Users/yoshitaka.koitabashi/Desktop/iLearn/src/components

~/Desktop/iLearn/src/components
❯ tree .             
.
├── alsContent.tsx
├── contentsSelect.tsx
├── falcon9Content.tsx
├── header.tsx
└── spaceContent.tsx

0 directories, 5 files

今回の実装の説明に使用するのは、こちらです。
・contentsSelect.tsx
コンテンツを選択する画面

Home画面からの遷移時に、各コンテンツがふんわり浮かび上がるアニメーションを作成しました。
参考 react-native: Animated
こちらの実装なのですが、単純で、Animatedというライブラリと副作用fookであるuseEffectを利用したパターンになります。

contentsSelect.tsx
import 'react-native-gesture-handler';
import React, { useRef, useEffect } from 'react';
import {
  SafeAreaView, ScrollView, StyleSheet, Animated, TouchableOpacity,
} from 'react-native';
import { Card, Title } from 'react-native-paper';
import { createStackNavigator } from '@react-navigation/stack';
import AppHeader from './header';
import spaceContent from './spaceContent';
import alsContents from './alsContent';
import falcon9Contents from './falcon9Content';

const contents = ({ navigation }) => {
  const fadeSpace = useRef(new Animated.Value(0)).current;
  const fadeAls = useRef(new Animated.Value(0)).current;
  const fadeFalcon9 = useRef(new Animated.Value(0)).current;

  const spaceContentFadeIn = () => {
    Animated.timing(fadeSpace, {
      toValue: 1,
      duration: 500,
      useNativeDriver: true,
    }).start();
  };

  const alsContentsFadeIn = () => {
    Animated.timing(fadeAls, {
      toValue: 1,
      duration: 2000,
      useNativeDriver: true,
    }).start();
  };

  const falcon9ContentsFadeIn = () => {
    Animated.timing(fadeFalcon9, {
      toValue: 1,
      duration: 3000,
      useNativeDriver: true,
    }).start();
  };

  useEffect(() => {
    spaceContentFadeIn();
    alsContentsFadeIn();
    falcon9ContentsFadeIn();
  });

  return (
    <SafeAreaView style={styles.container}>
      <ScrollView
        contentContainerStyle={styles.contentContainer}
      >
        <TouchableOpacity
          onPress={() => {
            navigation.navigate('Home');
          }}
        >
          <AppHeader />
        </TouchableOpacity>
        <Animated.View style={[{ opacity: fadeSpace }]}>
          <Card
            onPress={() => navigation.navigate('宇宙って?')}
            style={styles.cardPadding}
          >
            <Card.Content>
              <Title style={styles.cardTitle}>宇宙って</Title>
              <Card.Cover
                source={require('../../public/img/alien.png')}
                style={styles.cardImg}
              />
            </Card.Content>
          </Card>
        </Animated.View>
        <Animated.View style={[{ opacity: fadeAls }]}>
          <Card
            onPress={() => navigation.navigate('ALSって知ってる?')}
            style={styles.cardPadding}
          >
            <Card.Content>
              <Title style={styles.cardTitle}>ALSって知ってる</Title>
              <Card.Cover
                source={require('../../public/img/health.png')}
                style={styles.cardImg}
              />
            </Card.Content>
          </Card>
        </Animated.View>
        <Animated.View style={[{ opacity: fadeFalcon9 }]}>
          <Card
            onPress={() => navigation.navigate('Falcon9がすごい')}
            style={styles.cardPadding}
          >
            <Card.Content>
              <Title style={styles.cardTitle}>Falcon 9がすごい</Title>
              <Card.Cover
                source={require('../../public/img/startup_isometric.png')}
                style={styles.cardImg}
              />
            </Card.Content>
          </Card>
        </Animated.View>
      </ScrollView>
    </SafeAreaView>
  );
};

const Stack = createStackNavigator();

const contentsSelect = () => (
  <Stack.Navigator>
    <Stack.Screen
      name="知識の森"
      component={contents}
    />
    <Stack.Screen
      name="宇宙って?"
      component={spaceContent}
    />
    <Stack.Screen
      name="ALSって知ってる?"
      component={alsContents}
    />
    <Stack.Screen
      name="Falcon9がすごい"
      component={falcon9Contents}
    />
  </Stack.Navigator>
);

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  cardImg: {
    height: 300,
  },
  cardPadding: {
    top: 60,
    marginBottom: 20,
    borderRadius: 5,
    marginLeft: 20,
    marginRight: 20,
  },
  cardTitle: {
    fontWeight: 'bold',
  },
  contentContainer: {
    paddingBottom: 50,
  },
});

export default contentsSelect;

・spaceContent.tsx
宇宙についてのコンテンツの詳細画面

詳細画面で少し面白い箇所が、下記です。
何をしているかというと、Home画面に戻す動作をしているのですが、dispatch(StackActions.popToTop())をしないと、navigationのHistoryが消されず想定外の動作をしてしまいます。

navigation.navigate('Home');
navigation.dispatch(StackActions.popToTop());
spaceContent.tsx
import * as React from 'react';
import {
  ScrollView, StyleSheet, View, Image, TouchableOpacity,
} from 'react-native';
import {
  Card, Paragraph, Chip, Avatar, Title,
} from 'react-native-paper';
import { StackActions } from '@react-navigation/native';
import { Text } from 'react-native-elements';
import AppHeader from './header';

const spaceContent = ({ navigation }) => (
  <View style={styles.container}>
    <ScrollView
      contentContainerStyle={styles.contentContainer}
    >
      <TouchableOpacity
        onPress={() => {
          navigation.navigate('Home');
          navigation.dispatch(StackActions.popToTop());
        }}
      >
        <AppHeader />
      </TouchableOpacity>
      <Card
        style={styles.cardPadding}
      >
        <Card.Content>
          <Title style={styles.cardTitle}>宇宙ってなんだろう??</Title>
          <Card.Cover
            source={require('../../public/img/alien.png')}
          />
        </Card.Content>
      </Card>
      <Card
        style={styles.cardPadding}
      >
        <Card.Content>
          <Paragraph
            style={styles.nextCardMessage}
          >
            Topics
          </Paragraph>
          <View style={styles.row}>
            <Chip style={styles.chip}>
              <Text style={styles.chipText}>宇宙開発</Text>
            </Chip>

            <Chip style={styles.chip}>
              <Text style={styles.chipText}>Jaxa</Text>
            </Chip>

            <Chip style={styles.chip}>
              <Text style={styles.chipText}>ISS</Text>
            </Chip>
          </View>
        </Card.Content>
      </Card>
      <Card
        style={styles.cardPadding}
      >
        <Card.Content>
          <Paragraph
            style={styles.nextCardMessage}
          >
            作者
          </Paragraph>
          <View style={styles.row}>
            <Avatar.Image size={70} source={require('../../public/img/space-travel.png')} />
            <Text style={styles.avatarMessage}>
              Koitabashi Yoshitaka
            </Text>
          </View>
        </Card.Content>
      </Card>
      <Card
        style={styles.cardPadding}
      >
        <Card.Content>
          <Paragraph
            style={styles.nextCardMessage}
          >
            物語
          </Paragraph>
          <Text h3 style={styles.storyTitle}>
            はじめに
          </Text>
          <Text style={styles.storyBody}>
            宇宙の誕生は約138億年前のビッグバンから始まります
          </Text>
          <Image
            source={require('../../public/img/moon2.png')}
            style={{ width: 300, height: 200 }}
          />
          <Text h4 style={styles.storyTitle}>
            ビックバンって〜?
          </Text>
          <Text style={styles.storyBody}>
            人間のまばたきよりも短い時間の中で起こった超高エネルギーの爆発ビックバンです
            ビッグバンにより小さな物質同士が結合し合い星の素となるチリやガスが生まれました
            {'\n'}
            さらにそれらの物質がくっつき合い恒星や惑星といった星々が生まれたのです
            {'\n'}
          </Text>
          <Image
            source={require('../../public/img/moon1.png')}
            style={{ width: 300, height: 200 }}
          />
          <Text style={styles.storyBody}>
            誕生以来宇宙は膨張を続けておりその膨張は加速し続けているといわれています
            {'\n'}
            そのため宇宙の大きさは現在の科学でも解明できていません
          </Text>
        </Card.Content>
      </Card>
    </ScrollView>
  </View>
);

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  backButton: {
    paddingTop: 10,
    paddingBottom: 10,
  },
  cardPadding: {
    textAlign: 'center',
    top: 60,
    marginBottom: 20,
    borderRadius: 5,
    marginLeft: 20,
    marginRight: 20,
  },
  cardTitle: {
    marginBottom: 15,
    fontSize: 20,
    fontWeight: 'bold',
  },
  cardMessage: {
    marginTop: 15,
    fontSize: 20,
    fontWeight: 'bold',
  },
  nextCardMessage: {
    marginBottom: 20,
    fontSize: 20,
    fontWeight: 'bold',
  },
  row: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    paddingHorizontal: 12,
  },
  chip: {
    backgroundColor: '#2096F3',
    margin: 2,
  },
  chipText: {
    color: '#ffffff',
  },
  avatarMessage: {
    marginLeft: 30,
    marginTop: 20,
    fontWeight: 'bold',
    textAlign: 'left',
  },
  storyTitle: {
    marginTop: 20,
    marginBottom: 20,
    fontWeight: 'bold',
  },
  storyBody: {
    marginTop: 20,
    fontWeight: 'bold',
  },
  contentContainer: {
    paddingBottom: 60,
  },
});

export default spaceContent;

おわり

・ 説明が雑になってきているので、だんだん追記していきます。w
・ 現在は、各コンテンツの内容をハードコーディングしているのですが、いずれ専用のAPIを作成するつもりなので、そこはとりあえず置いておきます。
・ あとは、Qittaのようにmarkdownで誰でも編集できるようにしていきたいと思ってます。

参考文献

宇宙について親子で楽しく学ぼう

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

シューティングゲームの当たり判定をQRコード読み取りでやってみた

使ってみたい人はこちらのリンクからどうぞ

スマホ対応はまだできていませんので、、PCでお願いします。。
射撃ボタン押すと、ページ下の方に判定に使う画像が表示されます。
https://musing-thompson-7d10a0.netlify.app/

かざすQRコードはこちらで。
ファイル名

ARのシューティングゲームを作りたい

ので、当たり判定の部分をどう実現するか検討中です。

以前、機械学習で当たり判定をする処理を作成しましたが、(↓のリンク)
画面に照準を入れて、その照準に入っているときに撃つと当たるようにしたいのと、
機械学習では別の人と判定される可能性があるので、
今回は、照準部分にQRコードの読み取りを使い当たっているかどうか判定しました。

機械学習を使って、PCのカメラでザクを補足して撃てるようにしてみた。

構成

image.png

・カメラ映像をcanvas(画像)に変換
・変換したcanvasの照準部分だけを切り出し
・照準部分でQRコードを読み込めた場合、当たったこととする

コード(CodePen)

See the Pen QRコードの生成 & 読取 by sawa (@sawakoshi_yy) on CodePen.

大きく表示できないのでCodePenのURLも貼っておきます
https://codepen.io/sawakoshi_yy/pen/GRqeojK

嫁さんに使ってもらいました

嫁:照準の中にQRコード2つ並べたら両方反応するの?
私:それは、、無理ちゃうかな。。
嫁:画像にはちゃんとQRコード入ってるのに、当たらんことがあるんやけど。当たってるやん!ってなるんやけど。
私:せやねん。。。写真がぶれると駄目みたいですね。。
嫁:シューティングゲームなのに。
私:そう、ですね。。
嫁:近づきすぎると当たらないね。
私:そう、ですね。。枠からはみ出しちゃうので、敵が近すぎると当たらなくなりますね。。
嫁:ガンダムにQRコード付けるの?
私:そうそう。ガンダムじゃないけど、QRコードを体に付けて戦う。
嫁:頭に1つだけ?
私:体中に付けないといけないですね。。
嫁:お風呂沸いたよ。
私:あ、はい。

まとめると、
■課題
・QRコードが並んでいると上手く判定できない
・写真がぶれると外れる側に判定されてしまう
・近づきすぎるとQRコードが照準からはみ出すので、外れる側に判定されてしまう。
・QRコードを体に複数貼る必要がある

課題まとめ

・QRコードが並んでいると上手く判定できない (フィードバック)
・写真がぶれると外れる側に判定されてしまう (フィードバック)
・近づきすぎるとQRコードが照準からはみ出すので、外れる側に判定されてしまう。 (フィードバック)
・QRコードを体に複数貼る必要がある (フィードバック)
・スマホ対応が必要
・ネイティブアプリ化が必要

ARのシューティングゲームとなると、動画から画像を切り出したときに、
どうしてもぶれてしまう気がしています。
もっと上手いこと当たり判定できる方法を考えていきたいです。

参考

【JavaScript】ブラウザ上でQRコードを生成/解析
Canvasリファレンス
drawImage()メソッド(画像類の貼付け)。
HTML5のCanvasで画像をリサイズ&トリミングして中央表示する方法

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

ていつに法方るべ並に逆を字文でtpircSavaj

はじめに

かと.emag llabesab a gnihctaw retfa tnaruatser a ot gniog eb lliw I dna nos ym ,krow retfa ,yadoT:ばえ例。ねよすでいなめ読はに単簡とるすに字文さ逆をかとトッベァフルアはとあ。すまりかかが間時にのむ読は文い長がすまけ解み読に単簡は字文さ逆い短。たしましに逆を字文うよいくにみ読とざわ。ねよすでいくにみ読に常非ていてっなにさ逆が字文は事記のこ、しかしやい。すまいざごうとがりあていだたいでん読を事記のこ !dlrow olleh

え..なに..これ..???
sick_panic_man.png

あっ!? 逆さから文字を読めばいいのか!!!!!
hirameki_man.png

本題

ん"ん"ん"ん"でもなぁ。。。。。長ったらしい逆さの文章読むのめんどくさいな。。。。。
プログラミングでどうにか解読できないかな????って思ったのが今回この記事を書こうと思った理由。

調べたらこれでできるみたい。。。。

 文字列.split('').reverse().join('');

それぞれの関数の意味ってなんぞ・・・・・・

split関数

  • 文字列を配列形式にしてくれる関数
  • splitの引数パラメータでアルファベットごとに切り分けたり、単語ごとに切り分けられる(詳しいことは割愛します)
//文字列
const str = `!dlrow olleh`;

//split関数
const splitString = str.split(` `);


console.log(splitString); 
//表示結果: [ '!dlrow', 'olleh' ]

reverse関数

  • 配列の最初の要素と最後の要素を反転させる
//配列
const arrayString = ['world!','hello']

console.log(arrayString);
//表示結果: Array ['hello', 'world!'];

join関数

  • 配列全要素を順に連結した文字列を作成する
  • 引数に入れた文字が連結部分に表示され、引数無しなら”,”が表示される。 
  • 引数に``のみなら,が消えた文字列になる
//配列
const elements = ['今日の', 'ご飯は', 'カレーです']; 

//" , "を含んだ文字列になる
console.log(elements.join());
//表示結果: "今日の,ご飯は,カレーです"

//" A "を含んだ文字列になる
console.log(elements.join('A'));
//表示結果: "今日のAご飯はAカレーです"

//" , "を含まない文字列になる
console.log(elements.join(``));
//表示結果: "今日のご飯はカレーです"

実際に逆さ文字を戻してみる

reverseString(str){
  str.split('').reverse().join('');
}

reverseString("かと.emag llabesab a gnihctaw retfa tnaruatser a ot gniog eb lliw I dna nos ym ,krow retfa ,yadoT:ばえ例。ねよすでいなめ読はに単簡とるすに字文さ逆をかとトッベァフルアはとあ。すまりかかが間時にのむ読は文い長がすまけ解み読に単簡は字文さ逆い短。たしましに逆を字文うよいくにみ読とざわ。ねよすでいくにみ読に常非ていてっなにさ逆が字文は事記のこ、しかしやい。すまいざごうとがりあていだたいでん読を事記のこ !dlrow olleh");


//出力結果
hello world! この記事を読んでいただいてありがとうございます。
いやしかし、この記事は文字が逆さになっていて非常に読みにくいですよね。
わざと読みにくいよう文字を逆にしました。
短い逆さ文字は簡単に読み解けますが長い文は読むのに時間がかかります。
あとはアルファベットとかを逆さ文字にすると簡単には読めないですよね。
例えば:Today, after work, my son and I will be going to a restaurant after watching a baseball game.とか

結論

正直なぜこんなことを熱心に調べたんだろう。。。。。。って今思ってます。
なんならこんな素晴らしいサイトがありました(白目)。
https://www.nap.st/arranged_in_reverse/?lang=ja

いや、頑張った意味〜〜〜〜〜〜〜〜〜

おわり。

資料

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

JavaScript 非破壊的に要素を追加した配列を生成する

結論

push()を使わず、スプレッド構文を使って新たな配列を生成する。

サンプル

sample.js
let students = [['sato'], ['suzuki'], ['takahashi'], ['tanaka'], ['ito']];
//配列の各要素にインデックスの値を追加したい。e.g. ['sato'] -> ['sato', 0]

push()

push.js
let addIndex = students.map(function(el, idx) {
 el.push(idx);
 return el;
});
console.log(addIndex); // [['sato', 0], ['suzuki', 1], ['takahashi', 2], ['tanaka', 3], ['ito', 4]] 

// 元の配列も変更されてしまう。
console.log(students); // [['sato', 0], ['suzuki', 1], ['takahashi', 2], ['tanaka', 3], ['ito', 4]]

スプレッド構文

spread.js
let addIndex = students.map((el, idx) => [...el, idx]);
//インデックスが追加されている。
console.log(addIndex); // [['sato', 0], ['suzuki', 1], ['takahashi', 2], ['tanaka', 3], ['ito', 4]] 

// 元の配列はそのまま。
console.log(students); // [['sato'], ['suzuki'], ['takahashi'], ['tanaka'], ['ito']];

本記事の内容は別記事にて@shiracamusさんにご教授頂きました!
ありがとうございました!

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