MAGAZINE

ルーターマガジン

JavaScript/Node.js

HTML上の自動長体をjavascriptライブラリ非依存で実現する

2022.02.09
Pocket

7segOCR動画のOCRと、ここ最近頻繁にブログを書いている鈴木です。通算3記事目ですが冒頭で名乗ったのは初めてでした。こうも立て続けに文章を書いていると、段々と言葉を紡ぐのも滑らかにできるようになるもの。月並な話にはなりますが、このような文章を書く際は、まず体裁は気にせず、とりあえず書き切る、というのは大事なことで、レイアウトは後回しでいいどころか、内容が確定してからの方が検討しやすいものです。文章のみならず絵でも、多分曲とかも色々と。

あとは、こちらも凡庸ですが、常時アイデアを拾えるようにしておくこともやはり大事です。以上の序文の原案も、帰宅中の浅草橋-新小岩間でスマホにメモしたものだったりする訳で。

という自省はさておき、今回の内容は、私の担当した前2記事とは打って変わって、不幸にも冒頭で後回しにされた、レイアウトにまつわるお話です。ちなみに、特段IEなどのブラウザへの対応は考慮しません。

Contents

そもそも長体とは

長体とはこんな風に、

いろはにほへとちりぬるをわかよたれそつねならむうゐのおくやまけふこへてあさきゆめみしゑいもせす(ん)

いろはにほへとちりぬるをわかよたれそつねならむうゐのおくやまけふこへてあさきゆめみしゑいもせす(ん)

1行に収めたいが、文字数が多くて普通に書くと複数行に渡ってしまったり、white-space: nowrap;などとCSSを書こうものなら見切れてしまったりするテキストがある場合などに、以下のように

いろはにほへとちりぬるをわかよたれそつねならむうゐのおくやまけふこへてあさきゆめみしゑいもせす(ん)

文字を水平方向に押しつぶすことで無理矢理目的を達成する際の、縦長になった字体のことである。無理矢理とは言え、実際6〜7割狭めたところで普通に読めるし、むしろ少し縦長にした方がシュッとして見た目がきれいまである。これが自動で行われるのが「自動長体」であり、そうなるように実装されているサイト・アプリ自体はそれほど珍しくはないだろう。そしてそれを実現するコード自体もちらほら公開されているのだが、特にjQueryに依存するものが多く、そうしたライブラリに依存しないものが見つからない。そこでそれを作ろうということである。

余談だが、ミスリーディングなアイキャッチに反して、「長体」は英語で、"long body"ではなく"narrow"である。また、縦方向に押しつぶすのは「平体」(="wide")になる。

自動長体の基本実装

親要素の幅と長体対象要素の幅の取得

適切な長体を実現するには、「親要素の幅を見て、さらに長体の対象とする要素のテキストを改行しない場合の幅も見て、親要素に収まるような倍率で水平方向に圧縮する」というアプローチが自然である。ここで、長体の対象とする要素は、auto_narrowクラスに属するような仕様とする。

要素幅を取得するようなjsコードは巷に溢れているため、敢えて紹介するまでもないが、親要素については単純に、要素オブジェクトのclientWidthプロパティを見ることで取得する。一方でauto_narrow要素については、以下のような流れで取得する。

  1. テキストの改行を禁止する
  2. テキストオーバーフロー時に水平方向にスクロールするようにする
  3. scrollWidthプロパティからスクロールを考慮した幅を取得する
  4. スクロールバーを消すため、オーバーフロー時のスクロールを無効にする

取得した各要素の長さから圧縮率を計算し、それを基にtransformで長体をかければ良い。その際、変形の原点を左上に設定することを忘れないように。

表内セルにおける注意点

ところで、表内での自動長体の使用には少し注意が必要である。HTMLにおける表は、デフォルトではセル内部の要素に応じて幅を調整するように設定されているため、そのまま実装した自動長体を適用する(<td><div class="auto_narrow">ほげ</div><td>のようにする。)と、自動調整との兼ね合いで適切に長体をかけることができない。そこで、幅の自動調整を無効化するために、table-layout: fixedをtable要素に適用し、さらにwidth: 100%;などと幅を指定する必要がある。このようにすることで、中身に依らず、幅を指定した列はその幅に、その他は等幅になるように列幅が定まるようになり、初めて適切な長体が表示される。

作品タイトル
Title
Author
Year
ようこそ実力至上主義の教室へ
Classroom of the Elite
Syougo Kinugasa
2015
妹さえいればいい。
A Sister's All You Need
Yomi Hirasaka
2015
冴えない彼女の育てかた
How to Raise a Boring Girlfriend
Fumiaki Maruto
2012
かぐや様は告らせたい
Kaguya-sama Love is War
Aka Akasaka
2015
やはり俺の青春ラブコメはまちがっている
My youth romantic comedy is wrong AS I EXPECTED.
Wataru Watari
2011
青春ブタ野郎はバニーガール先輩の夢を見ない
Rascal Does Not Dream of Bunny Girl Senpai
Hajime Kamoshida
2014

やったね!これでこんな感じの表も見た目を崩すことなく作れるよ!

リサイズ時の調整

ここまでで基本的な実装は完了したが、このままだと親要素のサイズが変わったときにそれに応じた変形がされないままである。そこで最後に、親要素の変形をトリガーとして適切に圧縮率を変えるようにしていく。

要素のリサイズを検知するには、ResizeObserver APIを用いる。コードは次章にある通りだが、コールバックとして長体をかける関数を呼び出しておき、あとは各auto_narrowクラスオブジェクトの親要素を監視するようにしておけば良い。

↓ウィンドウ幅を狭めてみてください。↓

いろはにほへとちりぬるをわかよたれそつねならむうゐのおくやまけふこへてあさきゆめみしゑいもせす(ん)

実装

以下のコードを貼り付けるだけで実装可能である。この記事(WordPress)でも、デフォルトのCSSの一部を上書きする必要があるが、簡単に実装できた。


<script>
  function narrow(auto_narrow, parent){
    auto_narrow.style.overflow = "scroll";
    auto_narrow.style.whiteSpace = "nowrap";
    real_width = auto_narrow.scrollWidth;
    auto_narrow.style.overflow = "visible";

    ideal_width = parent.clientWidth;
    shrink_ratio = ideal_width / real_width;
    if(shrink_ratio < 1){
      auto_narrow.style.transformOrigin = 'top left';
      auto_narrow.style.transform = `scale(${shrink_ratio}, 1)`;
    }
    return;
  }

  auto_narrows = Array.from(document.getElementsByClassName("auto_narrow"));
  parents = auto_narrows.map(x => x.parentNode);

  // Initialize
  for(let i = 0; i < parents.length; ++i){
    narrow(auto_narrows[i], parents[i]);
  }

  // When parent size is changed.
  const auto_narrow_observer = new ResizeObserver((entries) => {
    entries.forEach(({target, _}) => {
      auto_narrows = target.getElementsByClassName("auto_narrow");
      for(let auto_narrow of auto_narrows){
        narrow(auto_narrow, target);
      }
    })
  })

  for(let auto_narrow of auto_narrows){
    auto_narrow_observer.observe(auto_narrow.parentNode);
  }
</script>

ちなみにアイキャッチはうちの犬である。「長体」ということで採用してみました。疑われることもあるけれど、柴犬なのです。

参考

Pocket

CONTACT

お問い合わせ・ご依頼はこちらから