Quantcast
Channel: CSSタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8771

内容に応じてサイズが可変する を素敵に実装する

$
0
0

概要

内容に応じてサイズが可変する textareaを、できるだけ手間をかけず、スマートな実装を試みます。
しかも、ネイティブのフォームが持っている利点をそのまま活かして、堅牢でアクセシブルな設計を目指します。

標準 textareaの難点

HTML の textarea要素は基本的に高さが固定されていて使い勝手が悪いです。3行分くらいしか領域がなくて、長い文章を打つのがとにかく苦痛なんていうこともザラです。

最近のブラウザ実装では、多少気を利かせてくれているのか、テキストエリアの領域をドラッグで拡大・縮小できます。

textarea をマウスドラッグでリサイズしているところの動画。

ただ私は思うのです。めんどくさいし、最初っから、入力するテキスト量に応じて自動的に伸び縮みしてくれればいいのに……と。スクロールバーなんて、1ページにひとつあればじゅうぶんなんですよ。

実装方法

難しいことはありませんが、HTML と CSS、JS が協調して動作します。

HTML

<labelfor="FlexTextarea">伸縮するテキストエリア</label><divclass="FlexTextarea"><divclass="FlexTextarea__dummy"aria-hidden="true"></div><textareaid="FlexTextarea"class="FlexTextarea__textarea"></textarea></div>

CSS

.FlexTextarea{position:relative;font-size:1rem;line-height:1.8;}.FlexTextarea__dummy{overflow:hidden;visibility:hidden;box-sizing:border-box;padding:5px15px;min-height:120px;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;border:1pxsolid;}.FlexTextarea__textarea{position:absolute;top:0;left:0;display:block;overflow:hidden;box-sizing:border-box;padding:5px15px;width:100%;height:100%;background-color:transparent;border:1pxsolid#b6c3c6;border-radius:4px;color:inherit;font:inherit;letter-spacing:inherit;resize:none;}.FlexTextarea__textarea:focus{box-shadow:0004pxrgba(35,167,195,0.3);outline:0;}

JavaScript

functionflexTextarea(el){constdummy=el.querySelector('.FlexTextarea__dummy')el.querySelector('.FlexTextarea__textarea').addEventListener('input',e=>{dummy.textContent=e.target.value+'\u200b'})}document.querySelectorAll('.FlexTextarea').forEach(flexTextarea)

コードの大半は CSS ですし、JavaScript もなんかしてる? ってくらい簡素なものです。ですがしっかり動きます。

動作サンプル

サイズ可変の textarea の動作サンプル(CodePen)

解説

コードこそ簡素なものですが、とりあげて解説したい箇所はたくさんあります。

動作原理の概要

ざっくりと説明します。textarea要素は親要素の大きさに追随して大きさが決まるようになっています。position: absoluteで絶対配置をし、同時に widthheight100%を指定することで、親要素の大きさと同一にしています。

.Textarea__textarea{position:absolute;top:0;left:0;width:100%;height:100%;}

そして親要素がテキストの量に応じた大きさになるように、入力されたテキストを .Textarea__dummyにそのまま流し込みます。

textarea.addEventListener('input',e=>{dummy.textContent=e.target.value})

すると、高さを指定していない .Textarea__dummyはテキスト量に応じて自動的に大きさが決まり、連動して textareaの大きさも決まるという按配です。

textarea がテキスト内容に応じて拡大している様子。

原理解説用の 3D 表示の FlexTextarea

基本はこれだけです。続いて、細かい部分を解説していきます。

最小・最大の高さを指定する

何もテキストが入力されていないときの最小の高さと、たくさん入力されたときの最大の高さを設定できます。設定しないことも可能で、min-heightを指定しなければ1行分の高さになり、max-heightを指定しなければ、内容に応じてどこまででも大きくなります。個人的には min-heightの指定のみで良いだろうと思います。

.FlexTextarea__dummy{min-height:120px;max-height:480px;}

入力された改行を改行として表示する

テキストエリアの値をそのまま .Textarea__dummyに流し込むと、改行が半角スペースに変化してしまいます。これは DOM において、改行文字はホワイトスペースとして等価に扱われてしまうためです。とても有名な話ですが、HTML で改行をするには <br>タグを使う必要があります。

ですが、改行文字を改行として反映するための CSS プロパティがあります。

.FlexTextarea__dummy{white-space:pre-wrap;}

こうしておけば、次のような単純なテキスト代入でも、改行文字が改行として表示されるようになります。

textarea.addEventListener('input',e=>{dummy.textContent=e.target.value})

「改行文字を <br>に文字列置換して、innerHTMLに代入する」ようなアプローチは、セキュリティ脆弱性を孕みやすいので、ぜったいにやめてね!!

textareadummyの細かな挙動を統一する

ここが最も難所です。

テキストエリアに設定される大きさは、完全に dummyの表示に依存しています。そのため、「何文字で改行するか」「改行ごとにどれくらい大きさが変わるか」「禁則処理ルール」などの点を同一にしていかなくてはいけません。条件を同一にするためのファクターは以下のとおりです。

  1. フォント
  2. 文字サイズ
  3. 行の高さ
  4. 文字間隔
  5. 罫線の幅
  6. padding の大きさ
  7. 改行できない文字が連続したときの取扱い

これらを解決するために、以下のコードが必要になります。

.FlexTextarea__dummy{box-sizing:border-box;padding:5px15px;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;border:1pxsolid;}.FlexTextarea__textarea{box-sizing:border-box;padding:5px15px;border:1pxsolid#b6c3c6;font:inherit;letter-spacing:inherit;}

テキストエリアにはたいてい、borderpaddingの設定が必要になるでしょう。それにあわせて dummyにも同じ大きさで borderpaddingを指定しています。また、ボックスの大きさ計算の基準をそろえるために box-sizing: border-boxも必要です。

文字周りをそろえるために、font: inherit, letter-spacing: inheritを指定します。これで、フォント、ウエイト、行の高さが揃います。letter-spacing: inheritも必要です。

テキストエリアは「aaaaaaaaaaaa……」と入力されたとしても、矩形を突き出ず、必ず改行される仕様になっています。dummyもそれにあわせるため、 overflow-wrapと、後方互換のための word-wrapプロパティも設定します。

手動リサイズは行わないようにする

テキストエリアが自動的にリサイズされるようになったので、右下に表示されているリサイズハンドルは不要になりました。

.FlexTextarea__textarea{resize:none;}

フォーカスリングを表示する

伸縮可能かどうかに関係はありませんが、テキストエリアがフォーカスを受け取ったときに、ちゃんとそれとわかるインジケーターを表示してあげましょう。たいていはデフォルトの outlineで良いでしょう。余裕があれば独自のインジケーターを表示させるとカッコいいでしょう。

.FlexTextarea__textarea:focus{box-shadow:0004pxrgba(35,167,195,0.3);outline:none;/* box-shadow を代わりに使っているから outline は不要 */}

スクリーンリーダー等の支援技術に配慮する

.FlexTextarea__dummyには、テキストエリアに入力された文字がそのまま流し込まれています。同要素には visibility: hiddenプロパティが設定されているとはいえ、念のため HTML にも、aria-hidden属性を使って、この要素がコンテンツとは無関係であることを示しておきましょう。

<divclass="FlexTextarea__dummy"aria-hidden="true"></div>

完成

このようにして出来上がった FlexTextarea コンポーネントを皆さんお使いください。


Viewing all articles
Browse latest Browse all 8771

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>