20191211のJavaScriptに関する記事は30件です。

年末まで毎日webサイトを作り続ける大学生 〜54日目 JavaScriptでじゃんけんゲームを作る〜

はじめに

こんにちは!@70days_jsです。

じゃんけんゲームを作りました。
例のごとく何も参考にせずに作りました。
ので、もっといいじゃんけんのアルゴリズムがあれば教えて欲しいです。

今日は54日目。(2019/12/11)
よろしくお願いします。

サイトURL

https://sin2cos21.github.io/day54.html

やったこと

こんな感じのじゃんけんゲームを作りました(gif)↓
test3.gif

勝ちは赤、
負けは青、
引き分けは黄色で表現しています。

ソース全文

html↓

  <body>
    <section>
      <div id="jankenWord">勝負の刻!</div>
    </section>
    <section>
      <div id="MyjankenImage"></div>
      <div id="jankenImage"></div>
    </section>
    <section>
      <div id="rock" class="jankenButton">グー</div>
      <div id="scissors" class="jankenButton">チョキ</div>
      <div id="paper" class="jankenButton">パー</div>
    </section>
  </body>

css↓

body {
  margin: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

section {
  display: flex;
}
div {
  width: 100px;
  height: 100px;
  border: solid 1px black;
  margin: 50px 0 0 50px;
  display: flex;
  justify-content: center;
  align-items: center;
}
#jankenImage {
  background-size: cover;
}

#MyjankenImage {
  background-size: cover;
}

#jankenImage::before {
  content: "敵";
  margin-top: 130px;
}
#MyjankenImage::before {
  content: "Me";
  margin-top: 130px;
}

.jankenButton {
  border-radius: 50%;
  cursor: pointer;
}

.click-none {
  pointer-events: none;
}

JavaScript↓

let janken = {
  0: "rock",
  1: "paper",
  2: "scissors"
};

let img = {
  rock: "day54/rock.png",
  scissors: "day54/scissors.png",
  paper: "day54/paper.png"
};

let jankenImage = document.getElementById("jankenImage");
let MyjankenImage = document.getElementById("MyjankenImage");
let jankenWord = document.getElementById("jankenWord");
let jankenButton = document.getElementsByClassName("jankenButton");

for (var i = 0; i < jankenButton.length; i++) {
  let jankenChoice = jankenButton[i];
  jankenChoice.addEventListener("click", function() {
    let random = Math.floor(Math.random() * 3);
    jankenImage.style.backgroundImage = "url(" + img[janken[random]] + ")";
    MyjankenImage.style.backgroundImage = "url(" + img[jankenChoice.id] + ")";
    judge(random, jankenChoice.id);
  });
}
//引数はtekidaは乱数、meは選択した要素のid
function judge(tekida, me) {
  let teki = janken[tekida];
  if (teki === me) {
    jankenWord.innerHTML = "ヒキワケ";
    jankenWord.style.backgroundColor = "rgba(255,250,50, .5)";
  } else if (me === "rock" && teki === "scissors") {
    jankenWord.innerHTML = "カチ";
    jankenWord.style.backgroundColor = "rgba(255,50,50, .5)";
  } else if (teki === "rock" && me === "scissors") {
    jankenWord.innerHTML = "マケ";
    jankenWord.style.backgroundColor = "rgba(55,50,250, .5)";
  } else if (me.length > teki.length) {
    jankenWord.innerHTML = "カチ";
    jankenWord.style.backgroundColor = "rgba(255,50,50, .5)";
  } else {
    jankenWord.innerHTML = "マケ";
    jankenWord.style.backgroundColor = "rgba(55,50,250, .5)";
  }
}

じゃんけんのアルゴリズム

重要なのはここですよね。

今回、僕はグーチョキパーをhashにしました。

let janken = {
0: "rock",
1: "paper",
2: "scissors"
};

その時、文字数に着目しました。
すると、rock以外は文字数が大きいほど強い法則を見つけました。
scissors > paper
paper > rock
なので、以下のように書きました。

if (teki === me) {
ヒキワケ
} else if (me === "rock" && teki === "scissors") {
カチ
}else if (teki === "rock" && me === "scissors") {
マケ
} else if (me.length > teki.length) {
カチ
} else {
マケ
}

文字数じゃなく、数字を使っても良かっても良かったかもしれませんね。
ただ、まあ、これでじゃんけんは一応成立します。

感想

ロジックを考えてちゃんと動いてくれると楽しかったりします。
ただ、あまり正確すぎたり最適を求めたりしすぎたりするのは自分の性に合わないということも分かりました。
これからもゆるく楽しく作れればいいな。

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

JavaScriptでAuth0 を使った個人認証ログインサイトの作成

JavaScriptでAuth0 を使った個人認証ログインサイトの作成

今週はauth0を使ってサイトを作ってみたいと思います。

参考資料

【資料1】超初心者がAuth0でログイン機能を実装した

【資料2】Bootstrap 4 Buttons
Bootstrapでのボタンの作り方を調べた

環境

Node.js v10.16.3
Windows 10 pro
Visual Studio Code v1.39.1

準備

Auth0のサイトにアクセス。
サインインからアカウントを作成します。
【資料1】を参考に初期設定を入力し、出来たフォルダをダウンロードし、解凍します。
解凍前のフォルダは一応置いておくと後から失敗してもやり直せるセーブデータのようですので
大事においておきましょう。

VC起動

解凍したフォルダをVCで開き、その中にある vanillajs-01-login/01-login へ移動します。

cd vanillajs-01-login/01-login                   

動必要なライブラリは既にpackage-look.json内にデータがあるので、ターミナルに入力し、一気にインストール。

npm i               

package.jsonの中にショートカットキーでstartが設定されているのでこちらを使用
image.png

npm start

image.png

良きです。

index.html

 <!DOCTYPE html>
<html class="h-100">

<head>
  <meta charset="UTF-8" />
  <title>SPA SDK Sample</title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" />
  <link rel="stylesheet" type="text/css" href="/css/auth0-theme.min.css" />
  <link rel="stylesheet" type="text/css" href="/css/main.css" />
  <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/styles/monokai-sublime.min.css" />
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/solid.css"
    integrity="sha384-r/k8YTFqmlOaqRkZuSiE9trsrDXkh07mRaoGBMoDcmA58OHILZPsk29i2BsFng1B" crossorigin="anonymous" />
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/fontawesome.css"
    integrity="sha384-4aon80D8rXCGx9ayDt85LbyUHeMWd3UiBaWliBlJ53yzm9hqN21A+o1pqoyK04h+" crossorigin="anonymous" />

  <link rel="stylesheet" href="https://cdn.auth0.com/js/auth0-samples-theme/1.0/css/auth0-theme.min.css" />
</head>

<body class="h-100">
  <div id="app" class="h-100 d-flex flex-column">
    <div class="nav-container">
      <nav class="navbar navbar-expand-md navbar-light bg-light">
        <div class="container">
          <div class="navbar-brand logo"></div>
          <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav"
            aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
          </button>

          <div class="collapse navbar-collapse" id="navbarNav">
            <ul class="navbar-nav mr-auto">
              <li class="nav-item">
                <a href="/" class="nav-link route-link">Home</a>
              </li>
            </ul>
            <ul class="navbar-nav d-none d-md-block">
              <!-- Login button: show if NOT authenticated -->
              <li class="nav-item auth-invisible">
                <button id="qsLoginBtn" onclick="login()" class="btn btn-primary btn-margin auth-invisible hidden">
                  Log in
                </button>
              </li>
              <!-- / Login button -->

              <!-- Fullsize dropdown: show if authenticated -->
              <li class="nav-item dropdown auth-visible hidden">
                <a class="nav-link dropdown-toggle" href="#" id="profileDropDown" data-toggle="dropdown">
                  <!-- Profile image should be set to the profile picture from the id token -->
                  <img alt="Profile picture" class="nav-user-profile profile-image rounded-circle" width="50" />
                </a>
                <div class="dropdown-menu">
                  <!-- Show the user's full name from the id token here -->
                  <div class="dropdown-header nav-user-name user-name"></div>
                  <a href="/profile" class="dropdown-item dropdown-profile route-link">
                    <i class="fas fa-user mr-3"></i> Profile
                  </a>
                  <a href="#" class="dropdown-item" id="qsLogoutBtn" onclick="logout()">
                    <i class="fas fa-power-off mr-3"></i> Log out
                  </a>
                </div>
              </li>
              <!-- /Fullsize dropdown -->
            </ul>

            <!-- Responsive login button: show if NOT authenticated -->
            <ul class="navbar-nav d-md-none auth-invisible">
              <button class="btn btn-primary btn-block auth-invisible hidden" id="qsLoginBtn" onclick="login()">
                Log in
              </button>
            </ul>
            <!-- /ログインボタンの応答有 -->

            <!-- Responsive profile dropdown: show if authenticated -->
            <ul class="navbar-nav d-md-none auth-visible hidden justify-content-between" style="min-height: 125px">
              <li class="nav-item">
                <span class="user-info">
                  <!-- Profile image should be set to the profile picture from the id token -->
                  <img alt="Profile picture" class="nav-user-profile d-inline-block profile-image rounded-circle mr-3"
                    width="50" />
                  <!-- Show the user's full name from the id token here -->
                  <h6 class="d-inline-block nav-user-name user-name"></h6>
                </span>
              </li>
              <li>
                <i class="fas fa-user mr-3"></i>
                <a href="/profile" class="route-link">Profile</a>
              </li>

              <li>
                <i class="fas fa-power-off mr-3"></i>
                <a href="#" id="qsLogoutBtn" onclick="logout()">Log out</a>
              </li>
            </ul>
          </div>
        </div>
      </nav>
    </div>

    <div id="main-content" class="container mt-5 flex-grow-1">
      <div id="content-home" class="page">
        <div class="text-center hero">
          <img class="mb-3 app-logo"
            src="https://images.unsplash.com/photo-1555041469-a586c61ea9bc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80"
            alt="JavaScript logo"/>
          <h1 class="mb-4">furniture</h1>

          <p class="lead">

        </div>

        <div class="next-steps auth-visible hidden"> <!-- div class=  auth-visible hidden で非表示 -->
          <h2 class="my-5 text-center">あなたの好きな家具は?</h2>



          <div class="row">
            <div class="col-md-5 mb-4">
                <img src="http://.jpg" 
                width="200" 
                height="200"/>
                <h6 class="mb-3">
                  <p>イームズ サイドシェルチェア

                  </p>
                  <button type="button" onclick="myFunction1()"class="btn btn-info">解説</button>
                  <p><div id="demo" onclick="myFunction1();"></div></h6></p>
            </div>

            <div class="col-md"></div>
            <div class="col-md-5 mb-4 ">
              <img src="http://.jpg" 
              width="200" 
              height="200"/>
              <h6 class="mb-3">
                  <p>ハンス・J・ウェグナー ザ・チェアペーパーコード <br>
                    PP-503PC
                  </p>
                  <button type="button" onclick="myFunction2()"class="btn btn-info">解説</button>
                  <p><div id="demo2" onclick="myFunction2();"></div></h6></p>
            </div>

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

      <div class="page" id="content-profile">
        <div class="container">
          <div class="row align-items-center profile-header">
            <div class="col-md-2">
              <img alt="User's profile picture" class="rounded-circle img-fluid profile-image mb-3 mb-md-0" />
            </div>
            <div class="col-md">
              <h2 class="user-name"></h2>
              <p class="lead text-muted user-email"></p>
            </div>
          </div>

          <div class="row">
            <pre class="rounded">
                <code id="profile-data" class="json"></code></pre>
          </div>
        </div>
      </div>
    </div>

    <footer class="bg-light text-center p-5">
      <div class="logo"></div>
      <p>
        project provided by
        <a href="https://auth0.com">SAYUMI</a>
      </p>
    </footer>
  </div>

  <script>
    function myFunction1() {
      document.getElementById("demo").innerHTML = 'ミッドセンチュリーを代表する Charles&Ray Eames(チャールズ&レイ・イームズ)の代表作DSRチェア。';
    }
  </script>

<script>
  function myFunction2() {
    document.getElementById("demo2").innerHTML = " デンマークの巨匠デザイナー、ハンス・J・ウェグナー。代表作とも言えるのが、この「ザ・チェア」です。";
  }
</script>



  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.bundle.min.js"></script>
  <script src="js/auth0-theme.min.js"></script>
  <script src="https://cdn.auth0.com/js/auth0-spa-js/1.2/auth0-spa-js.production.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/highlight.min.js"></script>
  <script src="js/ui.js"></script>
  <script src="js/app.js"></script>
</body>

</html>

完成

2019-12-11_05h32_18.png

2019-12-11_05h32_46.png

image.png
【ログイン後】
image.png

コピペなので隠しちゃいましたが、解説ボタンを押すと解説が表示されるようになりました!!

2019-12-11_05h34_22.png

今回は同じサイト内で、ログインの有無を条件に非表示と表示のコードを書き分けて作成しました。
ありがとうございました!!

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

全自動タピオカインスタ映えロボットを作った話

はじめに

釣りタイトルっぽくてすみません。
Webからロボティクスまで、いろんな技術を使ってタピオカを茹でたりバエさせたりした話をまとめました。
まずは全体像を紹介し、細かい技術的な話はリクエストなどがあれば後々まとめようかなという感じです。

ことの始まり

なんだなんだこの列は...

待っていたのは黒のつぶつぶが入った飲み物。
そう、タピオカティー。

「タピオカティー」は今年もっとも流行ったといっても過言ではないです。
ぐるなび総研は2019年の日本の世相を反映し象徴する「今年の一皿」としてタピオカを選んだそうです。(詳しくはこちら)

Instagramに投稿されていくタピオカティー。
Instagramで#タピオカを検索すると2,161,212件(※2019/11/28 現在)もヒットしました。
keywordtoolで調べたところ、Googleでのタピオカ検索数は以下のようになっています。
タピオカ Google検索数

「タピオカを茹で、ミルクティーを入れ、そしてバエさせインスタに投稿する。」

これから話すのは、ひょんなことをキッカケに大学のメンバー4人が集い、この工程を技術で実現できないかやってみた話です。
通称、インスタピオカロボットを作ってタピオカをバエさせるまでで詰まったところや思ったところなどをまとめました。

筆者は「バエさせる画像を選択する/Web View」「インスタ投稿」を主に担当し、バエさせる角度の検討や多少のモデリング、このロボットを作ったことのまとめとしてポスター作成などをしました。

全体構成

このロボットを作るときにまず行ったのが細分化です。
後々のバグ発見の速度をあげるために、それぞれ依存関係を無くし、独立して動くようにしました。(マイクロサービスのイメージ。)
その後、メッセージのやり取りで全体を動かしていくことを目標にしました。
その結果、ほとんど全てROS Topicで通信しています。(ROSのwikiはこちら)

Web Viewでどの画像がタップされたのか、MATLABとの連携、Arduinoとの連携もROSで通信しています。

puppeteerの部分は新規カメラ画像が取得されたことを検知して実行されるようになっています。

システム全体の流れとしては、
「WebViewで参考にするバエ画像を選択」
   ↓
「参考画像のIDからあらかじめ出力しておいた角度に変換」
   ↓
「タピオカをゆでる」
   ↓
「タピオカミルクティーにする」
   ↓
「バエさせたいタピオカミルクティーのコップの角度が先の参考画像と角度と一致するようにロボットアームを動かす」
   ↓
「写真を撮る」
   ↓
「Instagramに投稿する」
です。

当初はこの流れでやりたかったのですが、Arduino Megaが燃えるなどの諸処のトラブルで茹でるところとミルクティーにするところはこの時期を書いたときにはまだ実装してません。それ以外のところは実装済みです。
system.png

環境

  • Linux Ubuntu 16.04 LTS
  • Arduino Mega
  • ROS Kinetic
  • Python2.7(ROSの関係で)
  • Html/CSS/JavaScript
  • MATLAB Release 2019a
  • Headless Chrome

バエさせる画像を選択する/Web View

限りなくInstagramのUIに近づけました。
CSS Flexbox(Flexible Box Layout Module)で作りました。
わかりにくいですが周りの余白もInstagramのUIと同じカラーになっています。

じっくりInstagramのUIを触ってみて初めてわかったのですが、興味深いことにWeb版のUIをどんどん縮小していく(スマホサイズにしていく)とスマホのアプリでよく見るスタイルになります。
そしてWeb版だと投稿するボタンがないので普通にやろうとしてもできないみたいです。(もしかしたらできるのかも?)

今回はスマホで見ることはないので実装したUIもそこまではしませんでしたが、PCではしっかり動くようなレスポンシブデザインにしました。
Screen Shot 2019-12-10 at 2.24.36.png

UIも少しだけこだわりました。
よくある?画像をホバーしたときに暗くなるやつとか...

insta1.gif

進捗情報をROS Topicで取得して更新するなどもできるようになりました。

insta2.gif

茹でる

CAD図は以下に置いておきます。(作った人すごい!)
タピオカを入れ、タピオカを茹でる機構を実装しました。
原点にリミットスイッチが付いているのでそこで原点出しができます。(3Dプリンタのアレ)
エンコーダ付きのブラシレスモータでX, Z軸を移動できます。
おたまの駆動とミルクティーを入れるための蛇口部分はサーボモータで制御しています。

cad.png

バエる角度検出

Sobelエッジ検出法とハフ変換でタピオカのカップの楕円を検出します。
Sobelエッジ検出法でエッジを検出し、ハフ変換で楕円の検出を行いました。
速度を求めるためにここはMATLABで実装しました。
実際にエッジの検出結果は以下のようになりました。

cup.png

実際に撮られるようなプラスチック製のカップはまだまだ調整が必要だなと感じました。
cup2.png

間違って背景も検出されてしまうのでYOLOv3などを使ってカップを先に検出してからカップの楕円を検出するなどの技術的工夫は必要そうでした。

cup3.png

バエる角度にカメラを動かす

バエる角度がわかったらその位置にカメラを動かす必要があります。
これはマニピュレータを作って動かしました。(作った人すごい!)
青いブロックをタピオカとしたときに、円弧を描くように逆運動学を解いて関節の角度を計算します。
これで先ほどのモデルとなる画像と新しく撮るタピオカのバエ角度が一致した場合、写真を撮ります。

normaldist_kde_anim.gif

インスタ投稿

Instagramには2つのAPI(旧API新API)があります.(※旧APIは2020年には完全廃止予定)

新APIに関してはビジネスアカウント向けとなっているため、そもそも個人のアカウントであれこれできないらしい。
いやいやあるだろと思って探してみたところ、画像を投稿するContent Publishing APIを発見。
しかし、一番上にこんなことが書いてありました。

The Content Publishing API is in closed beta with Facebook Marketing Partners and Instagram Partners only. We are not accepting new applicants at this time.

要約すると「できない」。

APIではInstagramへのログインができないし、本来やりたかった投稿ができない...。(これができればもっと楽にできた...Twitterにはあるしここら辺どうにかしてほしい...)

人間の手を加えずにインスタに投稿する方法を一旦白紙で考え始め、次に取り掛かったのがpuppeteer(できれば堂々とAPI叩きたかったがないのでしぶしぶ...泣)。
puppeteerでは、人間が直接GUIを操作することなく、ブラウザ(Chrome)を制御できます。
スクレイピングやE2Eテストでよく使われているものです。
人間がブラウザで行えるほとんどのことがpuppeteerで実現できるため、今回の場合はロボットの手と思ってもいいかもしれません(マニピュレータとか使ってスマホ制御するにもコストが以下略)。

気をつけたこと

スクレイピングはできれば避けたかったのですが、仕方ないので法的なギリギリラインで実装することにしました。
ここら辺に関しては初めて行うので取り扱いに十分気をつけつつ、資料や前例などを参考にしました。
場合によっては、刑法上の責任を問われる可能性があるため、事前に入念に調べました(実装よりも大変でした笑)。
もし間違っていることなどがあればご教授いただければと思います。

調べたところによると気をつける必要のある問題は大きく分けて以下の3つ。
1. 著作物
2. Webサイトへの過度なアクセス
3. 利用規約

1. 著作物

そもそも情報の取得に関しては一切行なっていないです。
利用するデータは全て個人で撮影したものに限定しました。
当初はInstagramの#タピオカに投稿されている画像データを使用し、それに類似した画像を撮影し投稿するということをやりたかったのですが、それは利用規約に反するため避けました。

不正な方法を用いて、アカウントの作成、情報へのアクセス、または情報の取得を試みることは禁止されています。
これには、弊社から明示的な許可を得ることなく、自動化された手段を用いてアカウントを作成したり、情報を取得したりする行為が含まれます。

2. Webサイトへの過度なアクセス

スクレイピングでどれだけアクセスしたら違法なのか、という基準が見当たらなかったです(ある場合はとても知りたい)。
そのため前例として岡崎市立中央図書館事件(Livrahack事件)を参考に考えてみました。
この事件では「偽計業務妨害罪」というところが論点になったようで、その罪が成立するかどうかは、スクレイピングによって相手の「業務を妨害したかどうか」が判断基準らしいです。

そのため、ギリギリのラインとなりますが「人間が行えるスピードで投稿(スクレイピング)をする」ことでその業務妨害を避けようと思いました。
当然ですが並列処理などはせず、人間が実際にタイピングをしたり画像を選択するスピードで画像を投稿する、ということです。
連投も控えるようにしました。
これにより、サーバからすれば1ユーザが投稿することと大差がないためここもパスできたと考えます。

3. 利用規約

当然のことではあるが、アカウント作成時に利用規約に同意します。
いくつかの資料を読んでいくと利用規約に「スクレイピング禁止」と書かれているケースがあるらしいです。
実際にInstagramの利用規約を読んでいったところ、一番今回のやりたいことに引っかかりそうなところとして、先にも記述しましたが以下の文章がありました。

不正な方法を用いて、アカウントの作成、情報へのアクセス、または情報の取得を試みることは禁止されています。
これには、弊社から明示的な許可を得ることなく、自動化された手段を用いてアカウントを作成したり、情報を取得したりする行為が含まれます。

今回、アカウント作成は人間の手で行なっています。
情報へのアクセス、または情報の取得を試みる行為に関しては一切行なっていません。
自動化された手段を用いて記述されていることも行なっていないので、これに関してもパスはできると考えました。
(そもそも、データ解析などで大量のInstagramのデータを取得して機械学習する、みたいなことをやっていたらアウトなのかもしれませんが、今回に関しては個人の投稿を人間ができるペースでするので、サーバへの負担が一番の論点と考えました)

以上のことから無理のない範囲で実装を行いました。

実装

無事に画像選択から投稿するところまで実装できました。
フローとしては、図に示すようにログインからファイル変更(バエた画像)を検知し、その画像を投稿します。
このファイル検知の部分で先ほどマニピュレータで撮った画像を検知します。

実装フロー

申し訳ないですが、実装したコードに関しては不正に利用されることを防ぐため伏せておきます。
気になる人は自分で実装してみてください。ただ利用には十分気をつけてほしいです。
puppeteerとInstagramに関していろいろ言いたいことはあるのですが、それはまた別の機会に譲ろうかと思います。
Web版のInstagramでは画像投稿ができないので、しっかりスマホサイズに落としてからpuppeteerで操作する必要があります!(もしかしてスクレイピング防止のためにWeb版は禁止しているのか??)

失敗パターン

しっかり黒幕撮って失敗しました。
エッジ検出周りの精度を上げる必要がありそうです。
あとマニピュレータの剛性がないから作り直し...。
プロトタイプ1号機なので今後とも改良は必要そうです。

最後に

実際に手足頭を動かして実装する、という工程でお互いの技術を理解し、実際にモノを作り上げられたのは楽しかったです。
リアルを相手にすると、重力とか光とか諸々を考慮して作っていかないといけないのでモノづくりは奥深いし楽しいなと思いました!
実装するだけでなくこのように公開するにあたって、なるべくわかりやすく説明するために過去の事例や流行り、法律周りも調べたのですが、その過程で普段気づけなかったいろんなことに触れられて非常に勉強になりました。
またひょんなことから何かしたいなと思います。

最後に、協力してくれたIくん、Kくん、Sくんに感謝!

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

新人でも、楽がしたい! ~議事録の準備~

「この業務は新人がやる」
誰が言い出したのか分かりませんが、どんな会社でも
「新人に担当させる仕事」
があると思います。

さらに、
「新人への評価は、仕事をいかに効率よく処理するかで決まる」
というのも間違いありません。

今回は、面倒なこと 特に雑務 が嫌いな私が楽をするために、新人業務を自動化したお話をご紹介します。

最後までお読みいただくと、以下のアウトプットを自動化するノウハウが得られます。
こちらは、MTGの時間になったら自動で議事録を準備してくれるアプリケーションです。
Screen Shot 2019-12-12 at 1.58.31.png

はじめに

この記事に興味を持っていただき、誠にありがとうございます。
私は訳あって2019年7月に新卒入社し、3ヶ月間の研修の後、10月に現場配属された新米エンジニアです。

2ヶ月ほど働いていると、
「いつも同じことやってるな」
「これって自動化できそうだな」
と感じるネタが溜まってきたので、それらを「自動化した話」をご紹介します。

新人の仕事 ≒「議事録の準備」を自動化したお話です。

新人 → 議事録の準備

社内外を問わず、MTGが開かれれば議事録、議事メモの準備は必須です。

私には「MTGが開かれる前までに、議事録のフォーマットを整備し、MTG開始後すぐにメモ取れる状況にする」というタスクが与えられたので、Googleカレンダーにリマインドをセットして、毎回手作業で議事録を準備していました。

手作業での準備手順は、大まかには以下のプロセスです。
1. GoogleドライブにMTG用のフォルダを作成
2. フォルダ内にGoogleドキュメントを新規作成
2. ヘッダーとフッターに Credential を記入
3. MTGの基本情報(日時、場所、出席者)を記入
4. ドキュメントへのURLをSlackに投稿

配属当初は難なくこなしていたのですが
- だんだん面倒になってくる
- 移動がバタバタしていると、準備を忘れる
- いつも「前回のフォーマットをコピー」して流用している

というマンネリ状態になりました。
そこで、議事録の準備をオール自動化しました。

用意するもの

  • Slack の Incoming webhook
  • 議事録のテンプレート(Google ドキュメント)
  • 議事録を作成する会議通知名の一覧(Google スプレッドシート)
  • Google App Script の実行環境
  • (このプログラム以降は、もう議事録は手動で準備しないという意思)

全体の流れ

大まかな処理の流れは、
1. GAS を定期実行
2. Google カレンダーの、直近のイベントを取得
3. 議事録を作成する会議通知名の一覧を取得
4. [2] == [3] となるイベントが存在したら、議事録準備開始
5. 準備した議事録へのURLを Slack に投稿する

となります。
作業的に依存関係のない部分から順に説明します。

作業開始

以下、議事録の準備を自動化するための、環境整備についてです。

議事録を作る会議通知名の一覧

Googleアカウントと共に仕事をされる方であれば、Google カレンダーを確認すれば、多種多様な「会議通知」が登録されていると思います。それらの会議通知のうち、「議事録を作成するMTG」のイベント名をスプレットシートに書き出します。

ex)
議事録を作成する会議通知名が
- インフラ進捗報告
- アプリ進捗報告
- 【PJ】週次定例会
- 【内部】隔週会
の場合
Screen Shot 2019-12-12 at 0.23.40.png

この時、作成したスプレッドシートのURLは
https://docs.google.com/spreadsheets/d/abcedfg12345/edit?folder=hogefuga#gid=0
のようになります。この時の idabcedfg12345 になります。id は Google App Scriptとの連携時に用いるので、必要になり次第確認してください。

議事録のテンプレート

議事録は「毎回ゼロから」作成するのではなく「ある程度のフォーマットが決まって」おり、それに従って作成するものだと思います。過去の議事録を参照すれば「毎回必ず記入する部分」が見つかるので、それらを網羅した議事録のテンプレートを Google ドキュメントで作成します。

毎回必ず記入する部分の候補としては、以下が考えられます。
- ヘッダーとフッターのCredential
- ページ番号
- 会議名記入欄
- 会議場所記入欄
- 出席者記入欄
- 決定事項/TODOの記入欄

私が作成したテンプレートでは、「出席者記入欄」には MTGに出席する可能性のある全員の名前 を記入しています。それにより、実際のMTG中は「いない人を出席者から削除する」のみで済み「あの人誰?→ 名刺リストを確認 → この人か!」を回避できます。

Google ドキュメントで作成した議事録のテンプレートにも id があります。
こちらも後の作業で利用しますので、必要になり次第確認してください。

Slack Incoming Webhook の発行

slack api から、incoming webhook を発行してください。
incoming webhook の値は厳重管理してください。

Google App Script のコーディング

事前準備が完了したので、議事録作成を代行してくれるコードの作成を開始します。

今回は GAS の処理を役割ごとに関数化したので、関数ごとに各機能を説明します。
- カレンダーから直近のイベント名を取得
- スプレッドシートから議事録を作る会議通知名を取得
- 議事録を作成するかの判定
- 議事録を新規作成し、slackにURLを投稿

カレンダーから直近のイベント名を取得

Google アカウントユーザの、直近1時間の予定一覧を取得します。

function getCalendarEventList() {

  var now = new Date();
  var oneHourFromNow = new Date(now.getTime() + (1 * 60 * 60 * 1000));
  var events = CalendarApp.getDefaultCalendar().getEvents(now, oneHourFromNow);

  var eventList = [];
  for (i = 0; i < events.length; i++) {
    eventList.push(events[i]);
  }

  // Googleカレンダーに登録された「直近1時間分」のイベントの配列を返す
  return eventList;  
}

スプレッドシートから議事録を作る会議通知名を取得

議事録を作成するMTG名が記入されたスプレッドシートから、そのMTG名一覧を取得します。スプレッドシートの読み込みは「関数の実行ごと」に行うので、MTG名の追加/削除は自由に実施できます。

function getEventTitleFromSpredsheet() {

  // 議事録を作成するカレンダーの会議通知名一覧が記載されたスプレッドシートのID
  var SPREDSHEETPATH = "<spredsheet_id>"

  var ss = SpreadsheetApp.openById(SPREDSHEETPATH);
  var sheet = ss.getSheets()[0];
  var range = sheet.getRange(1, 1, ss.getLastRow());
  var values = range.getValues();

  var meethingList = [];
  for (i = 0; i < values.length; i++) {
    value = values[i];
    meethingList.push(value[0]);
  }

  // スプレッドシートに記載されたMTG名の配列を返す
  return meethingList;
}

議事録を作成するかの判定

「議事録を準備するか」を判定してくれるロジックを実装します。
会議1つに議事録1つが基本なので、会議の開始時間を判定軸としたロジックも追加します。

function start(){

  // 議事録自動準備プログラムで、最初に起動する関数
  // 「直近のイベント一覧」と「議事録を作成するMTG名」を取得し、一致する名前があれば議事録を準備する

  meethingList = getEventTitleFromSpredsheet();
  eventList = getCalendarEventList();

  var createFlag = false;
  var meethingName = '';

  for (i = 0; i < meethingList.length; i++) {
    for (j = 0; j < eventList.length; j++) {
      if (meethingList[i] == eventList[j].getTitle()) {

        var functionTriggeredTime = new Date();
        var meethingStartTime = eventList[j].getStartTime();

        // 会議の開始時間が、プログラム起動時間より後ならば、createFlag=trueにする。
        // 会議中にプログラムが起動した場合には、議事録は作成されない。
        if (functionTriggeredTime < meethingStartTime){
          createFlag = true;
          meethingName = eventList[j].getTitle();
        }

      }
    }
  }

  if (createFlag) {
    createMinutes(meethingName);
    Logger.log('議事録を準備しました。');
    Logger.log('MTG名:' + meethingName);
  } else {
    Logger.log('議事録は準備されませんでした。');
  }
}

議事録を新規作成し、slackにURLを投稿

「議事録を作成する」と判定された場合の、議事録を作成するロジックを組みます。
<folder_id> には、新規作成された議事録を保存するフォルダの id を入力してください。

function prepareMinutes(meethingName){

  // 議事録を作成するフォルダのID
  var FOLDERID = '<folder_id>';
  // 議事録のフォーマットファイルのID
  var FILEID = '<document_id>';
  // slack Incoming Webhook
  var POSTURL = '<slack_incoming_webhook>';

  // 議事録のフォーマット、作業フォルダを取得する
  var folderPath = DriveApp.getFolderById(FOLDERID);
  var formatFilePath = DriveApp.getFileById(FILEID);

  // 現在時刻から「YYYYddmm_(会議名)」 の文字列を作成する
  var today = new Date();
  var year = today.getFullYear();
  var month = '' + (today.getMonth() + 1);
  var day = '' + today.getDate();
  if (month.length < 2)
    month = '0' + month;
  if (day.length < 2)
    day = '0' + day;

  var YYYYmmdd = year + month + day;
  var minutesName = YYYYmmdd + '_' + meethingName; // 議事録のファイル名:「YYYYmmdd_MTG名」

  // 議事録フォーマットをコピーして、指定のフォルダに議事録を新規作成する
  var newFile = formatFilePath.makeCopy(minutesName, folderPath);
  var fileUrl = newFile.getUrl();

  // slackに議事録へのurlを投稿する
  var message = '議事録を準備しました。' + '\n\n' + '会議名:' + minutesName + '\n' + 'URL : ' + fileUrl;
  var jsonData = {
    'text':message
  };
  var payload = JSON.stringify(jsonData);
  var options = {
    'method':'post',
    'contentType':'application/json',
    'payload':payload
  };

  UrlFetchApp.fetch(POSTURL, options);
}

以上の処理により、「議事録を自動で準備させる」プログラムの完成です。
最後に、プログラム定期実行の設定をします。

GAS の定期実行

Google App Script の Current project's triggers からプログラムの定期実行が設定できます。
Add Trigger で以下の項目を入力し、GAS を1時間ごとに起動させます。

Screen Shot 2019-12-12 at 1.32.59.png

slackへの議事録URLの投稿

正しく動作すれば、MTG時間になると、slackで以下の投稿が得られます。
Screen Shot 2019-12-12 at 1.58.31.png

まとめ

以上により、議事録の準備はオール自動化されました。
事前準備、コーディング作業は大変ですが、1度作ってしまえば、もう議事録準備に費やす工数は無くなると思われます。

以上、長文にお付き合いいただき、有難うございました。

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

ipadでブラウザ拡張機能のようなものを実行してみた

はじめに

自作したブラウザ拡張機能をスマホやタブレットのブラウザでも使えないかなと思って調べてみました。

すると
Androidではいくつか方法があるが
iosでは拡張機能を使う方法がないようです。

それでも何とかiosでブラウザ拡張機能を実行できないかと試したところ

iosの「ショートカット」アプリを使うことで、コンテンツスクリプトを使ったchrome拡張機能を実装できました。

やったこと

iosの「ショートカット」アプリを使ってamazonDriveを画像Viewer化する自作拡張機能ををipadのsafariブラウザ上で実行した。

実行結果

hyilhl.gif

仕組み

1.safariで拡張機能を実行したいwebページを開く
2.ショートカットを実行
3.githubから拡張機能に必要なファイルを取得する
4.取得したファイルからDOMを生成し、jsを実行する
Slide1.jpg

githubにリポジトリを作成し、コードを追加する

chrome拡張機能の置き場所はcorsによるクロスドメイン通信でファイルが取得できるなら何処でもいいです。
コードをgitで管理したかったので今回はgithubに置きました。

無題.png

ショートカットアプリからJS実行

「ショートカット」アプリの使い方はこちらのqiita記事を参考にさせて頂きました。

iPhone 「ショートカット」アプリで JavaScript を実行する

1.「ショートカットを作成」を選択
2.検索欄に「Web」と入力
3.「WebページでJavaScriptを実行」を選択

ショートカットを下記の内容に変更します。

function Main() {

    let jsCount = 0;

    this.run = function () {
        run();
    }

    function run() {

        const base = "https://api.github.com/repos/shiraki-s/amazonDrive/contents/";
        //chrome拡張機能のmanifest.jsonを取得する
        request(base + "manifest.json", function (text) {

            const json = JSON.parse(text);
            const decode = decodeURIComponent(escape(window.atob(json.content)));
            const json2 = JSON.parse(decode);

            //manifest.jsonからjsとcssの相対パスを取得する
            const jses = json2.content_scripts[0].js;
            const csses = json2.content_scripts[0].css;

            //cssをgithubから取得しテキストとして読み込み、DOMを生成
            createCssTag(base, csses);

            //jsをgithubから取得しテキストとして読み込み、DOMを生成
            createJsTag(base, jses, function (script) {

                //全てのjsファイルのDOM生成が完了したら、コンテンツスクリプトを実行する
                init(base, script, jses.length);
            });
        });

    }

    function init(base, script, max) {

        setTimeout(function () {

            if (max == jsCount) {

                const s = document.createElement('script');
                s.innerHTML = script + ' new DriveManager().init("' + base + '");';
                document.body.appendChild(s);

            } else {
                init(base, script, max);
            }


        }, 500);

    }

    function createCssTag(base, array) {

        for (let i = 0, len = array.length; i < len; i++) {

            request(base + array[i], function (text) {
                const json = JSON.parse(text);
                const decode = decodeURIComponent(escape(window.atob(json.content)));

                const style = document.createElement('style');
                style.type = 'text/css';
                style.innerHTML = decode;
                document.body.appendChild(style);
            });

        }

    }

    function createJsTag(base, array, onLoad) {

        for (let i = 0, len = array.length; i < len; i++) {

            const index = i;

            request(base + array[i], function (text) {

                jsCount++;
                const json = JSON.parse(text);
                const decode = decodeURIComponent(escape(window.atob(json.content)));

                if (index == array.length - 1) {
                    onLoad(decode);
                    return;
                }

                const s = document.createElement('script');
                s.innerHTML = decode;
                document.body.appendChild(s);
            });

        }

    }

    function request(url, callback) {

        var request = new XMLHttpRequest();

        request.onreadystatechange = function () {

            if (request.readyState == 4) {

                if (request.status == 200) {
                    callback(request.responseText);
                    return;
                }
            }

        }

        request.open("GET", url, true);
        request.send();
    }

}

var result = [];
new Main().run();

completion(result);

4.設定から「共有シートに表示」をON
5.完了
6.safariから作成したショートカットを実行する

最後に

この方法でコンテンツスクリプトを使ったchrome拡張機能をipadのsafariで実行できました。

今回は自作した拡張機能のコードを使用したので問題はないですが
この方法ではどんなjavascriptでも実行が可能なので、第三者が作成したコードを使う際には注意が必要です。

最後まで読んでいただき、ありがとうございました。

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

Javascriptまとめ 【if文】【for文】2019.12.11

【if文】

if(条件式1) {
  条件式1がtureのとき実行される処理
} else if(条件式2){
  条件式1がfalseで、条件式2がtureのとき実行される処理
} else {
  条件式1、2がfalseのとき実行される処理
}

※条件式の()と、処理文の{}は省略不可

【for文】

for(let i = 0; i < 10; i = i + 1){
  繰り返される処理
}

※「i < 10」の部分で回数を決めている
※「i = i + 1」の部分は処理が1回繰り返されるごとに呼ばれる

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

Redux: redux-sagaで非同期処理を試す ー 公式のカウンター作例を使って

redux-sagaは、非同期処理が同期的に書け、複数の処理を同時並行で実行できるライブラリです。GitHubのredux-saga/examples/には、簡単なカウンターの作例が3つ収められています。その中でもっともシンプルなredux-saga/examples/counter-vanillaは、index.htmlひとつにまとめられていて、ビルドが要りません。この作例(サンプル001)のコードを段階に分けてご説明しましょう。さらにReactも加えて、コンポーネントで組み立てた作例は「React + Redux: redux-sagaで非同期処理を試す」で解説しています。ご興味のある方はご覧ください。

サンプル001■Redux: Counter example with redux-saga

See the Pen Redux: Counter example with redux-saga by Fumio Nonaka (@FumioNonaka) on CodePen.

Reduxでカウンターをつくる

JavaScriptコードを書きはじめる前に、CDNからのReduxとredux-sagaのライブラリの読み込みです。公式作例はunpkgを用いています。けれど、redux-sagaのバージョンが1.0.0-beta.1と少し古いようです。そこで、新しいライブラリ(1.0.5)が手に入るcdnjsに差し替えましょう。

<!-- <script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
<script src="https://unpkg.com/redux-saga@1.0.0-beta.1/dist/redux-saga.min.js"></script> -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.4/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux-saga/1.0.5/redux-saga.umd.min.js"></script>

まず、redux-sagaは使わずに、Reduxのアプリケーションをつくります。数値を含むカウンターのテキストと、ボタンは[+][-][Increment if odd]の4つです。

<div>
    <p>
    Clicked: <span id="value">0</span> times
    <button id="increment">+</button>
    <button id="decrement">-</button>
    <button id="incrementIfOdd">Increment if odd</button>
    </p>
</div>

Reduxアプリケーションでは、処理の開始をActionで知らせます。Actionを受け取って、どのActionかtypeで判別して結果を返すのが、つぎのReducerです。今回stateは数値で、その加算あるいは減算が行われます。

function counter(state, action) {
    if (typeof state === 'undefined') {
        return 0
    }
    switch (action.type) {
        case 'INCREMENT':
            return state + 1
        case 'DECREMENT':
            return state - 1
        default:
            return state
    }
}

Reduxはアプリケーションにひとつだけ備わるStoreが状態を一手に管理します。そのStoreをつくるのがcreateStore()です(「React + Redux入門 02: フィールドに入力したテキストを項目リストに加える」02「Storeをつくる」参照)。引数にはReducerを渡します。

ボタンクリック(clickイベント)のリスナー関数から呼び出しているのが、Actionを送り出すdispatch()です。引数のActionオブジェクトは、少なくとも識別のためのプロパティtypeをもたなければなりません。これが、前掲Reducerによる判別に使われるのです。

subscribe()は、Actionが送られるたびに、引数のリスナー関数を呼び出します。Storeがもつstateを参照するのがgetState()です。

var store = Redux.createStore(
    counter
)
var valueEl = document.getElementById('value')
function render() {
    valueEl.innerHTML = store.getState().toString()
}
render()
store.subscribe(render)
document.getElementById('increment')
.addEventListener('click', function () {
    store.dispatch({ type: 'INCREMENT' })
})
document.getElementById('decrement')
.addEventListener('click', function () {
    store.dispatch({ type: 'DECREMENT' })
})
document.getElementById('incrementIfOdd')
.addEventListener('click', function () {
    if (store.getState() % 2 !== 0) {
    store.dispatch({ type: 'INCREMENT' })
    }
})

このコードにより、ボタンごとに決められたActionが送り出され、カウンタの数値となるstateの値が変わります。すると、ページのカウンタの数がその値で書き替えるという仕組みです。なお、[Increment if odd]のボタンは、カウンタが奇数のときのみカウントアップします。

redux-sagaで非同期処理を行う

つぎに、redux-sagaで非同期の処理を行いましょう。SagaはReducerに替わって、Storeに送られるあらかじめ決められたActionを監視します。コードは以下のとおりてす。Sagaのアプリケーションへの組み込みと起動は、このあとご説明します。

非同期の処理を扱うため、ジェネレーター関数のfunction*宣言を用いることにご注目ください(「ジェネレーター関数」参照)。rootSaga()が、あとでミドルウェアから実行される関数です。yieldキーワードに定めたtakeEvery()は、第1引数(INCREMENT_ASYNC)のActionを監視し、送り出されるたびに第2引数(incrementAsync())の関数を呼び出します。

関数incrementAsync()は、delay()で引数のミリ秒数実行を止めます。実行再開後、Storeに引数オブジェクトのActionを送り出すのがput()です。つまり、1秒待ってカウントアップされることになります。

const effects = ReduxSaga.effects
function* incrementAsync() {
    yield effects.delay(1000)
    yield effects.put({type: 'INCREMENT'})
}
function* counterSaga() {
    yield effects.takeEvery('INCREMENT_ASYNC', incrementAsync)
}

それでは、SagaをStoreにミドルウェアとして組み込みます。Sagaミドルウェアをつくるのが、createSagaMiddleware()です。ミドルウェアは、applyMiddleware()でStoreに適用します。そのうえで、run()によりSagaを実行してください。前掲関数render()を定義するコードの前に加えるのが、つぎのステートメントです。

const createSagaMiddleware = ReduxSaga.default
const sagaMiddleware = createSagaMiddleware()
var store = Redux.createStore(
    counter,
    Redux.applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(counterSaga)

こうして、前掲サンプル001の作例が書き上がりました。

コードの構文を改める

サンプル001にはECMAScript 2015のconstと古いvar宣言が混じっています。コード全体をECMAScript 2015の構文に改めましょう1。まず、宣言varは定数constに書き替えます(値を上書きしませんので、letは使いません)。

// var store = Redux.createStore(
const store = Redux.createStore(
    counter,
    Redux.applyMiddleware(sagaMiddleware)
)

// var valueEl = document.getElementById('value')
const valueEl = document.getElementById('value')

つぎに、無名関数にはアロー関数式=>を用いましょう。

document.getElementById('increment')
// .addEventListener('click', function () {
.addEventListener('click', () =>
    store.dispatch({ type: 'INCREMENT' })
)
document.getElementById('decrement')
// .addEventListener('click', function () {
.addEventListener('click', () =>
    store.dispatch({ type: 'DECREMENT' })
)
document.getElementById('incrementIfOdd')
// .addEventListener('click', function () {
.addEventListener('click', () => {
    if (store.getState() % 2 !== 0) {
        store.dispatch({ type: 'INCREMENT' })
    }
})
document.getElementById('incrementAsync')
// .addEventListener('click', function () {
.addEventListener('click', () =>
    store.dispatch({ type: 'INCREMENT_ASYNC' })
)

これでECMAScript 2015の構文にできました。あとひとつ、プロパティのinnerHTMLtextContentに差し替えます。セキュリティやパフォーマンスの点から、後者で足りる場合にはこちらを使った方がよいからです。MDNのリファレンスから、引用しておきましょう。

「element.innerHTML」「セキュリティの考慮事項

プレーンテキストを挿入するときにはinnerHTMLを使用せず、代わりにNode.textContentを使用することをお勧めします。これは渡されたコンテンツを HTML として解釈するのではなく、生テキストとして挿入します。

「Node.textContent」「innerHTMLとの違い

textContentはパフォーマンスを向上させる場合があります。テキストが HTML として解析されないためです。さらに、textContentを使用することで XSS 攻撃を防ぐことができます。

function render() {
    // valueEl.innerHTML = store.getState().toString()
    valueEl.textContent = store.getState().toString()
}

構文を改めたサンプル002もCodePenに公開しました。

サンプル002■ Redux + ES6: Counter example with redux-saga

See the Pen Redux + ES6: Counter example with redux-saga by Fumio Nonaka (@FumioNonaka) on CodePen.


  1. GitHubのREADME_ja.mdで「counter-vanilla」の説明を見ると、「ES2015を使っていない素のJavaScriptとUMDビルドを使用したデモ」と書かれています。けれど、英語の「counter-vanilla」はECMAScript 2015には触れていません。おそらく、日本語版の説明が古いのでしょう。 

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

React.useRef()を使って無駄なレンダリングを減らそう

はじめに

本記事は、ReactのuseRefについて紹介する入門的記事です。公式に書いてある内容の焼き直しみたいな物なので、ちょっとrefについて理解が怪しいかな?と思う方が読者対象になります。

React refの基本的な使い方・登場シーン

さて、refの主な登場シーンとしてはDOMに紐付けて使う場合が多いのではないでしょうか。

inputとref(CodeSandbox)

Focus Inputボタンを押すとinputエリアにフォーカスします。

const App = () => {
  const textInputRef = useRef();
  return (
    <div className="App">
      <h1>useRef with Text Input</h1>
      <button onClick={() => textInputRef.current.focus()}>Focus Input</button>
      <input ref={textInputRef} type="text" />
    </div>
  );
};

videoとref(CodeSandbox)

初回レンダリング時にvideo srcが設定され、videoはmp4ファイルを再生できる状態でレンダリングされます。

const App = () => {
  const videoRef = useRef();
  useEffect(() => {
    videoRef.current.src =
      'https://sample-videos.com/video123/mp4/240/big_buck_bunny_240p_10mb.mp4';
  }, []);
  return (
    <div className="App">
      <h1>useRef with Video</h1>
      <video ref={videoRef} controls width={360} height={240} />
    </div>
  );
};

DOMと組み合わせるだけがrefの全てではない(おおげさ)

ここからが、記事タイトルについての中身になります。
useState()はlocal stateを管理できるHooksですが、useState()で用意したstate変数の更新用関数を呼ぶと、その時点で再レンダリングが走ります。以下で言うところのsetState関数が更新用関数ですね。

const [state, setState] = useState();

そのため、再レンダリングが必要ない値の管理にuseState()を使うと無駄なレンダリングが走ることになってしまいます。(シンプルな処理だと実害は少ないかと思います)

そこでuseRef()です。
APIドキュメントFAQにもあるように、useRef()で生成したref変数は値を保持し続けるオブジェクトとして利用できます。

const ref = useRef();

生成されたref変数は{ current: ... }というオブジェクトを与えられ、このref.currentを自由に書き換えることが許されているのですが、これを書き換えた際には再レンダリングが走らないという特性を持ちます。(特性というか単なるJSのオブジェクト変数の書き換えなのですが)
なので、先に述べたように再レンダリングは必要なく値のみを変更して保持するために使えたり、コンポーネントの前後のレンダリング間で値を保持し続けたい場合に、useRef()が使えます。
ということが本記事の伝えたいことになります。なんと。まぁ、公式にも書いてあるんですが。

useState()とuseRef()の比較

最後に、似たような記述内容を書いてuseState()とuseRef()を見比べてみます。
まずはuseState()の場合です。
以下のコードは無限にレンダリングが起こります。
1回目のレンダリング処理でsetText('hoge');が呼ばれると、空文字からhogeへtext変数が更新されて再レンダリングが走ります。そして、それ以降もレンダリングの度にsetText('hoge');が呼ばれるので無限レンダリングとなります。

const Text = () => {
  const [text, setText] = useState('');
  setText('hoge');
  return <div>{text}</div>;
}

次に、useRef()を同じような記述構成で書いてみます。
結果は、1回のレンダリングのみでhogeが表示されます。

const Text = () => {
  const textRef = useRef('');
  textRef.current = 'hoge';
  return <div>{textRef.current}</div>;
}

特に意味のないコードですが、理解のための例でした。

おわりに

公式ドキュメントのおさらいでしたが、APIの用途を知っておくと今後の開発でも役に立つと思います。
是非、無駄なレンダリングが走ってる場面に遭遇したり、レンダリング間で値を保持しい場面に遭遇したらuseRef()のことを思い出してみてください。

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

EasyAutocompleteを日本語対応っぽくする

投稿日、13日の金曜日やんけ~~~

0.前置き

autocompleteを実装するために、jQueryライブラリの「EasyAutocomplete」を使用することにしました。

EasyAutocomplete - jQuery autocomplete plugin

1.問題点

ローマ字でサジェストを出す前提で作られているため、部分一致していないとサジェストを出してくれません。
 → 漢字の候補を用意しただけでは、ローマ字・ひらがな・かたかなの「読み」での検索ができない

今回は都道府県の入力を補完してほしかったので、その機能を補完する設定をまとめてみました。

2.できたもの

ひらがな・カタカナ・ローマ字で都道府県の検索ができます。
(右側はデフォルトのテキストボックスです。)

See the Pen auto suggest by かぶきち (@cubkich) on CodePen.

3.HTML

CodePenでは記載がないですが、動かすためにはjQueryとEasyAutocompleteを先に読み込む必要があります。

index.html
<head>
    <!-- 省略 -->
    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/easy-autocomplete/1.3.5/jquery.easy-autocomplete.min.js"></script>
    <!-- 省略 -->
</head>

サイトに合わせて適宜フォーム等を設置します。

index.html
<form>
    <div>
        <label>
            都道府県            
            <input id="autocomplete">
        </label>
        <label>
            自由入力<br>
            <input type="text" placeholder="自由入力">
        </label>
    </div>
    <button type="submit"> 探す</button>
</form>

今回は普通ののテキストボックス(自由入力)もあります。
(掛け合わせ検索をする前提で作った)

4.CSS

特にMUSTのものは無いので、任意でスタイルをつけてください。
サジェスト部分のリストのスタイルは.easy-autocomplete-container ul liとかで設定できると思います(適当)。

5.JS

大まかな流れ

流れとしては、自作のjQueryプラグインを作り、その中で本家のEasyAutocompleteを実行する形になります。
入力に対してJSONから一致する項目を検索するのですが、「ひらがな」、「カタカナ」、「ローマ字」をそれぞれ登録するのは 面倒くさい 更新性が落ちてしまうので、、カタカナのみを登録し、そこから検索できるようにしています。(JSONは後ほど紹介します)

jquery.easy-autocomplete-extender.js
/* 大まかな流れ */
(function($) {
    /*
        ここで入力文字列を加工して必要なオプションを設定する
            入力文字列→カタカナに変換
            カタカナの読み方から検索→リストを表示
    */
})(jQuery);

$(function() {
    const options = {
        placeholder: "【プレースホルダーのテキスト(任意)】"
    }
    /* 本家プラグインの実行 */
    $("【対象のセレクタ】").areaSuggest("【使用するjsonファイルへのパス】", options);
});

ひらがなをカタカナに変換

この関数を通すことで入力されたひらがなはカタカナに変換されます。

jquery.easy-autocomplete-extender.js
function hiraToKana(str) {
    return str.replace(/[\u3041-\u3096]/g, function(match) {
        var chr = match.charCodeAt(0) + 0x60;
        return String.fromCharCode(chr);
    });
}

ローマ字をカタカナに変換

@recordare さんのJavaScriptでローマ字をカタカナに変換する関数を参考にさせていただきました。
(先人の知恵、ありがとうございます…ありがとうございます…)
(TS→JSにコンパイルして使用しています。)

この関数を通すことで、入力されたローマ字はカタカナに変換されます。

jquery.easy-autocomplete-extender.js
const tree = {
    a: '', i: '', u: '', e: '', o: '',
    // ~~~
};
function convertRomanToKana(original) {
    const str = original.replace(/[A-Za-z]/, function(s) {String.fromCharCode(s.charCodeAt(0) - 65248)}).toLowerCase(), // 全角→半角→小文字
        len = str.length;
    let result = '',
        tmp = '',
        index = 0,
        node = tree;
    const push = function(char, toRoot) {
        toRoot = toRoot ? toRoot : true;
        result += char;
        tmp = '';
        node = toRoot ? tree : node;
    };
    while (index < len) {
        const char = str.charAt(index);
        if (char.match(/[a-z]/)) { // 英数字以外は考慮しない
            if (char in node) {
                const next = node[char];
                if (typeof next === 'string') {
                    push(next);
                } else {
                    tmp += original.charAt(index);
                    node = next;
                }
                index++;
                continue;
            }
            const prev = str.charAt(index - 1);
            if (prev && (prev === 'n' || prev === char)) { // 促音やnへの対応
                push(prev === 'n' ? '' : '', false);
            }
            if (node !== tree && char in tree) { // 今のノードがルート以外だった場合、仕切り直してチェックする
                push(tmp);
                continue;
            }
        }
        push(tmp + char);
        index++;
    }
    tmp = tmp.replace(/n$/, ''); // 末尾のnは変換する
    push(tmp);
    return result;
}

一旦まとめ

ここまで、EasyAutocompleteのオプション部分で使用する関数をつらつらと書いていきましたので、一旦それらを使ってまとめてみます。

jquery.easy-autocomplete-extender.js
/*
* [must] path: jsonまでのパスを入力します
* [any] options: 本家easyAutocompleteで使用できるoptionを設定できます
*/
$.fn.areaSuggest = function(path, options) {
    const _self = $(this);
    options = options || null;

    $.ajax({
        url: path,
        dataType: "json",
    }).done(function(data) {
        const default_options = {
            data: data,
            getValue: function(elm) {
                return elm.name;
            },
            list: {
                match: {
                    enabled: true,
                    method: function(name, phrase) {
                        let input = phrase;
                        if(phrase.match(/^[ぁ-んー]*$/)) {
                            // 入力がひらがなのとき、ひらがな→カタカナ
                            input = hiraToKana(phrase);
                        } else if(phrase.match(/^[a-zA-Z]*$/)) {
                            // 入力がローマ字のとき、ローマ字→カタカナ
                            input = convertRomanToKana(phrase);
                        }
                        // ひらがなでもローマ字でもないとき、そのまま

                        const thisData = $.grep(data,
                            function(elm, i) {
                                if(elm.name === name) {
                                    return elm;
                                }
                            });

                        // カタカナのときはカタカナで検索、それ以外はそのまま検索
                        return input.match(/^[ァ-ンヴー]*$/)
                            ? thisData[0].kana.indexOf(input) > -1
                            : thisData[0].name.indexOf(input) > -1;
                    },
                },
                // 10件まで表示する
                maxNumberOfElements: 10,
            },
            // 入力からから10ミリ秒後に実行
            requestDelay: 400,
            placeholder: "地域を入力してください",
        };
        // オプションが入力されていれば、その内容を書き換え
        if(options) {
            $.each(options, function(i, val) {
                default_options[i] = val;
            });
        }

        // 本家easyAutocompleteの実行
        _self.easyAutocomplete(default_options);
        return _self;
    }).fail(function(data) {
        console.error("ERROR: ajax failed");
    });
}

JSON

検索対象のJSONファイルです。
一応、ベタ書きやxmlでも動くようですが、今回はJSONで準備しています。

入力がカタカナに変換されたときは、"kana"を検索します。
入力が変換されずにそのままのときは、"name"を検索します。

sample.json
[
    {"name": "北海道", "kana": "ホッカイドウ"},
    {"name": "青森県", "kana": "アオモリケン"},
    {"name": "岩手県", "kana": "イワテケン"},
    {"name": "宮城県", "kana": "ミヤギケン"},
    {"name": "秋田県", "kana": "アキタケン"},
    // 続く…
]

まとめ

そのままでは文字の完全一致?しかマッチしなかったeasyAutocompleteが日本語にも対応できるようになりました。
もともと英語圏での使用が想定されていたと思うので、アルファベットの場合はそれだけで十分でしたが、
日本語のひらがな・カタカナ・漢字も網羅できるようになり、使える幅がぐっと広がったような気がします。

サジェスト機能を使いたいけど、Googleカスタム検索とかそこまでじゃないんだよなぁ…ってときに使えるかなぁと。

まぁ、都道府県に限った話でいうと、チェックボックスとかセレクトボックスで良いじゃん?となるかもしれませんが、毎回下の方まで移動するという手間は省けるのではないでしょうか。
(私は比較的上の方なのそんなに困ってはいないのですが笑)

最後に、拡張分のjquery.easy-autocomplete-extender.jsのソースを張っておしまいです。(長い)

jquery.easy-autocomplete-extender.js
/* jquery.easy-autocomplete-extender.js */
(function($) {
    $.fn.areaSuggest = function(path, options) {
        const _self = $(this);
        options = options ? options : null;
        $.ajax({
            url: path,
            dataType: "json",
        }).done(function(data) {
            const default_options = {
                data: data,
                getValue: function(elm) {
                    return elm.name;
                },
                list: {
                    match: {
                        enabled: true,
                        method: function(name, phrase) {
                            let input = phrase;
                            if(phrase.match(/^[ぁ-んー]*$/)) {
                                input = hiraToKana(phrase);
                            } else if(phrase.match(/^[a-zA-Z]*$/)) {
                                input = convertRomanToKana(phrase);
                            }

                            const thisData = $.grep(data,
                                function(elm, i) {
                                    if(elm.name === name) {
                                        return elm;
                                    }
                                });

                            return input.match(/^[ァ-ンヴー]*$/)
                                ? thisData[0].kana.indexOf(input) > -1
                                : thisData[0].name.indexOf(input) > -1;
                        },
                    },
                    maxNumberOfElements: 10,
                },
                requestDelay: 400,
                placeholder: "地域を入力してください",
            };
            if(options) {
                $.each(options, function(i, val) {
                    default_options[i] = val;
                });
            }

            _self.easyAutocomplete(default_options);
            return _self;
        }).fail(function(data) {
            console.error("ERROR: ajax failed");
        });
    }

    const tree = {
        a: '', i: '', u: '', e: '', o: '',
        k: {
            a: '', i: '', u: '', e: '', o: '',
            y: { a: 'キャ', i: 'キィ', u: 'キュ', e: 'キェ', o: 'キョ' },
        },
        s: {
            a: '', i: '', u: '', e: '', o: '',
            h: { a: 'シャ', i: '', u: 'シュ', e: 'シェ', o: 'ショ' },
            y: { a: 'キャ', i: 'キィ', u: 'キュ', e: 'キェ', o: 'キョ' },
        },
        t: {
            a: '', i: '', u: '', e: '', o: '',
            h: { a: 'テャ', i: 'ティ', u: 'テュ', e: 'テェ', o: 'テョ' },
            y: { a: 'チャ', i: 'チィ', u: 'チュ', e: 'チェ', o: 'チョ' },
            s: { a: 'ツァ', i: 'ツィ', u: '', e: 'ツェ', o: 'ツォ' },
        },
        c: {
            a: '', i: '', u: '', e: '', o: '',
            h: { a: 'チャ', i: '', u: 'チュ', e: 'チェ', o: 'チョ' },
            y: { a: 'チャ', i: 'チィ', u: 'チュ', e: 'チェ', o: 'チョ' },
        },
        q: {
            a: 'クァ', i: 'クィ', u: '', e: 'クェ', o: 'クォ',
        },
        n: {
            a: '', i: '', u: '', e: '', o: '', n: '',
            y: { a: 'ニャ', i: 'ニィ', u: 'ニュ', e: 'ニェ', o: 'ニョ' },
        },
        h: {
            a: '', i: '', u: '', e: '', o: '',
            y: { a: 'ヒャ', i: 'ヒィ', u: 'ヒュ', e: 'ヒェ', o: 'ヒョ' },
        },
        f: {
            a: 'ファ', i: 'フィ', u: '', e: 'フェ', o: 'フォ',
            y: { a: 'フャ', u: 'フュ', o: 'フョ' },
        },
        m: {
            a: '', i: '', u: '', e: '', o: '',
            y: { a: 'ミャ', i: 'ミィ', u: 'ミュ', e: 'ミェ', o: 'ミョ' },
        },
        y: { a: '', i: '', u: '', e: 'イェ', o: '' },
        r: {
            a: '', i: '', u: '', e: '', o: '',
            y: { a: 'リャ', i: 'リィ', u: 'リュ', e: 'リェ', o: 'リョ' },
        },
        w: { a: '', i: 'ウィ', u: '', e: 'ウェ', o: '' },
        g: {
            a: '', i: '', u: '', e: '', o: '',
            y: { a: 'ギャ', i: 'ギィ', u: 'ギュ', e: 'ギェ', o: 'ギョ' },
        },
        z: {
            a: '', i: '', u: '', e: '', o: '',
            y: { a: 'ジャ', i: 'ジィ', u: 'ジュ', e: 'ジェ', o: 'ジョ' },
        },
        j: {
            a: 'ジャ', i: '', u: 'ジュ', e: 'ジェ', o: 'ジョ',
            y: { a: 'ジャ', i: 'ジィ', u: 'ジュ', e: 'ジェ', o: 'ジョ' },
        },
        d: {
            a: '', i: '', u: '', e: '', o: '',
            h: { a: 'デャ', i: 'ディ', u: 'デュ', e: 'デェ', o: 'デョ' },
            y: { a: 'ヂャ', i: 'ヂィ', u: 'ヂュ', e: 'ヂェ', o: 'ヂョ' },
        },
        b: {
            a: '', i: '', u: '', e: '', o: '',
            y: { a: 'ビャ', i: 'ビィ', u: 'ビュ', e: 'ビェ', o: 'ビョ' },
        },
        v: {
            a: 'ヴァ', i: 'ヴィ', u: '', e: 'ヴェ', o: 'ヴォ',
            y: { a: 'ヴャ', i: 'ヴィ', u: 'ヴュ', e: 'ヴェ', o: 'ヴョ' },
        },
        p: {
            a: '', i: '', u: '', e: '', o: '',
            y: { a: 'ピャ', i: 'ピィ', u: 'ピュ', e: 'ピェ', o: 'ピョ' },
        },
        x: {
            a: '', i: '', u: '', e: '', o: '',
            y: {
                a: '', i: '', u: '', e: '', o: '',
            },
            t: {
                u: '',
                s: {
                    u: '',
                },
            },
        },
        l: {
            a: '', i: '', u: '', e: '', o: '',
            y: {
                a: '', i: '', u: '', e: '', o: '',
            },
            t: {
                u: '',
                s: {
                    u: '',
                },
            },
        },
    };
    function convertRomanToKana(original) {
        const str = original.replace(/[A-Za-z]/, function(s) {String.fromCharCode(s.charCodeAt(0) - 65248)}).toLowerCase(),
            len = str.length;
        let result = '',
            tmp = '',
            index = 0,
            node = tree;
        const push = function(char, toRoot) {
            toRoot = toRoot ? toRoot : true;
            result += char;
            tmp = '';
            node = toRoot ? tree : node;
        };
        while (index < len) {
            const char = str.charAt(index);
            if (char.match(/[a-z]/)) {
                if (char in node) {
                    const next = node[char];
                    if (typeof next === 'string') {
                        push(next);
                    } else {
                        tmp += original.charAt(index);
                        node = next;
                    }
                    index++;
                    continue;
                }
                const prev = str.charAt(index - 1);
                if (prev && (prev === 'n' || prev === char)) {
                    push(prev === 'n' ? '' : '', false);
                }
                if (node !== tree && char in tree) {
                    push(tmp);
                    continue;
                }
            }
            push(tmp + char);
            index++;
        }
        tmp = tmp.replace(/n$/, '');
        push(tmp);
        return result;
    }

    function hiraToKana(str) {
        return str.replace(/[\u3041-\u3096]/g, function(match) {
            var chr = match.charCodeAt(0) + 0x60;
            return String.fromCharCode(chr);
        });
    }
})(jQuery);

読み込むHTML側での実行等をお忘れなく

index.html
<script>
$(function() {
    const options = {
        placeholder: "都道府県"
    }
});
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javascriptでパズルゲームを作ってみた

javascriptでものを作るvol.2

phina.jsというライブラリを使い、パズルゲームの制作をしました。
作っている中で
this.〜
を何回も出会いthisについて気になったので調べて見ました。

javascriptでのthisの使い方は4種類あることがわかりました。

メソッド呼び出し
関数呼び出し
コンストラクタ呼び出し
apply,call呼び出し

javascriptを勉強すればするほど奥が深いと感じました。

パズルゲーム制作に参考にしたサイト
https://phiary.me/phina-js-sliding-puzzle-game/

thisの使い方で参考にした記事
https://qiita.com/takeharu/items/9935ce476a17d6258e27

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

axiosとasync/awaitの関係性

axiosはhttpライブラリですが、正直よくわからずなんとなくasync/awaitと合わせて使うといい感じでデータが取得できると思い、使っていました。
事実それで取得できていました。
しかし、エラーハンドリングやその他処理を考える時に理解が足りないと感じたので、
改めてどういった挙動をするのか理解したかった為、示します。

試してみる

axios.js
//1.async・awaitなしのaxios
const response = axios.get('https://●●●※webapiを想定');
console.log(response);

⬇︎コンソールで確認(戻り値はPromiseオブジェクト)

/*
Promise {<pending>}
__proto__: Promis
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: Object
*/


//2.asyncのみ定義したaxios
async function test(){
  const response = axios.get('https://●●●※webapiを想定');
  console.log(response);
}
test();

⬇︎コンソールで確認(戻り値はPromiseオブジェクト)

/*
Promise {<pending>}
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: Object
*/


//3.async・awaitを定義したaxios
async function test(){
  const response = await axios.get('https://●●●※webapiを想定');
  console.log(response);
}
test();

⬇︎コンソールで確認(戻り値は実データ)

/*
{data: {…}, status: 200, statusText: "", headers: {…}, config: {…}, …}
*/


//async・awaitを定義したaxiosを違う関数から呼び出すパターン
async function asyncFunction(){
  const response = await axios.get('https://●●●※webapiを想定');
  return respnonse;
}
function test(){
  const response = asyncFunction();
  console.log(response);
}
test();

⬇︎コンソールで確認(戻り値はPromiseオブジェクト)

/*3番で実データになっていたはずがPrimiseオブジェクトにもどっている!!
Promise {<pending>}
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: Object
*/


まとめ 
・axiosはPromiseを返します。
・asyncはPromiseを返します。
・asyncとaxiosではPromiseを返します。
・axiosをasync/awaitすると実データを返します。(ただし関数内部のみです)
・別関数からaxiosのasync/awaitを呼び出すとPromiseを返します。
 これはasync/awaitが常にPromiseを返すことを意味します。

以上です。

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

Riot.js v4 CLI

Riot.js Advent Calendar 2019 の11日目が空いていたので埋めます。

Riot command line tool

@riotjs/cli
普段使うことはないですが、プロジェクトでのCI/CDを考えたら必要になる時が来る。ハズ。
今は複数人で各ページを開発して、特定の人がビルド&デプロイという流れになっている。
引き出し確保のためにも一度勉強しておきます。(でもその時がきたらWebpack使うんだろうな)
@riotjs/cli Version 4.0.3の内容で記載します。

コマンド説明

書式

riot [オプション] [対象ファイル|対象ディレクトリ]...
オプションは省略可能
ファイルは複数指定可能
ディレクトリを指定することも可能

説明

Riot.js のファイルをこのriotコマンドでプリコンパイルすることが出来ます。
先にnpm i @riotjs/cli -gでインストールしておきます。

オプション

-o, --output

Output directory where your javascript files will be generated

riot -o [出力先ディレクト] [対象ファイル|対象ディレクトリ]...
JavaScriptファイルを生成する出力先ディレクトを指定します。
このオプションを指定しない場合はカレントディレクトリに出力されます。

> riot -o dist hello.riot
hello.riot -> dist\hello.js

-f, --format

Specify output format - either: amd, cjs, esm, iife, or umd - default: esm

riot -f [出力形式] [対象ファイル|対象ディレクトリ]...
JavaScriptの出力形式を指定します。
指定できる出力形式はamd, cjs, esm, iife, umd
このオプションを指定しない場合はesmで出力されます。

> riot -f umd hello.riot
hello.riot -> c:\hello.js

補足

  1. amd : (Asynchronous Module Definition)
    define, return
    非同期, フロントエンド向け

  2. cjs : (CommonJS)
    require, module.exports
    同期, バックエンド向け

  3. esm : (ES Modules)
    import, export
    標準モジュール, 多くの最新ブラウザで動作, シンプルに記述できる

  4. iife: (Immediately Invoked Function Expression)
    (function () { }());
    即時実行関数式

  5. umd: (Universal Module Definition)
    module.exports, define
    フロントエンドとバックエンド向け(amd+cjs), ESMのフォールバックとして使われる傾向

-e, --extension

Change riot components file extension - default: riot

riot -e [拡張子] [対象ファイル|対象ディレクトリ]...
Riotコンポーネントファイルの拡張子を指定します。
このオプションを指定しない場合はriotが使われます。

> riot -e tag hello.tag
hello.tag -> c:\hello.js

-s, --sourcemap

Add inline or a file sourcemaps to the generated files - either: inline or file

riot -s "[inline|file]" [対象ファイル|対象ディレクトリ]...
"inline"を指定すると、生成したJavaScriptファイルの中にソースマップを追加します。
"file"を指定すると、[対象ファイル].js.mapを生成します。

> riot -s "inline" hello.riot
hello.riot -> c:\hello.js

> riot -s "file" hello.riot
hello.riot -> c:\hello.js
* hello.js.mapも生成される

-c, --config

Specify the path to a configuration file to compile your tags

riot -c [設定ファイル] [対象ファイル|対象ディレクトリ]...
タグをコンパイルするための設定ファイルを指定します。

hello.config.js
export default {
  sourcemap: 'file',
  output: 'dist'
}
例1
> riot -c hello.config.js hello.riot
hello.riot -> dist\hello.js

設定ファイルに記述したオプションは引数で指定しても無視されます。
一方設定ファイルに無いオプションは有効になります。

例2
> riot -c hello.config.js -o out -f umd hello.riot
hello.riot -> dist\hello.js
* -o outは無視されるが、umdフォーマットで出力される

-w, --watch

Watch for changes

riot -w [対象ファイル|対象ディレクトリ]...
変更を監視します。対象ファイルが変更されるたびにコンパイルが実施されます。

> riot -w hello.riot
Watching... hello.riot

-v, --version

Print the cli version

riot -v
Riot CLIのバージョンを出力します。

> riot -v
4.0.3

-h, --help

You're reading it

riot -h
Riot CLIのヘルプを出力します。

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

yumemi.vue memo

アウトプットの重要性

新しいことはメモに
日々なんでもアウトプット
積み重ね

NuxtでVueライフを快適に

容易に環境を整えたい
Nuxt.js  Vueを快適に作るため
ルーティングが自動的に設定を生成
Vuex store をインポート

シンプル

NuxtでかくとVueのコードがシンプルに
SSRだけでなくVueが快適になるよ!

Vueでネイティブアプリを Ionic Vue

Native開発
weex-vue-Render

Ionic -> web技術でネイティブアプリ風に
iOSでネイティブアプリを作るのは割と簡単
- Ionicコンポーネントを基本使おう

宣言的UIってなんなの??

欲しいUIを命令的に書かないで宣言的に書いていくことで可読性が上がる
宣言的に書いた方がすっきりするよね

vue-nextのソースコードを読み始める

vue-next -> 乱暴にいうとVersion3

  • 利用されるツール
    istanbul -> テスト時のカバー率を確認できる
    consoler -> 色付きでコンソールに表示
    rollup -> バンドルするやつ

  • ディレクトリ構成

  • どこを読むといいのか
    packagesに便利な奴がある
    vue-nextはディレクトリがわかりやすく

compiler :コンパイル時に使う奴
run-time :実行時に使う奴

jQuery使いがVueを使った話

Vueの強み
- タグのようにコンポーネントがかける
- イベントの紐付けをタグとするから見やすい
- 配列要素の描画が簡単

Vuexで何をするか、何をしないか

Vuex -> 状態管理の方法のパターンを提供(Fluxなどから影響)
単方向フローを強制する
グローバルデータの中央一元管理

安全にグローバルデータを管理したい
Page(View)に提供するもの

アンチパターン

  • emit,propの代わりに使う
    再利用性が下がる
    テストが難しくなる

  • 全てのデータをVuexにおく

  • 全てのロジックをVuexにおく
    ロジックの単体テストが難しくなる

責務以外は全部アンチパターン!

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

GraphQL でカクテル検索サービスを実装してみた

はじめに

こんにちは。自分は普段 React でフロントを実装しています。最近 GraphQL に興味があって、REST 以外の選択肢を知っていると技術選定の幅が広がると思い、GraphQL で何かサービスを実装してみました。最後に感想を述べていますので、GraphQL への理解を深める参考になれば幸いです。以下に実際に動作する成果物を載せておきます。

サンプルとコード

clinet: https://liquor-react.netlify.com/
server: https://liquor-graphql-server.glitch.me/graphql
client source: https://github.com/yoneda/liquor-react-client
server source: https://github.com/yoneda/liquor-graphql-server

全体の構成

フリーで使えるデータを探していたところ、RapidAPI というサービスが見つかりました。そこで酒やカクテルを検索できる Cocktail DB API が見つかったので今回はこちらを使用しました。クライアントは React、サーバーは GraphQL で実装しました。GraphQL は、DB だけでなく、他の REST API だったり、または他の GraphQL も接続できるということでした。GraphQLはDBと接続されることが多いと思いますが、今回はREST形式のRapidAPI と接続しました。GraphQL は BFF のような働きをします。図にすると以下のような感じです。

graphql.png

使用技術

今回使った技術は以下の通りです。
クライアント: react, apollo-client, react-scripts, reach-router
サーバー: express, express-apollo-server, superagent, nodemon

サーバーサイド

サーバーサイドは Express + ApolloServer で実装しました。ApolloServer ではスキーマを定義してそれに対応するリゾルバを実装していきます。RapidAPI から返されたJSON を見ながらスキーマを定義します。カクテルのリストを返すqueryは以下のようにしました。

  enum DrinkCategory{
    COCKTAIL
    COFFEE
    BEER
    OTHER
  }
  type Query{
    allDrinks(category: DrinkCategory): [Drink!]!
  }
  type Drink{
    id: ID!
    name: String!
    url: String!
    category: DrinkCategory
  }

クライアント

クライアントは React + ApolloCLient で実装しました。React で GraphQL のクエリを発行するのはシンプルでした。コンポーネントのルートで GraphQL の URI を設定する、useQuery の Hooks でデータをフェッチする、この2つだけでサーバーと接続可能です。

実装した感想

Apollo Client の強力なキャッシュ機能が便利

Apollo Clientは、サーバーからのフェッチ結果をどこでもローカルにキャッシュします。Aコンポーネントでブログ記事を取得した後、Bコンポーネントから同じものをフェッチしようとしたときは、再度サーバーにリクエストを送らず、ApolloClinet がキャッシュしているデータにアクセスするので高速に表示されます。こういった実装はこれまでは Redux によって行われていました(AコンポーネントとBコンポーネントがアクセス可能な Redux の値を用意する。Aコンポーネントでブログ記事を取得した結果を Redux に入れておいて、Bコンポーネントがフェッチしようとしたときは代わりにRedux の値にアクセスするようにする)。Redux によってAction や Reducer を書いて、ほぼ自前で実装してたキャッシュの機能が、ApolloClient では最初から用意されています。コードを書く量が減ると思います。

バリデーションの手間が減る

サーバーサイドを Node.js で実装していて、バリデーションしたいときは別途ミドルウェアを組み込む必要がありました。ここでいうバリデーションとは、Request のパラメータ値の型をチェックしたり、その値が必須かどうかなどをチェックするような工程です。GraphQL でクライアント-サーバー間で型が保証されているというのは、開発者を安心させます。

GraphQL はリクエスト回数が少なくてすむ

REST と GraphQL の特徴を比較したときに、どちらが効率的にデータを取得できるかというと、自分は GraphQL の方が優れていると思います。特に、あるデータとその関連データを取得するような場面では、GraphQL の良さを発揮できるようでしょう。例えば、ブログ記事とその記事についたコメントを取得するAPIを実装してみるとします。

GET /articles/:id
GET /articles/:id/comments

REST では、上記のようにと2回リクエストを送るように実装するのが自然です。(もちろん、ブログ記事とそのコメントを同時に取得するような実装も可能です。1つのリソースに GET / POST / PUT / DELETE を割り当てて設計していく REST の思想に忠実に従うと、上記のような設計のほうが自然ではないかと思います。)

query{
  article(id: $id){
    title
    body
    postedComments{
      comment
      user
    }
  }
}

GraphQLでは、1回のリクエストで取得できます。さらに、必要なフィールドだけ指定して取得できます。無駄なフィールドは取得せず効率的です。GraphQLはデータを効率的に取得できるような設計になっていると感じました。

次回(予定)

今回はqueryだけだったので、次回はmutationやユーザ認証まで実装したいと思ってます。実装でき次第、この記事に追記するか、新しい記事として投稿する予定です!

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

【ES6初心者向け】classってなに?基礎と使い方についてまとめました

Riactを覚えるに当たって、classの習得は必需だったので、殴り書きしていたメモをまとめました。
かなり基礎的なまとめになっているので、JS初心者の方、classの実装未経験の方向けの記事になっていると思います。

Classの概要

オブジェクト指向 と呼ばれるプログラミング言語についての概念がある。
オブジェクト指向とは プログラムを現実のモノのように構成する 指向 のこと。
classはモノの説明書のこと。メリットはわかりやすくするため。
オブジェクトに関してはこの記事がわかりやすい。
C++,C#やjavaなどの言語ではクラスタイプベースのオブジェクト指向と呼ばれclass機能が備わっているが、かつてのJSはプロトタイプベースのオブジェクト指向なjsにはないので、頑張って似せようとしてきた。
プロトタイプはオブジェクトの元となるオブジェトで、ただの見せかけであり、厳密にいうとクラスではない。
このプロトタイプをさらにわかりやすく書ける構文をclass構文という。
大掛かりな開発では可読性、保守性のため、クラスベースで書くのが一般的のようです。

いままで(ES5)のオブジェクト指向を意識した構文

厳密にいうとclassではないですが、なるべくclassライクな書き方をしたものです。

prototype.js
var Squea = function(width,height,color){
 this.width = width; this.height = height; this.color = color;
 }//コンストラクタ 
Squea.prototype.squeaCompute = function(){
 console.log(this.width * this.height); 
} 
Squea.prototype.squeaColor = function(){
 console.log(this.color); 
}//メゾット 

var a = new Squea(100,400,"");//インスタンス 
a.squeaCompute(); //40000 
a.squeaColor(); //赤

classをつくるぞというときは、classの名前の頭文字は大文字にすることが一般的(Pascal記法)
クラスからオブジェクト(インスタンス)を生成するイメージ。
クラスは定義、設計図、型枠のイメージで、オブジェクト(インスタンス)は実体。
コンストラクタはオブジェクトで利用するプロパティを準備するために利用するメゾット。
prototype、newプロパティによって、メゾット内のthissqueaコンストラクタと紐づく。
これがないと正常にプログラムが動かないので注意すべきところ。
しかしこの書き方だと、コンストラクタとメソッドの定義が分離されているため、クラスとしてまとまりがなく分かりづらく感じる。

ES6からのClass構文

ES6から、上記の構文をわかりやすくまとめる書き方が採用された。
これがクラス構文。

class.js
class Squea { constructor(width, height, color) { 
    this.width = width; this.height = height; this.color = color;
 } 
squeaCompute() {
     console.log(this.width * this.height);
} 
squeaColor() {
    console.log(this.color); } 
}

let a = new Squea(100,400,"");
let b = new Squea(400,900,"");
a.squeaCompute(); //40000
a.squeaColor(); //赤 
b.squeaColor(); //青

コンストラクタとメゾットが同じ{}で括られていて、可動性が高くなった。
ただしこのClassの概念は糖衣構文といって見せかけの構文に過ぎないようです。
これから書き方について詳しく説明していきます。

class構文の書き方

クラスの生成

クラスというのはオブジェクトを効率よくオブジェクトを作成していくための「設計図」です。

class Person{}
const a= new Person();
console.log(a);//Person {}

現在Nameクラスには中身がないが、new演算子によってインスタンスが生成されました。

リテラルでも表記できる

const Person = class{}

これにより、関数リテラルと同じ表現の仕方ができる。

プロパティを使用するためのコンストラクター

中身が空では何もできないのでプロパティを追加していきます。
そのために必要なのがコンストラクターです。

class Person{
  constructor(name,age,from){
    this.name = name;
    this.age = age;
    this.from = from;
  }
}
const a = new Person('ben',25,'ibaraki');
const b = new Person('bob',38,'chaina');
console.log(a.name); //ben
console.log(b.name); //bob

プロパティは下記で定義できます。

this.property = value;

thisについて

コンストラクター配下のthisはクラスが生成したインスタンスで、自分自身を表します。

インスタンスの生成

設計図(class)から実際にオブジェクトを生成します。
クラスからオブジェクトを生成するには、「new クラス名()」。
それをインスタンスと呼びます。
インスタンスに入れた引数は、コンストラクタで定義した引数順にプロパティとして代入さてていきます。

呼び出し

呼び出し方は
『インスタンスを入れた変数名.呼び出したいプロパティ』で呼び出せます。
普段使ってるオブジェクトと扱い方が似ています。

メゾットを定義したい

class Person {
  constructor(name, age, from) {
    this.name = name;
    this.age = age;
    this.from = from;
  }
//メゾットの定義
  toString() {
    console.log(`${this.name}${this.age}才です。`);
  }
}
const a = new Person("ben", 25, "ibaraki");

console.log(a.toString());
//benは25才です。

メゾットはクラス内に記述することで、「this.~」で準備しているプロパティにアクセスできる。
呼び出しは、「インスタンスを代入した変数.メゾット」。

もちろん、引数を持たせることができる。

class Person {
//コンストラクタは中略
  toString(who = "") {
    console.log(`${who}${this.name}${this.age}才と言う`);
  }
}
const a = new Person("ben", 25, "ibaraki");

console.log(a.toString("bobに"));
//bobにbenは25才と言う

読み込みのみ、または、書き込みのみのプロパティを作成

getter/setterを使います。
getter/setterでは定義の際、クラス内部からしか呼び出せない「※プライベート変数」を使う。
これをしないとエラー(Maximum call stack size exceeded)が起きる
※「_name」のこと

定義の仕方

class Person {
  constructor(name, age, from) {
    this.name = name;
    this.age = age;
    this.from = from;
    }
  get name() {
    return this._name;
  }
  set name(value) {
    this._name = value;
  }
}

const a = new Person("ben", 25, "ibaraki");
console.log(a.name);//ben
a.name = "bob";
console.log(a.name);//bob

getter,setterのメリット

  • 読み込みのみ、または、書き込みのみのプロパティを作れる。
  • 読み込み時、または、書き込み時に追加の操作を行える。例えば、読み込み回数をカウントするなど。
  • 他のプロパティから自動生成される値の場合は、必要になるまでその自動生成の計算を遅延できる。
  • プロパティの読み込みや書き込みについて、全く異なる処理に書き換えできる。(このような動作は非推奨)

setterのみ指定

class Person {
//constructorは中略
  set from(value) {
    this._from = value;
  }
}
const a = new Person("ben", 25, "ibaraki");
console.log(a.from);//undefined
a.from = "usa";
console.log(a.from);//undefined

書き込みことはできても、呼び出し(読み込み)はできない。

getterのみ指定

class Person {
 constructor(name, age, from) {
    this.name = name;
    this.age = age;
    this.from = from;
  }
  get from() {
    return this._from;
  }
}
const a = new Person("ben", 25, "ibaraki");
console.log(a.from);//エラー;Cannot set property from of #<Person> which has only a getter

『this.from = from;』が書き込んでいることになるので、エラーが起きる。

『this.from = from;』を消すと下記になる。

class Person {
  constructor(name, age, from) {
    this.name = name;
    this.age = age;
  }
  get from() {
    return this._from;
  }
}
const a = new Person("ben", 25, "ibaraki");
console.log(a.name);//ben
console.log(a.from);//undefined

動的なアプリでないとイメージしにくいですが、このようにgetterのみなら、読み取り専用、setterのみなら書き込み専用のプロパティになります。

振り返り

普段JQueryを使って小規模、中規模のサイト作成をしている私にとってはclassは難しすぎました。業務に出番がない&classで書くほどの規模でないので想像しにくい部分があったからです。
とにかくアプリを作りまくるしかないと思いました。
getter/setterの部分に関してはまとめましたが、理解しきれない部分があるのでダメ出しをお待ちしております。
読んでいただいてありがとうございました。

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

jsのimportとrequireの違い

はじめに

jsで外部ファイルを読み込む際に、
importと書いてある場合とrequireと書いてある場合があります。

この2つの違いがよくわからなかったので確認しました。

モジュールとは

importとrequireの違いを確認する前に、
前提知識となる「モジュール」について簡単に説明します。

ある程度の規模のjsアプリを作ると、
1つの大きなjsファイルにすべてのコードを書くのではなく
機能ごとにjsファイルを分けて管理したくなります。

そして、その機能ごとに分割したjsファイルを
メインとなるjsファイルで必要に応じて読み込んで利用するイメージです。

この分割した機能ごとのjsファイルを「モジュール」と呼びます。

モジュールの読み込み方法

モジュールを読み込むための方法、仕様は
何種類かあり、
それぞれ書き方が違い、動く環境も違います。

そのモジュール読み込みの仕様の主要なものとして、
ESM (ECMAScript Modules)と
CJS (CommonJS Modules)があります。
※ほかにもいくつかあるが、主要なのはこの2つ

今回のテーマであるimportを使うのがESM方式で、
requireを使うのがCJS方式となります。

それでは、
具体的にそれぞれの書き方の違いや動く環境の違いを説明していきます。

importとrequireの違い

import(ESM)

概要

import(ESM)は、
ES6で決められたモジュール読み込みの仕様です。

ES6の仕様ですので、
import文は
ChromeやFirefoxなどのブラウザでそのまま動かすことができますが、
IEでは動きません。

ESのバージョンとかブラウザによって動いたり動かなかったりという話が分からない場合は
こちらの記事でご確認ください。
ES(ECMAScript)とは?jsがブラウザによって動いたり動かなかったりするのはなぜ?

書き方

import文の書き方です。
モジュール側と読み込み側それぞれ書き方があります。

モジュール側

モジュール側では、いつものように関数やクラスを定義して、
その頭にexportを付けることで
import可能なモジュールとして定義できます。

module.js
export const helloWorld = function() {
    console.log('Hello World!!');
}

読み込み側

読み込み側では、
import文を使って先ほどのモジュールを読み込みます。

main.js
import { helloWorld } from './module'

helloWorld();
// 出力:Hello World!!

require(CJS)

概要

require文は、CommonJSの仕様で、
Nodejsの環境で動作してくれる書き方です。

Nodejs環境ということはつまり、サーバサイドでの実行ということになります。

多くの場合はブラウザ側でのjs実行になると思いますが、
ブラウザ側ではrequire文は動作しません。

書き方

require文のモジュール側、読み込み側それぞれの書き方です。

モジュール側

モジュール側では
module.exports と書いて、関数やクラスなどを定義します。

module.js
module.exports = function() {
    console.log('Hello World!!');
}

読み込み側

読み込み側では
require文を使って先ほどのモジュールを読み込みます。

main.js
const helloWorldModule = require('./module.js');

helloWorldModule();
// 出力:Hello World!!

import、require両方を、どの環境でも動くようにするには?

上記の通り、
import文は
・Chromeなどでは動くがIEなどES6に対応していないブラウザでは動かない
require文は
・Nodejs(サーバサイド)では動くがブラウザ側実行のjsでは動かない
という動作環境の制限があります。

ですが、環境関係なく
import文もrequire文も利用したいということがあると思います。

どの環境でも動作するようにするためには、
webpackなどのモジュールバンドルツールを利用する方法があります。

webpackというツールのイメージとしては、
・上記の書き方の例でいうmain.jsのような「読み込み側」のファイルを変換対象として指定する
・ツールを実行する
・ファイルに書かれているimportrequireなどの文を解析してくれる
・必要な外部ファイル(モジュール)を取ってきて全部1つのファイルとしてまとめて出力する
ということができるものです。

webpackは、
importやrequireや今回言及していない別のモジュール構文にも対応しており
これらを読み取ってすべてモジュールを1つのファイルとしてまとめてくれます。(「バンドルする」という)

そして、最終的にその1つにまとめられたjsファイルを
htmlで読み込むことで
必要なモジュールの機能がすべて利用できるようになります。

今回webpackの具体的な使い方は説明しませんので
別の記事を参考にしてみてください。
Webpackってどんなもの?

まとめ

■import
・ES6の仕様
・Chromeなどでは動くがIEなどES6に対応していないブラウザでは動かない
■require
・CommonJSの仕様
・Nodejs(サーバサイド)では動くがブラウザ側実行のjsでは動かない

どの環境でも動作させるためには
webpackなどでモジュールバンドルして1つのファイルにまとめてから利用する。

備考

Laravel Mix

今回モジュールバンドルツールとしてwebpackを例に挙げましたが、
このwebpackのラッパーツール(より便利に進化させたツール)のLaravel Mixをお勧めしたいです。
webpackの設定をシンプルに、簡単に記述できるツールです。
「Laravel」という名前がついていますがLaravelが関係ないフロントエンドのアプリでも利用可能です。
Laravel Mixとは?webpackをより便利に、簡単に。Laravel以外でも使えるよ。

babel

import構文をbabelに通すとrequire文に変換されます。

importのままならChromeなどのブラウザでそのまま動いていたのに、
babelを通したらrequire文に変換されてブラウザ側では実行できなってしまう。
ということがないように気を付けましょう。

babelで変換後、webpackに通してモジュールバンドルする必要があります。

babelについてわからない方はこちらを参考に。
polyfill、babelとは?jsをどのブラウザでも動くようにしてくれる。(IE対応)

参考

https://www.wakuwakubank.com/posts/466-javascript-module-import-export/
https://matatsuna.hatenablog.com/entry/2017/12/03/150520
https://qiita.com/kamykn/items/45fb4690ace32216ca25

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

JavaScriptを使用してお問合せフォームのメール入力を1行+拡大表示で明瞭化する

もうみんな当たり前に実装している事かもしれませんが、最近メールフォームのメールアドレスの入力の扱いを変更したので記録します。

なぜメールアドレスは2回入力するの?

お問合せメールフォームのメールアドレス入力欄は、1回派ですか?2回派ですか?
多くのWEB制作者の方は、確認用メールアドレス入力欄を作って2回入力にしていると思います

1e8d9c425c8727b1c87824517cb4485d.png

その方法はベスト?

メールアドレス入力欄は、「コピペができ、入力内容が不一致の場合エラーを返す」ものと「コピペができなくて、入力内容不一致の場合エラーを返す」の2パターンがあります。

前者の方は、もともと間違えたアドレスを2回入力したらアウトだし、
後者も、間違えたまま2回入力したらアウトです。

2回入力、いらないんじゃない??

どうせ誤入力したら、2回でも1回でも連絡できないことには変わりがない!じゃぁ2回入力の意味ってないんじゃないか?ユーザーが面倒なだけなのではないか?という声が2018年?2017年頃から聞こえてくるようになりました。

メールアドレスの確認フィールドをなくすべき理由
https://uxmilk.jp/70576

上記のリンクを要約すると、

1.メール入力欄を一番上にする
2.メール入力欄を大きく表示する
3.メール入力時に注意をうながすコメントを書く

など、2回入力することではなく、明瞭化が一番大切!メールフォームのデザイン見直していこうという内容です。

2回入力やめようという空気はあっても、では何がベストかまで追求することがなかったのですが、JavaScriptで明瞭な表示が実現できそうなので紹介します。

(前置き長すぎすみません)

CSSで拡大表示は有効か?

bdf6e0af657654b3d2b2e55622221012.png

<input type="email">は1行入力のため改行は入力できません。
複数行入力を行う場合は<textarea>を使用しますが、下記の理由から<textarea>をメールアドレス入力欄にするのは好ましくないです。

<input type="email">は、メールアドレスの入力時に使用します。SUBMIT時に入力フォーマットのチェックが行われ、エラーの場合はSUBMITが中止されメッセージが表示されます。
※非対応のブラウザもあるため利用には注意が必要です。
引用:https://web-designer.cman.jp/html_ref/abc_list/input_email/

JavaScriptで入力内容を拡大表示する

メールフォームの作法はそのままで、入力欄の下に拡大表示すれば、PCでもスマホでも美しく表示ができるので、JavaScriptを採用します。
JavaScriptは問い合わせフォームのあるページだけで読み込めばいいので、読み込みの分岐などを入れるといいです。

[完成図]
a1b6b924334eded72ca67d2fa83475d6.png

1.headにJavaScriptを表記する

qiita.rb
<script type="text/javascript">
    window.onload = function () {
        document.getElementById( "mailinput" ).onkeyup = function(){
                mailvewgetValue();
            };
        }
    function mailvewgetValue() {
        var $mailvewvalue = document.getElementById( "mailinput" ).value;
        document.getElementById( "mailoutput" ).innerHTML = $mailvewvalue;
        }
        </script>

"mailinput""mailoutput"は任意の名前に変更OKです。

参考 http://alphasis.info/2013/09/javascript-dom-event-onkeyup/

2.HTMLのメールフォームに、入力内容を反映するboxを設置

qiita.rb
<input type="email" name="mail" id="mailinput" size="40" >
<div id="mailoutput"></div>

3.CSSで体裁を整える

qiita.rb
    font-size: 32px;
    overflow-wrap: break-word;
    word-wrap: break-word;
    overflow: hidden;
    padding: 10px;
    margin-top: 10px;
    font-size: 170%;
    font-weight: bold;
    font-family: Arial, Helvetica, sans-serif;
    word-wrap: break-word;
    overflow-wrap: break-word;
    line-height: normal;
3-1.CSSの注意点

word-wrap: break-word;overflow-wrap: break-word;がないと、スマホで表示した際に自動改行されず入力内容が画面の見えないところに突き抜けていきます。

4.さようなら確認用メールアドレス入力欄できました!

a1b6b924334eded72ca67d2fa83475d6.png

スマホ表示のときは折返し表示してくれて、バッチリです!
c262fc3e1fe018e3890df389a63545ce.png

5.MW WP FORMで実装する

WordPressのメールフォーム「MW WP FORM」でも行えます。

5-1.メールフォームのショートコードの設定

フォームタグを追加するときに、id(mailinput)を設定して、表示用のboxをhtmlで記載するだけです。

ad6cbe05b4211c69b4d0384a8f70d6b2.png

実際のコードはこうなります。

qiita.rb
[mwform_email name="mail" id="mailinput" size="40" ]
<div id="mailoutput">

あとは、CSSとJavaScriptを記載すれば完了!

5-2.JavaScriptの読み込みページを指定する

ただでさえ重いWordPressに、数行とはいえ必要ないScriptを全ページで読み込むのはいただけません。
なので、JavaScriptはfunction.phpから、お問合せフォームのある固定ページを読み込んだときだけheaderに出力するように設定しましょう。

wp_headアクションをつかいます。

add_action( 'wp_head', 'hook_javascript' );

qiita.rb
function add_wp_head (){
     if( ){
       echo 'ここにJavaScriptを書いて表示する';
  }
}
add_action ('wp_head','add_wp_head',1);

実際のコード

qiita.rb
function add_wp_head (){
    if(is_page( '11' ) ){
        echo '<script type="text/javascript">
        window.onload = function () {
            document.getElementById( "mailinput" ).onkeyup = function(){
                mailvewgetValue();
            };
        }
        function mailvewgetValue() {
            var $mailvewvalue = document.getElementById( "mailinput" ).value;
            document.getElementById( "mailoutput" ).innerHTML = $mailvewvalue;
        }
        </script>';
    }
}
add_action ('wp_head','add_wp_head',1);

電話番号記入が保険になる?

今回実装したJavaScriptの拡大表示で、メールを2回入力する手間はなくなり、
少しの明瞭化を加えて、PCでもスマホでもメールアドレスの入力内容を確認しやすくなりました。

でも、間違ったことに気づかずに送ってしまうとどうにもなりません。

ユーザーは、お問合せをしたのに返事が来ないと思うし
受け取った側はメールエラーで返事のしようがないし。

溝ができるときはできる()
お問合せフォームには電話番号の記入欄を設けると、メールがエラーのときは電話回答ができます。
しかし、メールでお問い合わせする方って「電話番号まで教えたくないわ」と思っている場合も多く、090-1234-5678という番号で送ってくる方も(;´∀`)
やっぱりメール入力を間違ってたら詰むのです。

100%取りこぼしなしは難しいのです。
ちゃんちゃん。

おわりに

WordPress山にこもりっきりではダメかも…と思い、JavaScriptの勉強をはじめたばかりのヒヨコ?ですが、JavaScriptで何ができるかを少しずつ掴んできました。
ごくごく初期の簡単なものだけど、満足(´ω`)HAHAHA

他にもこんなやり方あるよー!とか間違いがあったら、教えてくださると嬉しいです。

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

AR年賀状を作る [WebAR]

PlayCanvasでAR年賀状を作ります。

PlayCanvasでARを作成するにはこちらの記事をご覧ください。

前回の記事で紹介されているのは

  • PlayCanvasでARコンテンツを作る
    • PlayCanvasについて
    • ARマーカーについて
    • プロジェクトの作成方法について

https://qiita.com/yushimatenjin/items/6fe5963afc14f57ba40d
今回紹介するのはAR年賀状を作るための機能を作ります。

  • 3D空間上にPlayCanvasで文字を表示する
  • 3Dモデルをインポートする方法
  • エフェクトを追加する方法

説明しないこと
- 年賀状に印刷すること
年賀状を書いたことがないので、年賀状の印刷方法については分かる人に聞いていただけると嬉しいです。

動画

output.gif

3D空間上にPlayCanvasで文字を表示する

「あけおめ」メッセージを追加をします。あけおめメッセージを追加するにはフォントとスクリーンを作ること作ることができます。
PlayCanvasでは.ttf形式のフォントを使用することが出来るので、その形式のフォントを使用します

準備

フォントを追加する
  1. M Plusフォントをダウンロード

  2. mplus-1m-bold.ttfをドラッグアンドドロップでPlayCanvasのエディター上にアップロードします。

  3. フォントに日本語を対応させる

    アップロードしたフォントをASSETからクリックすると、FONT → Charactersに使用したい文字列を追加して、Process Fontをクリックします。文字列を一度に追加したい場合は16進数で指定した、RANGEで追加もできます。

日本語を使用するための準備これでできました。次からはフォントをゲーム画面(AR)に表示出来るようにします。

追加

  1. 2D Screenと文字を追加する

HIERARCHYの中から → ➕ → User Interface → 2D Screenを追加します。

2DScreenを使用することでカメラの影響を受けない配置が可能になります。

  1. 2D スクリーンにText Elementを追加します。 (画像は追加後のHIERARCHY)

a. HIERARCHYから2D Screenを選択
b. ➕ → User Interface → Text Elementを追加

これで2D Screenの中にText Elementを追加することができました。

Text Elementについて

Text Elementを選択肢 → ELEMENT → Fontの中に先程設定した M Plusのフォントを追加します。

これでPlayCanvasエディター上で文字を表示することが出来るようになりました。

3Dモデルをインポートする方法

PlayCanvasではFbx, Objについてはドラッグアンドドロップすることで使用することができます。

  1. 3Dモデルをダウンロードする 3Dモデルをダウンロードするのには Google Polyなどは可愛い動物などのモデルが比較的自由に利用可能なモデルが配布されています。

今回使用した3DモデルはRatになります。

  1. PlayCanvas Editorにドラッグアンドドロップでアップロードする

FBX, OBJ形式の、3DモデルについてはPlayCanvasにドラッグアンドドロップをすることで配置することができます。

FBX, OBJ形式でテクスチャがうまく反映されない場合にはBlenderなどの3Dのソフトウェアで設定して保存し直すと反映されるようになります。

参考

エフェクトを追加する

PlayCanvasでエフェクトを使用するにはParticle Systemを追加します。

Particle Systemを追加

  1. ➕ → Particle Systemを追加します

Particle Systemの設定について

https://support.playcanvas.jp/hc/article_attachments/360050770994/smoke.gif

このようなエフェクトは簡単に作れます。作り方についてはこちらが参考になります。

https://support.playcanvas.jp/hc/ja/articles/360038579874

前回の記事と今回の記事を合わせることでリッチなARコンテンツを作れるようになります。
PlayCanvasはARToolKitの他にも8thwallというWebARのライブラリに対応していますので他のコンテンツもご覧ください

以下PlayCanvas開発で参考になりそうな記事の一覧です。

入門
- PlayCanvas入門- モデルの作成~ゲームに入れ込むまで
- JavaScriptでスロットを実装する。【PlayCanvas】
- 3Dモデルのビューワーを3分で作る【初めてのPlayCanvas】

応用
- PlayCanvasのコードエディターでes6に対応する
- Gulpのプラグインを書いたらPlayCanvasでの開発がめちゃくちゃ便利になった
- PlayCanvas Editorに外部スクリプトを読み込む新機能が追加されたので開発方法を考える。- Reduxを組み込む

その他の記事はこちらになります。
- React Native + PlayCanvasを使ってスマートフォンゲームを爆速で生み出す
- PlayCanvasのエディター上でHTML, CSSを組み込む方法
- 【iOS13】新しくなったWebVRの使い方

PlayCanvasのユーザー会のSlackを作りました!

少しでも興味がありましたら、ユーザー同士で解決・PlayCanvasを推進するためのSlackを作りましたので、もしよろしければご参加ください!

日本PlayCanvasユーザー会 - Slack

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

AR年賀状を作る

PlayCanvasでAR年賀状を作ります。

PlayCanvasでARを作成するにはこちらの記事をご覧ください。

前回の記事で紹介されているのは

  • PlayCanvasでARコンテンツを作る
    • PlayCanvasについて
    • ARマーカーについて
    • プロジェクトの作成方法について

https://qiita.com/yushimatenjin/items/6fe5963afc14f57ba40d
今回紹介するのはAR年賀状を作るための機能を作ります。

  • 3D空間上にPlayCanvasで文字を表示する
  • 3Dモデルをインポートする方法
  • エフェクトを追加する方法

説明しないこと
- 年賀状に印刷すること
年賀状を書いたことがないので、年賀状の印刷方法については分かる人に聞いていただけると嬉しいです。

動画

output.gif

3D空間上にPlayCanvasで文字を表示する

「あけおめ」メッセージを追加をします。あけおめメッセージを追加するにはフォントとスクリーンを作ること作ることができます。
PlayCanvasでは.ttf形式のフォントを使用することが出来るので、その形式のフォントを使用します

準備

フォントを追加する
  1. M Plusフォントをダウンロード

  2. mplus-1m-bold.ttfをドラッグアンドドロップでPlayCanvasのエディター上にアップロードします。

  3. フォントに日本語を対応させる

    アップロードしたフォントをASSETからクリックすると、FONT → Charactersに使用したい文字列を追加して、Process Fontをクリックします。文字列を一度に追加したい場合は16進数で指定した、RANGEで追加もできます。

日本語を使用するための準備これでできました。次からはフォントをゲーム画面(AR)に表示出来るようにします。

追加

  1. 2D Screenと文字を追加する

HIERARCHYの中から → ➕ → User Interface → 2D Screenを追加します。

2DScreenを使用することでカメラの影響を受けない配置が可能になります。

  1. 2D スクリーンにText Elementを追加します。 (画像は追加後のHIERARCHY)

a. HIERARCHYから2D Screenを選択
b. ➕ → User Interface → Text Elementを追加

これで2D Screenの中にText Elementを追加することができました。

Text Elementについて

Text Elementを選択肢 → ELEMENT → Fontの中に先程設定した M Plusのフォントを追加します。

これでPlayCanvasエディター上で文字を表示することが出来るようになりました。

3Dモデルをインポートする方法

PlayCanvasではFbx, Objについてはドラッグアンドドロップすることで使用することができます。

  1. 3Dモデルをダウンロードする 3Dモデルをダウンロードするのには Google Polyなどは可愛い動物などのモデルが比較的自由に利用可能なモデルが配布されています。

今回使用した3DモデルはRatになります。

  1. PlayCanvas Editorにドラッグアンドドロップでアップロードする

FBX, OBJ形式の、3DモデルについてはPlayCanvasにドラッグアンドドロップをすることで配置することができます。

FBX, OBJ形式でテクスチャがうまく反映されない場合にはBlenderなどの3Dのソフトウェアで設定して保存し直すと反映されるようになります。

参考

エフェクトを追加する

PlayCanvasでエフェクトを使用するにはParticle Systemを追加します。

Particle Systemを追加

  1. ➕ → Particle Systemを追加します

Particle Systemの設定について

https://support.playcanvas.jp/hc/article_attachments/360050770994/smoke.gif

このようなエフェクトは簡単に作れます。作り方についてはこちらが参考になります。

https://support.playcanvas.jp/hc/ja/articles/360038579874

PlayCanvasにARを追加する

前回の記事と今回の記事を合わせることでリッチなARコンテンツを作れるようになります。
PlayCanvasはARToolKitの他にも8thwallというWebARのライブラリに対応していますので他のコンテンツもご覧ください

以下PlayCanvas開発で参考になりそうな記事の一覧です。

入門
- PlayCanvas入門- モデルの作成~ゲームに入れ込むまで
- JavaScriptでスロットを実装する。【PlayCanvas】
- 3Dモデルのビューワーを3分で作る【初めてのPlayCanvas】

応用
- PlayCanvasのコードエディターでes6に対応する
- Gulpのプラグインを書いたらPlayCanvasでの開発がめちゃくちゃ便利になった
- PlayCanvas Editorに外部スクリプトを読み込む新機能が追加されたので開発方法を考える。- Reduxを組み込む

その他の記事はこちらになります。
- React Native + PlayCanvasを使ってスマートフォンゲームを爆速で生み出す
- PlayCanvasのエディター上でHTML, CSSを組み込む方法
- 【iOS13】新しくなったWebVRの使い方

PlayCanvasのユーザー会のSlackを作りました!

少しでも興味がありましたら、ユーザー同士で解決・PlayCanvasを推進するためのSlackを作りましたので、もしよろしければご参加ください!

日本PlayCanvasユーザー会 - Slack

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

【JavaScript】Objectかどうか判定する方法

Objectかどうか判定する方法

結論: Object.prototype.toString.call()を使う。

typeofを使用した場合

以下の出力結果を予想できますか?

console.js
console.log(typeof {});
console.log(typeof []);
console.log(typeof null);
console.log(typeof undefined);
result
> "object"
> "object"
> "object"
> "undefined"

Javascriptのtypeofでは、{}, [], nullの結果が全て"object"になります。

Object.prototype.toString.callを使用した場合

console.js
console.log(Object.prototype.toString.call({}));
console.log(Object.prototype.toString.call([]));
console.log(Object.prototype.toString.call(null));
console.log(Object.prototype.toString.call(undefined));
result
> "[object Object]"
> "[object Array]"
> "[object Null]"
> "[object Undefined]"

それぞれ[object *]のformatで出力されます。

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

パスワードは 90 年代の代物だ(JSConfカンファレンス参加メモ)

この記事はフューチャー 2 Advent Calendar 2019の 11 日目の記事です。
昨日は@Tetsu_minorityさんの記事でした。
Futureアドベントカレンダーはその1もあります!!ぜひご覧ください。

ごあいさつ

11/30、12/1 に開催されたJSConf JPに参加してきましたが、
たくさんのレクチャーがあり初めて知ることばかりでした。
今回はその中から、個人的に面白かったパスワードについてのレクチャーを
おさらいを兼ねてまとめてみようと思います。

Password is so 1990s(「パスワードは 90 年代の代物だ」) by Sam Bellen

「パスワード」の歴史

  • パスワードの歴史ははるか昔、古代ローマ人の時代にさかのぼる。
  • 10世紀ごろには「ひらけゴマ」という「パスワード」も誕生した。
  • 1961年にはタイムシェアリングシステムが登場。1台のコンピュータを複数人のユーザーでシェアして使うことができるようになった。
  • 1970年代には「ハッシュ化」の技術が生まれる。パスワードをよりセキュアに保存することができるように。
  • 1990年代には、ハッキングがより深刻な問題として顕在化した。

パスワードの種類

ところでパスワードとは何か

それは 秘密をシェアするすべてのもの のことである!

色々なパスワード

  • 意味のないアルファベットの並んだ文字列など、複雑なものは覚えられにくい。
    • つまり、複雑であれば他人から推定されにくい。
  • 一方でPINコードのように簡単なものは覚えやすい。
    • つまり、他人から推定されやすい。
    • よってPINコードなどは生体認証(などの複雑な認証)と合わせて使うとより安全である。
  • パターン認証は推定されにくいほどのものではない。
    • 覚えやすくはある。物の認証に使われる。

パスワードの問題点

  • 忘れたらリセットしなきゃいけない。
  • リセットの作業がとにかく面倒!
  • 手間を省くためにパスワードマネジャーを使うことになるが、パスワードが必要なサイトが増えるほどハックされる可能性も高い。

より良いパスワードにするためには

  • 複雑なパスワードにする
  • 個人情報を含めない
  • パスワードを再利用しない
  • 頻繁にパスワードを変更する

これらが有効だが、誰もそんなことしない。Sam Bellen氏だってしない。
そこで、

パスワードレスな認証

  • ワンタイムパスワード
    • 一度だけ使用できる。短時間で有効期限切れる。
    • ユーザーに直接届く。iOSやAndroidならば届いたメッセージから推測して入力できる。
    • 二段階認証が必要ない。
    • ただし:携帯が必要である。
    • メールで送信できれば問題ない?否、メールも解析される恐れがある。
  • 認証アプリ
    • 時系列だったり、プッシュ型だったりする。
    • アプリケーションと認証サービス間で秘密の情報を共有する必要がある。
  • SMS認証
    • 覚えなくてはならないパスワードが減る。
    • 信頼できるサービスにのみパスワードを教えるだけで良い!
    • ただし;他のサービスに認証を依存することになる。
  • 他の認証アプリも、二段階認証の一つとしてよく使われる。

Web認証API

webauthnというクールな認証APIがある

  • キーベースの認証である。
  • 認証はハードウェアで行う。
  • 新しいキーを生成し、保存するようになっている。
  • モダンなデバイスだと認証装置がビルトインになっている。(touch IDなど)

どのように動作するか?

  • クレデンシャル登録・・・一度登録すれば、いつでも認証できるようになる!

    1. 認証のリクエストがサーバーに送信される。
    2. レスポンス(「Challenge」と呼ばれていた)がデバイスに返ってくる。
    3. ユーザーがデバイスを操作する。(ChallengeにSignする)
    4. デバイスからサーバーへSigned ChallengePublic Keyraw IDが送られる。
    5. サーバーはユーザー名と一緒にそれを保存する。
    6. 登録完了 image.png
  • 認証

    1. ユーザーがユーザー名をクライアントに入力する。
    2. ChallengePublic Keyraw IDがサーバーから返ってくる。
    3. ユーザーがデバイスを操作する。(ChallengeにSignする)
    4. デバイスからサーバーへSigned Challengeと、登録時と同じPublic Keyが送られる。
    5. 認証完了 image.png

webauthnで嬉しいこと

  • ハードウェア認証なのでinternal、externalも分けられる。
  • ハードウェアを通しての認証なので、パスワードを入力する必要がない!

課題もある

  • ユーザーがクレデンシャルをどう管理するか
  • デバイスをまたいでのクレデンシャル発行
    • 紛失したデバイスの認証システムはどう復旧するか、など

それでも

  • webauthnはパスワードに取って代わるシステムとなるだろう。
    • (Auth0などの認証サービスの代わりにはならないだろう)
  • W3C勧告にもなっており、様々なブラウザが対応しようとしている。
  • GoogleやGithubではすでにwebauthnを使える!

所感

  • この発表によって、ログインにはIDとパスワードのセットが当たり前、という思い込みを打ち破られました。
  • 時代はクラウド!と言われる中でハードウェア認証なんだ、、と最初は戸惑ったが、物理的な認証であれば自分が持っている限り盗まれることはない=ハックされにくいという点では納得できる気もしました。

参考

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

画像をドラッグ&ドロップで登録してプレビュー表示

備忘録

input:fileの画像登録時にドラッグ&ドッロップでinputに値を渡し、プレビューを出す記述。
(通常登録も対応)

JSライブラリ依存せずに使用(してるはず)。

CODEPEN

See the Pen 画像をドラッグ&ドロップで登録してプレビュー表示 by manabu tanaka (@tonkatsu) on CodePen.

記述

html ドラッグ&ドロップするエリア
<form method="post" enctype="multipart/form-data">
    <div id="dragDropArea">
        <div class="drag-drop-inside">
            <p class="drag-drop-info">ここにファイルをドロップ</p>
            <p>または</p>
            <p class="drag-drop-buttons">
                <input id="fileInput" type="file" accept="image/*" value="ファイルを選択" name="photo" onChange="photoPreview(event)">
            </p>
            <div id="previewArea"></div>
        </div>
    </div>
    <button type="submit" name="submit" value="登録">登録<button>
</form>
css 簡単に整形
#dragDropArea{
  background-color: #f4f4f4;
  margin: 10px;
  padding: 10px;
  border: #ddd dashed 5px;
  min-height: 200px;
  text-align: center;
}
#dragDropArea p{
    color: #999;
    font-weight: bold;
    font-size: 14px;
    font-size: 1.4em;
}
#dragDropArea .drag-drop-buttons{
    margin-top: 20px;
    font-size: 12px;
    font-size: 1.2em;
}
.drag-drop-buttons input{
    margin: auto;
}

JS ドラドロイベントでごにょごにょ&選択時にごにょごにょ
var fileArea = document.getElementById('dragDropArea');
var fileInput = document.getElementById('fileInput');
fileArea.addEventListener('dragover', function(evt){
  evt.preventDefault();
  fileArea.classList.add('dragover');
});
fileArea.addEventListener('dragleave', function(evt){
    evt.preventDefault();
    fileArea.classList.remove('dragover');
});
fileArea.addEventListener('drop', function(evt){
    evt.preventDefault();
    fileArea.classList.remove('dragenter');
    var files = evt.dataTransfer.files;
    console.log("DRAG & DROP");
    console.table(files);
    fileInput.files = files;
    photoPreview('onChenge',files[0]);
});
function photoPreview(event, f = null) {
  var file = f;
  if(file === null){
      file = event.target.files[0];
  }
  var reader = new FileReader();
  var preview = document.getElementById("previewArea");
  var previewImage = document.getElementById("previewImage");

  if(previewImage != null) {
    preview.removeChild(previewImage);
  }
  reader.onload = function(event) {
    var img = document.createElement("img");
    img.setAttribute("src", reader.result);
    img.setAttribute("id", "previewImage");
    preview.appendChild(img);
  };

  reader.readAsDataURL(file);
}

参考サイト

https://www.kabanoki.net/1552/
http://koheik.hatenablog.com/entry/2016/07/08/152936
https://qiita.com/sanapon1020/items/77d37fe1fd6f87740e1b

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

Node-REDのfunctionノードをフローライブラリに登録できるオリジナルノードに変換してみよう

 こんにちは、(株)日立製作所 研究開発グループ サービスコンピューティング研究部の横井一仁です。

 本ドキュメントでは、ノード開発容易化ツール「Node generator」の使い方をご紹介します。前の記事で紹介した画像認識を行うNode-REDのfunctionノードを、共有可能なオリジナルノードに変換してみます。

imagerecognition2.png

Node generatorとは

 Node generatorとは、Node-REDのオリジナルノードを自動生成するコマンドラインツールです。

 JavaScriptコードを書いたfunctionノードを共有可能なオリジナルノードに変換したり、Swagger Codegenの様にOpenAPIドキュメント(Swagger)からノードのソースコードを生成したりできます。

 日立が開発し、OSS化した本ツールは、現在OpenJS Foundation (Linux Foundation)管理下のNode-RED公式のサブプロジェクトとなっており、開発コミュニティと協力して機能拡張が行われているOSSになっています。

 本ツールは下記サイトで紹介されている様に、CiscoやIBMなどのNode-RED活用企業でも活用されています。

functionノードからオリジナルノードを生成

 本ドキュメントで用いた動作環境は、Node-REDをインストール済のWindows環境です。Node generatorはmacOSやLinuxにも対応していますので、その場合は適宜ファイルパス等を変更して試してみてください。

(1) functionノードのコードをライブラリに書き出す

 まず、functionノードのJavaScriptコードをファイルとして書き出す操作を行います。

 本操作は、functionノードのノードプロパティ上にある本のマークのボタンをクリックし、「ライブラリへ保存」を選択すればOKです。ライブラリにコードを書き出すと、Node-REDのホームディレクトリの下の「lib¥functions」ディレクトリ(Windowsの場合は「C:¥Users¥<ユーザ名>¥.node-red¥lib¥functions」)にJavaScriptのコードが入ったファイルが書き出されます。

(2) Node generatorをインストール

 管理者モードでコマンドプロンプトを起動し、ビルドツール(Windowsの場合のみ)とNode generatorをインストールします。

npm install -g node-red-nodegen
npm install -g windows-build-tools    (Windowsの場合のみ)

(3) 管理者モードのコマンドプロンプトを閉じる

(4) コマンドプロンプトを開き、ホームディレクトリへ移動

 ユーザモードでコマンドプロンプトを開き直し、cdコマンドを実行してホームディレクトリに移動します。

cd

(4) functionノードのJavaScriptコードからノードを生成

 Node generatorを用いてfunctionノードのJavaScriptコードからノードを生成します。

node-red-nodegen .node-red¥lib¥functions¥image-segmenter.js

 以上でノードの作成ができました。カレントディレクトリ内にnode-red-contrib-image-segmenterディレクトリが生成されているはずです。このディレクトリの中には、以降の手順で編集するpackage.json(モジュールの情報が記載されているファイル)とnode.js(Node-REDランタイムで実行されるノードのソースコードファイル)が含まれています。

 次にこのノードをNode-REDから呼び出せるように設定してみます。

(5) 生成されたディレクトリに移動

cd node-red-contrib-image-segmenter

 今回は、外部モジュールを利用しているため、追加で下記(5-A)(5-B)の手順も行います(functionノードが外部のnpmモジュールを使っていない場合は(5-A)(5-B)の手順はスキップしてください)。

(5-A) package.jsonファイルに依存関係を追加(17行目付近)

  "keywords": [
    "node-red-nodegen"
  ],
  "dependencies": {     <- 依存関係を追加
    "@codait/max-image-segmenter": "0.1.4"  <- 依存関係を追加
  },                    <- 依存関係を追加
  "devDependencies": {
    "node-red": "0.18.7",

(5-B) node.jsファイルに定義を追加(206行目付近)

        }
        sandbox.global.set("imageSegmenter", require('@codait/max-image-segmenter'));  <- vm.createContext()の前に定義を追加
        var context = vm.createContext(sandbox);
        try {
            this.script = vm.createScript(functionText, {

(6) 依存モジュールのインストール、シンボリック作成の準備

npm link

(7) Node-REDのホームディレクトリに移動

cd
cd .node-red

(8) シンボリックリンク作成

npm link node-red-contrib-image-segmenter

(9) Node-REDを起動

 node-redコマンドを用いてNode-REDを起動します。もし起動中の場合はCtrl+cで終了してから再度Node-REDを起動し直してください。

node-red

 Node-REDフローエディタ( http://:<ポート番号> )にアクセスすると、機能カテゴリの中に青色のimage-segmenterノードが登場しているはずです。

 本ノードは、書き出し元のfunctionノードと同様に、前にnode-red-contrib-browser-utilsモジュールのfile injectノード、後ろにdebugノードをつなぐことで、画像認識を行うことができます。

generatednode.png

最後に

 Node-REDは、非エンジニアでも開発できるビジュアルプログラミングツールであるため、コーディングの能力が必要となるfunctionノードを多用するとNode-REDの良さが薄れてしまいます。Node generatorを用いて、functionノードを再配布可能なノードに変換し、フローライブラリに登録することで、非エンジニアの方も再利用ができる様になるでしょう。

 Node generatorの使い方については、詳細の日本語ドキュメントも公開していますので、ぜひ見てみてください。

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

Vue.jsのWebアプリケーションで、なぜSEO対策にNuxt.jsは必要なのか。SEOのためにNuxt.jsのVue-metaを導入方法を紹介します。

 はじめに

この記事はLinkbal (リンクバル) Advent Calendar 2019の11日目の記事です。Vueの初心者のタンです。
今回は、弊社のプロダクションWebサイトにVueが導入されていますので。Vueに関する見識を最初から学んでいます。
ECサイトプロダクトを提供する弊社はSEO対策に特に気にしないといけないですから、今日は、SEO対策のためにVueのWebアプリケーションにNuxt.jsが必要になことを話したいと思います。

Vue.jsのWebアプリケーションでSEO対策が必要になる理由は?

普通のVue.jsでは、シングルページアプリケーション(Single Page Application : SPA)を簡単に作成しています。これは、もともと空のIndex.htmlというファイルが一つしかなくて、純粋にJavaScriptで生成されたアプリケーションです。
Webのコンテンツは JavaScriptが最初にロードされた後、サーバサイドから取り出されて、Index.htmlに書き込まれす。Webのルートの切り替えもJavaScriptもが処理します。

ほとんど、Webサイトランキングを失う理由はJavaScriptの不適切な処理のせいです。 実際に、SEOに関してフロントエンドフレームワークのようなVue.jsフレームワークには多くの問題があります。いくつかの以下問題です。

  • 単一ページアプリケーション(SPA)フレームワークです。
  • ページのロードスピードが遅い
  • メタ、カノニカル、およびサイトマップを更新するのが難しい。

Nuxt.jsとは何でしょうか?

Nuxtは、最新のWebアプリケーションを作成するためのVue.jsに基づくプログレッシブフレームワークです。
Vue.js 2.0に加え、Vue-Router、Vue-Meta、Vuex(ストアオプションを使うときのみ)というライブラリをNuxt.jsにインクルードしています。Nuxt.jsの主なメリットはWebアプリケーションの非同期データ、ミドルウェア、ルーティングなどを管理することです。詳しくように https://nuxtjs.org/ を参考できます。

SEO対策になんでNuxt.jsは必要なのか??

Nuxt.jsを採用すると、ユニバーサルアプリケーションを簡単に作成できます。

ユニバーサルアプリケーションは、Webサーバーにサイトコンテンツのデータをプリロードし、レンダリングされたHTMLをレスポンスとしてブラウザーにを返しすということです。
なので、SEOを改善し、ロードを高速化できるし、他の様々な利点を提供されます。
ユニバーサルアプリケーションでは、JavaScriptが読み込まれる前に、<head><title><meta><h1>、などのようなHTMLタグが事前にロードされて、コンテンツがページに表示されます。

これらのタグにより、クローラー(Googlebotなど)が正しくページの内容を評価できているよになります。

VueのWebアプリケーションのすべてページをハンドルするNuxt.jsの方法

Nuxt.jsのVue-metaというライブラリは、Webアプリケーションの各ページの<head>要素を処理します。 ページはルートを表すNuxtの用語であり、各ページはページフォルダー内にあります。

Nuxtでは、アプリケーションのページ内で<head>プロパティを定義する3つの方法を提供しています。

1. Vueファイルの全てページにデフォルトのメタを定義できます。

nuxt.config.js ファイル内にメタ情報を定義することは欠かせないです。

nuxt.config.js
export default {
  head: {
    titleTemplate: '%s - Nuxt.js',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' }
      { hid: 'description', name: 'description', content: 'Meta description' }
    ]
  }
}

<head> に設定できるオプション一覧は vue-meta のドキュメント を参照できます。

2. Vueファイルの静的ページにもメタ情報を定義できます。

現在のページの HTMLの<head> タグを定義するために head()メソッドを使う必要です。
ページのデータを使って独自のメタタグを定義することもできます。head()メソッド内で this変数を使ってデータを取り出すことができます。

pages/index.vue
<template>
  <h1>{{ title }}</h1>
</template>

<script>
export default {
  data () {
    return {
      title: 'Hello World!'
    }
  },
  head () {
    return {
      title: this.title,
      meta: [
        { hid: 'description', name: 'description', content: 'My custom description' }
      ]
    }
  }
}
</script>

3. 動的ページのメタタグの設定

動的ページ(アクセスした時の状況に応じて異なる内容が表示されるWebページ)のメタタグを適当にカスタマイズできます。 ユーザープロファイルページはの一つの例です。

パラメータを使って動的なルーティングを定義するには .vue ファイル名またはディレクトリ名に アンダースコアのプレフィックス を付ける必要があります。

pages/
--| users/
-----| _id.vue

自動的に以下が生成されます:

router.js
router: {
  routes: [{
    name: 'users-id',
    path: '/users/:id?',
    component: 'pages/users/_id.vue'
  }]
}

静的ページのように動的ページで、head()メソッド内で this変数を使ってデータを取り出して、ページのメタタグを定義することもできます。
そのユーザープロフィールページのメタタグを以下のように定義します。

pages/users/_id.vue
<script>
  head () {
    let user = this.user;

    return {
      title: `${user.fullName} @(${user.userName}) - Nuxt.js`,
      meta: [{
        hid: `iOSUrl`,
        property: 'al:ios:url',
        content: `myapp://user?screen_name=${user.userName}`
      },
      {
        hid: `description`,
        name: 'description',
        content: `${user.fullName}'s public profile at Nuxt.js`
      }]
    }
  }
</script>

小さい注意点は hidのプロパティです:

Vue-metaを使用すると、元のタグを置き換えるのではなく、重複するタグが作成されます。 ただし、Webサイトをクロールするときにタグが重複しているとSEOルールに違反する可能性があるため、各メタタグに一意のhidプロパティを常に設定して、一意に識別することをお勧めします。 hidプロパティがあると、vue-metaがタグを複製する代わりにタグを置き換えるようにわかります。

hidプロパティやメタタグが重複などについてもっと詳しくように、こちらで参考できます。

終わりに

Nuxtでは、ユニバーサルアプリケーションでhead要素をレンダリングする方法を多く制御できます。これはSEO対策にに役立ちます。 nuxt.config.jsファイル内にグローバルデフォルトを定義するための多くのオプションがあり、さらに各ページのheadメソッドにアクセスして、カスタマイズすることができます。

上記はSEO対策にNuxt.jsのいくつかの利点を学んだ知識です。私のVueの初心者のような方を助けると望みます。

参考した内容

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

SEOのためにNuxt.jsのVue-metaを導入方法について。

 はじめに

この記事はLinkbal (リンクバル) Advent Calendar 2019の11日目の記事です。Vueの初心者のタンです。
今回は、弊社のプロダクションWebサイトにVueが導入されていますので。Vueに関する見識を最初から学んでいます。
ECサイトプロダクトを提供する弊社はSEO対策に特に気にしないといけないですから、今日は、SEO対策のためにVueのWebアプリケーションにNuxt.jsが必要になることを話したいと思います。

Vue.jsのWebアプリケーションでSEO対策が必要になる理由は?

普通のVue.jsでは、シングルページアプリケーション(Single Page Application : SPA)を簡単に作成しています。これは、もともと空のIndex.htmlというファイルが一つしかなくて、純粋にJavaScriptで生成されたアプリケーションです。
Webのコンテンツは JavaScriptが最初にロードされた後、サーバサイドから取り出されて、Index.htmlに書き込まれす。Webのルートの切り替えもJavaScriptもが処理します。

ほとんど、Webサイトランキングを失う理由はJavaScriptの不適切な処理のせいです。 実際に、SEOに関してフロントエンドフレームワークのようなVue.jsフレームワークには多くの問題があります。いくつかの以下問題です。

  • 単一ページアプリケーション(SPA)フレームワークです。
  • ページのロードスピードが遅い
  • メタ、カノニカル、およびサイトマップを更新するのが難しい。

Nuxt.jsとは何でしょうか?

Nuxtは、最新のWebアプリケーションを作成するためのVue.jsに基づくプログレッシブフレームワークです。
Vue.js 2.0に加え、Vue-Router、Vue-Meta、Vuex(ストアオプションを使うときのみ)というライブラリをNuxt.jsにインクルードしています。Nuxt.jsの主なメリットはWebアプリケーションの非同期データ、ミドルウェア、ルーティングなどを管理することです。詳しくように https://nuxtjs.org/ を参考できます。

SEO対策になんでNuxt.jsは必要なのか??

Nuxt.jsを採用すると、ユニバーサルアプリケーションを簡単に作成できます。

ユニバーサルアプリケーションは、Webサーバーにサイトコンテンツのデータをプリロードし、レンダリングされたHTMLをレスポンスとしてブラウザーにを返しすということです。
なので、SEOを改善し、ロードを高速化できるし、他の様々な利点を提供されます。
ユニバーサルアプリケーションでは、JavaScriptが読み込まれる前に、<head><title><meta><h1>、などのようなHTMLタグが事前にロードされて、コンテンツがページに表示されます。

これらのタグにより、クローラー(Googlebotなど)が正しくページの内容を評価できているよになります。

VueのWebアプリケーションのすべてページをハンドルするNuxt.jsの方法

Nuxt.jsのVue-metaというライブラリは、Webアプリケーションの各ページの<head>要素を処理します。 ページはルートを表すNuxtの用語であり、各ページはページフォルダー内にあります。

Nuxtでは、アプリケーションのページ内で<head>プロパティを定義する3つの方法を提供しています。

1. Vueファイルの全てページにデフォルトのメタを定義できます。

nuxt.config.js ファイル内にメタ情報を定義することは欠かせないです。

nuxt.config.js
export default {
  head: {
    titleTemplate: '%s - Nuxt.js',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' }
      { hid: 'description', name: 'description', content: 'Meta description' }
    ]
  }
}

<head> に設定できるオプション一覧は vue-meta のドキュメント を参照できます。

2. Vueファイルの静的ページにもメタ情報を定義できます。

現在のページの HTMLの<head> タグを定義するために head()メソッドを使う必要です。
ページのデータを使って独自のメタタグを定義することもできます。head()メソッド内で this変数を使ってデータを取り出すことができます。

pages/index.vue
<template>
  <h1>{{ title }}</h1>
</template>

<script>
export default {
  data () {
    return {
      title: 'Hello World!'
    }
  },
  head () {
    return {
      title: this.title,
      meta: [
        { hid: 'description', name: 'description', content: 'My custom description' }
      ]
    }
  }
}
</script>

3. 動的ページのメタタグの設定

動的ページ(アクセスした時の状況に応じて異なる内容が表示されるWebページ)のメタタグを適当にカスタマイズできます。 ユーザープロファイルページはの一つの例です。

パラメータを使って動的なルーティングを定義するには .vue ファイル名またはディレクトリ名に アンダースコアのプレフィックス を付ける必要があります。

pages/
--| users/
-----| _id.vue

自動的に以下が生成されます:

router.js
router: {
  routes: [{
    name: 'users-id',
    path: '/users/:id?',
    component: 'pages/users/_id.vue'
  }]
}

静的ページのように動的ページで、head()メソッド内で this変数を使ってデータを取り出して、ページのメタタグを定義することもできます。
そのユーザープロフィールページのメタタグを以下のように定義します。

pages/users/_id.vue
<script>
  head () {
    let user = this.user;

    return {
      title: `${user.fullName} @(${user.userName}) - Nuxt.js`,
      meta: [{
        hid: `iOSUrl`,
        property: 'al:ios:url',
        content: `myapp://user?screen_name=${user.userName}`
      },
      {
        hid: `description`,
        name: 'description',
        content: `${user.fullName}'s public profile at Nuxt.js`
      }]
    }
  }
</script>

小さい注意点は hidのプロパティです:

Vue-metaを使用すると、元のタグを置き換えるのではなく、重複するタグが作成されます。 ただし、Webサイトをクロールするときにタグが重複しているとSEOルールに違反する可能性があるため、各メタタグに一意のhidプロパティを常に設定して、一意に識別することをお勧めします。 hidプロパティがあると、vue-metaがタグを複製する代わりにタグを置き換えるようにわかります。

hidプロパティやメタタグが重複などについてもっと詳しくように、こちらで参考できます。

終わりに

Nuxtでは、ユニバーサルアプリケーションでhead要素をレンダリングする方法を多く制御できます。これはSEO対策にに役立ちます。 nuxt.config.jsファイル内にグローバルデフォルトを定義するための多くのオプションがあり、さらに各ページのheadメソッドにアクセスして、カスタマイズすることができます。

上記はSEO対策にNuxt.jsのいくつかの利点を学んだ知識です。私のVueの初心者のような方を助けると望みます。

参考した内容

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

【javascript】配列の要素検索、文字列検索のコードレシピ

個人的メモ用
ES2016向け
後々追記

Array.prototype.find()

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/find

find() メソッドは、提供されたテスト関数を満たす配列内の 最初の要素 の 値 を返します。

構文
arr.find(callback(element[, index[, array]])[, thisArg])

配列を前方向から検討し、はじめに合致した要素の値を返却する。条件に合致しない場合はundefinedを返す。

基本形

const ar = [3, 5, 7, 9, 12];

const found = ar.find(el => el > 10);
const found2 = ar.find(el => el > 5);
const found3 = ar.find(el => el > 1000);

console.log(found);
console.log(found2);
console.log(found3);

// 12
// 7
//undefined

応用

配列内のオブジェクトをプロパティの一つで検索

const inventory = [
    {name: 'apples', quantity: 2},
    {name: 'bananas', quantity: 0},
    {name: 'cherries', quantity: 5}
  ];

  function isCherries(fruit) { 
    return fruit.name === 'cherries';
  }


  console.log(inventory.find(isCherries)); 
  // { name: 'cherries', quantity: 5 }

Array.prototype.findIndex()

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex

findIndex() メソッドは、配列内の要素が指定されたテスト関数を満たす場合、配列内の インデックス を返します。そうでない場合は -1 を返します。

構文
arr.findIndex(callback[, thisArg])

Array.prototype.find()とは異なり、要素の値ではなく添字を返す

基本形

const ar = [5, 7, 9, 11, 15];
const isLargeNum = (ele) => ele > 3;
const isLargeNum2 = (ele) => ele > 13;
const isLargeNum3 = (ele) => ele > 100;

console.log(ar.findIndex(isLargeNum));
console.log(ar.findIndex(isLargeNum2));
console.log(ar.findIndex(isLargeNum3));
//0
//4
//-1

応用

配列から6より大きい値の添字を検索する

const ar = [ 1, 5, 7, 9, 11, 13] ;
//オプションにindexと配列を指定する
var check = function ( value, index, array ) {
    if( value >=6  ) {
        return true ;
    } else {
        return false ;
    }
}
console.log(ar.findIndex(check));   
//2

trueとなった場合にその値のインデックス番号がメソッドの返り値となる。

check( 1, 0, array ) ;  // false
check( 5, 1, array ) ;  // false
check( 7, 2, array ) ;  // true (この時点で処理を終了)
check( 9, 3, array ) ;  // false
check( 11, 4, array ) ; // true
check( 13, 5, array ) ; // true

配列の要素を前方から検索し、合致した場合そのindexを返却する

const fruits = ["java", "python", "r", "ruby", "scala"];

console.log(fruits.findIndex(fruit => fruit ==  "ruby"))
//2

String.prototype.indexOf()

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf

indexOf() メソッドは、呼び出す String オブジェクト中で、 fromIndex から検索を始め、指定された値が最初に現れたインデックスを返します。値が見つからない場合は -1 を返します。

構文
str.indexOf(searchValue[, fromIndex])

基本形

var paragraph = 'The quick brown fox jumps over the lazy dog. If the dog barked, was it really lazy?';

var searchTerm = 'dog';
var indexOfFirst = paragraph.indexOf(searchTerm);

console.log(indexOfFirst)
//40

応用形

検索開始位置を指定する

var str = 'java, ruby, ruby, scala, ruby';

// 14番目の文字から検索を開始する
var result = str.indexOf( 'ruby', 14 );

console.log(result);
//25
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PlayCanvasで簡単に簡単なARコンテンツを作る!

PlayCanvasとは?

PlayCanvasとはWebGLベースのゲームエンジンのJavaScriptのライブラリで、開発~公開までをウェブ上で行うことができます。
ARやVRについても簡単に開発することができます。

PlayCanvasのユーザー作成についてはこちらをご覧ください

アカウントの作成 - PlayCanvas

今回作ったもの

このQRコード(ARマーカーを読み込むとウェブ上でARが遊べます)

pattern-akeome (1).png

今回はこれをどうやって作るかという記事になります。

1.公式サンプルにアクセス

1.png

  1. アカウントを作成したら公式サンプルのAR Starter Kitにアクセスします。

  2. プロジェクトの画面からForkをします。

2. プロジェクト名を入力

3.png

  1. Fork後のプロジェクトの名前を入力します。日本語も使えるので今回は「AR年賀状」とします。

Forkをしたプロジェクトにアクセスをして「Editor」に入ります。

4.png

開発画面を起動する

5.png

Forkをすると、新しいプロジェクトが作成され、編集ができるようになりますのでEDITORをクリックします。

Publishする

PlayCanvasはエディターからPUBLISHができるのでそちらでPUBLISHをします。
標準のマーカーを読み取ると動かすことができます。

publish.png
PUBLISH / DOWNLOADをクリック

6.png
PUBLISH TO PLAYCANVASをクリック

7.png
URLを取得

ARマーカーとQRコードを作成する

QRコードを作成する

QRコードを作成するウェブサイトなどでPlayCanvasのPUBLISHされたURLを使って作成します。

https://www.cman.jp/QRcode/

image.png

ARマーカーを作成する

作成したQRコードをチラのウェブサイトから.pattファイルに変換します。

https://jeromeetienne.github.io/AR.js/three.js/examples/marker-training/examples/generator.html

PlayCanvasにARマーカーを作成する

paat.png
ARマーカーを設定する
ダウンロードした、.pattファイルをドラッグアンドドロップしてPlayCanvasのエディターにアップロードします。

アップロードしたあとMatrix Markerを選択し、アップロードした、.pattファイルを適用します。

もう一度PUBLISHする

変更を加えたのでもう一度Publishします。

QRコードを読み込む

QRコードを読み込み、iPhoneの場合はSafariを使用して確認します。
カメラの権限を求められますので許可をし、ARマーカーにカメラをかざすとPlayCanvas製のゲームが表示されます。

PlayCanvas開発で参考になりそうな記事の一覧です。

入門
- PlayCanvas入門- モデルの作成~ゲームに入れ込むまで
- JavaScriptでスロットを実装する。【PlayCanvas】
- 3Dモデルのビューワーを3分で作る【初めてのPlayCanvas】

応用
- PlayCanvasのコードエディターでes6に対応する
- Gulpのプラグインを書いたらPlayCanvasでの開発がめちゃくちゃ便利になった
- PlayCanvas Editorに外部スクリプトを読み込む新機能が追加されたので開発方法を考える。- Reduxを組み込む

その他の記事はこちらになります。
- React Native + PlayCanvasを使ってスマートフォンゲームを爆速で生み出す
- PlayCanvasのエディター上でHTML, CSSを組み込む方法
- 【iOS13】新しくなったWebVRの使い方

その他関連

- PlayCanvasタグの付いた記事一覧

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

PlayCanvasで簡単に簡単なWebARコンテンツを作る!

AR Advent Calendar 2019 7日目

投稿が遅くなってしまいました、ARコンテンツ7日目です。PlayCanvasを使用してWebARのコンテンツを簡単に作る方法を紹介します。

PlayCanvasとは?

PlayCanvasとはWebGLベースのゲームエンジンのJavaScriptのライブラリで、開発~公開までをウェブ上で行うことができます。
ARやVRについても簡単に開発することができます。

PlayCanvasのユーザー作成についてはこちらをご覧ください

アカウントの作成 - PlayCanvas

今回作ったもの

このQRコード(ARマーカーを読み込むとウェブ上でARが遊べます)

pattern-akeome (1).png

動画

output.gif

今回はこれをどうやって作るかという記事になります。

1.公式サンプルにアクセス

1.png

  1. アカウントを作成して公式サンプルのAR Starter Kitにアクセスします。

  2. プロジェクトの画面からForkをします。

2. プロジェクト名を入力

3.png

  1. Fork後のプロジェクトの名前を入力します。日本語も使えるので今回は「AR年賀状」とします。

Forkをしたプロジェクトにアクセスをして「Editor」に入ります。

4.png

開発画面を起動する

5.png

Forkをすると、新しいプロジェクトが作成され、編集ができるようになりますのでEDITORをクリックします。

Publishする

PlayCanvasはエディターからPUBLISHができるのでそちらでPUBLISHをします。
標準のマーカーを読み取ると動かすことができます。

publish.png
PUBLISH / DOWNLOADをクリック

6.png
PUBLISH TO PLAYCANVASをクリック

7.png
URLを取得します。

ARマーカーとQRコードを作成する

QRコードを作成する

QRコードを作成するウェブサイトなどでPlayCanvasのPUBLISHされたURLを使って作成します。

https://www.cman.jp/QRcode/

ARマーカーを作成する

作成したQRコードをチラのウェブサイトから.pattファイルに変換します。

https://jeromeetienne.github.io/AR.js/three.js/examples/marker-training/examples/generator.html

PlayCanvasにARマーカーを設定する

paat.png

ARマーカーを設定する

ダウンロードした.pattファイルをPlayCanvasのエディター上に、ドラッグアンドドロップしてPlayCanvasのエディターにアップロードします。アップロードしたあとMatrix Markerエンティティを選択し、アップロードした、.pattファイルを適用します。

もう一度PUBLISHする

変更を加えたのでもう一度PUBLISHします。

QRコードを読み込む

QRコードを読み込み、iPhoneの場合はSafariを使用して確認します。
カメラの権限を求められますので許可をし、ARマーカーにカメラをかざすとPlayCanvas製のゲームが表示されます。

文字や3Dモデルのインポートやエフェクトについての説明はこちらの記事をご覧ください。

PlayCanvasでAR年賀状を作る

以下PlayCanvas開発で参考になりそうな記事の一覧です。

入門
- PlayCanvas入門- モデルの作成~ゲームに入れ込むまで
- JavaScriptでスロットを実装する。【PlayCanvas】
- 3Dモデルのビューワーを3分で作る【初めてのPlayCanvas】

応用
- PlayCanvasのコードエディターでes6に対応する
- Gulpのプラグインを書いたらPlayCanvasでの開発がめちゃくちゃ便利になった
- PlayCanvas Editorに外部スクリプトを読み込む新機能が追加されたので開発方法を考える。- Reduxを組み込む

その他の記事はこちらになります。
- React Native + PlayCanvasを使ってスマートフォンゲームを爆速で生み出す
- PlayCanvasのエディター上でHTML, CSSを組み込む方法
- 【iOS13】新しくなったWebVRの使い方

PlayCanvasのユーザー会のSlackを作りました!

少しでも興味がありましたら、ユーザー同士で解決・PlayCanvasを推進するためのSlackを作りましたので、もしよろしければご参加ください!

日本PlayCanvasユーザー会 - Slack

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

Nuxt × processmd × Netlify でブログサイトを作る

はじめに

この記事はJAMstack Advent Calendar 2019の12日目の記事です。
JAMstackについては既に今回のアドベントカレンダーの記事でも他の方が記事を書いてくださっているので省略いたします。

今回の記事ではNuxt.jsのジェネレート機能を使って記事をマークダウンで書くことができるブログサイトについて紹介します。
また、Nuxt初心者なので間違えている部分も多くあるかもしれませんがご容赦くださいm(_ _)m

今回利用した主なもの

  • Nuxt.js
    • Vue.jsアプリケーションを構築するためのフレームワーク
    • 静的ジェネレート機能も実装されているので今回はこれを使ってジェネレート
  • Netlify
    • 静的ホスティングサービス
  • Github
    • ここにリポジトリを置いておく
    • GithubのmasterにプッシュされたらNetlifyにデプロイされるように設定をしておく
  • processmd
    • マークダウンをコマンドラインでJSONに変換してくれるnpmモジュール
  • TypeScript
    • JSに型をつけることで精神的に優しいコーディング環境を

記事更新フロー

今回は下記のようなフローで記事を更新できるようにしました。

  • 1. Markdownで記事を書く
  • 2. processmdでMarkdownからJSONを生成
  • 3. Nuxt.jsでJSONを元に記事ページをジェネレート
  • 4. Githubにコミット&プッシュ
  • 5. プッシュを受けてNetlifyに自動デプロイ

プロジェクトの作成

Nuxtプロジェクトの立ち上げ

create-nuxt-app でサクッと立ち上げます。
今回はこんな感じで設定を行いました。

create-nuxt-app v2.12.0
✨  Generating Nuxt.js project in .
? Project name nuxt-markdown-demo
? Project description My fantastic Nuxt.js project
? Author name Shoch0922
? Choose the package manager Npm
? Choose UI framework None
? Choose custom server framework None (Recommended)
? Choose Nuxt.js modules Axios, Progressive Web App (PWA) Support
? Choose linting tools ESLint, Prettier
? Choose test framework None
? Choose rendering mode Single Page App
? Choose development tools jsconfig.json (Recommended for VS Code)

その後にTypeScript用の設定をします。
Nuxt2.9でのTypeScript設定は公式サイトのドキュメントがよくできているのでこれを参考にします。

processmdの設定

まずはprocessmdをnpmでインストール

npm i processmd -D

そして以下のようなnpm scriptを設定しておきます。

{
  "scripts" : {
    "md": "processmd ./content/posts/**/*.md --stdout --outputDir ./content/posts/json > summary.json"
  }
}

上のコマンドで./content/posts/**/に格納されているMarkdownファイルを元に記事ごとにJSONファイルを生成し
記事ごとのJSONの情報をまとめたsummary.jsonを生成してくれます。

各記事のMarkdownを作成する

記事用のMarkdownを作ります。

記事のメタ情報的な部分はYAML front matterの形式に合わせて記述します。
それ以外は普通にMarkdownの記述で記事をガシガシ書いていきます。

---
title: hoge
created_at: 2019-12-01
image: dummy.jpg
---

## 見出し 2

本文です。本文です。本文です。本文です。本文です。本文です。
本文です。本文です。本文です。本文です。本文です。本文です。
本文です。本文です。本文です。本文です。本文です。本文です。
本文です。本文です。本文です。本文です。本文です。本文です。

...

ここで開発用に書いたサンプル記事をprocessmdを使ってJSONファイルに書き出します。

Nuxtでの記事データ読み込み

Nuxtで書き出したJSONを読み込んで状態管理しておくstoreを作り、getterを2つ定義します。
一つはfileMapを取得するもの、もう一つはsourceFileArray(元となるmdのパスが格納されている配列)を取得するものです。

import blogs from '../summary.json'
export type Blogs = typeof blogs

export const state = () => ({
  data: blogs
})

export const getters = {
  getSourceFileArray(state: any) {
    return state.data.sourceFileArray
  },
  getFileMap(state: any) {
    return state.data.fileMap
  }
}

記事一覧の実装

記事一覧コンポーネントを作成します。

<template>
  <ul v-if="blogs">
    <li v-for="blog in blogs" :key="blog.id">
      <nuxt-link :to="link(blog.date, blog.title)">
        <figure>
          <img :src="`/images/${blog.image}`" :alt="blog.title" />
        </figure>

        <div class="overlay">
          <h2>{{ blog.title }}</h2>
        </div>
      </nuxt-link>
    </li>
  </ul>
</template>

<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
import moment from 'moment'

export declare interface IBlogList {
  id: number
  title: string
  image: string
  date: string
}

@Component
export default class BlogList extends Vue {
  private blogs: Array<IBlogList> = []
  private blog: IBlogList

  private created() {
    const data = this.$store.getters['blogData/getFileMap']

    Object.keys(data).forEach((val, i) => {
      const blog: IBlogList = {
        id: i,
        title: data[val].title,
        image: data[val].image,
        date: moment(data[val].created_at).format('YYYY-MM-DD')
      }
      this.blogs.unshift(blog)
    })
  }

  private link(date: string, sulg: string) {
    return `/blog/${date}/${sulg}`
  }
}
</script>

かなりシンプルな作りになりました。
storeのgetterを叩いてfileMapを取得し、各記事のデータをforEachで取り出します。
取り出したデータを実際に表示するためにメンバ配列に格納し、この配列をv-forでループし仮想DOMをレンダリングします。

各記事ページへのリンクのパラメータとしてdatetitleを使います。
このURLを作成するためのメソッドを用意し、各ページへのリンク先を指定します。

これで記事一覧は完成です。

記事ページ

最後に記事の個別ページを実装します。

<template>
  <div class="wrapper">
    <Header />
    <main>
      <div class="container">
        <div
          class="content"
          v-html="sanitizeHTML(bodyHtml, { allowedTags: false })">
        </div>

        <div class="return">
          <nuxt-link to="/">TOPへ戻る</nuxt-link>
        </div>
      </div>
    </main>
  </div>
</template>

<script lang="ts">
import { Vue, Component } from 'nuxt-property-decorator'
import sanitizeHTML from 'sanitize-html'

import Header from '~/components/Header.vue'

@Component({
  components: {
    Header
  }
})
export default class BlogSingle extends Vue {
  private sanitizeHTML: sanitizeHTML = sanitizeHTML

  validate({ params, store }) {
    return store.state.blogData.data.sourceFileArray.includes(
      `./content/posts/${params.date}-${params.sulg}.md`
    )
  }

  asyncData({ params }) {
    return Object.assign(
      {},
      require(`~/content/posts/json/${params.date}-${params.sulg}.json`),
      {
        params
      }
    )
  }
}
</script>

validateメソッドを使って実際にその記事のMarkdownファイルが存在しているかをチェックします。
バリデートを通過したらasyncDataメソッドを使ってコンポーネントのローディング前に該当記事のjsonデータを読み込みます。

読み込んだデータを元にv-htmlで表示をします。
自分で書いたMarkdownを表示してるだけなので、セキュリティリスクはほとんどありませんが気持ちsanitizeHTMLを使ってサニタイズします。

あとは良しなにCSSを当てて上げればブログの完成です。

デプロイ

まずはGithubに任意のリポジトリを作ります。
そしてNetlifyで新しいプロジェクトを作ります。Githubへ認証をすれば許可してるリポジトリが勝手に出てくるので便利です。

TypeScript化したNuxtプロジェクトをNetlifyにデプロイする肝はこの設定です。
image.png

Build Commandの部分をしっかりTypeScript対応させます。
そして公開するディレクトリを選んでデプロイします。

ビルドが無事に完了したらサイト公開完了です!
https://quirky-almeida-833f9a.netlify.com/

まとめ

Nuxtを使ってMarkdownで書かれた記事からJAMstackなブログを作ることができました!
Netlifyを使うことによってGithubにコミット&プッシュをするだけでデプロイが完了するので運用もかなり簡単なサイトを構築することができました。

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

Intersection Observerでスクロールアニメーションをつくってみる

はじめに

今までスクロールに連動した処理を実装するときは、jQueryのscrollイベントを使ってきました。
しかし上記の処理だとスクロールの度に実行され、パフォーマンスに影響を及ぼすことがあります。

今回はIntersection Observer APIを使い、より効率的なスクロール連動アニメーションをつくってみました。

Intersection Observer API (交差監視 API) とは

ターゲットとなる要素が、祖先要素もしくは文書の最上位のビューポートと交差する変更を非同期的に監視する方法を提供します。
https://developer.mozilla.org/ja/docs/Web/API/Intersection_Observer_API

つまり、これを使うとスクロールして特定の位置にきた時のみに処理を実行できます。

scrollイベントではブラウザ幅の変更で特定の位置が変わった際に、resize処理を考える必要がありました。
Intersection Observerはviewportの変更で要素の位置が変わっても自動的に反応します。
スクロールの度に処理が実行されることがないので、パフォーマンス的にも良いとされてます。

つくってみた

See the Pen Scroll_Animation by M_H (@hitsujiball) on CodePen.

JavaScript
/**
* スクロールアニメーション
*/
const _setScrollAnime = () => {
  const sectionPos = document.querySelectorAll('.js-sec-target'); // 監視対象の要素

  // IntersectionObserverのオプション設定
  const options = {
    root: null,
    rootMargin: '-50% 0px',
    threshold: 0.5
  };

  // IntersectionObserverを呼び出す
  const observer = new IntersectionObserver(anchorSetting, options);

  // ターゲット(sectionPos)を監視する
  for(let p = 0; p < sectionPos.length; p++) {
    (function() {
      observer.observe(sectionPos[p]);
    })();
  }

 // 要素が交差する度(スクロール位置がターゲットに到達する度)に _setCurrent関数 を呼び出す
 function anchorSetting(entries) {
    for(let i = 0; i < entries.length; i++) {
      (function() {
        if(entries[i].isIntersecting) {
          _setCurrent(entries[i]);
        }
      })();
    }
  };

  // div.anime に .is-currentクラス を付与する関数
  const _setCurrent = (target) => {
    //.is-currentクラス が付与された要素を取得
    const currentIndex = document.querySelector('.js-anime.is-current');

    // currentIndex に .is-currentクラス が付いていたら削除(初期化)
    if(currentIndex !== null) {
      currentIndex.classList.remove('is-current');
    }

    // 現在地の div.anime に .is-currentクラス を付与
    const currentNew = document.querySelector(`.${target.target.children[1].classList[1]}`);
    currentNew.classList.add('is-current');
  };
};

_setScrollAnime();

作り方

  1. section.js-sec-targetがブラウザの高さ50%までスクロールしたら、_setCurrent()を実行します。 alt
  2. div.anime(グレーの正方形)に.is-currentクラスを付与します。(※アニメーションはCSS側で実装しました。) alt

注意点

IE11はポリフィルが必要

IE11はIntersectionObserverに対応していないので、ポリフィルを追加する必要があります。
https://github.com/w3c/IntersectionObserver/tree/master/polyfill

感想

今回はスクロールアニメーションを作りましたが、他にも画像の遅延読み込みやグロナビのカレント表示など、様々な場面で活躍しそうだなと思いました。
パフォーマンスを考慮したコーディングができるよう、恐れず積極的に使ってみたいです。

参考サイト

以下を参考にさせていただきました。
https://developer.mozilla.org/ja/docs/Web/API/Intersection_Observer_API
https://ics.media/entry/190902/

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