みなさまこんにちは!
Advent Calendar 4日目ということで、今回Webに関するネタをご紹介します。

 

フロントエンド実装のときに、枠内にメッセージを収めるためにみなさまはどうしていますか?

完全にハードコーディングであれば文字が枠に収まるように調整すればいいですが、
最近は動的にデータを取得することが当たり前となり、それに応じていろいろ調整しなければなりません。

しかし、レイアウトやデザインの都合上、枠のサイズを変えたくない・・・・・・
そのために枠のサイズを固定にする場合も多いと思います。

短文であれば、このようにおさまりますね!

 

しかし、長文だった場合・・・・・・

 

このようにはみだしてしまいますね!
例になっている私のお寿司?への思いも、これでは正しく伝えることができませんね(?)

こうならないようにどうすべきでしょうか?

 

1.ぶった切る

overflow: hidden;
文字に対するスタイルを上記のように指定すると「ぶった切る」ことができます。

このようにはみ出した文については隠れました!

ですが、これでは文が途中なのか、あえて途中で切ったのかわからず、イメージ的にもあまりよろしくない・・・

 

2.美しくぶったぎる省略する

white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;

文字に対するスタイルを上記のように指定すると、1行に収まらなかった分については、最後に…がついて省略されます。


・・・・最後に力尽きた・・・みたいですね(?)

 

1行だけでなくて最終行だけ省略したい、という場合は、
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 5;

とすると、5行目が省略されます。

フォントサイズ的に5行目が最終行だということがわかれば、この設定でいいかもしれませんね。

 

3.文字数に合わせてフォントサイズを自動調整する

「省略せずに全部表示したい・・・・・でもレイアウトは変えたくない・・・・」
お寿司ネタでなくても、こんなリクエストがでたら・・・・・どうしましょう?
私のお寿司?への思いも上記の手法ですべて伝わったのか・・・・・答えはNOですね?

これはCSSではできないので、JavaScriptを使います!

この状況を改めて見てみると、
「【文字で構成される領域の高さ】が【枠の高さ】を上回っている」
という状態ですね。

これをJavaScriptのコードで表現しましょう。
const textElem = document.getElementById("soto");
これで枠、およびその中にあるテキストのDOMを取得します。
わかりやすいように、textElemという変数に代入しておきましょう。

【枠の高さ】

この部分は、
textElem.getBoundingClientRect().height
で表現できます。

 

【文字で構成される領域の高さ】

これは
textElem.scrollHeight
で表現できます。

目的を達成するために、
「【文字で構成される領域の高さ】が【枠の高さ】と同一、または下回る状態」を作る必要があります。
つまり、コードだと
textElem.scrollHeight <= textElem.getBoundingClientRect().height
ですね!

なので、これがまだ実現していない状態のときには、ひたすら文字を小さくする、という処理を行います。

// for文
for (
  let size = 30;
  textElem.scrollHeight > textElem.getBoundingClientRect().height && size > 1;
  size--
) {
  textElem.style.fontSize = size + "px";
}

size = 30は初期値です。今回デフォルトで設定している値が30なのでこの数値にしていますが、必要に応じて変えてください。
textElem.scrollHeight > textElem.getBoundingClientRect().heightのときは今回の目的を満たしていない状態です。

つまり、上記の構文は
「【文字で構成される領域の高さ】が【枠の高さ】を上回っている」限り、サイズの変更をし続けよ
という命令になります。

目的を満たしていなければ満たすまで処理をせよ、ということです。

しかしこれだけだと、sizeがマイナスになったときも動き続け、文字が小さくなるどころかなくなってしまうので、ここは
&& size > 1
(かつ、sizeが1より大きい)
をfor文が繰り返される条件に追加し、フォントサイズが1になったときにストップする処理を付け加えます。
(1pxはもうミクロサイズだと思いますが・・・・・・・・・)
size--は、for文を繰り返すたびにsizeを1減らして、という意味です。

あとは、textElem.style.fontSizeに数値を代入すればいいですが、
単位の”px”をお忘れなきよう!

この処理を、
・文章の選択ボタンを押した時
・ウインドウのリサイズで枠の幅に変化があったとき
に実行するようにしたいので、
上記イベントがあったときに実行される関数に上記の処理を書いておけばOKです!(後述のサンプルコードではresize()という関数にまとめてあるのでそれを記述しました。)

試しに、サイズを狭めていってみます!

28px→22px→17pxという流れできちんと調整されていますね!

今回、ご紹介したコードのすべてを貼り付けておきます。
これでHTMLファイルを作成し、開いていただければ遊ぶことができますのでぜひどうぞ!

<html>
  <body>
    <div class="button" onclick="selectShort()">短文</div>
    <div class="button" onclick="selectLong()">長文</div>
    <div id="soto"></div>
  </body>
  <script>
  const shortText = "お寿司食べたい?";
  const longText = "私が好きなお寿司のネタとして、サーモン、本マグロ、はまちが挙げられます。いずれにしても脂がのっていてとても気に入っています。そういえばお寿司屋さんといえば弊社から歩いても行けるところに、私がよく行くお店があるのですが、1000円以下から食べられる「回らないお寿司」でとてもおいしいです。私一人でも、同僚とのランチでも行きます。";
  const textElem = document.getElementById("soto");

  function selectShort() {
    textElem.innerText = shortText;
    resize();
  }

  function selectLong() {
    textElem.innerText = longText;
    resize();
  }

  function resize() {
    /* 文字数が少なくなったときのため、フォントサイズを戻せるようにします。
    他にstyleの属性があればfont-sizeに関するところを除いてstyleに上書きしましょう。
    今回はないのでstyle属性ごと削除します。*/
    textElem.removeAttribute('style');
    console.log(textElem.getBoundingClientRect().height , textElem.scrollHeight);
    for (
      let size = 30;
      textElem.getBoundingClientRect().height < textElem.scrollHeight && size > 10;
      size -= 3
      /* 文字がはみ出すサイズが存在していたので、1ずつ減らすのを3ずつ減らすという少し速いペースでフォントサイズを小さくしてみました。
      こちらには正解不正解はなく、場合によって調整して遊んでみてください。*/
    ) {
      textElem.style.fontSize = size + "px";
      // textElem.setAttribute("style", `font-size: ${size}px`); // こちらも可能
    }
  }

  //ブラウザを開いたとき
  selectShort();

  //ウインドウサイズを変えたとき
  window.addEventListener('resize', () => {
    resize();
  });

  </script>
  <style>
  .button {
    width: 100px;
    margin: 8px;
    padding: 8px;
    font-size: 24px;
    background-color: #ff8900;
  }

  #soto {
    width: 90%;
    height: 216px;
    font-size: 30px;
    border: 3px solid #ff8900;
    padding:8px;

    /*
    1行で省略するとき
    */
    /*
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    */

    /*
    複数行で省略する時 
    */
    /*
    overflow: hidden;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 5;
    */
  }
  </style>
</html>

 

最後に

みなさま、いかがでしたでしょうか?
今回は文字数と領域に応じてフォントサイズを調整する方法をご紹介しましたが、
今後動的にテキストを表示するようなサービスやシステムで、このような実装をすべてする、となると大変ですよね。

この記事の意味がもしかするとなくなるかもしれませんが、
今回の問題に辿り着く前に、やはり文字数の制限はある程度決めておいたほうがいいと個人的には思います。
このあたりを制限することにより、レイアウトの設計もしやすくなります。
制限を設けるということはマイナスな面もありますが、他にすべきこと、タスクの量も制限によってマイナスにすることができます。
逆に制限がないということはキリがない、ということであり、レイアウトなどに関する議論やパターン、そしてそれに伴うタスクにもキリがないということになります。
制限を設けることにより、システム開発がしやすくなるのです。
サービスを求める人々、クライアントのニーズに応じて、事前にうまく設定していくことが必要ですね。

最後までお読みいただき、ありがとうございました!