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

指定した画像の上にスタンプのせるやつ作った

はじめに

指定した画像の上にスタンプのせるサービスあるじゃないですか
勉強になるかと思ったので似たようなものを作ってみました

どんなやつ

gif.gif

url
https://nena3.github.io/gurasan_stamp/

source
https://github.com/nena3/gurasan_stamp

使っている技術

Vue.js, JavaScript, Canvas
サーバーはGithubPagesを使用

仕様

画像をアップロード

アプロドされた画像を表示

アイコンをすきなとこに動かす

合成した画像を出力
画像出力部分は
DOMを動かして最終的にCanvasでそれを再現するような仕様になっている

詰まったところ

canvasのrotateがちょっと難しかった

参考
https://weblike-curtaincall.ssl-lolipop.jp/blog/?p=594

こめんと

iphoneとか古いブラウザでは動かないかもしれない

あとめっちゃ使いづらいしサングラスのアイコンとか透けとるやないかい

サンタの帽子もフチが透けとるやないかい

おわりに

GithubPagesすげえ!

GithubPagesがあれば簡単なwebサービスはタダでサクサクつくれますね

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

JavaScript async/awaitに関して

async/awaitとは?

  • Promiseによる非同期処理を簡潔に記述するための記法。
  • 非同期処理はthenを繋げる形で記載もできるが、async/awaitは可読性を向上させる。

使い方

  1. asyncを付与したfunctionを用意
    asyncを付与するとPromiseを返却し、return後にPromiseがresolveされる。
  2. 上記関数内のPromise処理部分の前にawaitを付与する。
    awaitPromiseの解決待ち、resolveされた値を取得する。
function sugarPromise(){
  return new Promise(function(resolve) => {
     setTimeout(() => {resolve('hogehoge2')}, 10000)
  });
}

async function sugarSync(){
  const result = await sugarPromise();
  // sugarPromise()内でPromiseが解決するまで次の処理を行わない
  console.log(result);
}

sugarSync();
console.log('hogehoge1');

実行すると

hogehoge1
hogehoge2

となるはず。(1と2は10秒の間隔)

注意点

  • 複数のawaitを記述するとそれぞれ待たないといけない。上記の例を以下の通り書き直すと10秒×3=30秒待たないといけない。
const result1 = await sugarPromise();
const result2 = await sugarPromise();
const result3 = await sugarPromise();

次の通り記載すると並列で処理が可能。

const result1 = sugarPromise();
const result2 = sugarPromise();
const result3 = sugarPromise();
const x1 = await result1;
const x2 = await result2;
const x3 = await result3;
console.log(x1);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RaspberryPiで監視カメラ 暗い部屋の撮影編

はじめに

前回、RaspberryPiで監視カメラを作成したのですが、暗い部屋でベビーモニタとして利用するには工夫が必要という状況になっていました。

暗い部屋で使うために

  • USBライトの制御
  • カメラの設定変更

について調査してみたので、まとめておきます。

USBライトの制御

ハードウェア

Can☆Doで買ったUSBライトを使用します。

usb.jpg

RaspberryPiに差してもlsusbで認識されないので、クラスとして認識するような仕組みはなくUSBコネクタから電力供給しているだけの動作のようです。

たぶん、どのメーカのUSBライトも同じ仕様だと思います。
Linux側からUSBポートの電源供給を制御する方法を探してみます。

hub-ctrl

ぐぐったら、hub-ctrlというソフトを使うことでUSBの制御ができるとのこと。

手順通りにgccでビルドしてインストールできました。

gcc -o hub-ctrl hub-ctrl.c -lusb

以下のコマンドで点灯、消灯ができました。

sudo hub-ctrl -h 0 -P 2 -p 0
sudo hub-ctrl -h 0 -P 2 -p 1

権限設定等

pythonからhub-ctrlを起動する場合にsudoのパスワード入力が問題になるので回避策を探します。

最初はsudoしなくてもUSB操作ができるグループを追加すればいけると思いましたが、そういうグループは無いんですね。
グループを追加はあきらめてsudoのパスワード要求をしないように設定を変更します。

sudo visudo

/usr/local/bin/hub-ctrl にコピーしたのでこれにNOPASSWDを指定した行を追加します

monitor_user    ALL=NOPASSWD: /usr/local/bin/hub-ctrl

これでsudoでのパスワード要求されなくなりますので、pythonからの呼び出しに支障はなくなりました。

クライアント側

HTML

点灯制御するトリガーのボタンを用意します。

usb.html
<html>
  <head>
    <script src="./usb.js"></script>
  </head>
  <body onload="on_load();">
    <input type="button" value="usb" onclick="on_button_light();">
    <div>
      <canvas id="canvas_image" width="640" height="480"></canvas>
    </div>
  </body>
</html>


javascript

ボタンのハンドラでUSBライトの点灯制御を要求するコマンドを送信します。

usb.js
var image_socket = null;

var usb_socket = null;
var mode_usb = true;


function on_load()
{
  // 画像通信用WebSocket接続
  img_url = "ws://" + location.hostname + ":60002"
  image_socket = new WebSocket(img_url);
  image_socket.binaryType = 'arraybuffer';
  image_socket.onmessage = on_image_message;

  usb_url = "ws://" + location.hostname + ":60004"
  usb_socket = new WebSocket(usb_url);
  usb_socket.binaryType = 'arraybuffer';
}

function on_button_light()
{
  mode_usb = mode_usb ? false : true;

  var command_data = { mode: mode_usb };
  usb_socket.send(JSON.stringify(command_data));
}


function on_image_message(recv_data)
{
  // 受信したデータをbase64文字列に変換
  var recv_image_data = new Uint8Array(recv_data.data);
  var base64_data = ""
  for (var i=0; i < recv_image_data.length; i++) {
    base64_data += String.fromCharCode(recv_image_data[i]);
  }

  // 画像をcanvasに描画
  var canvas_image = document.getElementById('canvas_image');
  var ctx = canvas_image.getContext('2d');
  var image = new Image();
  image.onload = function() {
    ctx.drawImage(image, 0, 0);
  }
  image.src = 'data:image/jpeg;base64,' + window.btoa(base64_data);
}


サーバー側

USBの電源制御するWebSocketのサーバーを用意します。

USB制御サーバー

クライアント側で送信したON/OFFの要求を受けてhub-ctrlを呼び出します。

UsbServer.py
import asyncio
import websockets
import json
import subprocess


class UsbServer:
    def __init__(self, loop, address, port):
        self.loop = loop
        self.address = address
        self.port = port


    async def _handler(self, websocket, path):
        while True:
            try:
                recv_data = await websocket.recv()
                dic_data = json.loads(recv_data)
            except:
                print('usb recv Error.')
                break

            if dic_data['mode']:
                command = ['sudo', 'hub-ctrl', '-h', '0', '-P', '2', '-p', '0']
            else:
                command = ['sudo', 'hub-ctrl', '-h', '0', '-P', '2', '-p', '1']

            print(command)
            try:
                process = subprocess.Popen(command, stdout=subprocess.PIPE)
                output = process.stdout.read().decode('utf-8')
                print(output)

            except:
                print('usb proc Error.')

    def run(self):
        self._server = websockets.serve(self._handler, self.address, self.port)
        self.loop.run_until_complete(self._server)
        self.loop.run_forever()

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    wss = UsbServer(loop, '0.0.0.0', 60004)
    wss.run()

USBの親HUBごと電源をOFFにするのでUSB AudioもOFFになるのは問題ですが、電源制御対応の子USB HUBを挟めば対応できるそうです。(手持ちのHUBでは動作確認できませんでした)

カメラの設定変更

USBライトの明かりだけはうまく撮影できなかったので、カメラ側の設定もいじることにします。

PiCameraで変更できるプロパティで明るさを制御します。

明るく撮影する設定

前回作成したImageサーバーにフレームレート、シャッタースピード、露出補正の設定を追加します。

公式の説明を読んでいろいろ設定してみましたが、バージョンで動作が違うので正しい設定がよくわかりませんでした。ネットの情報もばらばらで正解がよくわからないです。試行錯誤してうまくいった設定値にしています。

ImageServer.py
import asyncio
import websockets
import io
from picamera import PiCamera

class ImageServer:

    def __init__(self, loop, address , port):
        self.loop = loop
        self.address = address
        self.port = port
        self.camera = PiCamera()
        self.camera.framerate = 1
        self.camera.exposure_compensation = 10
        self.camera.shutter_speed = 1000 * 8000

    async def _handler(self, websocket, path):
        with io.BytesIO() as stream:
            for _ in self.camera.capture_continuous(stream, format='jpeg', use_video_port=True, resize=(640,480)):
                stream.seek(0)

                try:
                    await websocket.send(stream.read())
                except:
                    print('image send Error.')
                    break

                stream.seek(0)
                stream.truncate()

    def run(self):
        self._server = websockets.serve(self._handler, self.address, self.port)
        self.loop.run_until_complete(self._server)
        self.loop.run_forever()

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    ws_is = ImageServer(loop, '0.0.0.0', 60002)
    ws_is.run()


動作確認

シャッタースピードが遅いのでライトのON/OFFしてから少し待たないと更新されないですが、それなりに撮影できています。

anime_usb.gif

おわりに

USBの電源制御とシャッタースピードの制御でなんとか実用レベルになりました。

omake.jpg

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

Raspberry Pi で最新鋭のユーロ地図を作る話

欧州 1:1,000,000 地図のオープンデータ EuroGlobalMap を最新鋭のウェブ地図にする、しかも Raspberry Pi 一台で、という話を以下のリンク先にまとめました。

https://hackmd.io/@hfu/inazo-steps

次のバージョンは、概ね1ヶ月後を目当てに作ろうと思います。以上、国連ベクトルタイルツールキット(UNVT)でした。

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

Javascriptでスライドショー作ってみたぉ。

..html

<!DOCTYPE html>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<meta http-equiv="X-UA-Compatible" content="ie=edge">

<title>Document</title>

<link rel="stylesheet" href="css/styles.css">

<div class="container">

    <main>
        <img>
    </main>

    <nav>
        <ul>
            <li id="play">PLAY</li>
            <li id="prev">&lt;</li>
            <li id="next">&gt;</li>
        </ul>
    </nav>

    <ul class="thumbnails">
    </ul>
</div>


..javascript

'use strict';

{

const images = [
    'img/pic00.png',
    'img/pic01.png',
    'img/pic02.png',
    'img/pic03.png',
    'img/pic04.png',
    'img/pic05.png',
    'img/pic06.png',
    'img/pic07.png',
];

//今何番目の画像を表示しているかという変数
let currentNum = 0; 

// サムネイル画像をクリックしたときの関数処理
function setMainImage(value){
    document.querySelector('main img').src = value;
}

// メインのイメージのソースをimages配列のcurrentNum番目とする
// document.querySelector('main img').src = images[currentNum];
setMainImage(images[currentNum]);


// currentクラスを外す独自の関数
function removeCurrentClass(){
    document.querySelectorAll('.thumbnails li')[currentNum].classList.remove('current');
}

function addCurrentClass(){
    document.querySelectorAll('.thumbnails li')[currentNum].classList.add('current');
}



// thumbnailsクラスの取得
const thumbnails = document.querySelector('.thumbnails');

// サムネイル画像たちの生成
images.forEach((value, index) => {
    const li = document.createElement('li');

    if(index === currentNum){
        li.classList.add('current');
    }

    li.addEventListener('click', () => {
        setMainImage(value);
        removeCurrentClass();
        currentNum = index;
        addCurrentClass();
    });

    const img = document.createElement('img');
    img.src = value;
    li.appendChild(img);
    thumbnails.appendChild(li);
});


const next = document.getElementById('next');
next.addEventListener('click', () => {
    removeCurrentClass();
    currentNum++;

    if(currentNum === images.length){
        currentNum = 0;
    }

    addCurrentClass();
    setMainImage(images[currentNum]);
});
const prev = document.getElementById('prev');
prev.addEventListener('click', () => {
    removeCurrentClass();
    currentNum--;

    if(currentNum < 0){
        currentNum = images.length - 1;
    }

    addCurrentClass();
    setMainImage(images[currentNum]);
});

}

..css

.container{
width: 300px;
margin: 0 auto;
user-select: none;
}

ul{
list-style: none;
padding: 0;
margin: 0;
}

main{
margin-bottom: 8px;
}
main img{
width: 300px;
height: 200px;
vertical-align: bottom;
}
nav{
margin-bottom: 8px;
}
nav ul{
display: flex;
justify-content: space-between;
}
nav li {
width: 60px;
border: 1px solid #dedede;
border-radius: 4px;
font-size: 12px;
padding: 4px;
text-align: center;
cursor: pointer;
user-select: none;
}
nav li:hover{
background: #f8f8f8;
}

play{

width: 140px;

}
.thumbnails{
display: grid;
grid-template-columns: repeat(5, 56px);
gap: 5px;
}
.thumbnails li {
cursor: pointer;
opacity: 0.4;
}
.thumbnails li:hover{
opacity: 1;
}
.thumbnails li.current{
opacity: 1;
}
.thumbnails img{
width: 56px;
vertical-align: bottom;
}

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

javascript非同期処理~Promise,async,awaitとコールバック関数~

きっかけ

仕事でvue.jsを使うことになって非同期処理について勉強していたところ、Promiseとかasyncとかawaitとかで?????となったのでふんわりな理解だったコールバック関数について学び直そうと決意

現状の私の理解

  • コールバック関数は関数に入れる関数。それ以外は...

非同期処理でやれることの例

  • ボタンがクリックされた後に何かを表示する
  • ファイルの読み込みが完了した後に何かをする

非同期処理のイメージ

  • 処理を関数単位で考えた時
    • 実行中の関数の中で非同期処理が発火する
    • 実行中の関数の終了を持ってコールバック関数を実行する スクリーンショット 2019-10-02 8.07.51.jpg

※こちらのイメージは拾い画なのですが拾い元が特定できなくてリンクを載せることができません。。。知ってる方いましたら教えて...

関数を実行するときと値として扱う際の書き方の違い

  • カッコ【()】をつけるかつけないか
hoge.js
const max1 = Math.max(1, 2); // これはカッコがついてるので関数の計算結果が入る
const max2 = Math.max; // これはカッコがないので関数自体が入る
  • カッコなしを実行しようとするとnot a functionエラーが発生する

相手に実行してもらうのがコールバック関数

  • 以下で考えるとconsole.logがコールバック関数となる。
huga.js
setTimeout(function() {
  console.log('Hello!');
}, 2000);

関数を受け取る関数を高階関数と呼ぶ

  • さっきのコードで言うとsetTimeoutが高階関数。

非同期処理の書き方

  • 1.非同期処理関数はコールバック関数を受け取る高階関数にする
  • 2.利用者は「終わったら実行したい処理」をコールバック関数として渡す
  • 3.非同期処理関数は処理が終わったらコールバック関数を呼び出す

DOMのイベントで非同期処理

  • クリックイベントにコールバック関数(console.log)を紐付け
piyo.js
document.querySelector('.my-button').addEventListener('click', function(event) {
  console.log('clicked!');
});

- 関数名でも紐付け可能

puyo.js
function callback(event) {
  console.log('Hello'!);
}

document.querySelector('.my-button').addEventListener('click', callback);

非同期処理を記述するPromiseの基本

  • 今までの内容で非同期処理には高階関数とコールバック関数が必要なことがわかったと思うが、処理が多くなるとネストが多段になって読みにくくなってしまう。
  • そこでPromiseと言う仕組みを使うことで簡潔に記述できる
  • 使い方とサンプルは以下
    • 1.まずPromiseをnewすることによりPromiseオブジェクトを作成。
    • 2.Promiseのコンストラクタには、実行したい処理を書いた関数を渡し、処理が済んだらresolve関数を呼び出すことで終了を明示する。
    • 3.Promiseオブジェクトのthenメソッドに、Promise終了後に処理したい関数を渡す。
    • 4.「Promiseの実行が済んだ後にxxxする」
promise.js
const promise = new Promise((resolve, reject) => {
    setTimeout(() => { 
        console.log('hello');//実行したい処理
        resolve();//実行したい処理が終わったことを明示。thenメソッドへ。
    }, 500);
});

promise.then(() => console.log('world!'));

Promise〜値を渡す1〜

  • resolveには値を渡すこともできる
  • resolve関数に渡した値は、thenメソッドで受け取ることができる。
  • 以下サンプル(XHRのレスポンスをresolve関数に渡している)
resolve.js
const promise = new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', 'foo.txt');
    xhr.addEventListener('load', (e) => resolve(xhr.responseText));
    xhr.send();
});

promise.then((response) => console.log(response));

Promise〜値を渡す2〜

  • rejectresolve同様、値を渡すことができる
  • rejectが呼ばれたらcatchが実行されてエラー終了させる
reject.js
const promise = new Promise((resolve, reject) => reject('error'));

promise.then(() => console.log('done')) // thenは実行されない
       .catch((e) => console.log(e)); // 「error」とだけ表示される

thenを繋げるthenチェーン!!!

  • これがPromiseのいいところみたいでthenを繋げて書くことができる。
then.js
function printAsync(text, delay) {
    const p = new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(text);
            resolve();
        }, delay)
    });

    return p;
}

printAsync('hello', 500)
//500ミリ秒ごとに「hello」「world」「lorem」「ipsum」と表示する
    .then(() => printAsync('world', 500))
    .then(() => printAsync('lorem', 500))
    .then(() => printAsync('ipsum', 500));

Promiseを使わない時と使った時のソース比較サンプル(ファイルオープン)

  • 使わないとき
nothen.js
function openFile(url, onload) {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.addEventListener('load', (e) => onload(e, xhr));
    xhr.send();
}

openFile('foo.txt', (event, xhr) => {
   openFile('bar.txt', (event, xhr) => {
     openFile('baz.txt', (event, xhr) => {
         console.log('done!');
     });
   });
});
  • 使った時
then.js
function openFile(url) {
    const p = new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.addEventListener('load', (e) => resolve(xhr));
        xhr.send();
    });

    return p;
}

openFile('foo.txt')
    .then((xhr) => openFile('bar.txt'))
    .then((xhr) => openFile('baz.txt'))
    .then((xhr) => console.log('done!'));

複数のPromiseの終了を待つPromise.all

  • 上記と同じ処理を書き換えてみる
  • 配列で渡してthenは1個で書ける。
  • thenの結果は配列で返ってくるため注意
promise.all
function openFile(url) {
    const p = new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.addEventListener('load', (e) => resolve(xhr));
        xhr.send();
    });

    return p;
}

const promise = Promise.all([openFile('foo.txt'),
                             openFile('bar.txt'),
                             openFile('baz.txt')]);
promise.then((xhrArray) => console.log('done!'))

async関数とawait

  • やっと来ました。これについて知りたくて遡りしてたら結構知ってないといけないことが多かった。。。

  • awaitはPromiseを同期的に展開する(ように見せかける)機能。

  • Promiseオブジェクトを返す関数を呼び出す前につけると取得を待ってから次の処理へ行ってくれる

  • awaitは仕様条件があり、asyncがついた関数の中でしか利用できないと言う条件がある。

  • さっきのファイルオープンの処理をasync/awaitを使って書くと以下のようになる

async.js
function openFile(url) {
    const p = new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.addEventListener('load', (e) => resolve(xhr));
        xhr.send();
    });

    return p;
}

async function loadAllFiles() {
    //ファイルオープンを待って次の処理へ
    const xhr1 = await openFile('foo.txt');
    //ファイルオープンを待って次の処理へ
    const xhr2 = await openFile('bar.txt');
    //ファイルオープンを待って次の処理へ
    const xhr3 = await openFile('baz.txt');
    console.log('done!');
}

loadAllFiles();

async/awaitのエラー検出

  • try-catchが書けるのでその中に書いていく
async.js
function openFile(url) {
    const p = new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.addEventListener('load', (e) => resolve(xhr));
        xhr.send();
    });

    return p;
}

async function loadAllFiles() {
  try {
    //ファイルオープンを待って次の処理へ
    const xhr1 = await openFile('foo.txt');
    //ファイルオープンを待って次の処理へ
    const xhr2 = await openFile('bar.txt');
    //ファイルオープンを待って次の処理へ
    const xhr3 = await openFile('baz.txt');
    console.log('done!');
  } catch(error) {
    const {
      status,
      statusText
      } = error.response;
      console.log(`Error! HTTP Status: ${status} ${statusText}`);
    }
  }
}

loadAllFiles();


あとは書いて覚える!!!!!

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

フォトショになりたい ver1. トーンカーブ

フォトショになりたい

  • 画像加工系の機能を一つづつ実装するだけ

概要

  • トーンカーブ
  • LUT
  • 露出
  • ガンマ
  • 両対数スケール
  • コントラスト

注: この記事はPython3 & OpenCV で画像処理を学ぶ[3] 〜 トーンカーブ と LUT を理解する実装実験と内容が鬼ほど被っています

トーンカーブ

↓これ
スクリーンショット 2019-10-02 17.33.43.png

要するに$[0, 255] \rightarrow [0, 255]$の関数

LUT

全ピクセルに毎回演算してたら重すぎてたまったもんじゃない

→$[0, 255] \rightarrow [0, 255]$で、基本的には整数値なのだから256×256の表があればいい

LUT(Look Up Table)

トーンカーブ編集系のエフェクトは、全て「LUTをいじる→LUTに従って画像を変換する」とすると良い

editLUT.js
function editLUT(img) {
  const lut = new Array(256);
  for (let i = 0; i < 256; i += 1) {
    let val = hoge(i); //ここでエフェクトをかける
    lut[i] = val;
  }
  LUT(img, lut);
  return img;
}

露出

k倍するだけ

f(v) = kv

ただ、kが1を超える時に明るいところがクリップするので対処する

exposure.js
function exposure(x, t) {
  return Math.min(x * t, 255);
}

ガンマ

正規化してγ乗するだけ

f(v) = v^\gamma

(詳しくはガンマ補正のうんちくを参照)

gammaFunc.js
const gammaFunc = (val, gamma) => 255 * Math.pow(val / 255, 1 / gamma);

両対数スケール

ガンマよりも両端に関して滑らからしい
(詳しくはスケール対数曲線をトーンカーブとする画像補正を参照)

f(v) = \frac{{\rm log}(1+Cv)}{{\rm log}(1+C)}\\

逆関数:

f^{-1}(v) = \frac{e^{v{\rm log}(1+C)}-1}{C}
log_log_scale.js
const log_log_scale = (v, C) =>
    C == 0 ? v
  : C > 0 ? 255*Math.log(1+v/255*C)/Math.log(1+C)
  : -255*(Math.exp(v/255*Math.log(1-C))-1)/C

コントラスト

暗いところをより暗く、明るいところをより明るく→S字カーブ

基本的にはシグモイドでいい
(詳しくはシグモイド関数でコントラスト強調を参照)

でも定義域が有界じゃないので少し修正してもいい
(詳しい議論はSigmoid曲線の定義域と値域を[0,1]にしたいだけを参照)

pseudoSigmoid.js
const pseudoSigmoid (x, t, MAX) => // Maxは255とか
    t === 0 ? x
  : t > 0 ? (Math.asinh((2 * x / MAX - 1) * Math.sinh(t)) / 2 / t + 0.5) * MAX
  : (Math.sinh(t * (2 * x / MAX - 1)) / 2 / Math.sinh(t) + 0.5) * MAX;

中心の値をいじりたければシグモイド関数でコントラスト強調の実装が良さそう

まとめ

  • トーンカーブはLUTを使って実装する
  • この辺りの関数は後に他でも用いるので覚えておく
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CypressとResamble.jsを使ったwebサイトの差分比較

VueやReactなどを使って運用していると色んな所に影響するようなコンポーネントをさわるケースが出てくると思います。

そのときに影響範囲がある画面をポチポチして確認するのがしんどいので
予め確認したいページを指定してキャプチャを取れる状態にしておき

差分があった場合に、画像で出力してくれる仕組みを作ってみました。

手順

  1. Cypressで比較したい画像のキャプチャを撮っておく
  2. 1で撮ったキャプチャと比較したい画像のキャプチャを撮る
  3. Resamble.jsで1と2の画像を比較して差分がある場合、差分画像を出力する

Cypressでキャプチャを撮る

Cypressで下記のようにページごとに、キャプチャを撮って現時点の日付で保存しておきます。(/e2e/screenshot/内の指定のフォルダに保存されます。)

画面サイズごとにキャプチャを取りたい場合は、それぞれ画面ごとに保存したいフォルダをかえて保存しておきます。

画面サイズを変更する場合はcy.viewportを指定します。

https://github.com/kamem/clover.blue2/blob/master/test/diff/index.js

/test/e2e/integration/screenshot.js
import moment from 'moment'
const host = 'http://localhost:1341/'

const pages = [
  {
    name: 'top',
    path: ''
  },
  {
    name: 'about',
    path: 'about'
  }
]

pages.forEach(({ name, path }) => {
  context(name, () => {
    beforeEach(() => {
      cy.visit(`${host}${path}`)
    })

    it(`${name} screenshot`, () => {
      cy.screenshot(`${name}/normal/${moment().format()}`)
    })
  })
})

pages.forEach(({ name, path }) => {
  context(`small_${name}`, () => {
    beforeEach(() => {
      cy.viewport(320, 480)
      cy.visit(`${host}${path}`)
    })

    it(`${name} screenshot`, () => {
      cy.screenshot(`${name}/small/${moment().format()}`)
    })
  })
})

Resamble.jsを使って差分画像の出力

/e2e/screenshot/内のCypressで保存したファイルをフォルダ単位で検索して。
そのフォルダ内の最新の2件を比較する仕組みにしました。

package.jsonに下記を記述して、nodeでResemble.jsで画像の差分を出力を動作するようにしました。

package.js
"scripts": {
  "diff": "node test/diff/"
}

コードはざっくりなので参考程度に...

https://github.com/kamem/clover.blue2/blob/master/test/e2e/integration/screenshot.js

/test/diff/index.js
const fs = require('fs')
const path = require('path')
const compareImages = require('resemblejs/compareImages')
const _ = require('lodash')
const mkdirp = require('mkdirp')

const getDiff = async (img1, img2, output = './output.png', options = {}) => {
  const data = await compareImages(
    fs.readFileSync(img1, () => {}),
    fs.readFileSync(img2, () => {}),
    options
  )

  if (data.misMatchPercentage >= 0.01) {
    mkdirp(path.dirname(output), err => {
      if (err) return console.error(err)
      fs.writeFile(output, data.getBuffer(), () => {})
    })
  }
}

const searchDir = dir => {
  return fs
    .readdirSync(dir)
    .filter(item => !fs.existsSync(item))
    .map(item => {
      const filePath = `${dir}/${item}`
      return {
        dir,
        item: fs.statSync(filePath).isDirectory() ? searchDir(filePath) : item
      }
    })
}

const diffPngFiles = dirObj => {
  const pngFiles = getDiffFiles(dirObj)

  if (pngFiles.length === 2) {
    getDiff(
      `${pngFiles[0].dir}/${pngFiles[0].item}`,
      `${pngFiles[1].dir}/${pngFiles[1].item}`,
      `${pngFiles[0].dir.replace('e2e', 'diff')}.png`
    )
  }

  dirObj.forEach(({ dir, item }) => {
    if (Array.isArray(item)) {
      diffPngFiles(item)
    }
  })
}

const getDiffFiles = dirObj => {
  const pngFiles = dirObj.filter(({ item }) => ~item.indexOf('png'))

  return _.sortBy(pngFiles, ({ dir, item }) => {
    return -fs.statSync(`${dir}/${item}`).ctimeMs
  }).slice(0, 2)
}

diffPngFiles(searchDir('./test/e2e/screenshots'))

「すごい頑張って差分テストを!」って感じではないですが。
一旦サクッと差分ポイントが確認できるーぐらいで使ってみるのはいいかなという感じです。

今後もこのあたりもちょっとうまい方法ないかなという部分は考えて行きたいです。

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

CypressとResamble.jsを使ったwebサイトの差分画像比較

VueやReactなどを使って運用していると色んな所に影響するようなコンポーネントをさわるケースが出てくると思います。

そのときに影響範囲がある画面をポチポチして確認するのがしんどいので
予め確認したいページを指定してキャプチャを取れる状態にしておき

差分があった場合に、画像で出力してくれる仕組みを作ってみました。

手順

  1. Cypressで比較したい画像のキャプチャを撮っておく
  2. 1で撮ったキャプチャと比較したい画像のキャプチャを撮る
  3. Resamble.jsで1と2の画像を比較して差分がある場合、差分画像を出力する

Cypressでキャプチャを撮る

Cypressで下記のようにページごとに、キャプチャを撮って現時点の日付で保存しておきます。(/e2e/screenshot/内の指定のフォルダに保存されます。)

画面サイズごとにキャプチャを取りたい場合は、それぞれ画面ごとに保存したいフォルダをかえて保存しておきます。

画面サイズを変更する場合はcy.viewportを指定します。

https://github.com/kamem/clover.blue2/blob/master/test/diff/index.js

/test/e2e/integration/screenshot.js
import moment from 'moment'
const host = 'http://localhost:1341/'

const pages = [
  {
    name: 'top',
    path: ''
  },
  {
    name: 'about',
    path: 'about'
  }
]

pages.forEach(({ name, path }) => {
  context(name, () => {
    beforeEach(() => {
      cy.visit(`${host}${path}`)
    })

    it(`${name} screenshot`, () => {
      cy.screenshot(`${name}/normal/${moment().format()}`)
    })
  })
})

pages.forEach(({ name, path }) => {
  context(`small_${name}`, () => {
    beforeEach(() => {
      cy.viewport(320, 480)
      cy.visit(`${host}${path}`)
    })

    it(`${name} screenshot`, () => {
      cy.screenshot(`${name}/small/${moment().format()}`)
    })
  })
})

Resamble.jsを使って差分画像の出力

/e2e/screenshot/内のCypressで保存したファイルをフォルダ単位で検索して。
そのフォルダ内の最新の2件を比較する仕組みにしました。

package.jsonに下記を記述して、nodeでResemble.jsで画像の差分を出力を動作するようにしました。

package.js
"scripts": {
  "diff": "node test/diff/"
}

コードはざっくりなので参考程度に...

https://github.com/kamem/clover.blue2/blob/master/test/e2e/integration/screenshot.js

/test/diff/index.js
const fs = require('fs')
const path = require('path')
const compareImages = require('resemblejs/compareImages')
const _ = require('lodash')
const mkdirp = require('mkdirp')

const getDiff = async (img1, img2, output = './output.png', options = {}) => {
  const data = await compareImages(
    fs.readFileSync(img1, () => {}),
    fs.readFileSync(img2, () => {}),
    options
  )

  if (data.misMatchPercentage >= 0.01) {
    mkdirp(path.dirname(output), err => {
      if (err) return console.error(err)
      fs.writeFile(output, data.getBuffer(), () => {})
    })
  }
}

const searchDir = dir => {
  return fs
    .readdirSync(dir)
    .filter(item => !fs.existsSync(item))
    .map(item => {
      const filePath = `${dir}/${item}`
      return {
        dir,
        item: fs.statSync(filePath).isDirectory() ? searchDir(filePath) : item
      }
    })
}

const diffPngFiles = dirObj => {
  const pngFiles = getDiffFiles(dirObj)

  if (pngFiles.length === 2) {
    getDiff(
      `${pngFiles[0].dir}/${pngFiles[0].item}`,
      `${pngFiles[1].dir}/${pngFiles[1].item}`,
      `${pngFiles[0].dir.replace('e2e', 'diff')}.png`
    )
  }

  dirObj.forEach(({ dir, item }) => {
    if (Array.isArray(item)) {
      diffPngFiles(item)
    }
  })
}

const getDiffFiles = dirObj => {
  const pngFiles = dirObj.filter(({ item }) => ~item.indexOf('png'))

  return _.sortBy(pngFiles, ({ dir, item }) => {
    return -fs.statSync(`${dir}/${item}`).ctimeMs
  }).slice(0, 2)
}

diffPngFiles(searchDir('./test/e2e/screenshots'))

「すごい頑張って差分テストを!」って感じではないですが。
一旦サクッと差分ポイントが確認できるーぐらいで使ってみるのはいいかなという感じです。

今後もこのあたりもちょっとうまい方法ないかなという部分は考えて行きたいです。

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

Intl.PluralRules で助数詞表現

JavaScript ではIntl.PluralRulesを使うといいかと思います。

サンプル

const pr = new Intl.PluralRules('en-US', { type: 'ordinal' });
const or = { other:'th', one:'st', two:'nd', few:'rd' }
const o = n => n + or[ pr.select(n) ];

o(1); //1st
o(2); //2nd
o(3); //3rd
o(4); //4th
o(11); //11th
o(21); //21st

参考:
Intl.PluralRules - JavaScript | MDN
cldr-numbers-full/numbers.json at master · unicode-cldr/cldr-numbers-full · GitHub

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

Intl.PluralRules で序数詞表現

JavaScript ではIntl.PluralRulesを使うといいかと思います。
Intl.PluralRules.prototype.selectメソッドは、数値を引数に取り、言語に応じたその数値のカテゴリ("zero", "one", "two", "few", "many","other")を返します。たとえば英語では、1は"one"、11は"other"が返ってきます。

「1は"st"だけど11は"th"」のような処理の部分を、「"one"は"st"、"other"は"th"」のように簡略化できます。
サンプル

const pr = new Intl.PluralRules('en-US', { type: 'ordinal' });
const or = { other:'th', one:'st', two:'nd', few:'rd' }
const o = n => n + or[ pr.select(n) ];

o(1); //1st
o(2); //2nd
o(3); //3rd
o(4); //4th
o(11); //11th
o(21); //21st

参考:
Intl.PluralRules - JavaScript | MDN
cldr-numbers-full/numbers.json at master · unicode-cldr/cldr-numbers-full · GitHub

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

フィリピンIT Coding Boot Camp 6ヶ月コースの4ヶ月目で思うこと

現在、フィリピンのセブでもないマニラでもないどこかの涼しい雨ばかりの街でIT Boot Camp(6ヶ月)に参加した僕が感じていることやIT勉強したい、身に付けたいけど最善の方法って結局学校?それとも働いた方がいい?などの答えのヒントに少しでもなればなぁと思います。(あくまで全て一個人の僕の感想です)

そもそもIT Boot Campて何?

これは、知らない方もいると思いますが、このIT Boot Campはビリーズブートキャンプの仲間ではなく、アメリカやイギリスなどでも開催されているITの知識を詰め込む集中講座のようなものです!

IT Boot Campの特徴
-超短期集中
-勉強する範囲が6ヶ月コースなどになってくると結構広い。
-授業は全て英語
-毎日コード書く生活漬け
-(他は知りませんが)インターンシップ先を用意してくれる
...こんなところですかね。
とりあえずまとめると、6ヶ月と言う期間でコードを書きWebsiteを作れるように学習し、そしてクライアントを獲得できるまでに成長させよう!
みたいなノリのコースですね。

実際、仕事を自分で得れるほどまでに成長するの?

ここが全員気なっているところですよね〜。
結論から言います。(これもただの僕の意見です
ただ言われてるだけのことをこなして学校が終わり次第家に帰っていたら絶対無理です。
ただ誤解して欲しくないのは、別にコースを否定している訳ではなく、ただお金をいただいてオンライン上でクライアント様の希望を目に見える形にすると言うのはとても大変と言うことです。6ヶ月のコースでそれが完璧に身につくはずはありません。いくら学校に高い授業料を払っていても、自分で主体的に動かなければ生き残ってはいけません。

それならいかない方がいい?

そう思いがちですが、ちょっと待ってください。
だからこそIT Boot Campでは、エンジニアとして一番大事なスキルである自分をアップデートする方法を学べるんです。
これは本当に日常生活でも役に立つんですが、まず知らない事をすぐに調べると言う情報の収集の癖がつきますし、
その情報収集の仕方やcオーディングでバグが出た際の解き方など応用がきくテクニックもひたすら教えてくれます。

でも授業では、実際そのやり方を教えてくれるだけで自分のスキルとして落とし込むまでの時間は取ってくれないんですよ。
なので、終わった〜って家に帰ってしますと綺麗さっぱり忘れてまたぐるぐる同じことを学んで時間だけ過ぎて言ってしまいます。
要は、自分で学んだことをしっかりと整理しアウトプットできればそれなりに成果の上がるコースだと言うことです。

自分なりのメリットとデメリット

メリット
・コーディングの基礎は身につく(HTML、CSS、JavaScriptなど)。
・マネタイズやクライアントの獲得の仕方まで一応体系的に教えてくれる。
・やる気がある人にとってはいいスピード感でどんどん新しい分野や言語を教えてくれる
・仕事上だと何回もきくことに抵抗があるが生徒の立場なら何回も納得がいくまで聞ける。
・授業はほぼどこでも英語なので英語が上達する
・エンジニアになりたいけど、何をどうやって始めたらいいの?って言う方には唯一お勧め!
デメリット
・アカデミックな内容も多いので実践で活かせないこともある。
前回の記事でも書きましたが、やっぱり実際に企業に入ろうとすると現実的にたかだか数ヶ月しかコーディングかじってないと言う現実を痛感するので自分でアンテナをしっかりはってどの言語やどんなスキルが生きるかを模索し続けながら勉強していく必要がある事。
(これに関してはいいきっかけにもなるので、メリットでもありますね!)
・日本の案件は全くカリキュラム内では取ろうとしないので海外案件を狙うというハードルの高さが少しある。
・カルチャーギャップで苦しむ時がある(来てみて感じてみてください笑)

最後に

未経験で企業に入っていきなりコーディングを詰め込まれても正直限界がありますが、そのあとの仕事は自分で取らなくても用意されていると言うことになります。
一方、こういったコースを受講しいいご縁にも恵まれ、フリーランスとして活躍できれば自分の仕事を選ベルし嫌なら断れます。
値段だって交渉できます。とにかく規制がありません。自由です。

これは勧誘でもないし、IT Coding Boot Campというシステムへの悪口でもありません。
ただ、実際に足を踏み入れたことがない方や、少しでも興味がある方達がこの記事をみて『やっぱやーめた』とかなったり『背中を押せたり』できればなと思い本音で全て書いてあリます。
なので、なんか言っていることが矛盾していればこのコースのいい側面もあるけど嫌なところや不満も抱えながらコーディングに励んでいたということです笑

以上、僕の体験談でした。
何かありましたらコメントください。
読んでいただきありがとうございました。

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

WEBの知識0の私がURLを贈る

動機

誕生日に手紙を贈るのは普通すぎると思って, ちょっと勉強中のHTMLやらを使ってURLを贈ることにしました.
なんかまずい部分あれば教えていただけると幸いです...
この記事は躓いたところとかのメモになります.

あまりにも酷いコード全貌 : github

どんなものを作ったのか

生年月日いれて, 合致していたら次のページ
→ドット絵のGIFがお祝いする

52thとかいう書き方あってるんかな...

※iphone向けでしか作っていません.

開発環境

・sublime
・xampp 7.3.9

xamppを用いてlocalhostで作成画面を確認しながら作成しました.
絶対もっと楽な方法ある...

ドット絵

piskelというサイトでドット絵作成.ドット絵描くのが非常に楽しかった.

スマホ用に

やさしいWebアプリ
を参考にしました.

    <meta name="viewport" content="initial-scale=1.0">

でスマホ向け画面になってくれるみたいです.

画像の表示を横幅に合わせるために CSSで調整.

.gif img{
    width:100%;
}

誕生日を入力してもらうフォーム

誕生月・日のセレクトボタンを作成.

    <form>

        <select name="sel2">
            <option value="#"></option>
            <script type="text/javascript">
            for(var i=1;i<13;i++){
                document.writeln("<option>" +i + "月</option>");
            }
            </script>
        </select>

        <select name="sel3">
            <option value="#"></option>
            <script type="text/javascript">
            for(var i=1;i<32;i++){
                document.writeln("<option>" +i + "日</option>");
            }
            </script>

        </select>
    <input type=button value="決定" onClick="return check(sel2.value,sel3.value)">

    </form>

function check()では当人の誕生日が入力されたら次のページへ
という感じ.
違えばalertでます.

スマホでの音声出力

audioタグでは, safariやChromeで音楽の自動演奏ができないと知った時にはびっくりしました.
こちらのサイトを参考に, 画面タップで音楽が流れるような仕様に.

    <audio autoplay loop id="audio">
    <source src="happy_birthday_to_you_unpluged.mp3">
    </audio>

    <script type="text/javascript"> 
    document.addEventListener('click', audioPlay);
    function audioPlay() {
        document.getElementById('audio').play();
        document.removeEventListener('click', audioPlay);
    }   
    </script>   

最後に

こんな初歩的なものでも知識が0からだったので2~3日程度かかったきがする.
また何か作りたい...

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

JavaScript (文字列と数値)

JavaScriptとは

JavaScriptはWebに特化したプログラミング言語の1つで、動的なWebページの制作などに用いられます。

console.log()

()に入力された文字をコンソールに出力する。

文字列

出力させたいテキストをダブルクォーテーション(")で囲む。
console.log("〇〇");
文末には必ずセミコロン(;)を付ける。

コメントアウト

コメントをみなされ、web上には表示されない。
どのような意味を持つコードであるかを記すメモに使われる。
文頭に「//」と書く。

//「こんにちは」と出力する。
console.log("こんにちは");

数値

文字列と違い、クォーテーションで囲まない。
計算することができる。
+ - * / 数値と記号はすべて半角で記述する。
%を使うと、割ったときの余りを求められる。)

//文字列を出力
console.log("3+2");
//数値を出力
console.log(3+2);

文字列の連結

「+」記号を用いると、文字列同士を連結することができる。
「"天然"+"水"」とすると「"天然水"」という一つの文字列になる。

//文字列の連結
console.log("天然"+"");
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Cocos2d-html5でスプラッシュ画面時点から背景色を変える

cocos2d-html5で、スプラッシュ画面時点から背景色を変えたいと思いました。SPAから滑らかにCocos2dに移行しようと思ったのです。概ね当たり前の部分、つまり、html内・loading.jsでの色指定に関わる部分を弄れば何とかなるのですが、WebGLの時だけ、一瞬画面が黒くなります。

一瞬のことなので、後回しにしようとも思ったのですが、こういうのってちょっとしたことながら「すげえ挙動不安定アプリっぽい見た目」になるので、耐えきれず一応ソースを読みました。

過程はすっ飛ばして、

frameworks > cocos2d-html5 > cocos2d > core > renderer > RendererWebGL.js > _clearColor

に背景色を設定するしかない模様。

確かに、ライブラリの読み込み時に介入できる方法をcocos2dは提供していないので、これは困りものです。
少なくとも現バージョン3.17.2では、ライブラリのソースを書き換えざるを得ない感じ。

提案としては、ライブラリ初期化時に、呼び出し元HTMLの背景色を覗いて、_clearColorのデフォルト値にする、といった仕様が嬉しいんではないでしょうか。誰かプルリクしてあげて。

ちなみに、背景色を明るい色にすると、これに限らず色々な障害に阻まれる感じです。まだそのせいだとは確証できていませんが、TransitionCrossFadeとか。Cocos2d-HTML5って、黒背景で使う人がほとんどなのかも。

以上。

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

【JavaScript】非同期処理を同期的にする

JavaScript(Node.js)でHTTPリクエストを送ったり、ファイルの読取を実装しようとすると、コールバックだらけになって、HTTPリクエストの後に行う処理が実装しづらかったりする。
その場合、async/awaitやPromiseを使って同期的にすることができる。

async/await 入門(JavaScript) - Qiita
https://qiita.com/soarflat/items/1a9613e023200bbebcb3

HTTPリクエストで取得したCSVファイルをパースするサンプル↓

サンプル
const https = require('https');
const parse = require('csv-parse');
const moment = require('moment');

(async function() {
    const csvdata = await getCsv();
    const keepdata = await parseCsv(csvdata);
    fillEmptyRecord(keepdata);

    console.log('finish');
}());

function getCsv() {
    const url = 'https://www.mizuhobank.co.jp/market/csv/quote.csv';

    // csvファイル取得
    let csvdata = '';

    return new Promise(function(resolve, reject) {
        https.get(url, function(res) {
            res.on('data', function(data) {
                csvdata += data;
            });

            // getが終わった後
            res.on('end', function() {
                resolve(csvdata);
            });

            res.on('error', function() {
                reject(null);
            });
        });
    });
}

function parseCsv(csvdata) {
    let keepdata = [];

    return new Promise(function(resolve, reject){
        parse(csvdata, {
            skip_empty_lines : true
            ,from_line : 4
        })
        .on('readable', function() {
            // 略
        })
        .on('end', function() {
            resolve(keepdata);
        });
    });
}

function filterCsv(record) {
    let date = moment(record[0], 'YYYY/M/D');
    let startDate = moment().subtract(30, 'days');

    return date.isSameOrAfter(startDate);
}

function fillEmptyRecord(records) {
    // 中略
    for (let record of records) {
        console.log(record);
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】非同期処理を同期にしてコールバック地獄から抜け出す

概要

JavaScript(Node.js)でHTTPリクエストを送ったり、ファイルの読取を実装しようとすると、コールバックだらけになって、HTTPリクエストの後に行う処理が実装しづらかったりする。
その場合、async/awaitやPromiseを使って同期的にすることができる。基礎知識については、下記ページを参照。

async/await 入門(JavaScript) - Qiita

以下、Webサイトからcsvファイルを取得し、パースする処理を例にする。

コールバックで実装した場合

HTTPリクエストにhttps、csvのパースにcsv-parseを使う。
どちらもコールバック関数を使用する設計になっているため、ひたすら関数の呼び出しの連鎖が続き、処理の全体が把握しづらくなる。

コールバックを使用したサンプル
const https = require('https');
const parse = require('csv-parse');
const moment = require('moment');

(function() {
    getCsv();

    // ※ HTTPリクエストもcsvの処理も非同期なので、真っ先に出力されてしまうログ
    console.log('finish');
}());

function getCsv() {
    // csvファイル取得
    const url = 'https://www.mizuhobank.co.jp/market/csv/quote.csv';
    let csvdata = '';

    https.get(url, function(res) {
        res.on('data', function(data) {
            csvdata += data;
        });

        res.on('end', function() {
            // 次の処理を呼び出す
            parseCsv(csvdata);
        });

    });
}

function parseCsv(csvdata) {
    // csvファイルを配列へ変換する
    let keepdata = [];

    parse(csvdata, {
        skip_empty_lines : true
        ,from_line : 4
    })
    .on('readable', function() {
        let record = null;
        while (record = this.read()) {
            if (filterCsv(record)) {
                keepdata.push(record);
            }
        }
    })
    .on('end', function() {
        // 次の処理を呼び出す
        fillEmptyRecord(keepdata);
    });
}

function fillEmptyRecord(records) {
    // 休日など、空いている日付を翌日のレートで埋める
    let size = records.length;
    let index = size - 1;
    let newRecords = [];

    while (index > 0) {
        let date = moment(records[index][0], 'YYYY/M/D');
        let prevDate = moment(records[index - 1][0], 'YYYY/M/D');
        let diff = date.diff(prevDate, 'days');

        newRecords.splice(0, 0, records[index]);

        while (diff > 1) {
            let cloned = records[index].slice(0, records[index].length);
            date = date.subtract(1, 'days');
            cloned[0] = date.format('YYYY/M/D');

            newRecords.splice(0, 0, cloned);

            diff -= 1;
        }

        index -= 1;
    }

    // ここが処理の一番最後
    for (let record of records) {
        console.log(record);
    }
}

Promiseを使用した場合

先ほどの例では分かりづらい…という問題を解消するために、async/await や Promise を使用する。
こうするとエントリポイントとなる関数に処理の流れを表すことができるため、流れが把握しやすくなるし、実装もしやすい。

サンプル
const https = require('https');
const parse = require('csv-parse');
const moment = require('moment');

(async function() {
    const csvdata = await getCsv();
    const keepdata = await parseCsv(csvdata);
    fillEmptyRecord(keepdata);

    // ちゃんと最後に出力される
    console.log('finish');
}());

function getCsv() {
    // csvファイル取得
    const url = 'https://www.mizuhobank.co.jp/market/csv/quote.csv';
    let csvdata = '';

    return new Promise(function(resolve, reject) {
        https.get(url, function(res) {
            res.on('data', function(data) {
                csvdata += data;
            });

            // getが終わった後
            res.on('end', function() {
                resolve(csvdata);
            });

            res.on('error', function() {
                reject(null);
            });
        });
    });
}

function parseCsv(csvdata) {
    let keepdata = [];

    return new Promise(function(resolve, reject){
        parse(csvdata, {
            skip_empty_lines : true
            ,from_line : 4
        })
        .on('readable', function() {
            // 略
        })
        .on('end', function() {
            resolve(keepdata);
        });
    });
}

function fillEmptyRecord(records) {
    // 中略
    for (let record of records) {
        console.log(record);
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Cordovaで音声入力する。

UIWebViewではnavigator.mediaDevicesが取得できず、audio inputが出来ない。
cordova-plugin-audioinputを使う。

導入

cordova plugin add cordova-plugin-audioinput@1.0.1

サンプルコードが現時点で最新の1.0.2では動かなかったため、1.0.1を入れた。

Blob形式でデータを取得する流れ

  • 公式デモソースが教えてくれる。

https://github.com/edimuj/cordova-plugin-audioinput/blob/master/demo/wav-demo.js

Permissionを取る

準備

document.getElementById("startCapture").addEventListener("click", startCapture);

// 送られてくるデータを貯める
var onDeviceReady = function () {
    if (window.cordova && window.audioinput) {
        initUIEvents();

        consoleMessage("Use 'Start Capture' to begin...");

        // Subscribe to audioinput events
        //
        window.addEventListener('audioinput', onAudioInputCapture, false);
        window.addEventListener('audioinputerror', onAudioInputError, false);
    }
    else {
        consoleMessage("cordova-plugin-audioinput not found!");
        disableAllButtons();
    }
};

onAudioInput内部で、音声ストリーミングを取得する

開始

audioinput.start({audioSourceType: 0})

終了

//停止
audioinput.stop();

// waveへ変換する    
consoleMessage("Encoding WAV...");
var encoder = new WavAudioEncoder(captureCfg.sampleRate, captureCfg.channels);
encoder.encode([audioDataBuffer]);

consoleMessage("Encoding WAV finished");

var blob = encoder.finish("audio/wav");
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

owl carousel2 ことはじめ

'owl carousel2' とは

カルーセルデザイン(スライドデザイン)を表現するライブラリのひとつです

特徴としては

  1. レスポンシブ対応
  2. ドラッグでのスライド移動ができる
  3. スワイプでのスライド移動ができる

といった、スマートデバイス向けの挙動をサポートしています

同様のライブラリに slick など、いくつかのライブラリが存在します

Download and Install

前提

  • 必ずHTMLにjQueryをロードするようにしてください
  • また、以下に記すタグ埋め込みは必ずjQueryのものの下に記載するようにしてください
  • この記事を記述するに際して、使用しているライブラリやアプリのバージョンは以下の通りです
    • jQuery
      • 3.4.1
    • owl carousel2 本体
      • 2.3.4
    • chrome
      • 77.0 (77.0.3865.90 official build) (x64)
    • IE
      • 11 (11.356.18362.0)

公式サイトから

こちらDownload からzipをダウンロードできます

zipを展開し、cssは dist/assets 内の

  • owl.carousel.min.css
  • owl.theme.default.css

を任意のスタイルシートディレクトリにコピーし、javascriptは dist 内の

  • owl.carousel.min.js

を任意のスクリプトディレクトリにコピーしてください

CDNから

CDN がこちらからあるのでこれの

  • owl.carousel.min.css
  • owl.theme.default.css
  • owl.carousel.min.js

をそれぞれ <link> タグ、 <script> タグに追加してください

npm / yarn

あまり詳しく解説しません

使用法 / 例

part1

一番シンプルなデザインについて記載します

HTML

<body>
  <div class="your_awesome_carousel owl-carousel owl-theme">
    <div>
      <img src="your_awesome_image1.png">
    </div>
    <div>
      <img src="your_awesome_image2.png">
    </div>
    <div>
      <img src="your_awesome_image3.png">
    </div>
    <div>
      <img src="your_awesome_image4.png">
    </div>
    <div>
      <img src="your_awesome_image5.png">
    </div>
    <div>
      <img src="your_awesome_image6.png">
    </div>
  </div>
</body>

ハマりポイント

  1. <div class="owl-carousel owl-theme"> の部分について
    • この部分はカルーセルデザイン適用対象のrootになります
    • 必ず owl-carousel owl-theme の2つを設定する必要があります

備考

  1. カルーセルデザインを適用させたい箇所のタグを全て <div> にしていますが、実際は <ul><li></li>...</ul> のようなリストタグでも良いようです

CSS (SCSS)

特に設定しなくても動いてくれます

javascript (jQuery)

$(document).ready(function () {
  $('.your_awesome_carousel').owlCarousel();
});

EXAMPLE

https://codepen.io/nao-a/pen/gOYVLVe

part2

アイテムを中央にし、スクロールボタンが表示されているデザインを説明します

HTML

<body>
  <div>
    <ul class="owl-carousel owl-theme">
      <li>
        <img src="your_awesome_image1.png">
      </li>
      <li>
        <img src="your_awesome_image2.png">
      </li>
      <li>
        <img src="your_awesome_image3.png">
      </li>
      <li>
        <img src="your_awesome_image4.png">
      </li>
      <li>
        <img src="your_awesome_image5.png">
      </li>
      <li>
        <img src="your_awesome_image6.png">
      </li>
    </ul>
    <div class="owl-prev"></div>
    <div class="owl-next"></div>
  </div>
</body>

一番の変化は最後から3~4行目にある owl-prev owl-next のクラスを持つ <div> タグになります

これらがprev/nextのボタンに変化してくれます

CSS (SCSS)

.owl-item {
  opacity: 0.3;
  transition: 1s ease-out;
}
.owl-item.center {
  opacity: 1;
}

.owl-next {
  margin: 0 100px !important;
}
.owl-prev {
  margin: 0 100px !important;
}
.owl-item {
  opacity: 0.3;
  transition: 1s ease-out;

  &.center {
    opacity: 1;
  }
}

.owl {
  &-next {
    margin: 0 100px !important;
  }
  &-prev {
    margin: 0 100px !important;
  }
}

.owl-item & .owl-item.center

中央に来ているスライド以外を透過しています

owl-item 及び center はowl carousel2が実行時に自動的にセットしてくれるクラスになります

owl-next/owl-prev

無理矢理 左右のマージンをあけています

!important をつけないと margin 属性が効いてくれませんでした

しかし、もしかしたらほかの方法で設定することができるかもしれません

javascript (jQuery)

$(document).ready(function () {
  $('.owl-carousel').owlCarousel({
    center: true,
    items: 3,
    loop: true,
    margin: 10,
    nav: true,
    responsive: {
      600: {
        items: 1
      }
    }
  });
});

EXAMPLE

https://codepen.io/nao-a/pen/pozMReo

最後に

どのライブラリでも言えることですが、触る為のルールを会得すれば、簡単にカルーセルデザインを実装することができる良いライブラリであると感じます

しかし、slickと比較した際に、slickにはある beforeChange のイベントコールバックの設定に丁度当たるものが無い為、スライドした瞬間に center に来ている要素に対してスタイルを当てる、といったことは苦手な印象を受けました

さらっと触っただけですので、こちらももしかしたら別の方法によって達成可能かもしれません

よろしければご指摘くださいませ

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

JavaScript 覚えておくべき注意点!(1) JavaScriptで扱うデータ型 / 特殊文字の表現方法 / 変数にデータを記憶する方法 / 例

JavaScriptで扱うデータ型は7種類

  ※データ系とは? 「数値」のデータなら掛けたり割ったりすることができるが、「文字列」を掛けたり割ったりは出来ない。このようにデータにはいくつかの種類があります。

  • 文字列(String)-----------'Hello'、"こんにちは"などの文字列
  • 数値(Number)-----------100、15.23、-12.5などの数値
  • 真偽値(Boolean)---------true、falseの2値で真と偽を表わす
  • シンボル(Symbol)-------インスタンスが固有で不変となるデータ型
  • null-----------------------null値を意味する特殊キーワード
  • underfined---------------値が未定義であることを示す
  • オブジェクト(Object)---上記6つのいずれでもない特殊なデータ型

主なエスケープシーケンス

  ※改行などの特殊文字はそのままでは文字列として表現できないために、バックスラッシュ「\n」で表現されます。この特殊文字のことをエスケープシーケンスと言います。

hensuu-data.js
\n       // ---------------------改行する
\t       // ---------------------タブを入れる
\"       // ---------------------""で囲まれた文中に"を入れる
\'       // ---------------------''で囲まれた文中に'を入れる
\\       // ---------------------文字列中にバックスラッシュを入れる

変数にデータを記憶する方法

  • 代入演算子「=」を使う方法を「代入する」という。
  • 「=」は「左辺の変数に右辺の値を記憶する」という意味である
data-kioku.js
// 変数の宣言と利用
var name; // .....................変数宣言
name = 'Taro'; // ................nameに'Taro'を代入
console.log(name); // ............nameをconsole.logで表示

// 宣言と代入を同時にする例

var time = 60; // ................変数timeを宣言し、数値の60を代入
time = time * 60; // .............変数timeに「time * 60」の演算結果を代入
console.log(time); // ............3600

例 BMI計算プログラムを作成しましょう

  ※入力 → 処理 → 出力
  
  入力:体重・身長の値を得る
  処理:体重と身長をもとにBMIの値を計算する
  出力:計算結果をダイアログボックスで表示する

  この流れでプログラムを組み立てる

reidai.js
// 体重の数値を得る
var weight;
weight = prompt('BMIを測定します。まずはあなたの体重(kg)を入力してください');
// 身長の数値を得る
var height;
height = prompt('BMIを測定します。次にあなたの身長(m)を入力してください');
// 体重と身長からBMIを計算して、警告ダイアログに表示する
var bmi = weight / (height * height);
var message = 'あなたのBMIは「' + bmi + '」です。';
alert(message);

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

gatsby: command not found とエラーが出た時の対応

一般的なgatsbyのことはじめ

公式の手順通りにインストールしてくる
https://www.gatsbyjs.org/docs/quick-start

npm install -g gatsby-cli

gatsby: command not found と上手くいかない時がある

上記のコマンドではPCの設定などによっては上手くインストールできない場合があるようです。
そのためyarnを使用してインストールし直すと上手くいく。

yarnのインストールはこちらから
https://yarnpkg.com/lang/en/docs/install/#mac-stable

sudo yarn global add gatsby-cli

上記のコマンドでやり直したところ上手くいきました。

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

WYSIWYGエディタをCKEditorで作ってみた

WYSIWYGエディタって何?

「What You See Is What You Get(見たままが得られる)」エディタ。うぃじうぃぐって読むらしけど誰が読めるんやろう。

ユーザー側からタグ付けとかスタイリングとかをさせることなく、
簡単に良い感じに文章が作れるエディターです。
イメージでいうとQiitaとかWordpressの投稿のエディタ画面みたいな感じです。

これを簡単に実装できる方法を紹介します:rolling_eyes:

CKEditor vs Draft.js

実装しようと思い、調べましたがものすごく種類あるみたいですね。
https://qiita.com/rana_kualu/items/fc752de7d4f2224b29ee

最終2候補で悩みましたが、結局表題の通りCKEditorを使用しました。
理由はReact案件ではないことがメインです。(Reactが入ってるか否かで使い分けてもいいかも)

CKEditor

2003年から存在する老舗の安定したWYSIWYGエディター。
高水準のプラグインが多く、プラグインをCKEditorに組み込むことでほとんどのニーズがカバーできそう。

Draft.js

Facebook製で、単純な編集動作を備えたReactベースのWYSIWYGエディターコンポーネント
Reactを使ったプロジェクトなら相性良いと思う
参照:https://qiita.com/mottox2/items/9534f8efb4b09093a304

パッケージ作成方法

早速作成方法についてです。
webpackでもcdnでも作成できますが、パッケージをダウンロードするのが個人的にはおすすめです。

理由としては以下の通り!

  • CKEditorのダウンロードページを通して直感的に好みのパッケージを作成できる。
  • パッケージ内のコードを変更することで簡単にカスタマイズができる。

作成手順

https://ckeditor.com/ckeditor-4/download/

1. ダウンロードするパッケージの種類を選ぶ

中に入っているプラグインの数が変わります。カスタマイズしたいのであれば右のCustomizeを選択
スクリーンショット 2019-10-01 19.08.09.png

2. プリセットの選択

先ほどと同じですが、ベースとなるプリセットを選びます。
スクリーンショット 2019-10-01 19.08.46.png

3. プラグインの追加と削除

必要なプラグインを取捨選択します。右側から左側に必要なプラグインをドラッグ&ドロップで移動させます。(不要なプラグインは逆方向)
英語ですが簡単に説明もあり、詳細ページのリンクもあります。
スクリーンショット 2019-10-01 19.09.37.png

4. 好きな見た目と、言語の選択

  スクリーンショット 2019-10-01 19.10.29.png

5. ダウンロード!

スクリーンショット 2019-10-01 19.10.37.png

これで作成されたパッケージは以下の構成になっていると思います。
(Imageとか、要らないファイルとか多いです。必要に合わせて消しても良いかと思います)

├── ckeditor
│   ├── CHANGES.md
│   ├── LICENSE.md
│   ├── README.md
│   ├── adapters
│   │   └── jquery.js
│   ├── build-config.js
│   ├── ckeditor.js
│   ├── config.js
│   ├── contents.css
│   ├── lang
│   │   ├── ...
│   ├── plugin.js
│   ├── plugins
│   │   ├──...
│   │   │ 
│   ├── samples
│   │   
│   ├── skins
│   │   └── ...
│   ├── styles.js
│   └── vendor
│       └── promise.js

使用方法

1. ファイル設置

先ほどの呼び出すHTMLと同階層に設置してください
(HTML側からのリンク変わるだけなので正直どこでも良いですが…)
スクリーンショット 2019-10-02 10.30.56.png

2. HTML記述

<body>
    <div class="container">
      <form id="editor">
        <!-- textareaのクラス名にckeditorを指定するとcekditorに置き換わる -->
        <textarea class="ckeditor" name="editor"></textarea>
        <input id="editor__submit" type="submit" value="SEND">
      </form>
      <div class="preview"></div>
    </div>
    <!-- ckeditor下のckeditor.jsを呼び出す -->
    <script src="../ckeditor/ckeditor.js"></script>
    <script src="./js/app.js"></script>
  </body>

3. ckeditor編集

ckeditorの編集は ckeditor/config.js で行います。

/**
 * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
 * For licensing, see https://ckeditor.com/legal/ckeditor-oss-license
 */

CKEDITOR.editorConfig = function( config ) {
    // 必要なボタンはtoolbarGroupに追加
    config.toolbarGroups = [
        { name: 'document',    groups: [ 'mode', 'document', 'doctools' ] },
        { name: 'clipboard',   groups: [ 'clipboard', 'undo' ] },
        { name: 'editing',     groups: [ 'find', 'selection', 'spellchecker' ] },
        { name: 'forms' },
        { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
        { name: 'paragraph',   groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ] },
        { name: 'links' },
        { name: 'insert' },
        { name: 'styles' },
        { name: 'colors' },
        { name: 'tools' },
        { name: 'others' },
        { name: 'about' }
    ];
    // 環境問わず表示を日本語にする
    config.language = 'ja';
    // デフォルトのheightを変更
    config.height = 300;
};

*注記
heightはデフォルトで200,widthは100%になっている
https://ckeditor.com/docs/ckeditor4/latest/features/size.html

configで変えられる設定はこちら
https://ckeditor.com/docs/ckeditor4/latest/features/

4. おまけ

変換後が見えるようにプレビュー画面を作成
(send押したらデータを下のプレビュー画面に反映)
js/app.js

import $ from "jquery"

$(function(){
    const editorSubmit = $('#editor__submit')
    editorSubmit.on("click",function(e){
        e.preventDefault();
        // 送られるデータはCKEDITOR.instances.name属性名.getData()で取得可能
        const data = CKEDITOR.instances.editor.getData();
        // プレビュー画面に表示
        $(".preview").html(data)
    })
})

テキトーにCSSも

.container {
  margin: 50px;
  width: 60%;
}
#editor__submit {
  margin: 30px auto;
  padding: 10px;
  border-radius: 2px;
  border: 1px solid #d3d3d3;
  background: #d3d3d3;
  width: 100%;
}
.preview {
  min-height: 500px;
  border: 1px solid #d3d3d3;
  padding: 20px;
  line-height: 3;
}

結果

 UI
スクリーンショット 2019-10-02 11.31.35.png

 送信データ
スクリーンショット 2019-10-02 11.34.38.png

意外と簡単に実装できてびっくりでした!

余計なプラグイン入れてるのでごちゃごちゃしてますが、取捨選択することでイイカンジのエディタが簡単に作れそうです:sunglasses:

何か気づきや不備あればぜひコメントください:relieved:

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

ローディング画面を作ったら自信作になった件

今日、teratailの回答でローディング画面を作ったらなかなかうまくできたので記事にします。
以下コードとCodePenです。

<body>
  <div id="loading">
    LOADING
  </div>
  <main>
    <div class=menu>
      MeNu
    </div>
    <img id=topimage src=https://4k-wallpaper.com/3840x2160/4k-Ultra-HD_00348.jpg>
  </main>
</body>
* {
  margin: 0;
  padding: 0
}
main {
  display:none
}
#topimage {
  width: 100%
}
.menu {
  height:2em;
  line-height: 2em;
  position: absolute;
  top: -2em
}
window.onload = function () {
  $("#loading").delay(1000).fadeOut();
  $("main").delay(2000).fadeIn();
  $("#topimage").delay(3000).animate({width:"90%",marginLeft:"5%",marginTop:"2em",height:"auto"},"slow", "swing");
$(".menu").delay(3000).animate({top:"0"},"slow", "swing");
}

See the Pen PoYMbNp by asuchi0819 (@asuchi0819) on CodePen.

頑張った〜

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

React-Reduxが難しい? それは過去の話だ! ~ ToDoアプリを最小限の労力で記述する ~

Reduxが難しい? それは過去の話だ! ~ ToDoアプリを最小限の労力で記述する ~

 React-Reduxを簡単に利用するためのパッケージ@jswf/redux-moduleを利用して、最小限の労力でToDoアプリを作成するという内容です。

 プログラムはファイル一つだけにまとめました。
 内容は以下のように構成されています。

  • データ構造の定義
  • データ操作用クラスの作成
  • データ入力用コンポーネント
  • データ表示用コンポーネント

 今回はReduxラッパーの機能を使うため、データの入力と表示はコンポーネントは分けてあります。
 ここで見ていただきたいのは、propsもuseStateも使っていないということです。
 全てのデータはReduxのStore上に格納されます。
 しかしReduxを使う上での前提となるFlux的な定義は一切書く必要がありません。
 ReduxModuleクラスを継承することによって、読み書き全ての手続きがsetStateとgetStateメソッドに集約されます。

作ったもの

実働サンプル
GitHub上のソース
screenshot.gif

ソースコード

index.tsx
import React from "react";
import * as ReactDOM from "react-dom";
import { createStore } from "redux";
import { Provider } from "react-redux";
import {
  ModuleReducer,
  useModule,
  ReduxModule
} from "@jswf/redux-module";

/**
 *データ構造の定義(TypeScript使用時)
 *
 * @export
 * @interface TodoState
 */
interface TodoState {
  //入力中データの保持
  input: {
    title: string;
    desc: string;
  };
  //TODOリスト
  todos: {
    id: number;
    title: string;
    desc: string;
    done: boolean;
  }[];
  //TODOのID附番表index
  index: number;
}

/**
 *Todoデータ管理用クラス
 *
 * @export
 * @class TodoModule
 * @extends {ReduxModule<TestState>}
 */
export class TodoModule extends ReduxModule<TodoState> {
  //初期値
  protected static defaultState: TodoState = {
    todos: [],
    input: { title: "", desc: "" },
    index: 0
  };
  /**
   *ToDoリストを返す
   *
   * @returns
   * @memberof TodoModule
   */
  public getTodos() {
    return this.getState("todos")!;
  }
  /**
   *ToDoを追加(追加データはStoreに入っているので引数不要)
   *
   * @memberof TodoModule
   */
  public addTodo() {
    //indexのインクリメント
    const index = this.getState("index")! + 1;
    this.setState({ index });
    //必要データの読み出し
    const title = this.getState("input", "title")!;
    const desc = this.getState("input", "desc")!;
    //todoを追加
    const todos = [
      ...this.getState("todos")!,
      { id: index, title, desc, done: false }
    ];
    this.setState({ todos })!;
  }
  /**
   *ToDoの状態管理
   *
   * @param {number} id
   * @param {boolean} done
   * @memberof TodoModule
   */
  public updateDone(id: number, done: boolean) {
    const srcTodos = this.getState("todos")!;
    //状態の書き換え
    const todos = srcTodos.map(todo =>
      todo.id === id ? { ...todo, done } : todo
    );
    this.setState({ todos });
  }
  /**
   *ToDoの削除
   *
   * @param {number} id
   * @memberof TodoModule
   */
  public remove(id: number) {
    const srcTodos = this.getState("todos")!;
    //削除データを除外
    const todos = srcTodos.filter(todo => todo.id !== id);
    this.setState({ todos });
  }
}


/**
 *入力フォームコンポーネント
 *
 * @returns
 */
function FormComponent() {
  const todoModule = useModule(TodoModule);
  return (
    <div style={{ textAlign: "center" }}>
      <div>
        <div>タイトル</div>
        <input
          style={{ width: "20em" }}
          value={todoModule.getState("input", "title")!}
          onChange={e => todoModule.setState(e.target.value, "input", "title")}
        />
        <div>説明</div>
        <textarea
          style={{ width: "20em", height: "5em" }}
          value={todoModule.getState("input", "desc")!}
          onChange={e => todoModule.setState(e.target.value, "input", "desc")}
        />
        <div>
          <button onClick={() => todoModule.addTodo()}>Todoを作成</button>
        </div>
      </div>
    </div>
  );
}

/**
 *ToDo出力コンポーネント
 *
 * @returns
 */
function TodoListComponent() {
  const todoModule = useModule(TodoModule);
  const todos = todoModule.getState("todos")!;
  return (
    <div style={{ display:"flex",flexDirection: "column" ,alignItems:"center"}}>
      {todos.map(todo => (
        <div
          key={todo.id}
          style={{
            display: "inline-block",
            width: "20em",
            marginTop: "1em",
            border: "solid 1px",
            textAlign:"center"
          }}
        >
          <div>
            {todo.id}:{todo.title}{" "}
            <span
              style={{ cursor: "pointer" }}
              onClick={() => todoModule.updateDone(todo.id, !todo.done)}
            >
              {todo.done ? "完了" : "未完了"}
            </span>
            {todo.done && (
              <span
                style={{ cursor: "pointer" }}
                onClick={() => todoModule.remove(todo.id)}
              >
                削除
              </span>
            )}
          </div>
          <div>{todo.desc}</div>
        </div>
      ))}
    </div>
  );
}

//Reduxに専用のReducerを関連付ける
//他のReducerと併用することも可能
const store = createStore(ModuleReducer);
ReactDOM.render(
  <Provider store={store}>
    <FormComponent />
    <TodoListComponent />
  </Provider>,
  document.getElementById("root") as HTMLElement
);

まとめ

 ほぼReduxの影や形が消え去っています。
 今回の内容はコンポーネント間の連係や、データをStoreに集約することが目的であれば、かなり便利に使えると思います。

 パフォーマンスを考えて作る場合は、副作用の影響範囲を最小限に抑えるためデータ操作用のクラスを細分化したり、書き込みのみしか利用しないコンポーネント上ではwriteOnly属性を付けたりとチューニングが必要になりますが、それは別の記事で解説を入れたいと思います。

 Reduxの定義に疲れ果てた方はぜひ使ってみてください。

リンク

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

[メモ]FormDataオブジェクトにあるデータを全て削除する方法

概要

FormDataにあるすべてのデータ(エントリー)を削除する

ポイント

formData.keys()やformData.entries()のイテレーター内で削除せず、イテレーターの外側で削除する。

FormDataオブジェクトのデータを全て削除する
function deleteAllFormData(formData) {
    const keys = [];
    for (const key of formData.keys()) {
        keys.push(key);
    }
    for (const idx in keys) {
        formData.delete(keys[idx]);
    }
}

サンプルコード(html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Delete all formdata</title>
</head>
<body>

<form action="#" method="post" id="myForm" enctype="multipart/form-data">
    <input type="hidden" name="myKey01" value="myValue01">
</form>
<script>

    function deleteAllFormData(formData) {
        const keys = [];
        for (const key of formData.keys()) {
            keys.push(key);
        }
        for (const idx in keys) {
            formData.delete(keys[idx]);
        }
    }

    const formData = new FormData(document.querySelector("#myForm"));
    formData.append("myKey02", "myValue02");
    formData.append("myKey03", "myValue03");

    console.log("Show form entries");
    for (const [key, value] of formData.entries()) {
        console.log(`key:${key} value:${value}`);
    }

    console.log("Delete!");
    deleteAllFormData(formData);

    console.log("Show form again");
    for (const [key, value] of formData.entries()) {
        console.log(`key:${key} value:${value}`);
    }


</script>

</body>
</html>
実行結果(ログ)
Show form entries
key:myKey01 value:myValue01
key:myKey02 value:myValue02
key:myKey03 value:myValue03
Delete!
Show form again

アンチパターン

反復中に自身を変更する以下のようなコードはアンチパターン

for (const key of formData.keys()) {
    formData.delete(key);
}

(Javaでも、同期リストのIteratorで反復中に自身を変更(削除も含む)するとConcurrentModificationExceptionが発生するパターン)

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

canvasで描いた絵のURLをDBに保存する

概要

私は9月ごろから独学でRuby on Railsを始めたのですが、題名の通りcanvasで描いた絵をURLに変換してDBに保存する際に数日詰まったので自分への戒めのために今回の記事を書く運びとなりました。

詰まったところ

ArgumentError (When assigning attributes, you must pass a hash as an argument.):

ハッシュにしてから受け渡してください的なエラーが出る。

受け渡したデータ

JavaScriptでcanvasのデータをURL化しているため、そのままJavaScriptでPOSTしました。
こんなかんじ

//変数imageにはcanvasのURLが入っています
function send_url(image){
                var form = document.createElement('form');
                var request = document.createElement('input');

                form.method = 'POST';
                form.action = 'save';

                request.type = 'hidden';
                request.name = 'text';
                request.value = image;

                form.appendChild(request);
                document.body.appendChild(form);

                form.submit();
}

受け取り側はこのようになっています

private
    def post_params
          params.require(:text)
    end
end

この形式で受け取るとpost_paramsは送信されてきた文字列だけをデータに持ちます。

修正後

先ほどの関数post_paramsを次のように書き換えました

def post_params
      img = params.require(:text)
      hash_url = {"image_url" => img}
      return hash_url
end

力技です。

最後に

セキュリティなどの観点で見てこれが大丈夫なやり方なのかはわかりませんが、とりあえずこのハッシュにしたデータを渡すことで無事URLをDBに渡すことができました。

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

【Google Apps Script】チェックボックスでスプレッドシートの特定行の表示/非表示を切り替える

はじめに

スプレッドシートにチェックボックスを配置し、そのアクティブ状態によって指定行を表示/非表示の切り替えをしたいと要望がありました。
GASを使えばいけそうなので、早速試してみたので書いてみます。

完成図

movie.gif

下準備

スプレッドシートを新規作成。
1. A1〜A3に「A」「B」「C」と入力。
2. B1〜B3にチェックボックスを配置。チェックボックスは挿入→チェックボックスで配置できます。
3. C1〜C3に、カンマ区切りの適当な数字を入れます。ここに入れた行数が、チェックした場合の表示する行になります。

s1.png

ツール→スクリプトエディタを選択し、以下コードを貼り付けます。

コード全文

var CHECKBOX_COLUMN = 2; // チェックボックスの列
var CHECKBOX_COUNT = 3; // チェックボックスの数
var HIDDEN_FIELD_START = 6; // 何行目から消すのか
var HIDDEN_FIELD_END = 30; // 何行目まで消すのか

// スプレッドシートの値が変更された時に発火
function changeEvent() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var activeCell = sheet.getActiveCell();
  if (activeCell.getColumn() == CHECKBOX_COLUMN){ // チェックボックス列の値が変更された場合
    checkBoxEvent(sheet);
  }
}

// チェックボックスのチェック状態に合わせて表示・非表示を切り替える
function checkBoxEvent(sheet) {
  var rows = sheet.getRange(1, CHECKBOX_COLUMN, CHECKBOX_COUNT, 1).getValues();
  hiddenFields(sheet);
  for (var i = 0; i < rows.length; i++) {
    if (rows[i][0] === true) {
      var showRows = sheet.getRange(i + 1, CHECKBOX_COLUMN + 1).getValue().split(',');
      for (var j = 0; j < showRows.length; j++) {
          sheet.showRows(showRows[j]); // チェックボックスの1個右のセルの価に記載されている行数を表示する
      }
    }
  }
}

// 非表示行エリアを全非表示にする
function hiddenFields(sheet) {
  sheet.hideRows(HIDDEN_FIELD_START, HIDDEN_FIELD_END - HIDDEN_FIELD_START + 1);
}

動作確認

スプレッドシートのカーソルをB列のどこかに置きます。

  if (activeCell.getColumn() == CHECKBOX_COLUMN){ // チェックボックス列の値が変更された場合

ソースコードで見るとこの部分、B列の値変更イベントにのみ発火するように設定しているためです。

スクリプトエディタで実行→関数の実行→changeEventを選択。
実行許可のウィンドウが出るので、過去記事の過去記事のスクョを参考にしてください。

スプレッドシートの10〜30行目が非表示になり、チェックONになっているセル右隣で指定している行のみ表示されれば成功です。

トリガーの設定

チェックボックス変更した場合、自動的に行の表示/非表示されるように、スプレッドシートの変更イベントをトリガーとしてchangeEvent関数が自動実行されるよう設定します。

スクリプトエディタ「編集→現在のプロジェクトのトリガー」もしくは時計アイコンからトリガー設定画面に飛びます。
右下の「トリガーを追加」から、下記の通り設定します。

s2.png

movie.gif

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

【Blob・File・Base64・データURL・FileReader】 それぞれの特徴とブラウザへの表示について

はじめに

この記事では、ファイルを受け取ってからブラウザ上に表示するまでに登場する、インターフェースやオブジェクトの特徴を調べ、みなさんが要件に適したファイル操作を選択できるよう祈りながら書きました。

それぞれの特徴

Blob

  • HTML5のFile APIで定義されたインターフェースである
  • バイナリ・ラージ・オブジェクトの略である
  • Blobオブジェクトには、生のバイナリデータが格納されている
  • プロパティはsizeとtypeのみで、中のデータに直接アクセスすることはできない
  • Blobオブジェクトはimmutable(不変)であり、を直接編集することはできない
  • が、sliceメソッドによる分割や、コンストラクタによる結合を行うことができる

https://developer.mozilla.org/ja/docs/Web/API/Blob
https://ja.javascript.info/blob

File

  • HTML5のFile APIで定義されたインターフェースである
  • Blobインターフェースを継承しているため、Blobを引数に取るメソッドに渡すことができる
  • 異なるのは、よりファイルを扱いやすくするために、name,lastModifiedプロパティが追加されている
  •  <input type="file">にローカルファイルを与えると、files属性か、onchangeイベント経由で、 FileListオブジェクトが得られる。FileListオブジェクトに対してインデックスを渡すことでFileオブジェクトを取得することができる

https://developer.mozilla.org/ja/docs/Web/API/File
https://ja.javascript.info/file

FileReader

  • FileReaderは、Blob(File)オブジェクト内のバイナリデータを読み込むためのオブジェクト
  • FileReaderオブジェクトには、下記のようなメソッドがあり(非推奨のものは除く)、Blobオブジェクトを様々な形式で読み取ることができる
    • readAsArrayBuffer() →ArrayBuffer
    • readAsDataURL() →DataURL
    • readAsText() →ArrayBuffer
    • abort() →読み取り中断

https://developer.mozilla.org/ja/docs/Web/API/FileReader
https://ja.javascript.info/file

Base64

  • Base64というクラスやインターフェースは存在せず、バイナリデータをStringに変換するためのエンコード方式である
  • エンコード前のバイナリデータと比べて約33~37%データサイズが増加する

データURL

  • data:[<mediatype>][;base64],<data>という形式で記述されたURLである
  • 例えばテキストデータでは、data:text/plain;base64,dGVzdA==のような感じのURLで、これをアドレスバーに入力するとtestと表示される
  • jpeg画像ファイルを示すデータURLはdata:image/jpeg;base64,という文言から始まる
  • URL内にテキストや画像データが埋め込まれているため、URL自体が比較的大きいサイズになる

BlobやFileに格納されているファイルをブラウザ上に表示する方法

以下、画像ファイルの表示を例にして説明する。
画像ファイルをブラウザ上に表示するためには、imgタグのsrc属性にURL(データURLを含む)を与える必要がある。

URLを取得するには2つの方法がある
- Blob(File)をデータURLとして読み取る
- Blobを参照するURLを生成する

以下、方法とメリット・デメリットを記述する

BlobをデータURLとして読み取る

const reader = new FileReader();
reader.onload = function(e) {
  console.log(e.target.result) //データURLがコンソールに表示される
}
reader.readAsDataURL(blob) //ここでBlobオブジェクトを読み込み、読み込みが完了したタイミングでreader.onloadに定義した関数が実行される

メリット

  • ファイルの読み込みを非同期で行うことができる。
  • URLそのものにデータが埋め込まれているため、URLを直接サーバーに渡すことが可能。

デメリット

  • Base64エンコードの仕様に則り、データサイズが133%ほどに増える
  • メモリ上にファイルを展開するため、メモリを圧迫する

Blobを参照するURLを生成する

const imageUrl = window.URL.createObjectURL(blob) //URL生成
console.log(imageUrl) //データURLがコンソールに表示される
window.URL.revokeObjectURL(imageUrl) //URL破棄

メリット

  • URLの生成が即時完了する

デメリットあるため、

  • 一時的なURLであり、ブラウザを開き直したり、他端末や他ブラウザに渡してもアクセスできない
  • window.URL.revokeObjectURL()によってURLを破棄しないと、ブラウザを閉じるまでメモリ上にURLが残ってしまう
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsでスクロール時のイベントを定義する

やりたいこと

わかりづらいので、具体的にやりたかったことを書きます。

自分が作っていたアプリでは、「ページを下のほうにスクロールしたらトップへ戻るボタンを出したい」といった、「スクロールしたときに何かする」的な処理が結構ありました。

そのたびに、↓こんな感じにcreateddestroyedでイベントを登録・解除するのがめんどいな、と。

<script>
export default {
  created () {
    window.addEventListener('scroll', this.onScroll)
  },
  destroyed () {
    window.removeEventListener('scroll', this.onScroll)
  },
  methods: {
    onScroll () {
      // Do something
    }
  }
}
</script>

そこで、↓こんな感じに書くだけで、スクロールしたときのフックを定義できるようにしました。

<script>
export default {
  onScroll () {
    // Do something
  }
}
</script>

実装

公式ドキュメントをぱっと見した限り、ディレクティブをカスタマイズする手段はあるようですが、上記のようなフックのカスタマイズは見つかりませんでした。

なので、GlobalなMixinを作って実現しました。

main.js
import Vue from 'vue'
// ..
import GlobalMixin from './GlobalMixin'

Vue.mixin(GlobalMixin)

// ..

↓onScrollが定義されていた場合、windowのscrollイベントに登録し、destroyedで削除します。

GlobalMixin.vue
<script>
export default {
  mounted () {
    // Register onScroll event
    if (this.$options.onScroll) {
      window.addEventListener('scroll', this.$options.onScroll)
    }
  },
  destroyed () {
    // Unregister onScroll event
    if (this.$options.onScroll) {
      window.removeEventListener('scroll', this.$options.onScroll)
    }
  }
}
</script>

これだけです。

(余計なお世話かもしれませんが、↑このmixinはむやみに拡張しないでください)

そして、冒頭のように各コンポーネントでonScrollという名前でイベントを定義するだけです。

<script>
export default {
  data () {
    return {
      showButton: false
    }
  },
  onScroll () {
    this.showButton = window.scrollY > 100
  }
}
</script>

余談

公式のカスタムディレクティブの例でまさにスクロール時のイベントを定義するサンプルがあったんですが、ちょっとしっくりこないです…。

<div id="app">
  <h1 class="centered">Scroll me</h1>
  <div
    v-scroll="handleScroll"
    class="box"
  >
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. A atque amet harum aut ab veritatis earum porro praesentium ut corporis. Quasi provident dolorem officia iure fugiat, eius mollitia sequi quisquam.</p>
  </div>
</div>

@scroll的な感じで、.boxがスクローラブルな要素で、そのonscrollに定義される感じがするけど、結局window全体のonscrollに定義してるし、そもそも外からイベントを定義したいケースも思いつかない。

handleScrollの引数に渡したelementを直接弄ってるのも手続き的で気持ちよくないです…。

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

Vue.jsでスクロール時のイベントを便利に定義する

やりたいこと

わかりづらいので、具体的にやりたかったことを書きます。

自分が作っていたアプリでは、「ページを下のほうにスクロールしたらトップへ戻るボタンを出したい」といった、「スクロールしたときに何かする」的な処理が結構ありました。

そのたびに、↓こんな感じにcreateddestroyedでイベントを登録・解除するのがめんどいな、と。

<script>
export default {
  created () {
    window.addEventListener('scroll', this.onScroll)
  },
  destroyed () {
    window.removeEventListener('scroll', this.onScroll)
  },
  methods: {
    onScroll () {
      // Do something
    }
  }
}
</script>

そこで、↓こんな感じに書くだけで、スクロールしたときのフックを定義できるようにしました。

<script>
export default {
  onScroll () {
    // Do something
  }
}
</script>

実装

公式ドキュメントをぱっと見した限り、ディレクティブをカスタマイズする手段はあるようですが、上記のようなフックのカスタマイズは見つかりませんでした。

なので、GlobalなMixinを作って実現しました。

main.js
import Vue from 'vue'
// ..
import GlobalMixin from './GlobalMixin'

Vue.mixin(GlobalMixin)

// ..

↓onScrollが定義されていた場合、windowのscrollイベントに登録し、destroyedで削除します。

GlobalMixin.vue
<script>
export default {
  mounted () {
    // Register onScroll event
    if (this.$options.onScroll) {
      window.addEventListener('scroll', this.$options.onScroll)
    }
  },
  destroyed () {
    // Unregister onScroll event
    if (this.$options.onScroll) {
      window.removeEventListener('scroll', this.$options.onScroll)
    }
  }
}
</script>

これだけです。

(余計なお世話かもしれませんが、↑このmixinはむやみに拡張しないでください)

そして、冒頭のように各コンポーネントでonScrollという名前でイベントを定義するだけです。

<script>
export default {
  data () {
    return {
      showButton: false
    }
  },
  onScroll () {
    this.showButton = window.scrollY > 100
  }
}
</script>

余談

公式のカスタムディレクティブの例でまさにスクロール時のイベントを定義するサンプルがあったんですが、ちょっとしっくりこないです…。

<div id="app">
  <h1 class="centered">Scroll me</h1>
  <div
    v-scroll="handleScroll"
    class="box"
  >
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. A atque amet harum aut ab veritatis earum porro praesentium ut corporis. Quasi provident dolorem officia iure fugiat, eius mollitia sequi quisquam.</p>
  </div>
</div>

@scroll的な感じで、.boxがスクローラブルな要素で、そのonscrollに定義される感じがするけど、結局window全体のonscrollに定義してるし、そもそも外からイベントを定義したいケースも思いつかない。

handleScrollの引数に渡したelementを直接弄ってるのも手続き的で気持ちよくないです…。

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