2017/05/25

[JavaScript]動的なテキストの幅を取得し、スタイルを適用する

動的に生成されるテキストを、その幅にあわせてスタイルを適用したいときがある。
しかし、文字数による判定では英語や日本語による違い、等幅フォントやプロポーショナルフォントによる違いを考慮しなければならない。
バイト数による判定も、同様の違いや文字コードによる違いがある。

「テキストが表示されたときの幅」を取得し、その「幅」で判定できれば、それが一番正確だろう。
ということで、動的に表示されるテキストの幅(width)を取得し、幅にあわせてスタイルを適用できるようにする。


テキストの幅を取得する


テキストの幅を取得する方法は、以下の手順で行う。

  1. span要素を作成し、テキストを設定する
  2. bodyなどに一時的にspan要素を追加する
  3. HTMLElement.offsetWidthで要素の幅を取得する
  4. bodyに追加したspan要素を削除する

このようにしてHTMLElement.offsetWidthを使うことで、テキストの幅を取得できる。


その際、注意すべき点は3つ。

1つ目は、spanタグのようなinline要素を使うこと。
pタグのようなblock要素を使うと、親要素の横幅いっぱいに広がってしまうため、正確な「テキストの幅」が取得できない。

2つ目は、offsetWidthにはborder、padding、スクロールバーの幅が含まれること。(marginは含まれない)
もしCSSで要素型セレクタ(タグ指定)しているのなら、追加するspan要素に{ border: none; padding: 0; overflow: hidden; }を付けておくとよい。

3つ目は、DOMに追加しないとoffsetWidthが取得できないこと。
メモリ上にある要素では幅が取得できないので、一旦bodyなどに追加する必要がある。
ただ、要素の削除に失敗するとテキストが残ってしまうので、 { visibility: hidden; }を使って対策する。
{ display: none; }だと要素が完全に消えてしまうoffsetWidthが取得できないので注意。



動的なテキスト幅を取得し、スタイルを適用する


幅75pxの要素にテキストを表示するサンプル。
条件は以下のとおり。

  • できる限り省略記号を使わず、文字間隔を詰めてすべて表示する
  • 文字間隔を調整しても収まらない場合には、省略記号を使って表示する



<ul class="list">
<!-- Sample
  <li class="item">
    <p class="title -compress -ellipsis">あいうえお</p>
  </li>
-->
</ul>

.list {
  display: flex;
  flex-wrap: wrap;
  padding: 10px;
}
.list > .item {
  margin: 10px;
}
.item {
  width: 75px;
  height: 30px;
  box-shadow: 0 1px 3px rgba(0,0,0,.3);
  background-color: white;
}
.item > .title {
  overflow: hidden;
  text-align: center;
  line-height: 30px;
}

/* 文字間隔の調整 */
.title.-compress {
  letter-spacing: -4px;
}
/* 省略記号の表示 */
.title.-ellipsis {
  white-space: nowrap;
  text-overflow: ellipsis;
}

// 表示するテキストを作成
const generateTitle = i => {
  const chars = ['あ', 'い', 'う', 'え', 'お'];
  let title = '';
  [...Array(i)].forEach((a, j) => title += chars[j % 5]);
  return title;
};

// テキストの幅を取得
const getTextWidth = text => {
  const span = document.createElement('span');
  span.innerText = text;
  span.style.visibility = 'hidden';
  document.body.appendChild(span);

  // offsetWidthができたらすぐに消す  
  setTimeout(() => {
    document.body.removeChild(span);
  }, 0);
  
  return span.offsetWidth || 0;
};

// メイン
const main = () => {
  const list = document.getElementsByClassName('list')[0];

  // 10回ループする
  [...Array(10)].forEach((a, i) => {
    const item = document.createElement('li');
    item.classList.add('item');

    const title = document.createElement('p');
    title.classList.add('title');

    const text = generateTitle(i + 1);
    const width = getTextWidth(text);

    if (80 <= width && width <= 96) {
      // 5〜6文字
      title.classList.add('-compress');
    } else if (96 < width) {
      // 7文字以上
      title.classList.add('-compress', '-ellipsis');
    }

    title.innerText = text;

    item.appendChild(title);
    list.appendChild(item);
  });
};

main();




参考サイト







以上

written by @bc_rikko

0 件のコメント :

コメントを投稿