今どきの入力フォームはこう書け!「select編」

2021年5月29日

テクノロジー 特集

webエンジニアの味方、弓削田です。 入力フォームの書く時の鉄板方法をお伝えしているこのブログ企画、今回で第3回目になります。 ラジオボタン、チェックボックスは、少し難易度が高かったんですが、 今回やるselectタグは、難易度はさほど高くないので、これも適度に利用してもらえればいいかと思いますが、 このやり方を覚えてから僕は毎回やるようにしています。 そもそも、selectタグは、スマートフォンでは、何が問題なのかというと、 ある程度まではデザイン対応はできるんですが、
・文字がセンタリングできない ・スマホブラウザがどうしてもクセが強い
こんな理由で、もう少し普通のエレメントのように扱いたいし、CSSもセットしたいという意見も良く聞きます。

通常のselectタグの見た目

<!DOCTYPE html> <html> <head> <style> select:nth-of-type(1){ border:1px solid black; padding:5px; width:100px; } </style> </head> <body> <select> <option value="1">AAA</option> <option value="2">BBB</option> <option value="3">CCC</option> </select> <select> <option value="4">DDD</option> <option value="5">EEE</option> <option value="6">FFF</option> </select> </body> </html> GoogleChrome Safari Firefox iPhone 1つ目のselectタグにだけ、cssを適用してみました。 こうやってみると、どのブラウザも見え方が違うので、 やはりデザイン統一するというのは、必須と思われますね。

ソースコード

<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="select.css"> <script src="select.js"></script> <style> select:nth-of-type(1){ border:1px solid black; padding:5px; width:100px; } </style> </head> <body> <select onchange="javascript:console.log(this.value);"> <option value="">選択してください。</option> <option value="1">AAA</option> <option value="2">BBB</option> <option value="3">CCC</option> </select> <select onchange="javascript:console.log(this.value);"> <option value="4">DDD</option> <option value="5">EEE</option> <option value="6">FFF</option> </select> </body> </html> :root{ --select-color:orange; } [type="select"]{ border:2px solid var(--select-color); position:relative; display:inline-block; width:100px; height:40px; line-height:40px; text-align:center; border-radius:4px; vertical-align:middle; margin:4px; cursor:pointer; color:var(--select-color); white-space:nowrap; overflow:hidden; } [type="select"]:before{ content:""; position:absolute; display:block; width:0px; height:0px; right:5px; top:50%; transform:translate(0,-50%); border-style: solid; border-width: 12.1px 7px 0 7px; border-color: var(--select-color) transparent transparent transparent; } [type="pulldown"]{ position:absolute; border:0; margin:2px 0 0 0; padding:0; list-style:none; box-shadow:2px 2px 8px rgba(0,0,0,0.3) } [type="pulldown"] > *{ height:20px; margin:0; padding:5px; border:2px solid var(--select-color); color:var(--select-color); text-align:center; cursor:pointer; white-space:nowrap; overflow:hidden; } [type="pulldown"] > *:nth-of-type(n+2){ border-top:0; } [type="pulldown"] > *:hover{ background-color:var(--select-color); color:white; } window.$$select = (function(){ // labelタグに入っているcheckboxボタンの一覧を取得 let MAIN = function(){ let selects = document.querySelectorAll("select"); if(!selects){return;} for(let select of selects){ this.set_select(select); } window.addEventListener("click" , this.click_window.bind(this)); }; // ラジオボタンの設定フロー MAIN.prototype.set_select = function(select){ if(!select){return;} this.set_element(select); this.set_value(select); }; // 画面クリックした時の処理 MAIN.prototype.click_window = function(e){ // span-click let span = this.upperSelector(e.target , "span[type='select']"); if(span){ let select = span.nextSibling; if(!document.querySelectorAll("ul[type='pulldown']").length){ this.view_pulldown(select); } else{ this.close_pulldown(); } return; } let li = this.upperSelector(e.target , "ul[type='pulldown'] li"); if(li){ this.click_list(li); return; } this.close_pulldown(); }; // デザイン用エレメントの作成 MAIN.prototype.set_element = function(select){ let new_select = document.createElement("span"); new_select.setAttribute("type" , "select"); select.parentNode.insertBefore(new_select , select); select.style.setProperty("display","none",""); }; // プルダウンメニューの表示 MAIN.prototype.view_pulldown = function(select){ if(!select){return;} let view_select = select.previousSibling; if(view_select.getAttribute("type") !== "select"){return;} this.close_pulldown(); let pulldown = document.createElement("ul"); pulldown.setAttribute("type" , "pulldown"); pulldown.style.setProperty("top" , (view_select.offsetTop + view_select.offsetHeight) +"px",""); pulldown.style.setProperty("left" , view_select.offsetLeft +"px",""); pulldown.style.setProperty("width" , view_select.offsetWidth +"px",""); view_select.parentNode.insertBefore(pulldown , view_select); for(let i=0; i<select.options.length; i++){ let li = document.createElement("li"); li.textContent = select.options[i].text; li.value = select.options[i].value; pulldown.appendChild(li); } } MAIN.prototype.close_pulldown = function(){ let pulldowns = document.querySelectorAll("ul[type='pulldown']"); if(!pulldowns){return;} for(let i=pulldowns.length-1; i>=0; i--){ pulldowns[i].parentNode.removeChild(pulldowns[i]); } }; MAIN.prototype.click_list = function(li){ if(!li){return;} let ul = li.parentNode; let span = ul.nextSibling; let select = span.nextSibling; select.value = li.value; this.close_pulldown(); select.onchange(); this.set_value(select); }; // 値処理 MAIN.prototype.set_value = function(select){ if(!select){return;} let target = select.previousSibling; let value = select.options[select.selectedIndex].text; target.textContent = value; }; // lib MAIN.prototype.upperSelector = function(elm , selectors) { selectors = (typeof selectors === "object") ? selectors : [selectors]; if(!elm || !selectors){return;} var flg = null; for(var i=0; i<selectors.length; i++){ for (var cur=elm; cur; cur=cur.parentElement) { if (cur.matches(selectors[i])) { flg = true; break; } } if(flg){break;} } return cur; } return MAIN; })(); // 起動処理(ページ読み込み完了を待ってスタート) switch(document.readyState){ case "complete" : new $$select(); break; case "interactive" : window.addEventListener("DOMContentLoaded" , function(){new $$select()}); break; default : window.addEventListener("load" , function(){new $$select()}); break; }

解説

思いの外、長いプログラムになってしまいました。 簡単に擬似エレメントを追加するだけと思っていたんですが、 プルダウンリストも擬似構築するハメになってしまいました。 (あたりまえですよね・・・) でも、もとのselectタグには、ほぼ影響せずにコントロール&表示することができるので、 どのブラウザでも依存せずに表示できますし、 DOM構造も、基本的に変えていないので、そのままのシステムで利用できるはずです。 ただ、cssで、要素に対するデザインを細かく設定をしておかないといけないので、 そうした点は、そもそもそれを目的にしているのでヨシと判断しました。 プルダウンらしさを出すために、表示の右端に擬似要素で逆三角形を表示して、おきました。 こういうの大事ですよね。 あと、selectタグにonchangeイベントがついている場合も多いので、 値を切り替えたあとで、onchangeイベントを実行して、できるだけ動作を継承できるようにはしています。 他のイベントも必要だったら、機能追加してお使いください。 ちなみに、iphone独特のプルダウン選択よりも、パソコンと同様のプルダウン表示の方が 個人的には好きなので、この方法は鉄板であると、 改めて感じましたね。

デモ

See the Pen efo-select by YugetaKoji (@geta1972) on CodePen.

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。