※スマホ対応はしてません。

Google Chromeで音声認識する例。

カテゴリー: JavaScript

意識しないとすぐTwitterに書いて終わりにしてしまうので、気負わず軽い気持ちで書きます。

諸事情により音声認識APIを試しているんだけど、ウェブ標準でもそういうAPIが準備中であるのに気づきました。現状のGoogle Chromeでもそこそこ動くようです。せっかくなので(Chromeで)動くやつを用意してみました。

ginpei/speech-recognition-example

まだ実験的な機能だし、実際動きも微妙なところなので、やっぱりGoogleなりMicrosoft AzureなりのAPI使う方が良さそう。無料枠あるし。

基本的なコード

// 作成
const sr = new window.SpeechRecognition();

// 完了時に結果を表示
sr.addEventListener('result', (event) => {
  const text = event.results[0][0].transcript;
  console.log(text);
});

// 開始
sr.start();

音が止まると音声認識も自動で終了する様子。

各ブラウザー

Chrome

接頭辞付きの webkitSpeechRecognition で動く。まだ完璧ではない様子。

あとそういえば昔 <input x-webkit-speech> みたいなのあったなあ、みたいな記憶。

Firefox

まだ対応していない。コードは mozSpeechRecognition に対応しておいたので、実装されたらきっと動くことでしょう。知らんけど。

Edge

ちょうど最近開発版が出た、中身がChromiumの新Edgeはまだ動きが微妙な感じでした。

同じ webkit 接頭辞なのに動いたり動かなかったりは困るなあ。いや今は開発版だから良いんだけど。

Safari

試してないけど未対応らしい。

JPEG/Exif画像の回転情報を取れるやつ作った。

カテゴリー: JavaScript

新年明けましておめでとうございます。作ったよ。

インストール。

$ npm install @ginpei/exif-orientation

利用例。

import * as exif from '@ginpei/exif-orientation';
 
const orientation = await exif.getOrientation(fileOrBuffer);
console.log(
  `${orientation.rotation} degree,`,
  orientation.flipped ? 'flipped' : 'not flipped',
);

上記のものではないが実行例。

$ node from-node.js sample-images/090-flipped-5.jpg
Read image from: sample-images/090-flipped-5.jpg
Rotation: 90 degree, Flipped: true.

検索するといかにもな名前のやつとかが複数出てくるんだけど、先行事例に対する利点は特にないです、たぶん。

作りたいので作った感じのやつです。ちゃんと動くと思うけど。

Scoped package

名前が取られてたけど作っちゃったし変えるのもなあと思って、自分の名前 “@ginpei” 付きの形での公開としました。

READMEにも書いたけど、TypeScriptの場合は --moduleResolution node の設定が必要です。へえそうなんだ、知らなかった。

エラー例。

src/index.ts:1:23 - error TS2307: Cannot find module '@ginpei/exif-orientation'.

1 import * as exif from '@ginpei/exif-orientation';
                        ~~~~~~~~~~~~~~~~~~~~~~~~~~


Found 1 error.

Exif仕様

こちらのPDFを大いに参考にしています。感謝感謝。

ところでExifて日本発祥の規格なんだね。

富士フイルムが開発し、当時の日本電子工業振興協会 (JEIDA)で規格化

画像の回転

とかそういうのは、一切しないです。調査するまで。

やるならCanvasが良いのかな。いやJPEGの画素情報の位置を回転的に動かす方が再圧縮しないから綺麗そう。

一時的ならCSSでやるのも手。よし transform: matrix() 使おうぜ!

JPEG以外

Exifを他の規格の画像にも使えるらしいんだけど、JPEGのみの対応です。見ないし。

おしまい

本年もよろしくお願い致します。

2018年を振り返る。

カテゴリー: 日記

日記です。

まとめ

ぼちぼち作ったなー。

GitHub

GitHubプロフィールページのContributionsのキャプチャ。ぼちぼち草生えてる。
GitHubの1年分の活動結果。

自分のGitHubプロフィールページで2018年を開いて、これする。

(() => {
  const year = 2018;
  const days = [...document.querySelectorAll('rect.day')]
    .filter(e=>e.getAttribute('data-date').startsWith(year));
  const counts = days.map(e=>e.getAttribute('data-count')|0);
  const sum = counts.reduce((s,c)=>s+c);
  console.log(`${year}年${days.length}日中活動したのが${counts.filter(v=>v!=0).length}日、合計${sum}回、平均${(sum/days.length).toFixed(2)}回毎日。`);
})();

2018年365日中活動したのが196日、合計1466回、平均4.02回毎日。

作ったもの

Clock

1月。アナログ時計 + ポモドーロタイマー。

手頃に使えるアナログ時計が欲しいなと思って、PCの脇にスマホを立ててそっちに映す用に。

どうせならブラウザーじゃなくても動くようにしようかなと思って、後述のQuick NoteでPWAを試して、その後導入した。結果的にNuxt.jsがよしなにやってくれるのであまり気にする必要はなかったけど。

そう、Nuxt.js使ったけどあんまり便利機能使ってなかった。History APIを使うSPAもGitHub Pagesだと使えないし。

Quick Note

2月。オフラインでも動く簡単ノート。入力内容はLocalStoragに保存される。

PWAを作ってみたい!と思って作ったやつです。依存パッケージも一切なし。( devDependencies はあります。ESLintとか。)

なんならもうちょっとこうノートにタイトルを付けて保存とかするようにしても良かったかもしれない。でもまあそういう目的ではないし、作っても使わないだろうからよし。

Stop SNS

4月。Twitterとかの閲覧を阻害するChrome拡張、Firefoxアドオン。

ちょっと思うところあってブラウザー拡張作ってみよう、と思って。簡単に作れて良いね。自分で使ってません。

本編とは関係ないんだけど、SVGの図形に影を付けるってのを学んだ。これおもしろいな。

お前を消す方法 for GitHub

6月。どこか懐かしいマスコットを表示するChrome拡張、Firefoxアドオン。ジョークアプリ。

MicrosoftがGitHubを買収という報道を受けて。

まさかの窓の杜、やじうまの杜に掲載。

最近☆1評価付けられてるのに気付いた。何を求めてたってんだ……。

DevTools z-index

6月。 z-index を利用している要素とその値を開発者ツールから一覧できるChrome拡張、Firefoxアドオン。

仕事中の息抜きで書いたコードスニペットをもうちょっとこねてブラウザー拡張へ転用したもの。気軽に作ったんだけど、「便利だ」と割とウケが良かく嬉しかった。ただ実は自分で使ってないので、何がそんなに便利なのかよくわかってない……。

Pretty Letters

7月。 。Unicodeで定義されている特殊な見た目の文字を表示する。

変わった見た目の文字を使ってる人をたまにTwitterで見かけて、それを調べてたのが始まり。あー逆さ文字とかもやりたいな。

デザインがひどい。

Potoshop (v0-1)

8月。クリップボードから画像を貼り付け、サイズ変更や枠の付加を行うだけ。Photoshopではない。

ある晩眠れずにいて、諦めて起きてぽちぽちしてるうちにできあがったもの。これくらいの編集ができるやつが欲しかった。

その後もっとあれこれしたいなと思って一から作り直していて、当時の面影は残っていない。今でもだらだらいじってる……。一度終わらせた方が良いんだろな。

画像アップロード前に小さくして転送量を減らすRailsアプリの例

8月。Canvasを使って画像を小さくし、転送量ひいては転送時間を減らす例。Railsの体だが主となるのはもちろんJavaScript。

知人から相談されて「アップロードを早くする」コードを書いたついでにまとめたもの。

その後Exifのorientation情報(回転、反転)に対応してないことに気付いた。他のところで対応したけど、こいつに反映してないや。

RailsをDockerで動かす例

8月。Dockerを使い、開発環境自体にRubyをインストールしないで始めるやり方。

前述の例を作った、さらにそのついで。

書いてみたは良いけどどうなんだこれ。

Understanding transform:matrix()

11月。CSSの transform で使える関数 matrix() の動きを視覚的に試せるもの。

行列式を分かりやすく解説する記事を読んで感動したのがきっかけ。読みながら「あーそうか matrix() のやつはこういうことだったのか!」となって、「あれそうだっけ? 動かしてみるかー」みたいな流れから。

Code Chat Cat

未完成。リアルタイムにMarkdown文書を共有する。ワークショップでの利用を想定。

電子黒板みたいな感じで、講師が書いて生徒がそれを見るってのが欲しかった。HTMLとして出力された結果が生徒の手元にあるような状態になる。パワポ的なやつと比べた利点は、視力とかに依らない点、任意に(5分前とか)過去のものを見られる点、サンプルコードをコピペできる点、リンクが活きる点、そして生徒の反応(質問)をすぐ反映して全員に届け、残せる点。

他にも画像他のファイル添付だ控えめなコミュニケーションだ何だとかんだと考えていたら、UIどうしようと悩んで手が止まってしまった。欲しい機能を出して再設計して出直そう。

名前が気に入ってる。なお犬派。

やったこと

JavaScript勉強ついでにブラウザー拡張(アドオン)を作ってみようぜ会

6月。JavaScript自体の初級者向けのワークショップ。

やり方わかってきたので近くの人にお裾分けしたやつ。ちゃんと初級者の人も来てくれて、やった甲斐があった。メッセージとかで通信して「境界をまたぐ」というのは、やっぱり難しい概念なのだと再確認。どうやったらうまく伝えられるだろうか。

資料作成は、せっかくなのでVuePressに挑戦。

配列とかおれおれAdvent Calendar2018

12月。JavaScriptの配列と繰り返しにまつわる記事を1日から24日まで毎日投稿。

毎年恒例のひとりアドベントカレンダー。今年は始まる前に完成原稿を15日分程用意できたので、毎日締め切りに追われることはなかった……と言いたかったんだけど、結局毎日見直して大幅に加筆修正していたので、忙しかった。新規に書きたくなってしまったものもあり、実はいくつか記事が余った。

最初は毎日メソッドをひとつずつ紹介する程度のつもりだったんだけど、気が付いたら仕様書を読み漁っていた……。楽しかったからいいけど。

読者の想定レベルも謎な感じになってしまった。いやおれは楽しかったからいいけど!

来年は12月に入る前に完成原稿を24日分用意したい。

Lightning Talks

いろんな人の話を聞きたいなと思って、ここ数年個人的にLT会を主催している。

気が向いたときにやる感じなんだけど、2018年は5回開催できた。月1くらいを目指したい。参加人数が全然読めないんだよなー。3人だったり13人だったり……。

仕事

改善してこ。

おしまい

全体的に気持ちが向上してきた一年だった。やっぱり何か作るのは良いなあ。

Vue.jsとReactのアプリをいくつか作ってみて、ようやくわかってきた気がする。あとTypeScriptにもようやく手を出せて、こいつはなかなかのgame changerじゃねえかと。

本当は「毎月ひとつ何か作って出す」が目標だったんだけど、あんまり達成できなかった。結果的にそれくらいの数はやれたからヨシとするか。

2019å¹´

作りかけのものは完成させたいし、他にも作りたいものはまだまだあるので、どんどん作ってゆく所存。

あともっとワークショップやりたいでござる。

もう配列のメソッド(とか)全部説明する。(配列とかおれおれAdvent Calendar2018 – 24日目)

カテゴリー: JavaScript

LINDORのAdvent Calendar(本物)の24日目を開けたところ。
配列とかおれおれAdvent Calendar2018 – 24日目

全部やる。

一覧

配列を作成

  • []
  • new Array(), Array()
  • Array.from()
  • Array.of()

新規配列を返却

  • Array.prototype.concat()
  • Array.prototype.filter()
  • Array.prototype.map()
  • Array.prototype.reduce()
  • Array.prototype.reduceRight()
  • Array.prototype.slice()

検索

  • Array.prototype.every()
  • Array.prototype.find()
  • Array.prototype.findIndex()
  • Array.prototype.includes()
  • Array.prototype.indexOf()
  • Array.prototype.lastIndexOf()
  • Array.prototype.some()

配列以外を返却

  • Array.isArray()
  • Array.prototype.join()
  • Array.prototype.toLocaleString()
  • Array.prototype.toString()

破壊的操作

  • Array.prototype.copyWithin()
  • Array.prototype.fill()
  • Array.prototype.pop()
  • Array.prototype.push()
  • Array.prototype.reverse()
  • Array.prototype.shift()
  • Array.prototype.sort()
  • Array.prototype.splice()
  • Array.prototype.unshift()

反復

  • Array.prototype.entries()
  • Array.prototype.forEach()
  • Array.prototype.keys()
  • Array.prototype.values()
  • Array.prototype[Symbol.iterator]()

    (さらに…)

reduce()はArrayにて最強……おぼえておけ。(配列とかおれおれAdvent Calendar2018 – 23日目)

カテゴリー: JavaScript

LINDORのAdvent Calendar(本物)の23日目を開けたところ。
配列とかおれおれAdvent Calendar2018 – 23日目

配列界最強のメソッドです。(?)

まじ便利というか万能なので、ぜひご利用頂きたい。

ちょっとわかりづらいんだけど、こんな↓感じです。

const arr = [11, 22, 33];

const obj = arr.reduce((acc, value, index) => {
  acc[`number${index}`] = value;
  return acc;
}, {});
console.log(obj);
// { number0: 11, number1: 22, number2: 33 }

与えたコールバック関数の次に第2引数がある点に注目。

インターフェイスと使い方

result = arr.reduce(callback[, initialValue])
// callback = (accumulator, currentValue[, currentIndex, array]) => accumulator;

引数

コールバック関数と初期値の2つです。

第2引数の初期値に、例えばここに空のオブジェクトを与えたりします。

第1引数のコールバック関数が最初に受け取る第1引数は、その初期値になります。その値を操作して return すると、2回目以降のコールバック呼び出しの第1引数にはその return した値が与えられます。

戻り値

順に繰り返し、コールバック関数が最後に return した値が、 reduce() 全体の戻り値になります。

初期値をそのまま返してみる例

initial がコールバック関数の第1引数 acc として与えられて、毎回それを return するので以降も同じインスタンスを受け取り、最終的に結果もそれになります。

const arr = [11, 22, 33];

const initial = {};
const result = arr.reduce((acc, _, index) => {
  console.log(index, acc === initial);
  return acc;
}, initial);
console.log('result', result === initial);
// 0 true
// 1 true
// 2 true
// result true

初回だけ初期値を受け取ってみる例

return 時に新しいインスタンスを生成してみます。2回目はそれを受け取るので false 、また新しいインスタンスになって3回目も同じく false です。

const arr = [11, 22, 33];

const initial = {};
const result = arr.reduce((acc, _, index) => {
  console.log(index, acc === initial);
  return { ...acc };
}, initial);
console.log('result', result === initial);
// 0 true
// 1 false
// 2 false
// result false

数値を増やしてみる例

繰り返しながら操作する対象はオブジェクトに限らず、このように数値でも可能です。

const arr = [11, 22, 33];

const result = arr.reduce((acc, _, index) => {
  console.log(index, acc);
  return acc + 1;
}, 100);
console.log('result', result);
// 0 100
// 1 101
// 2 102
// result 103

初期値を省略した例

省略すると配列の最初の要素が与えられます。またコールバック関数はその次の要素から呼ばれます。

この例↓だと最初 arr[0] の 11 がコールバック関数で処理されず、最初から acc へ与えられます。

const arr = [11, 22, 33];

const result = arr.reduce((acc, value, index) => {
  console.log(index, acc, value);
  return value;
});
console.log('result', result);
// 1 11 22
// 2 22 33
// result 33

例

合計

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const sum = arr.reduce((acc, value) => acc + value);
console.log(sum); // => 55

最大値

Math.max(...arr) でやれるやつ。

const arr = [94, 39, 63, 87, 52, 3, 10, 95, 5];

const max = arr.reduce((acc, value) => (acc > value ? acc : value));
console.log(max); // => 95

マップ

map() 代わり。

const arr = [11, 22, 33];

const result = arr.reduce((acc, value, index) => {
  acc[index] = 1000 + value;
  return acc;
}, []);
console.log(result);
// [ 1011, 1022, 1033 ]

フィルター

filter() 代わり。

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const result = arr.reduce((acc, value) => {
  if (value % 3 === 0) {
    acc.push(value);
  }
  return acc;
}, []);
console.log(result);
// [ 3, 6, 9 ]

平坦化

flat() の代わり。

const arr = [
  11,
  [22, 33],
  44,
  [55, 66, 77],
];

const result = arr.reduce((acc, value) => acc.concat(value), []);
console.log(result);
// [ 11, 22, 33, 44, 55, 66, 77 ]

検索

find() 代わり。

const arr = [
  { id: '101', name: 'Alice' },
  { id: '102', name: 'Bob' },
  { id: '103', name: 'Charlie' },
];

const result = arr.reduce((acc, value) => {
  if (acc) {
    return acc;
  }

  if (value.id === '102') {
    return value;
  }

  return undefined;
}, undefined);
console.log(result);
// { id: '102', name: 'Bob' }

同じようにして some() 代替も作れる。

繰り返し

forEach() 代わり。

const arr = [11, 22, 33];

arr.reduce((_, value, index) => {
  console.log(index, value);
}, undefined);
// 0 11
// 1 22
// 2 33

初期値を省略すると最初の要素が呼ばれない点に注意。

IDでインデックス

識別子を含むオブジェクトの配列から、その識別子でアクセスできるオブジェクトを作成します。

const arr = [
  { id: '101', name: 'Alice' },
  { id: '102', name: 'Bob' },
  { id: '103', name: 'Charlie' },
];

const map = arr.reduce((acc, record) => {
  acc[record.id] = record;
  return acc;
}, {});
console.log(map);
// { '101': { id: '101', name: 'Alice' },
//   '102': { id: '102', name: 'Bob' },
//   '103': { id: '103', name: 'Charlie' } }

オブジェクトを複製

{ ...obj } や Object.assign() の代わり。

const obj = {
  foo: 123,
  bar: {
    hoge: 234,
    fuga: {
      message: 'Yay!',
    },
  },
};

const obj2 = Object.entries(obj).reduce((acc, [key, value]) => {
  acc[key] = value;
  return acc;
}, {});
console.log(obj2);
// { foo: 123, bar: { hoge: 234, fuga: { message: 'Yay!' } } }

reduce() より Object.entries() の方が良い仕事してる気もする。

深いコピーでオブジェクトを複製

const obj = {
  foo: 123,
  bar: {
    hoge: 234,
    fuga: {
      message: 'Yay!',
    },
  },
};

const deepCopy = (obj) => Object.entries(obj).reduce((acc, [key, value]) => {
  if (typeof value === 'object') {
    acc[key] = deepCopy(value);
  } else {
    acc[key] = value;
  }
  return acc;
}, {});

// 浅いコピー
const obj2 = { ...obj };
console.log(obj2);
// { foo: 123, bar: { hoge: 234, fuga: { message: 'Yay!' } } }
console.log(obj2.bar === obj.bar);
// true

// 深いコピー
const obj3 = deepCopy(obj);
console.log(obj3);
// { foo: 123, bar: { hoge: 234, fuga: { message: 'Yay!' } } }
console.log(obj3.bar === obj.bar);
// false

出現要素数を数える

何でもいいいんだけど、試しにHTML要素の要素名で。

const els = [...document.querySelectorAll('*')];
const counts = els.reduce((acc, el) => {
  const name = el.tagName.toLocaleLowerCase();
  if (!acc[name]) {
    acc[name] = 0;
  }
  acc[name] += 1;
  return acc;
}, {});
console.log(counts);
// Object { html: 1, head: 1, meta: 23, script: 12, title: 6, link: 32, body: 1, ul: 20, li: 186, a: 223, … }

クラス名とかにしてもおもしろそう。

細かいところ

仕様書の説明

珍しく仕様書にある説明が長いので、ちょっと見てみましょう。

Note 1

callbackfn should be a function that takes four arguments. reduce calls the callback, as a function, once for each element after the first element present in the array, in ascending order.

callbackfn is called with four arguments: the previousValue (value from the previous call to callbackfn), the currentValue (value of the current element), the currentIndex, and the object being traversed. The first time that callback is called, the previousValue and currentValue can be one of two values. If an initialValue was supplied in the call to reduce, then previousValue will be equal to initialValue and currentValue will be equal to the first value in the array. If no initialValue was supplied, then previousValue will be equal to the first value in the array and currentValue will be equal to the second. It is a TypeError if the array contains no elements and initialValue is not provided.

reduce does not directly mutate the object on which it is called but the object may be mutated by the calls to callbackfn.

The range of elements processed by reduce is set before the first call to callbackfn. Elements that are appended to the array after the call to reduce begins will not be visited by callbackfn. If existing elements of the array are changed, their value as passed to callbackfn will be the value at the time reduce visits them; elements that are deleted after the call to reduce begins and before being visited are not visited.

ノート 1

callbackfn は4つの引数を取る関数になるだろう (should) 。 reduce はこのコールバックを、関数として、配列に現れる最初の要素以降の各要素ごとに、昇順で呼ぶ。

callbackfn は以下の4の引数とともに呼ばれる: previousValue (前回の callbackfn 呼び出しで得られる値)、 currentValue (現在の要素の値)、 currentIndex 、横断中のオブジェクト。最初にコールバック関数が呼ばれたとき、 previousValue と currentValue は以下のいずれかになる。 reduce に initialValue が与えられた場合、 previousValue は initialValue に等しくなり、 currentValue は配列の最初の値と等しくなる。 initialValue が与えられなかった場合、 previousValue は配列の最初の値と等しくなり、 currentValue は2番目と等しくなる。配列がひとつも要素を持たず、かつ initialValue も与えられなかった場合、 TypeError になる。

reduce は呼ばれたオブジェクトを直接変化させないが、そのオブジェクトは callbackfn 呼び出しによって変化されてもよい (may) 。

reduce に処理される要素の範囲は最初の callbackfn 呼び出しの前に設定される。最初の reduce 呼び出しが開始した後に配列へ追加された要素は、 callbackfn から参照されることはない。配列の既存の要素が変更された場合、 callbackfn へ与えられる値は reduce がそれらを参照した時点での値になる。また、 reduce 呼び出しが始まった後、参照より前に削除された要素は、参照されない。

(訳注: 関数が配列要素を visit することを「参照」としました。)

うっ日本語読みづら。

空の配列でエラー

配列の要素ひとつ以上あるいは初期値を与えないとエラーに。

[].reduce(() => {});
// TypeError: Reduce of empty array with no initial value

途中で追加された要素は呼ばれない

const arr = [11, 22, 33];
arr.reduce((_, value, index, original) => {
  if (index === 0) {
    original.push(99);
  }
  console.log(index, value);
}, 0);
// 0 11
// 1 22
// 2 33

console.log(arr);
// [ 11, 22, 33, 99 ]

途中で変更された要素は変更後の値が利用される

const arr = [11, 22, 33];
arr.reduce((_, value, index, original) => {
  if (index === 0) {
    original[1] = 99;
  }
  console.log(index, value);
}, 0);
// 0 11
// 1 99
// 2 33

途中で削除された要素は呼ばれない

const arr = [11, 22, 33];
arr.reduce((_, value, index, original) => {
  if (index === 0) {
    delete original[1];
  }
  console.log(index, value);
}, 0);
// 0 11
// 2 33

途中じゃなくても、削除されて空き枠になっていれば飛ばされます。

空き枠は飛ばされる

まず空き枠、プロパティとして存在しない要素はコールバックで呼ばれません。これは他の繰り返し系配列メソッドと同様。

加えて、初期値を省略した場合に得られる値も、空き枠を飛ばした最初の要素が呼ばれます。

const arr = [, 22, 33,, 55];

arr.reduce((acc, value, index) => {
  console.log(acc, value, index);
  return acc;
});
// 22 33 2
// 22 55 4

初回コールバック関数呼び出し時の第1引数 acc は普通 arr[0] になるんだけど、それが存在しないので、代わりに最初に出現する arr[1] の 22 になります。第2引数 value はもちろんその次 33 。

また 33 の次、 arr[3] もないので、それを飛ばして2回目のコールバック関数呼び出しは arr[4] の 55 が value に与えられます。

途中で終了できない

例外投げるとかは別だけど、 for の break みたいな機能はないです。

  1. Repeat, while k < len

(存在しないものを飛ばしながら)単に繰り返すだけ。

その他

逆に回す reduceRight()

てのもあります。

おしまい

ややこしいやつだけど、大変便利なのでおぼえておくと何かと役に立ちます。

ちょっと読みづらいのが難点。

参考