anti scroll

ブラウザと小説の新しい関係を模索する

サイズの指定されていない画像タグにサイズを付けるjQueryプラグイン

既にあるのかもしれませんが、だからといってどうやって検索したらいいかわからないものは自分で作るしかない…ということで表題のものを作りました。

tategakibunko/jquery.image-size-assign · GitHub

概要

どういうものかというと、ようするに以下のようなことをするものです。

<!-- before -->
<img src="http://placehold.it/350x150">

<!-- after -->
<img src="http://placehold.it/350x150" width="350" height="150">

使い方

こんな感じです。

$(function(){
  $("img").imageSizeAssign({
    maxSize:{
      width:500,
      height:500
    },
    onSize:function(size){
      return size;
    },
    onComplete:function(){
      //console.log("all sizes are set");
    }
  });
});

オプションにmaxSizeを指定すると、あふれた時に元サイズのレートを保ったままリサイズします。

例えば上の例だとmaxSizeが500x500なので、1000x1000の画像だったら、500x500にリサイズされます。

参考にしたソース

javascript - Can I sync up multiple image onload calls? - Stack Overflow

Nehan Reader 0.9.20をリリース

どんなページも、縦書きや横書きのページ送りで読めてしまうGoogle Chrome拡張「NehanReader」のversion0.9.20をリリースしました。

chrome.google.com

実際に青空文庫の作品を表示すると、こんな感じに。

f:id:convertical:20150307115543p:plain

Yahoo News!とかだと、こんな感じに。

f:id:convertical:20150307131435p:plain

f:id:convertical:20150307131448p:plain

修正点は以下のとおり

  • たまに同じ文字が繰り返し表示されてしまうケースを修正
  • これまでサイズの指定されてない画像を固定で小さく表示していたものを、事前にサイズを取得して表示するように変更

マイナーバージョンが20と、なんだかおかしなことになっていますが、まだ1.0.0を宣言する自信はないので、もうしばらく上がっていくと思います。

(追記)なお公開して一時間たらずで0.9.21がリリースされた模様。

「貼るだけ」のアクセス解析?

今回アクセス解析を実装するにあたって疑問に思ったのですが、よくある「貼るだけでOK」なアクセス解析って、サイト所有者の確認ステップがないっぽいのがありますが、これって大丈夫なんでしょうか。

例えば誰かが勝手に他人のサイトを先に登録したら、後から正当なサイト所有者が登録しようとしても「既に登録されています」とかで弾かれてしまうのでは。

仮に同じサイトを登録できる仕様にしたとしても、今度はサイトの所有者でもない人が同じサイトのURLを登録して、アクセスを覗ける仕様になっちゃいますよね。

だって、どっちが正当な所有者かを検証していないので、両方に見せるしかないじゃないですか。

かといって「先に登録した人に見せる」という仕様にしたとしても「先に登録した人」=「サイトの所有者」であるとは限らないわけで。

つまりですね、確かに「解析タグを貼ること」はサイトの所有者にしかできないけれども、それはサイトの所有者を確認するステップを省いていい理由にはならんのでは…ということです。

もちろんウェブマスターツールとかgoogle analyticsは、サイトの所有者であることが確認されないと使えませんよね。先日公開したNovelyticsもそうです。

それとも貼るだけで所有者を確認する方法があるのでしょうか。僕が知らないだけで。

もしそうだったら誰か教えて下さると嬉しいです。

「Novelytics」の紹介 〜なぜ小説専用のアクセス解析が必要なのか〜

小説専用のアクセス解析

Novelyticsという小説専用のアクセス解析サービスを作りました。

なぜこんなものを作ったのかについて書きます。

アクセスをベースにしたログの限界

アクセス数というのは、言い方を変えれば単なるクリック数です。

当たり前ですが、これは「読まれたこと」を意味しません。

ページ送りの所要時間や、ページに含まれる字数などを考慮に入れず、ただクリックされただけのログで読者の反応をイメージするのは、非常に難しいことです。

しかしアクセスログが最終ページを開いたログを残していたら、作者は「読了された」と勘違いしてしまうのではないでしょうか。

しかし読者がいかに飽きっぽく冷たいのかは、縦書き文庫の読者分析グラフを見るとよくわかります。

だいたいどんな人気作品でも、先頭を開いた人が10とすると、最後まで読むのは1ぐらい。そんなものなのです。

というわけで小説専用のアクセス解析として作ったのがNovelyticsです。

このサービスでは、アクセス数ではなく「ページ送り」でログを取ります。

飛ばし読みは記録されず、信頼性のあるページ送りだけが「ページ番号」「ページ位置」「ページの字数」「所要時間」と共に記録されます。

f:id:convertical:20150305130900p:plain

グラフも出るので、どの位置で離脱するのか、どの位置までは読まれたのか、などがわかります。

f:id:convertical:20150305130919p:plain

使い方

1. ユーザー登録

ユーザー登録は、グーグルアカウントで行います。

グーグルアカウントがあれば、数クリックで登録は完了します。

2. サイトの登録

最初にすることは、アクセス解析を貼り付けるサイトを登録することです。

サイトのタイトル(後で編集可能)と、トップページのURL(後から修正できない)を登録します。

URLを修正する場合は、サイトの登録をいったん削除して作り直すことになるので注意して下さい。

3. サイトの認証

登録したサイトは一度だけ認証が必要です。

サイト一覧から「認証する」を押して下さい。

認証は、サイトのトップページにて表示される<head>タグの中に、指定されたメタタグを貼り付けることで行います。

はてなブログなら

  1. ブログの管理画面から「設定」を選択。
  2. 設定の中から「詳細設定」タブを選択。
  3. 詳細設定の「headに要素を追加」に指定されたコードを貼り付ける。

という手順です。

面倒かもしれませんが、第三者によるなりすましを防ぐためには、どうしても必要な認証なのです…

4. アクセス解析用コードの貼り付け

ブログのテンプレートなどを編集して、「アクセス解析用コード」をブログの</body>の直前に貼り付けます。

編集領域として</body>の直前が編集できないなら「フッター」とか「サイドバー」とかでもOKです。ただし</body>の直前が(ページの表示を妨げないという意味で)ベストです。

アクセス解析用のコードは、メニューから「埋め込みコード」を選択し「アクセス解析用コード」にあるコードを使って下さい。

はてなブログなら「デザインの編集」から「フッター」を編集して、コードを貼り付けたらいいと思います。

(追記)ただし、はてなブログのモバイル用のページはPROアカウントじゃないとフッターが編集できないっぽいです。

5. 「本にして読む」ボタンの設置

これで準備は整いました。

あとはページ送りを記録したいブログ記事の先頭に「本にして読むボタン」のコードを貼り付けて終了です。

「本にして読むボタン」のコードは、メニューから「埋め込みコード」へと進んで取得して下さい。

ボタンの動作をカスタマイズしたい人は、ヘルプから「ボタンのカスタマイズ」を参照して下さい。

意外と色々な設定ができます。

6. 動作テスト

まずは自分でページ送りしてみて(ただし飛ばし読みは無視されるので、時間をかけてページを送る)、実際に記録されることを確認したら、設定ページから自分自身のIPを設定すると良いと思います。

最後に

こういうログサービスは、縦書き文庫においては2006年ぐらいから標準で提供している機能なのですが、一般化したら面白いかなあと思って作りました。

ドラッグ・アンド・ドロップで、テキストを縦書きページ送りで表示するサイト「Nehan Text Reader」

HTMLファイルやテキストファイルをドラッグ・アンド・ドロップすると、縦書きページ送りで表示してくれるサイト「Nehan Text Reader」を公開しました。

tb.antiscroll.com

画面上(どこでもいい)にテキストファイルをドラッグ・アンド・ドロップすると、ビューアーが表示されます。

文字化けする場合は「表示設定」の「文字コード」を変更して、もう一度ドロップしてください。

テキストファイルで執筆してる人は、原稿の確認とかするのに使えるかもしれません。

ちなみに表示設定から横書きにもできます。

実際に放り込むとこんな感じに。

f:id:convertical:20150211173437p:plain

テキストファイルであればなんでもよくて、htmlファイルを放り込めばアウトラインも出ます。

単純なマッチで複数回replaceするのと、文字クラスを使って一回replaceするのでは、どちらが速いか

qiitaでこんな記事がありました。

innerText(textContent)/innerHTMLを使わずJavaScriptでHTMLエスケープ - Qiita

で、思い出したのですが「文字クラスでreplaceを一度で済ますより、単純なマッチを直列で繰り返したほうが速い」って話しをどこかで聞いた覚えがあるので、どのぐらい差があるのか、ちょっと試してみました。

念のため、元記事の関数でテーブル参照をしないバージョン(escapeCharClassEx)も用意。

3パターンのescape関数

// 元記事の関数
var escapeCharClass = function(content) {
  var TABLE_FOR_ESCAPE_HTML = {
    "&": "&amp;",
    "\"": "&quot;",
    "<": "&lt;",
    ">": "&gt;"
  };
  return content.replace(/[&"<>]/g, function(match) {
    return TABLE_FOR_ESCAPE_HTML[match];
  });
};

// 念のためテーブル参照しないバージョン
var escapeCharClassEx = function(content) {
  return content.replace(/[&"<>]/g, function(match) {
    switch(match){
    case "&": return "&amp;";
    case "\"": return "&quot;";
    case "<": return "&lt;";
    case ">": return "&gt;";
    }
  });
};

// 4回replaceバージョン
var escapeSerial = function(content){
  return content
    .replace(/&/g, "&amp;")
    .replace(/"/g, "&quote;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;");
};

検証プログラム

以下の様な感じで、入力文字が短い場合と長い場合の双方で速度を比較してみました。

var replicateString = function(text, times){
  var ret = [];
  for(var i = 0; i < times; i++){
    ret.push(text);
  }
  return ret.join("\n");
};

var testEscapeSpeed = function(){
  var text = "hoge&hage>hige<hoge";
  var text_short = replicateString(text, 10);
  var text_long = replicateString(text_short, 100);
  var replace_many = function(text, replace_fn, times){
    times = times || 1000;
    for(var i = 0; i < times; i++){
      replace_fn(text);
    }
  };

  console.time("char class short");
  replace_many(text_short, escapeCharClass);
  console.timeEnd("char class short");

  console.time("char class short ex");
  replace_many(text_short, escapeCharClassEx);
  console.timeEnd("char class short ex");

  console.time("serial short");
  replace_many(text_short, escapeSerial);
  console.timeEnd("serial short");

  console.time("char class long");
  replace_many(text_long, escapeCharClass);
  console.timeEnd("char class long");

  console.time("char class long ex");
  replace_many(text_long, escapeCharClassEx);
  console.timeEnd("char class long ex");

  console.time("serial long");
  replace_many(text_long, escapeSerial);
  console.timeEnd("serial long");
};

検証結果

Chrome40で走らせた結果、以下のような結果になりました。

test name time
char class short 7.573ms
char class short ex 4.213ms
serial short 1.972ms
char class long 296.516ms
char class long ex 264.621ms
serial long 128.274ms

単純なマッチで4回replaceしたバージョン(escapeSerial)が、短い文字列では4倍程度、長い文字列だと2倍ちょっとぐらい速いという結果でした。

いや、ちょっとおかしい

文字クラスを使った場合の関数は、単にマッチ後のコールバック関数呼び出しのオーバヘッドが入るだけではあるまいか、ということでescapeSerialにおける各replace処理を関数で置換するように変えたら、今度は4回呼び出しのほうがコールバックの回数が増えたぶん、少しだけ遅かったです。

test name time
char class short 5.004ms
serial short 5.218ms
char class long 2559.319ms
serial long 3020.997ms

ということで、

  1. 文字クラスで複数文字を一回でマッチ&置換させても、それぞれの文字で一回ずつ置換しても速度は殆ど変わらない
  2. しかしそれぞれの置換でコールバック関数を呼ぶとパフォーマンスは二倍ぐらい遅くなる
  3. 各関数の呼び出しの中でテーブル参照すると更に遅くなる

みたいです。

たぶん1については、4×1と1×4程度の違いなんだと思います。

GADTというものを知った

最近OCamlをようやくversion4系列にアップデートしたのですが。

その際にGADTとかいう言葉が気になったので調べていたら、ちょうどわかりやすいエントリーが。

Detecting use-cases for GADTs in OCaml

上のエントリによると、どうやらGADTというのは「代数型に型の強制を付けるためのもの」らしいです。

どいういうことかというと、例えばとあるexprという代数型を

type expr = 
  | Tint of int
  | Tbool of bool

と宣言した時、exprは中身がintの時もあれば、boolの時もある型ですよ、と宣言しているわけですが…

つまりTint 10Tbool falseも、同じexpr型に属することになりますよ、としているわけですが…

このexpr型を使って構成される「別の代数型」というものを考えた時、単にexpr型とだけ宣言されると、表現としてイマイチになることがあるのですよね。

例えば次のようなAbstract Syntax Treeを定義したとき…

type ast = 
  | Value of expr
  | IfExpr of expr * expr * expr

なんかIfExprexpr * expr * exprの部分の表現力が乏しくないですか?

exprが3つ並んでるんですけど、それぞれがどういう性質のexprを要求しているのか、よくわからないですよね。

もしこのIfExpr

IfExpr of (bool値のexpr) * (int値のexpr) * (int値のexpr)

みたいなニュアンスで型を制限できたら、安全だし、わかりやすくないですか?

これを、まさに可能にするのがGADTというやつらしく。

ちなみにGADTというのは、Generalized Algebraic Data Typeの略で日本語なら「汎用代数型」とでも訳すのでしょうか?

実際にexprとastをそれぞれGADTを使った型宣言(expr'とast'とする)に書き直すと、こういう感じになります。

type _ expr' = 
  | Gint: int -> expr' 
  | Gbool: bool -> expr'

type _ ast' = 
  | GValue: int expr'
  | GIfExpr: bool expr' -> int expr' -> int expr' -> int expr'

普通の代数型だとofが来る部分に:が来て、右側が「型の関数」みたいな記述になるのが特徴です。

で、構成される代数型に型の制約を付けたい場合はtype _ expr' = ...みたいに宣言します。

アンダースコアの部分は「代数型に何かしらの型アノテーションがつくよ」みたいなニュアンスなんでしょうか?

こうやって作られたGADTを使うと、

let statement_by_gadt : ast' = GIfExpr (Gint 10) (Gint 20) (Gint 30)

は、IfExprの一つ目のexpr'が「bool expr'」じゃないので、コンパイルの段階でエラーになります。

一方で、GADTじゃない、普通の代数型のastを使った場合、次の

let statement_no_gadt : ast = IfExpr (Tint 10) (Tint 20) (Tint 30)

は(型の上では)エラーではありません。

IfExprを構成するメンバーは全てexpr型と規定されているだけなので、型の上では正しいからです。

これを構文エラーにするには、evalするときにundefned patternとして、型エラーを自前で書くしかないわけですが、GADTで制約していればコンパイルの段階でエラーにしてくれます。

nanocのほうがjekyllより良いかも

nanocとは

nanocRuby製の静的サイトジェネレーターです。

静的サイトジェネレーターとは、固定的な内容のウェブサイトを作るのに使われるサイトジェネレーターです。

ホームページ制作ツールのような複雑なIDEではなく、コマンドラインとエディタでサクっとサイトが作れるので、プログラマーには人気があります。

昨今はそうしたツールでブログを公開する人も珍しくなくなりました。

ちなみにこの記事ではnanocの使い方は説明しません。本家に丁寧な説明があるので、そちらを参照して下さい。

jekyllとの違い

Ruby製の静的サイトジェネレーターといえば、jekyllも有名ですが、最近はnanocを主に使っています。

理由は記事の変換処理を柔軟に扱えるからです。

jekyllでも記事を変換する場合に、独自の処理を設定することは出来ますが、変換エンジン「そのもの」の切り替えしかできません。

しかしnanocでは、変換処理をfilterという単位で分割定義できて、それらを記事の「メタデータ」に応じて自由に組み合わせて適用させることができます。

例えば「コードハイライトの前処理は、プログラミング系の記事にだけ適用する」といった具合にです。

サンプルケース

マークダウンをCSSフレームワークと合わせて使った場合、デフォルトの出力では困ることがありますよね。

例えばタグに、そのCSSフレームワークで使う特別なクラス属性を付けたかったりとか。

でもマークダウンで次のように書くと、

## これはH2

もちろん次のように変換されるわけですが、

<h2>これはH2</h2>

こういう風に出したい時もあるわけじゃないですか。

<h2 class="my-header">これはH2</h2>

こういう場合、そのまま生でタグを打ってもいいのですが、せっかくマークダウンを使っているのですから、楽をしたいものです。

というわけで、nanocのfilterを定義しちゃいましょう。

filterを定義する

要件定義として、次のように宣言したら目的のコードが出力される、としましょう。

[myheader2 これはH2]

要はこのコードをマークダウンにかける前に、正規表現で目的のコードに変換すればいいのです。

そこでlib/filters.rbなどというファイルを作り、次のような処理を書いて、my_headerという識別子のフィルタを定義します。

class MyHeader < Nanoc::Filter
  identifier :my_header
  type :text
  def run(content, params={})
    content.gsub(/\[myheader(\d)\s+(.+?)\]/, "<h\\1 class='my-header'>\\2</h\\1>")
  end
end

次にRulesというファイルで、今作ったmy_headerというfilterを、マークダウン変換の前に差し込みます。

compile '*' do
  if item[:extension] == 'md'
    filter :my_header # <- これを追加!
    filter :kramdown # マークダウン変換処理
    layout 'default'
  end
end

しかしこれだと、全てのマークダウンファイルにmy_headerフィルタを通してしまいますよね。

なので、itemにuse_my_syntaxというメタデータが設定されているときだけ適用させるようにしてみます。

compile '*' do
  if item[:extension] == 'md'
    if item[:use_my_syntax] then # 独自文法使う?
      filter :my_header # <- これを追加!
    end
    filter :kramdown # マークダウン変換
    layout 'default'
  end
end

比較としてjekyllの独自コンバーター定義の方法を抜粋しておきます。

class Jekyll::Converters::Markdown::MyCustomProcessor
  def initialize(config)
    require 'funky_markdown'
    @config = config

  def convert(content)
    ::FunkyMarkdown.new(content).convert
  end
end

上記のconvertという関数を拡張するわけですが、見たらわかるように引数として元記事のcontentしかもらえないわけです。

しかしnanocでは記事に付いたメタデータを見ながら処理を分岐できるので、これは大きなアドバンテージです。

ちなみに記事のメタデータを付ける方法は、記事の先頭に次のようなヘッダーを書くだけです。

---
use_my_syntax: true
---

ヘッダーはYAMLで記述します。

最後に

最後に、nanoc.ymlのpruneについて。

prune:
  auto_prune: true
  exclude: [ '.git', '.hg', '.svn', 'CVS', ".gitignore", "push.sh" ]

auto_pruneを有効にすると、使用されていないファイルを削除してくれます。

デフォルトでは有効になっていませんが、trueに設定することをおすすめします。

これを有効にしてないと、元記事のファイル名を変えた時に、古いファイルが出力先に残り続けてしまいます。

例えばあるときにtop.mdというファイルを、welcome.mdに変えたくなったとしましょう。

そのまま放置してしまうと、出力先にtop/index.htmlというファイルが残ってしまいます(a.mdはa/index.htmlに出力されるのがデフォルトのルール)。

しかしprunetrueにしておくとtop/index.htmlは自動で削除されます。