ushumpei’s blog

生活で気になったことを随時調べて書いていきます。

React NativeでARを体感する、Introducing Expo AR

 9月末にExpoがアップデートされてiOSAR Kit対応が行われました。チュートリアルが公開されたのでちょっと手を出してみました。使用する要素としては、

  • expo
  • three
  • expo-three

のみです。3Dオブジェクトの生成はThree.jsレンダリングExpoExpo Threeという構成になっています。

Introducing Expo AR: Three.js on ARKit

デモ


Introducing Expo AR #1

感想

 Expoの制限はあるもののARアプリをJavaScriptでかけるというのは前提となる知識量のハードルが下がるかと思います。

 チュートリアルやると満足感が得られてしまうのでやや危険ですね。色々いじってみるのは大切な一歩だと思いますが、一歩踏み出した後に自分の発想が広がっていかないのがなかなか辛いところです。

サービスがSSL化するとき

 ものすごく短い話ですが、サービスがSSL化するときの対応を想定してみました。近々知り合いのサイトにSSL入れられることになりそうなのが理由です。そういえばはてなブログSSL化するそうですね!

概要

 httphttpsに書き換えるの面倒ですよね。省略できた気がします。(確かにできるみたいです参考: リンクのhttp:https:を省略して現在のプロトコルでリンク先にアクセスさせる)。ただ参考リンクのタイトル通り、通信は現在のスキームになるのでほおっておくと 本来SSLが必要なページでもhttpで接続されてしまいます。というのはかなり問題だと思うので、移行するときは以下の切り分けをするかもしれないです。

  • リソースとかユーザーの情報を扱わないリンクに関しては、スキーム省略
  • 明示的にssl化したページはしっかりhttps書いておく
    • またはsslページにリダイレクトさせる

切り分け方法: 追記(2017年10月3日)

  • 内部
    • リソースとかユーザーの情報を扱わないリンクに関しては、スキーム省略
    • 明示的にssl化したページはしっかりhttps書いておく
  • 外部
    • リンク先に合わせたスキームを書く

感想

 でも切り分けるくらいならhttpsに置換しちゃったほうが早くない?というと、それもそうだと思います。前に、「画面動かない!直して!->ssl切れちゃってました」、というhttps -> http現象があったので対応減らせたらいいなと思った次第です。

 混在コンテンツ(Mixed Content)に対しても効果があるそうです。

配列でsplitもどき(JavaScriptメモ)

 JavaScriptに関するメモです。配列(Array)を適当な部分で分割したかったのでsplitもどきの関数を書きました。文字列のsplitをちゃんと配列版にしたわけではないので「もどき」と言っています。

function split(array, separator) {
  return array.reduce(function(p, n, i) {
    if (n !== separator) {
      p[p.length - 1].push(n);
    } else {
      if (i !== 0 && i !== array.length - 1 && array[i - 1] !== separator) p.push([]);
    }
    return p;
  }, [[]])
}
// 's'で分割する
split(['a', 'b', 's', 'c', 'd', 's', 'e'], 's')
=>
[
  ["a", "b"],
  ["c", "d"],
  ["e"]
]

// 配列の最初と最後に区切り文字があるときは、空配列を作らずに無視する
// 連続している区切り文字も1つとみなし、空配列は作らない
split([1,2,3,3,3,3,3,4,5,4,3,2,2,1,1,2,3,3,4,1], 1);
=>
[
  [2, 3, 3, 3, 3, 3, 4, 5, 4, 3, 2, 2],
  [2, 3, 3, 4]
]

// 区切り文字が配列に含まれていないときは、配列そのものを要素に持った配列を返す
split([1,2,3,3,3,3,3,4,5,4,3,2,2,1,1,2,3,3,4,1], 0)
=>
[[1, 2, 3, 3, 3, 3, 3, 4, 5, 4, 3, 2, 2, 1, 1, 2, 3, 3, 4, 1]]

感想

 必要に駆られて書いてみたので、文字列のsplitの配列版にはなっていないです。スプレッドシートExcel方眼紙状態になっていて、そこからデータを取得しなきゃいけない、というときに使用しました。

 配列版splitを作る場合、配列を区切り文字にして配列を分割できるようにするのが自然だと思います。文字列のsplitをうまく使って配列版をかけないか?と思ったのですが、空文字とかの扱いが微妙です。

 なんか圏論とか使うと整理できそうな気がしました。文字列対象から配列対象への射を、配列関手で写した先みたいな。全然よくわかっていないです。圏論ということはHaskellでの実装を見ると面白いかもしれないです。(多分split的なものがある?)

ページに目次をつけて、項目を押したらスクロールする

 ものすごい小さい話ですが、毎回忘れてしまうのでメモします。

$('html,body').animate({
  scrollTop: window.pageYOffset + target.getBoundingClientRect().top - 8 // 要素がブラウザ上部ぴったりになるので少し隙間をあけたり
}, 500, 'swing');

 まずwindow.pageYOffsetについて、これはwindow.scrollYエイリアスだそうです。ページの一番上を0として、どのくらい下にスクロールしているかのピクセル値を返します。

 次にelement.getBoundingClientRect().topですが、現在クライアントが見ている画面の上端を0として、そこからどれくらい要素が離れているかをピクセル値で返します。例えば要素をスクロールして通り過ぎていたら、負の値が返ってきます。

  つまり、ページの一番上から現在表示している画面上端までの距離現在表示している画面上端から要素までの距離を足すことでページの一番上から要素までの距離を計算しているということになります。(多分絵で描くとわかりやすい???)

ページの一番上から現在表示している画面上端までの距離

f:id:ushumpei:20170925210302p:plain

現在表示している画面上端から要素までの距離

f:id:ushumpei:20170925210314p:plain

まとめ

 多分覚えたのでもう忘れないはずです。ぶっちゃけていうとすでにここに書いてありました。。。

おまけ

 デモです。

See the Pen MEbNmj by ushumpei(@ushumpei) on CodePen.

一枚にまとめたやつ

<html>
  <head>
    <style type="text/css">
      .container {
        display: flex;
        flex-direction: row;
        justify-content: space-between;
      }
      .menu {
        background-color: #eee;
        display: flex;
        flex-direction: column;
        flex: 1;
        height: 120px;
        justify-content: space-around;
        padding: 8px;
        position: sticky;
        top: 8px;
      }
      .content {
        display: flex;
        flex-direction: column;
        flex: 3;
        justify-content: space-between;
        margin-left: 8px;
      }
      .section {
        height: 100vh;
      }
      .section > h1 {
        background-color: #eee;
        padding: 8px;
        margin-top: 0;
      }
    </style>
    <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
    <script>
      window.addEventListener('load', function() {
        var menuItems = Array.from(document.getElementsByClassName('menu-item'));
        var sections =  Array.from(document.getElementsByClassName('section'));
        menuItems.forEach(function(item, i) {
          var target = sections[i];
          item.addEventListener('click', function() {
            $('html,body').animate({
              scrollTop: window.pageYOffset + target.getBoundingClientRect().top - 8 // 要素がブラウザ上部ぴったりになるので少し隙間をあけました
            }, 500, 'swing');
          });
        });
      });
    </script>
  </head>
  <body>
    <div class="container">
      <div class="menu">
        <a href="#" class="menu-item">section1</a>
        <a href="#" class="menu-item">section2</a>
        <a href="#" class="menu-item">section3</a>
        <a href="#" class="menu-item">section4</a>
        <a href="#" class="menu-item">section5</a>
      </div>
      <div class="content">
        <div class="section"><h1>section1</h1></div>
        <div class="section"><h1>section2</h1></div>
        <div class="section"><h1>section3</h1></div>
        <div class="section"><h1>section4</h1></div>
        <div class="section"><h1>section5</h1></div>
      </div>
    </div>
  </body>
</html>

google app scriptで背景色の置換

google app scriptで背景色の置換スクリプトを書いたのでメモします。以下の内容が含まれています。

  • 独自(「拡張ツール」)メニューの追加
  • モーダル(背景色置換モーダル)の表示
  • 初期値の挿入(テンプレートhtmlの使用)方法

概要

 主にgoogleのガイドを参考にして作成しました。とりあえずできることと、コードを記載します。

 できること;

  • スプレッドシート内での背景色の置換、置換実行モーダルの表示
  • 選択したセルの背景色を置換元としてデフォルト値として挿入

 そんなこといいから、という方は、スプレッドシートを開いて、「ツール」>「スクリプトエディタ」を選択し、スクリプトエディタのgsファイルにjavascriptコード、新規作成でhtmlファイルを作成しテンプレートファイル(index.html)を貼り付けてください。貼り付けたあとリロードするとメニューに「拡張ツール」が現れるかと思います。現れない場合はコメント頂けると幸いです。。。

f:id:ushumpei:20170925003319p:plain

 コード;

main.gs

function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('拡張ツール')
    .addItem('背景色置換', 'showModal')
    .addToUi();
}

function showModal() {
  var background = SpreadsheetApp.getActiveSheet().getActiveCell().getBackground();

  var template = HtmlService
    .createTemplateFromFile('index.html');
  template.from = background;
  
  var html = template
    .evaluate()
    .setWidth(300)
    .setHeight(150)
    .setSandboxMode(HtmlService.SandboxMode.IFRAME);

  SpreadsheetApp.getUi().showModalDialog(html, 'Replace background color');
}

function replaceBackgroundColor(from, to) {
  var sheet = SpreadsheetApp.getActiveSheet();
  var data = sheet.getDataRange();
  var backgrounds = data.getBackgrounds();
 
  var replacedCount = 0;
  for(var row = 0; row < backgrounds.length; row++) {
    for(var col = 0; col < backgrounds[row].length; col++) {
      if (backgrounds[row][col] != from) continue;
      sheet.getRange(row + 1, col + 1).setBackground(to);
      replacedCount++;
    }
  }

  ui = SpreadsheetApp.getUi();
  ui.alert('Successfully replaced!', replacedCount + ' cell\'s background color replaced to ' + to + ' from ' + from, ui.ButtonSet.OK);
}

index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
    <script>
      function handleClick() {
        document.getElementById('replace').setAttribute("disabled","disabled");
        var form = document.getElementById('form');
        var from = form.querySelector('input[name="from"]').value;
        var to = form.querySelector('input[name="to"]').value;
        if (!from || !to) return showError('Input #from and #to');
        google.script.run
          .withSuccessHandler(closeModal)
          .withFailureHandler(showError)
          .replaceBackgroundColor(from, to);
      }
      
      function closeModal() {
        google.script.host.close();
      }
      
      function showError(message) {
        document.getElementById('error').innerHTML = 'Error: ' + message;
        document.getElementById('replace').removeAttribute("disabled");
      }
    </script>
    <style type="text/css">
      html, body, #form { height: 100%; }
    </style>
  </head>
  <body>
    <div id="form" style="display: flex; justify-content: space-between; flex-direction: column;">
      <div style="display: inline-flex; justify-content: flex-end">
        <label for="from" style="flex: 1">From: </label>
        <input id="from" type="text" name="from" placeholder="from" value="<?= from ?>" style="flex: 3">
      </div>
      <div style="display: inline-flex; justify-content: flex-end">
        <label for="to" style="flex: 1">To: </label>
        <input id="to" type="text" name="to" placeholder="to" style="flex: 3">
      </div>
      <button id="replace" onclick="handleClick()">Replace</button>
      <p id="error" style="color: red"></p>
    </div>
  </body>
</html>

解説

下準備

 onOpenスプレッドシートファイルを開いたときに実行する関数を指定できます。ここではスプレドシートのUIのAPIを呼び出して、メニューの追加と、メニュー項目と選択時の実行関数の登録を行なっています。

 メニュー項目が選択された際に実行される関数は、選択中のセルの背景色取得、テンプレートへの値挿入、モーダルの表示などです。対応するコードを以下に記載します;

 まず現在選択中のセル(光っているセル)の背景色を取得するためにgetActiveCellを使ってセルを取得します。そのあとにgetBackgroud#?????? 形式のカラーコードを取得し格納します。

  var background = SpreadsheetApp.getActiveSheet().getActiveCell().getBackground();

次に、作成済みのhtmlテンプレートを読み込み、プレースホルダfromに対し背景色を割り当てます。プレースホルダーの指定は、

<input id="from" type="text" name="from" placeholder="from" value="<?= from ?>" style="flex: 3">

のようにvalue=<?= form ?>となっていて、この仕組みを使って置換前の背景色のデフォルト値、として現在選択中のセルの背景色を割り当てます。参考

  var template = HtmlService
    .createTemplateFromFile('index.html');
  template.from = background;

最後にモーダルの表示処理を行います。

  var html = template
    .evaluate()
    .setWidth(300)
    .setHeight(150)
    .setSandboxMode(HtmlService.SandboxMode.IFRAME);

  SpreadsheetApp.getUi().showModalDialog(html, 'Replace background color');

 evaluateメソッドを呼び出すことで、テンプレートからhtmlオブジェクトを生成します、モーダルとして表示するためにサイズを設定し、実行方式をiframeに指定しました。実行方法に関しては他に選択肢があるようで、調べきれていないです。なので特に深い意味はありません、チュートリアルに沿って、iframeを指定しただけです。

モーダル

 モーダルの記載方法に関しては1ページのhtmlを書いて行く感じです。heightwidthが決まっている以外はwebと同じ感覚だと言えます。ただし、少し違うのは、スクリプトエディタで定義した関数を呼び出す方法があるということです。方法としては提供されているAPIを使用します;

      function handleClick() {
        document.getElementById('replace').setAttribute("disabled","disabled");
        var form = document.getElementById('form');
        var from = form.querySelector('input[name="from"]').value;
        var to = form.querySelector('input[name="to"]').value;
        if (!from || !to) return showError('Input #from and #to');
        google.script.run
          .withSuccessHandler(closeModal)
          .withFailureHandler(showError)
          .replaceBackgroundColor(from, to);
      }

 handleChlick関数はユーザーが置換実行する際に押下するボタンに紐づいている関数です。概要としては、表示されたモーダルに対象の背景色、変換後の背景色を入力し、「置換」を押下したら、シート内の全てのセルに対して、背景色の置換を行うためのものです。処理の流れとしては二回連続のクリック禁止、入力した値の取得、軽いバリデーション、google.script.runAPIを使用した背景色置換スクリプトの実行です。

 詳細が必要なのはgoogle.script.runかと思います。このAPIgoogle.script.run.hogehoge()と記載することで、スクリプトエディタで定義したhogehoge関数を呼び出すことができます。またここでは、実行の成功時(withSuccessHandler)、失敗時(withFailureHandler)にコールバックを指定しています。コールバック関数は index.htmlで定義したものが指定可能です。

 以下はモーダルに値を入力して実行した際に呼び出されるスクリプトです。

function replaceBackgroundColor(from, to) {
  var sheet = SpreadsheetApp.getActiveSheet();
  var data = sheet.getDataRange();
  var backgrounds = data.getBackgrounds();
 
  var replacedCount = 0;
  for(var row = 0; row < backgrounds.length; row++) {
    for(var col = 0; col < backgrounds[row].length; col++) {
      if (backgrounds[row][col] != from) continue;
      sheet.getRange(row + 1, col + 1).setBackground(to);
      replacedCount++;
    }
  }

  ui = SpreadsheetApp.getUi();
  ui.alert('Successfully replaced!', replacedCount + ' cell\'s background color replaced to ' + to + ' from ' + from, ui.ButtonSet.OK);
}

 特に目新しいことはないかと思います。alertgetUiでUIから使用可能です。

感想

 まず不安に思ったこととしては、「それ標準機能であるよ」と言われることです。その辺どうなのでしょうか、、、たまにコード書きたい欲がまさってしまい、ちゃんとした検索を行わないという傾向があります。

 すごく感心したのが、google app scriptのモーダルでhtmlを表示できること、テンプレートを使用できることです。テンプレートを使用できるということは、webアプリケーションに近い何かを作れることだと思います。例えばSQLドライバーを作ってみたりすると楽しいかもです、スプレッドシートをデータベース、シートをテーブルと見立てて、とか思ったりします。

 javascriptでユーザーインプットを受け取る方法は、alertとかpromptとか柔軟性がなくて困ることがありました。webだと画面側をリッチにすればいいのですが、spreadsheetだとそうもいかないので、それを解消するためにガイドが役に立つのだと思います。