本文へジャンプ

クラウドフォントサービスの「あとから追加した文字にWebフォントが適用されない問題」と向き合ってみた

Posted by MONSTER DIVE

こんにちは。はじめまして。新参MONSTERです。
Webの未来に心躍らせるオトメ(え?)です。コードを書くこととぼんやりすることが好きなマークアッパーです。JavaScript勉強中です。

早く先輩MONSTERたちのお役に立てるようになりたいと、日々勉強、日々修行、時々苦行、な毎日を送っております(概ね楽しいです)。ブログを書くのもはじめてなので、生温かく見守っていただければ幸いです。どうぞよろしくお願いいたします。

Webフォント使ってますか?

さて、皆さま。
突然ですが、Webフォント、使ってますか? 使ってますよね? いいですよね、Webフォント。

弊社ではモリサワの「TypeSquare」というクラウドフォントサービスをよく利用しています。
日本語フォントは文字がたくさんあるからWebフォントとして使用するには重い><…という心配がありますが、このサービスはHTMLとCSSを解析して、必要な文字だけをピックアップしサブセット化してくれるという優れものであります。

ただ、ここでひとつ問題が…。

自動サブセット化の落とし穴

ページ内で使用されている文字だけをピックアップしてくれる…ここに落とし穴があります。CSSのtext-transformプロパティでアルファベットの大文字・小文字を切り替えた場合や、JavaScriptを使ってテキストを書き換えた場合などに、Webフォントが適用されない!なんで!?…ってなことになってしまうのです。最初からHTML内に記述のない文字は、サブセットに含まれないからですね。

簡単なサンプルを用意しました。

  1. 「Monster Dive」というテキストをすべて大文字での表示に変えます(CSS)
  2. 本文の1行目に「来たれ!モンスター!!」というテキストをあとから挿入します(JavaScript)

HTML

<h1 class="title">Monster Dive</h1>
<div class="text" id="text">
    <p>職人的な気質でものづくりを楽しめる、オタクな感性の持ち主、募集中。</p>
</div>

CSS

/* 見出しを白抜きのフォントにしてみます + 大文字表示にします */
.title {
    font-family: "Shin Go Futoline", sans-serif;
    text-transform: uppercase;
    /* 省略 */
}
/* 本文をちょっと不思議なフォントにしてみます */
.text {
    font-family: "Cinema Letter", sans-serif;
    /* 省略 */
}

JavaScript

window.onload = function() {
    var txtBlock = document.getElementById('text');
    var addBlock = document.createElement('p');
    addBlock.innerHTML = '来たれ!モンスター!!';
    txtBlock.insertBefore(addBlock, txtBlock.firstChild);
};

さて、これをブラウザで表示させてみますと…【サンプル1

blog_151120_sample01.png

小文字だったアルファベットを text-transform: uppercase; で大文字にした部分と、JavaScriptであとから挿入したテキスト部分に、Webフォントが適用されていませんね。
あとから挿入したテキストの「タ」の文字だけWebフォントが適用されていますが、これは最初から記述のあった本文内に「タ」が存在していたので、サブセットに含まれていたためです。
注:Internet Explorer 10以前ではこのような結果になりません(実行タイミングの問題か、このサンプルコードだと、あとから挿入したテキストにもWebフォントが適用されてくれちゃいます)。

さて。この問題をどうやって回避しましょう…

(1) 隠しテキストを仕込む!

ひとつは、隠しテキストをHTML内に仕込んでおく、という方法です。
HTML内に最初から記述されていればサブセットに含まれるわけなので、CSSでWebフォントが適用されるように指定したテキストを隠し要素として置いておけばOK、ということです。
見出し部分のテキストで試してみましょう。

HTML

<!-- 隠し要素内に見出し部分の要素を複製して、テキストを大文字にしておきましょう -->
<div class="hidden_text" id="hidden_text">
    <h1 class="title">MONSTER DIVE</h1>
</div>

CSS

/* 隠しテキストは隠しておきましょう.. */
.hidden_text {
    display: none;
}

これをブラウザで表示させてみますと…【サンプル2

blog_151120_sample02.png

「MONSTER DIVE」という大文字のテキストを隠し要素として仕込んでおいたので、無事に見出し部分がすべて白抜き文字のWebフォントになりました!めでたしめでたし!
…とは言え、隠しテキスト仕込むって、なんだか気持ち悪いですよね。(最初からぜんぶ大文字にしとけばいいじゃん、というツッコミはなしでお願いいたします。)

(2) TypeSquareのAPIを使う! 基本編

そしてもうひとつ、TypeSquareのAPIを使用する、という方法があります(セルフホスティングプランの場合を除きます)。
「リロード」という拡張機能を使うと、今回のようにJavaScriptであとから追加したり書き換えたりしたテキストにも、Webフォントを適用させることができます。
JavaScriptでテキストを追加する処理を書いたあとに、次の1行を追加してみます。

Ts.reload();

これをブラウザで表示させてみますと…【サンプル3

blog_151120_sample03.png

見事、JavaScriptであとから挿入したテキスト部分にも、Webフォントが適用されるようになりました。
おそらく、HTMLとCSSを解析し直して、Webフォント用のCSSを追加で書き出す仕組みになっているのだと思われます。

(3) TypeSquareのAPIを使う! 応用編

「ダイナミックリクエストCSS」という拡張機能もありまして、これを利用すれば、新しく追加したテキストに、新たに別のWebフォントを指定して適用させることもできます。試しに、あとから挿入するテキストにちょっと不気味なフォントを適用させてみましょう。

HTML

<!-- head要素内に空のstyle要素を追加しておきましょう -->
<style id="dynamic_css"></style>

JavaScript

window.onload = function() {
    var txtBlock = document.getElementById('text');
    var addBlock = document.createElement('p');
    var newFontText = '来たれ!モンスター!!';
    addBlock.innerHTML = newFontText;

    // ここからがTypeSquareのAPIを使ってる部分です
    callback = function(res) {
        var css = document.getElementById('dynamic_css');
        if (res.type === 'css') {
            css.textContent = res.data;
        }
    }
    dynamicRequest = function() {
        addBlock.style.fontFamily = 'monster_font';
        Ts.dynamicCss(callback, newFontText, 'Kointai-M', 'monster_font', '', '');
    }
    dynamicRequest();
    // ここまでです

    txtBlock.insertBefore(addBlock, txtBlock.firstChild);
};

これをブラウザで表示させてみますと…【サンプル4

blog_151120_sample04.png

はい。不気味なフォントを適用させることができました。
ただ、処理に少し時間がかかるのか、一瞬フォント適用前の状態で表示されたあとに、パッと切り替わるのが見えてしまいます。テキスト挿入処理をcallback関数の中に書けば、フォント適用前の状態が見えることはないですが、ページロード後ちょっと時間をおいてから挿入されることになるので、今度は本文がガクッと変化するのが見えてしまいます(それで大丈夫な場合には問題なく使える手だと思います!)。

とはいえ…

いずれにしても、対象の文字数・要素数があまりにも多い場合や処理が頻出する場合などは、ちょっと対応が難しいかもしれませんね。隠しテキストはなんだか気持ち悪いし、APIを使うのは処理速度が気になるし…といった感じで、場合によってはWebフォントの使用を諦める(デバイスフォントを利用する)、という手も考えなければいけないかもしれません。ぐぬぬです。

個人的な意見ですが、せめてアルファベットくらいは、それほど文字数も多くないので、大文字・小文字を合わせてサブセット化してもらえたら嬉しいなぁ〜(もしくは、text-transformプロパティの値も見てサブセット化してもらえたら嬉しいなぁ〜)なんて思ったりします。

モリサワ様、いかがでしょうか?(なんて、さり気なくご提案してみたり)

Recent Entries
MD EVENT REPORT
What's Hot?