ある値がどの範囲にあるか調べる方法
「ある値がどの範囲にあるか調べる」ために if else を 配列でデータ化するネタ
今回の記事は、デザイナーさんやフロントエンドエンジニアさんを対象に、javascriptの教科書本に書いてないと思われるようなネタを紹介してみます。
ある値がどの範囲にあるか調べる、とはどういう意味かの説明
a = 500 の場合、aは 1024 より小さくて、340 より大きい値です。
で、aの値は不明の場合で、aが 1024 以下で、340 より大きい値であるかどうかを調べたい場合、どんなプログラムでも大抵、こんな風にif文で判定するかと思います。
if(1024 >= a && 340 < a){… if文が真の時は、aの値が 1024以下で、340より大きい値 …}
webサイトのプログラムで、ある値がどの範囲にあるかを調べるプログラムの事例としてすぐに思いつくのが、レスポンシブなWEBサイトを作る時のウインドウサイズを調べて、要素や画像のサイズやレイアウトを変更したり、メニューを別の形に変更する場合ではないかと思ったりします。
ほとんどすべてのブラウザが対応してくれるようになったので、CSSのみでやる場合はメディアスクリーンを使うかもしれませんが、メディアスクリーンの主な使用目的はPCで見ている場合は1680以上や、yahooをお手本にしたメイン要素を960px+αにするサイズ、又は昔ながらの1024&800px、あとはノートパッドやスマホ用などで大きなくくりでCSSファイルを用意するためのものかと思います。
サイトデザインによる細かな値での調節(1000以上は960px、900pxなら860pxなどの微調節)や、google画像検索のような雰囲気で商品画像を並べてレイアウトするようなサイトでは、画像の数や位置を細かく調節するためにcssファイルを増殖させるのは大変なので、javascriptで処理するのではないかと思います。
つまり、「ある値がどの範囲にあるか調べる」は、javascriptで表示スタイルを変更するために、例えばウインドウサイズ a = 800 が想定するどの範囲にあるかを調べる、というような処理のことです。
今回考察するネタ
今回は、ページ内の要素の位置を変更したり、サイズを変更したりするjavascriptプログラムを作る時に、『取得したウインドウサイズの値がどの範囲にあるかを判定する方法を考えよう』というのがテーマになります。ついでに、範囲データの指定方法も考えます。
メインは値の範囲を調べることなので、「ブラウザごとのウインドウサイズの取得方法」「要素のサイズ変更方法」などは、今回は省略し、文字で書くだけにしています。
例 以下のようなサイトを作る場合
例:取得したウインドウサイズが…
■1024px 以上 →メインを960px、メニューを350px(文字16px)で左(left:10,top:50)に配置
■1024未満~800px以上 →メインを760px、メニューを250px(文字14px)で左(left:10,top:50)に配置
■800未満~500px以上 →メインを460px、メニューを200px(文字13px)&ヘッダーのアイコンクリックで表示(left:5,top:50)
■500px未満 →メインの幅を300px、メニューを幅150px(文字12px)で&ヘッダーのアイコンクリックで表示(left:5,top:100)
※値は違う値である点が重要なだけなので、適当です。
if~elseを使って普通に作るとこんな感じかと思います。※関数名は適当です。
引数 w … 取得したウインドウの横幅長さ
function hm_c_change(w){
if(w>=1024){
【メイン横幅を960pxにする処理】
【メニュー横幅を350pxにする処理】
【メニューの文字を16pxにする処理】
【メニューのleftを10pxにする処理】
【メニューのtopを50pxにする処理】
【メニューのdisplayをblockにする処理】
【フッターにあるメニューホバー用アイコンのdisplayをnoneにする処理】
}
else if(w>=800){
【メイン横幅を760pxにする処理】
【メニュー横幅を250pxにする処理】
【メニューの文字を14pxにする処理】
【メニューのleftを10pxにする処理】
【メニューのtopを50pxにする処理】
【メニューのdisplayをblockにする処理】
【フッターにあるメニューホバー用アイコンのdisplayをnoneにする処理】
}
else if(w>=500){
【メイン横幅を460pxにする処理】
【メニュー横幅を200pxにする処理】
【メニューの文字を13pxにする処理】
【メニューのleftを5pxにする処理】
【メニューのtopを50pxにする処理】
【メニューのdisplayをnoneにする処理】
【ヘッダーのメニューアイコンのdisplayをblockにする処理】
}
else{
【メイン横幅を300pxにする処理】
【メニュー横幅を150pxにする処理】
【メニューの文字を12pxにする処理】
【メニューのleftを5pxにする処理】
【メニューのtopを100pxにする処理】
【メニューのdisplayをnoneにする処理】
【ヘッダーのメニューアイコンのdisplayをblockにする処理】
}
}
まだ範囲データの数が少ないので、これでいいのでは?などと思ったりするかもしれませんが、例えば、範囲データが増えたり減ったり、変更された場合、当然ですが、else ifの部分を増やしたり減らしたりなどのプログラム修正が必要となります。なので、サンプルのように if~else で作ると範囲データの変更が大変、という問題点があります。
これは【範囲データ】を [ if ~ else if ~ else ] の判定部分で指定している…すなわち範囲の判定値がデータではなく、逐次判定処理になっているからです。
また、例のようなメインブロックとメニューのみではなく、上下の見出しの長さ、バナー、背景画像の処理、キャッチ画像、商品画像、説明画像、スペック
表組などのようにいろんな要素も変更しなくてはならなくなってくると、各要素をいちいちif~elseで処理していると、修正依頼が来たときに直す場所が大量に出てきたり、変更箇所が分かりづらい長い長いソースになってしまう可能性があるかと思います。
その上で例えば、横幅 980、860、720、650、450、を増やして500を削除、などのホントは対した処理ではないのに、javascriptソースが大規模改修レベルに見えるような、とても面倒な変更作業になってしまうかと思われます。
異なる値を変数にしてみると単純になります
引数 w … 取得したウインドウの横幅長さ
function hm_c_change(w){
var a,b,c,d,e,f,g;
if(w>=1024){
a='960px'; b='350px'; c='16px'; d='10px'; e='50px'; f='block'; g='none';
}
else if(w>=800){
a='760px'; b='250px'; c='14px'; d='10px'; e='50px'; f='block'; g='none';
}
else if(w>=500){
a='460px'; b='200px'; c='13px'; d='5px'; e='50px'; f='none'; g='block';
}
else{
a='300px'; b='150px'; c='12px'; d='5px'; e='100px'; f='none'; g='block';
}
【メイン横幅を (a) にする処理】
【メニュー横幅を (b) にする処理】
【メニューの文字を (c) にする処理】
【メニューのleftを (d) にする処理】
【メニューのtopを (e) にする処理】
【メニューのdisplayを (f) にする処理】
【フッターにあるメニューホバー用アイコンのdisplayを (g) にする処理】
}
変数にすることで、変更する値をデータ化した印象のソースになりました。
ですが、ウインドウサイズについてはまだデータになってない感じです。
範囲指定の値を配列にして、簡単に変更できるようにしてみます
範囲指定の変更が面倒、という問題点を解消するために、配列データで(例えば)降順の数値で指定してみます。
例:x=[1024,800,500]
この場合、980、860、720、650、450、を増やして500を削除などの場合、
x=[1024,980,860,720,650,450]
にするだけなので、範囲変更が簡単になります。
この範囲指定の配列と、先ほどの変更用変数値、そしてウインドウサイズの引数値 w をまとめるために必要なアイデアが、『ある値がどの範囲にあるか調べる方法』ということになります。
範囲指定値とウインドウサイズ w の位置関係
範囲指定値を配列にすると、ウインドウサイズ w は以下の【アルファベット】(例では【A】~【D】)のどれかの位置になります。
x=[1024,800,500]の場合、
A:1024以上、B:800以上1024未満(1023から800)、C:500以上800未満(799から500)、D:500未満(499以下)
このA~Dを 0~3 の数値に対応させれば、上記の各処理をswitch文で書くこともでき(今回はswitch文を使いません)、さらに先ほどの変数を配列にすることもできます。
※ Aの範囲なら 0 , Bの範囲なら 1 , Cの範囲なら 2 , Dの範囲なら 3
途中経過として、wの位置 A~D を 判定し、y= 0~3 の数値を作る部分をifelseで書き、変数を配列にするとこんな感じになります。
引数 w … 取得したウインドウの横幅長さ
function hm_c_change(w){
var y,
a=['960px','760px','460px','300px'],
b=['350px','250px','200px','150px'],
c=['16px','14px','13px','12px'],
d=['10px','10px','5px','5px'],
e=['50px','50px','50px','100px'],
f=['block','block','none','none'],
g=['none','none','block','block'];
if(w>=1024){y=0;}
else if(w>=800){y=1;}
else if(w>=500){y=2;}
else{y=3;}
【メイン横幅を a[y] にする処理】
【メニュー横幅を b[y] にする処理】
【メニューの文字を c[y] にする処理】
【メニューのleftを d[y] にする処理】
【メニューのtopを e[y] にする処理】
【メニューのdisplayを f[y] にする処理】
【フッターにあるメニューホバー用アイコンのdisplayを g[y] にする処理】
}
どの範囲にあるかを判定する処理
上記のような感じで範囲の部分だけはifelseで書くという形でもよいかもしれませんが、汎用的には使えないので、カテゴリ違いで違う値を指定したい場合にいちいち同じ形で書かなければならないのが面倒になるかもしれません。
なので、今回のテーマである「ある値が【配列で指定した数値の】どの範囲にあるか調べる」という関数を考えてみます。
範囲の値指定が配列なので、判定する単純な方法としては、for文で配列の値を順に比較するのが普通かと思われます。
(例) 範囲指定配列 x=[1024,800,500] と 判定したい値(ウインドウ横幅サイズ) w を渡すと
判定値が戻る関数
※関数名は適当です
function hm_wxv(w,x){
var i,l=x.length,c=-1,f=0;
for(i=0;i<l;i++){
c+=1;
if(w>=x[i]){
i=l;
f=1;
}
}
f||(c+=1);
return c;
}
後で使う 配列のインデックスになるように c の値を計算する形になり、fは範囲最小値未満の値を計算するためのものです。
例の場合、wの値によって、cやfが以下の値になります。
A : w=1024以上 [c=0 , f=1]
B : w=1024未満~800以上 [c=1 , f=1]
C : w=800未満~500以上 [c=2 , f=1]
D : w=500未満 forループ終了時点 [c=2 , f=0]
→最後まで f=0 の場合 c+=1 の結果、[c=3 , f=0]
「配列で範囲指定した時にある値がどの位置にあるか調べる」ための他の方法
for文で配列の値を1つずつ比較してカウントする形で例示した[A]~[D]の位置のどこにあるかを調べる方法を書きましたが、 範囲指定データが配列になってますので、配列メソッドと組み合わせると、もう少し簡単にできるアイデアを思いついたのでご紹介します。
アイデアの素は、「配列に比較したい値があるかどうかの判定 → array.indexOf(value)」です。
indexOfは、配列に同じ値があれば、その位置(配列インデックス)を返し、なければ-1を返す処理です。
最初に indexOf で調べた時に、配列に w と同じ値があれば、それをそのまま返し、なければ、配列に w の値を追加し、降順(又は昇順)に並び替え、再度 indexOf すれば、必ず w の値があるので、範囲を示すインデックス値として取得することができる、というアイデアになります。
ということで、indexOfで調べる方法(indexOf)、配列に値を追加する方法(concat or push)、配列の値を降順に並び替える方法(sort)の3つを組み合わせると、for文でcの値をカウントしたのと同じ結果を取得する関数が作れる、ということになります。
配列で範囲指定した時にある値がどの位置にあるか調べる配列処理的な方法
function hm_wxv2(w,x){
var c=x.indexOf(w),d=[];
if(c==-1){
d=x.concat(w);
d.sort(function(a,b){return b-a;});
c=d.indexOf(w);
}
return c;
}
※昇順 sortの定型処理
array.sort(function(a,b){return a-b;});
※降順 sortの定型処理
array.sort(function(a,b){return b-a;});
pushで配列の値を破壊的に変更するのが気になってしまう場合があるかと思いますので、concatでwを追加した新しい配列を作って処理する形にしています。
この関数は、範囲指定の配列データが降順になっていることと、範囲指定配列 x には重複したデータは設定しない、が必須条件となります。
今回のプログラムのまとめ
ついでに、記述を少しだけ減らしています。
範囲指定配列の値が3つなので、範囲を示すインデックスは0~3の4つになるので、a~gの各データは4つずつの値になります。
引数 w … 取得したウインドウの横幅長さ
function hm_c_change(w){
var x=[1024,800,500],
y=hm_wxv2(w,x),
a=[960,760,460,300][y]+'px',
b=[350,250,200,150][y]+'px',
c=[16,14,13,12][y]+'px',
d=[10,10,5,5][y]+'px',
e=[50,50,50,100][y]+'px',
f=[1,1,0,0][y]?'block':'none',
g=[0,0,1,1][y]?'block':'none';
【メイン横幅を a にする処理】
【メニュー横幅を b にする処理】
【メニューの文字を c にする処理】
【メニューのleftを d にする処理】
【メニューのtopを e にする処理】
【メニューのdisplayを f にする処理】
【フッターにあるメニューホバー用アイコンのdisplayを g にする処理】
}
function hm_wxv2(w,x){
var c=x.indexOf(w),d=[];
if(c==-1){
d=x.concat(w);
d.sort(function(a,b){return b-a;});
c=d.indexOf(w);
}
return c;
}
例えば、980を追加する場合、
x=[1024,980,800,500]
a=[960,860,760,460,300]…
b=[350,300,250,200,150]…
c=[16,15,14,13,12]…
d=[10,10,10,5,5]…
e=[50,50,50,50,100]…
f=[1,1,1,0,0]…
g=[0,0,0,1,1]…
のように各データ配列に値を追加する形になります。
まとめ
「ある値がどの範囲にあるか調べる関数 hm_wxv2」は、発想としては前回の記事「今日は何曜日?…getDay()の値は0~6」のgetDay()が曜日によって0~6の値を返し、その値を配列['日' , '月' , '火' , '水' , '木' , '金' , '土'](又は文字列'日月火水木金土')のインデックス番号として使う、というネタと同じ考え方になっています。
getDay()→ 戻り値 = 0 → '日'
hm_wxv2(w,x)→ 戻り値 = 0 → a = 960 , b = 350 , c=16 …
ということで、範囲指定データを配列で用意した時に、ある値がどの位置(変更する値配列のindex番号に利用)になるかを調べるための関数のアイデアについて紹介してみました。 javascriptのメソッドをいろいろ眺めてみると、単体で使う以外に、組み合わせるとおもしろいアルゴリズムのアイデアが浮かぶかもしれませんので、視点を変えてみてみるのもおもしろいかと思ったりします。
以上、長い記事を最後まで読まれたみなさん、お疲れさまでした。
この記事の範囲インデックス値取得関数を別記事にしたものを Qiita さんに投稿しました。
配列中にない値が範囲として含まれることを調べる方法
以下は自己満足的な蛇足の内容ですので、気にしないでください。
おまけ:今回紹介したネタ以外に範囲を示す配列インデックスを計算する方法を考えてみました
(2018.11.30追記)
for文での比較計算を、配列とforEachを使って比較して調べる方法
(例) 範囲指定配列 x=[1024,800,500] と 判定したい値(ウインドウ横幅サイズ) w を渡すと
判定値が戻る関数
※関数名は適当です
// a には x を渡します。
function hm_wxv_t2(w,a){
var d=[],c,f=1;
d=a.concat(w); //いきなり w を 配列 a に追加した作業用配列 d を用意します。
d.forEach(function(v,i){if(f&&(w-v)>=0)c=i,f=0;});
return c;
}
indexOf を使うのが一番楽だと思うのですが、indexOf がなくて forEach は使える、と言う場合(あるのか?それ…)で、頭の体操的に for 文で比較するタイプを forEach にしてみたものです。
以下、数値を入れた時の動作をメモしておきます
900の場合、d=[1024,800,500,900]
i=0 / f=1 (真) → 900-1024 が(偽)
i=1 / f=1 (真) → 900-800 が(真) → c=1 , f=0
i=2 / f=0 (偽) なので 900-500 は調べない
i=3 / f=0 (偽) なので 900-900 は調べない
c=1 となる
1024の場合、d=[1024,800,500,1024]
i=0 / f=1 (真) → 1024-1024 が(真) → c=0 , f=0
i=1 / f=0 (偽) なので 1024-800 は調べない
i=2 / f=0 (偽) なので 1024-500 は調べない
i=3 / f=0 (偽) なので 1024-1024 は調べない
c=0 となる
800の場合、d=[1024,800,500,800]
i=0 / f=1 (真) → 800-1024 が(偽)
i=1 / f=1 (真) → 800-800 が(真) → c=1 , f=0
i=2 / f=0 (偽) なので 800-500 は調べない
i=3 / f=0 (偽) なので 800-800 は調べない
c=1 となる
700の場合、d=[1024,800,500,700]
i=0 / f=1 (真) → 700-1024 が(偽)
i=1 / f=1 (真) → 700-800 が(偽)
i=2 / f=1 (真) → 700-500 が(真) → c=2 , f=0
i=3 / f=0 (偽) なので 700-700 は調べない
c=2 となる
500の場合、d=[1024,800,500,500]
i=0 / f=1 (真) → 500-1024 が(偽)
i=1 / f=1 (真) → 500-800 が(偽)
i=2 / f=1 (真) → 500-500 が(真) → c=2 , f=0
i=3 / f=0 (偽) なので 500-500 は調べない
c=2 となる
1024以上で例えば1200、d=[1024,800,500,1200]
i=0 / f=1 (真) → 1200-1024 が(真) → c=0 , f=0
i=1 / f=0 (偽) なので 1200-800 は調べない
i=2 / f=0 (偽) なので 1200-500 は調べない
i=3 / f=0 (偽) なので 1200-1024 は調べない
c=0 となる
500未満で例えば300、d=[1024,800,500,300]
i=0 / f=1 (真) → 300-1024 が(偽)
i=1 / f=1 (真) → 300-800 が(偽)
i=2 / f=1 (真) → 300-500 が(偽)
i=3 / f=0 (偽) なので 300-300 が(真) → c=3 , f=0
c=3 となる
ページトップへ