僕のイメージ(というか願望)としてはUIでComponentというからには下記ができて複数プロジェクトで使い回せてほしい。

  • コメント投稿
  • コメント編集
  • コメント削除

Image from Gyazo

昨今のWebアプリのUIに対しての要求は上がっているので

「今どきページ遷移しないで投稿・編集・削除したい」

という意見は最もだと思う。

vue.jsでいつも作ってるけど、いつもは以前のプロジェクトからcomments.vueなどのファイル一式(下記のようなやつ)をコピーしてきて編集する。

comments component

そこそこ工数かかる。こんなに編集とテストが必要ではComponentとは言えない(ように思う)。

外部からAPIのURLを渡すぐらいで上記が全部できるようになりたい。comments componentに関してはnpmでインストールしたい。

今のやり方ではAPIが違うだけでかなり変更が入る。APIやcommentが付く対象(postとか)が変わることに対応しようとすると、comment取得やコメント更新のXHR処理自体を外から注入する必要が出てくる。使いまわしたいJSコードの大半がそこなので、それを毎回書いて外から注入しなきゃいけないのでは全然楽になってない。

「今どきテキストエリアはMarkdownで書きたい」

という要求に対しては下記のようにnpmにできたので気軽に

「いいっすよ」

と言えるようになった。

textareaを画像アップ可能なmarkdown editorにするnpmモジュール - komagataのブログ

しかしコメント機能に関しては、

「できますが・・・ちょっとだけかかるかも・・・しれませんねぇ・・・」

という状態。vueだろうがreactだろうが同じだと思う。

何かいい方法は無いものかなぁ。

こちらについて、@tricknotesさんからズバリな回答をいただきました。

動的に増える要素へのイベント設定 | komagataのブログ

https://gyazo.com/aeb80b0b5ead1d5f03500d65ce5e165b

<ul>
  <li>foo</li>
  <li>bar</li>
</ul>

上記のようなHTMLでliが動的に増える場合、イベントのbubblingを利用して親の要素にイベントを設定しておいて下記のように捕まえればいいそうです。

const ul = document.querySelector("ul");
ul.addEventListener("click", (event) => {
  if (event.target.tagName === "LI") {
    console.log("FOO↑↑↑");
  }
});

これなら処理速度も安心ですね。

こういうやり方はEvent Delegationって呼ばれてるそうです。

スッキリしました!

東アジアの文字幅を取るeastasianwidthを以前npmに上げましたが、やっぱり怖話で使えそうなので文字列の幅を数字で取るメソッドを追加しました。(半角は1、全角は2みたいに)

javascriptで東アジアの文字幅を取得する - komagata

Ambiguous(曖昧)も含めて東アジアの文字列を考慮した感じで取ります。色んな環境で画面が崩れるようなのでもちゃんと取れるはず。

なんでこんなのが必要なのかというと、要は文字の折り返しを自前で実装するときに、monospaceのフォントで全角分幅を取るのか、半角分幅を取るのかを厳密にわかる必要があるからです。

komagata/eastasianwidth · GitHub

npmで作りましたが結局railsで使うのでgemも作りました。

komagata/eastasianwidth-rails · GitHub

スマホのonclick遅い問題(代わりにtouchstart使う)用のライブラリ、fastclickが定番っぽいので勝手にfastclick-rails作っときました。デフォルトでこうなってほしい。

komagata/fastclick-rails · GitHub

使い方

# Gemfile:
gem 'fastclick-rails'
// app/assets/javascripts/application.js
// require fastclick
# app/assets/javascripts/foo.js.coffee
$ ->
  new FastClick(document.body)

これでスマホの時は勝手にtouchstartになってくれます。300ms違うから体感的にもかなり違う。

怖話でjsで文字幅を知る必要があったところ、こちらで神コード発見しました。

東アジアの文字幅 (East Asian Width) の判定 - 中途

コメント欄でお伺いを立ててますが、昔の記事なのでご覧になってないかも。余りにも神だったのでnpmのパッケージにしました。

東アジアの文字幅って何?とかは長くなるので下記参照。

東アジアの文字幅 - Wikipedia

類似のに比べてコードがコンパクト、そしてcharCodeAtではデフォルト対応してないサロゲートペアに対応しているのが素敵です。勝手にnpmにすんな!と言われたら直ぐ消します。すみません。

komagata/eastasianwidth · GitHub

結局、consoleとは違って等幅フォントにしても全角は半角の2倍の幅というわけじゃないのでWebでの用途(怖話)には使えませんでしたが・・・。

Install

$ npm install eastasianwidth

Usage

var eastasianwidth = require('eastasianwidth');
console.log(eastasianwidth.eastAsianWidth('₩')) // 'F'
console.log(eastasianwidth.eastAsianWidth('。')) // 'H'
console.log(eastasianwidth.eastAsianWidth('뀀')) // 'W'
console.log(eastasianwidth.eastAsianWidth('a')) // 'Na'
console.log(eastasianwidth.eastAsianWidth('①')) // 'A'
console.log(eastasianwidth.eastAsianWidth('ف')) // 'N'
  • konacha: mochaをrailsで良い感じにするやつ
  • mocha: jsをrspecっぽくテストできるやつ(like a jasmine)
  • chai: mochaのassertionライブラリ
$ brew install qt
# Gemfile:
group :development, :test do
  gem 'capybara-webkit'
  gem 'konacha'
end
# config/initializers/konacha.rb:
Konacha.configure do |config|
  config.spec_dir = 'spec/javascripts'
  config.driver = :webkit
  config.stylesheets = %w(application)
end if defined?(Konacha)
# app/assets/javascripts/foo.js.coffee:
class Foo
  bar: ->
    'bar'
# spec/javascripts/foo_spec.js.coffee:
#= require foo
  
describe 'Foo', ->
  it '#bar', ->
    (new Foo).bar().should.equal('bar')
% rake konacha:run
F

  Failed: Foo #bar
    ReferenceError: Can't find variable: Foo

Finished in 0.00 seconds
1 examples, 1 failed, 0 pending

そりゃそうだ。だってcoffeeだと

(function() {
  var Foo;
  Foo = (function() {
    function Foo() {}
    Foo.prototype.bar = function() {
      return 'bar';
    };
    return Foo;
  })();
}).call(this);

こうやってグローバグ汚さないように囲われてるんだから。だからといって元々デフォルトOFFだった--bareオプションは今はデフォルトONになってるのでわざわざ外すもの気持ち悪い。

だったら

class @Foo

って書けばいいじゃん。って言ってるんだけどマジで?みんなどうやってるの?

sprockets-commonjsが標準で入ってファイル名にmoduleが必要じゃなきゃいいんだけどなあ。

ご存知の通りスマホのHTML5 Auidoは現状酷い状態です。

全プラットフォーム共通で再生できるフォーマットが無かったり、iPhoneは独自仕様で自動再生はできず、ユーザー操作由来のアクションからしか再生できなかったり、複数音が再生できなかったり、Androidは2.2までは全然動かなかったり。

対応策として知られているのがCSS SpriteならぬAudio Sprite。複数の音楽ファイルをつなげて一つのファイルにし、シーク位置を制御することで好きな音を鳴らしたりループしたりする姑息なテクです。

そんな面倒なことやってられるかと思ってたんですが、そろそろAudio Sprite用のツールも揃って来ました。

jukebox

zynga/jukebox

ひとつはAudio Sprite用の再生ライブラリjukebox。Zyngaが作ってるのでちょっとマジっぽいです。

var settings = { 
  spritemap: { 
    bgmusic: { 
      start: 0.00,
      end:  20.00,
      loop: true
    },
    shoot: { 
      start: 21.00,
      end: 22.50
    },
    kaching: { 
      start: 24.00,
      end: 24.75
    } 
  } 
};

var player = new jukebox.Player(settings);
player.play('bgmusic'); // faster, sprite entry's id
player.play(12.34); // slower, will search for matching sprite entry

一つにまとまった音楽ファイルと、それぞれの曲のstart位置、end位置、loopの有無の情報を元に簡単に再生できます。勿論AndroidでのFlashへのFallbackもやってくれます。

audiosprite

tonistiigi/audiosprite

もう一つが複数の音楽ファイルを一つにまとめる面倒な作業をやってくれるaudiospriteというnodeのライブラリ。

中身はffmpegのラッパーで、ファイルを全部をくっつけた上に各種プラットフォームをカバーするための複数種類のフォーマットで音楽ファイルを作成してくれます。更にjukeboxで使うための上記で言うsettingsにあたるデータをjsonで出力してくれるという気の効きよう。

iPhoneで一番パフォーマンスが出るCore Audio Format(caf)まで作ってくれるところが泣けます。

% audiosprite --help
info: Usage: audiosprite [options] file1.mp3 file2.mp3 *.wav
info: Options:
  --output, -o      Name for the output file.                             [default: "output"]
  --log, -l         Log level (debug, info, notice, warning, error).      [default: "info"]
  --autoplay, -a    Autoplay sprite name                                  [default: null]
  --silence, -s     Add special "silence" track with specified duration.  [default: 0]
  --samplerate, -r  Sample rate.                                          [default: 44100]
  --channels, -c    Number of channels (1=mono, 2=stereo).                [default: 1]
  --help, -h        Show this help message.

実際試したところ、ちょろっと音を出したい用途には使えそうです。しかし、スマホでネット上からダウンロードして鳴らすにはこのままでは駄目で、大きなファイルになった場合の動作が不安定でした。ローカルに何とかキャッシュする方法を探れば行けるかもしれませんが、僕が試したかった怖話のような大容量の音楽ファイルを鳴らす用途には難しそうです。

怖話でAndroidだけはカクカクして遅いので文字が位置文字ずつ出るのではなく、一行一気に出るように変更しました。

Androidというか、Androidのデフォルトブラウザー(MobileSafari?)だけで遅く、Android版FirefoxやChrome Betaでは速い。iPhoneは言わずもがな速い。

最小限のサンプルを作ってみたところ、Javascriptというより、DOMをいじった時に発生するReflowやRepaintが遅い。CSSでshadowやmarginなどによってReflowが起こる時だけで遅い訳でなく、Repaintも遅いっぽい。

Canvasで文字を扱うのは対応状況がまだ厳しい。

なんか手っ取り早い対策があればいいのだけど…。

怖話では怖い話をサウンドノベル風に表示するのに色々端折ると下記のようなJavascriptを実行しています。

var text = ["こ", "わ", "い", "は", "な", "し"];
setInterval(function(){
  line.innerHTML += text.shift()
}, 80);

これが、PCやiPhone(3GSとかでも)ならばいいんですが、Androidのブラウザーだと僕の持っているGalaxy Nexusでもかなりカクカクします。

innerHTMLが遅いっていうのはよく見るんですが、全部つないで最後に表示というものではなく、実際に一文字、一文字増えていくサウンドノベルみたいな表示をするときに他の方法がイマイチわかりません。何か詳しい人だったら一発で速くなりそうな気もするんですがなんとも…。

iOS Simulator - iPhone / iOS 4.3 (8F192)

Write below If you want to hide address bar on iPhone or Android.

window.scrollTo(0, 1)