JavaScript

スポンサー広告

横スクロール はみ出し要素検出

2017/07/16

勝手な思い込みかもしれませんが、レスポンシブ対応すると横スクロールは『悪』のような印象になってしまっていませんか?メディアクエリーのブレイクポイントではもちろん横スクロールは発生しませんが、ブレイクポイント間でなぜか横スクロールが発生してしまい、どの要素がはみ出しているのか探すのが大変だったことないでしょうか。

私はよくあります。どの要素が原因かが判れば、CSSの修正箇所もわかるのですが、どの要素が横スクロールの原因かを突き止めるまでにめっちゃ時間がかかってしまうことがあります。

もちろん、保険としてBODY要素に対してCSSでoverflow:hiddenをかけておくと横スクロールは発生しませんが、製作中にやってしまうと、見えていないところに大事なものが隠れていたりするかもしれません。なので、すべての要素を描画後にJavaScriptですべての要素の幅を取得してBODY要素の幅と比較してBODY要素の幅より大きいものがあるか無いか確認できれば機械的に判断できると思います。

すべてのブラウザで動くのがいいのですが、とりあえずchromeで動作確認しHTML、CSSの制作をchromeで確認しておけばいいと思います。IEのことは完全無視しています。

  1. HTMLとCSSと画像が読み込まれた後に動作する。
  2. BODY要素の幅を取得する。
  3. すべての要素を取得する。
  4. 要素ごとに幅(width,padding、border、marginの各左右の合計)を取得する。
  5. BODY要素の幅と比較する。
  6. BODY要素より大きければlog出力

のようなスクリプトがあれば便利な気がします。

これ以外にbox-sizingでborder-boxが指定されているとborder領域までがwidthに含まれるので要素の幅を取得するときに振り分けが必要です。

  1. getwidth(任意の名前の)関数を作成して、addEventListenerでloadで呼び出します。
  2. document.getElementsByTagName("body")でBODY要素を取得してwidthを取得(事前にCSSでBODYにはマージンパディングborderを0にしておく)。また、おそらくうまく取得してくれると思いますが、IFRAME要素などの埋め込み要素はきちんと幅の面倒は見ておいてください。読み込む内容によって幅が変わらないにようにしてください。
  3. document.querySelectorAll("*")ですべての要素を取得する
  4. すべての要素をループ処理して、window.getComputedStyle(tags[i],null).getPropertyValue("box-sizing")でbox-sizingの指定を確認する。更にbox-sizingの値により、各要素からwidth,padding、border、marginを取得して必要なものを加算する。
  5. 加算した値とBODY要素のwidthと比較する。
  6. はみ出していたら要素や幅などをconsole.logに出力する。

この方針でスクリプト作成すればレスポンシブの敵、横スクロールを壊滅できると思います。flex-boxははみ出すことがないと思いますので今回は無視します。

まずは
1.HTMLとCSSと画像が読み込まれた後に動作する
の部分を書きます。

javascript (getwidth.js)

function getwidth(){
  // はみ出し検出のスクリプト
}
window.addEventListener("load", getwidth, false);

これに適当な名前(getwidth.js)を付けて保存しHTMLから読み込む。window.addEventListener("load", getwidth, false);の部分はHTML,CSSが読み込まれDOMというものが作成されて画像も読みこみ済みの状態でgetwidth関数を実行してくれます。 document.addEventListener("DOMContentLoaded",getwidth,false); というパターンも考えられますが、これは画像がまだ読み込まれていない状態で関数を呼び出します。

HTMLでもCSSでも画像の幅が指定されていなければ画像を読み込み終わらないと幅が確定しない可能性があると思われますので、今回はDOMContentLoadedは不適切だと思われます。

HTML

<script src="getwidth.js"></script>

srcの値は適宜ご自身の環境に合わせてください。

次に
2.BODY要素の幅を取得する
の部分を作成します。

javascript (getwidth.js)

function getwidth(){
  'use strict';
  // 
  const bodyElem = document.getElementsByTagName("body");
  const bodyWidth = window.getComputedStyle(bodyElem[0],null).getPropertyValue("width"); // 縦スクロールバーの幅を含まない
  const numBodyWidth = parseInt(bodyWidth);
  console.log("body:",numBodyWidth);
}
window.addEventListener("load", getwidth, false);

document.getElementsByTagName("body");でBODY要素を取得します。window.getComputedStyle(bodyElem[0],null).getPropertyValue("width");BODY要素はおそらく1つしかないと思いますし、IFRAME要素内のBODY要素が0番目に現れることはないと思います。getComputedStyle(bodyElem[0],null)のgetPropertyValue("width")で、横スクロール発生基準が取得できました。この値は、縦スクロールバーの幅を含まない値になるのでPCブラウザで確認する場合はスクロールバーの幅を足しておくのがいいかもしれません。私の手元のchromeブラウザでは、縦スクロールバーの幅は17pxでした。

chromeの開発者ツールの左上のいろいろなデバイスのエミュレータ機能で検証すると縦スクロールバーは出てこないのでいいと思います。

次に
3.すべての要素を取得し、
4.要素ごとに幅(width,padding、border、marginの各左右の合計)を取得します。

javascript (getwidth.js)

function getwidth(){
'use strict';

  const bodyElem = document.getElementsByTagName("body");
  const bodyWidth = window.getComputedStyle(bodyElem[0],null).getPropertyValue("width");  // 縦スクロールバーの幅を含まない
  const numBodyWidth = parseInt(bodyWidth);
  console.log("body:",numBodyWidth);

  const tags = document.querySelectorAll("*"); // すべての要素を取得する
  for(let i=0; i < tags.length; i++){
    let tagMargingLeft = parseInt(window.getComputedStyle(tags[i],null).getPropertyValue("margin-left"));
    let tagMarginRight = parseInt(window.getComputedStyle(tags[i],null).getPropertyValue("margin-right"));
    let tagPaddingLeft = parseInt(window.getComputedStyle(tags[i],null).getPropertyValue("padding-left"));
    let tagPaddingRight = parseInt(window.getComputedStyle(tags[i],null).getPropertyValue("padding-right"));
    let tagBorderLeft = parseInt(window.getComputedStyle(tags[i],null).getPropertyValue("border-left"));
    let tagBorderRight = parseInt(window.getComputedStyle(tags[i],null).getPropertyValue("border-right"));
    let tagWidth = parseInt(window.getComputedStyle(tags[i],null).getPropertyValue("width"));

    let tagBoxSizing = window.getComputedStyle(tags[i],null).getPropertyValue("box-sizing");

    if(tagBoxSizing === "content-box"){
      var sumWidth = tagMargingLeft + tagMarginRight + tagBorderLeft + tagBorderRight + tagPaddingLeft + tagPaddingRight + tagWidth;
    }else{
      var sumWidth = tagMargingLeft + tagMarginRight + tagWidth;
    }
  }
}
window.addEventListener("load", getwidth, false);

基準の幅が取得できましたので、HTML各要素を取得して幅を算出していきます。document.querySelectorAll("*");の引数部分にユニバーサルセレクタを指定してすべての要素を取得します。戻り値はNodeListになります。すべての要素をループで処理をしたいのでNodeListのlengthプロパティで要素数分処理します。

document.defaultView.getComputedStyle(要素, null).getPropertyValue("cssプロパティ名");とかwindow.getComputedStyle(要素, null).cssプロパティ名;とか、いくつか書き方があるようです。getComputedStyleでいろいろとググってください。

更に今回要素はtagsにNodeListとして取得しています。tags.items(i)とするのが本来なのですが、配列の様にtags[i]としても取得できます。これで幅に影響するマージンの左右、ボーダーの左右、パディングの左右とwidthを取得します。getComputedStyleは実際に描画されている値になります。

更に、box-sizingでborder-boxであればwidthにボーダーの左右、パディングの左右が含まれます。 box-sizingの値がcontent-boxの場合マージンの左右、ボーダーの左右、パディングの左右とwidthがそれぞれ加算されていきますのでif文でそれぞれの場合の幅を加算します。

あとは、
5.加算した値とBODY要素のwidthと比較し
6.はみ出していたら要素や幅などをconsole.logに出力します。

javascript (getwidth.js)

function getwidth(){
'use strict';

  const bodyElem = document.getElementsByTagName("body");
  const bodyWidth = window.getComputedStyle(bodyElem[0],null).getPropertyValue("width");  // 縦スクロールバーの幅を含まない
  const numBodyWidth = parseInt(bodyWidth);
  console.log("body:",numBodyWidth);

  const tags = document.querySelectorAll("*"); // すべての要素を取得する
  for(let i=0; i < tags.length; i++){
    let tagMargingLeft = parseInt(window.getComputedStyle(tags[i],null).getPropertyValue("margin-left"));
    let tagMarginRight = parseInt(window.getComputedStyle(tags[i],null).getPropertyValue("margin-right"));
    let tagPaddingLeft = parseInt(window.getComputedStyle(tags[i],null).getPropertyValue("padding-left"));
    let tagPaddingRight = parseInt(window.getComputedStyle(tags[i],null).getPropertyValue("padding-right"));
    let tagBorderLeft = parseInt(window.getComputedStyle(tags[i],null).getPropertyValue("border-left"));
    let tagBorderRight = parseInt(window.getComputedStyle(tags[i],null).getPropertyValue("border-right"));
    let tagWidth = parseInt(window.getComputedStyle(tags[i],null).getPropertyValue("width"));

    let tagBoxSizing = window.getComputedStyle(tags[i],null).getPropertyValue("box-sizing");

    if(tagBoxSizing === "content-box"){
      var sumWidth = tagMargingLeft + tagMarginRight + tagBorderLeft + tagBorderRight + tagPaddingLeft + tagPaddingRight + tagWidth;
    }else{
      var sumWidth = tagMargingLeft + tagMarginRight + tagWidth;
    }

    if(numBodyWidth < sumWidth){
    console.log(tags[i],":",sumWidth,);
      console.log("marginl-left:",tagMargingLeft);
      console.log("border-left:",tagBorderLeft);
      console.log("padding-left:",tagPaddingLeft);
      console.log("width:",tagWidth);
      console.log("padding-left:",tagPaddingRight);
      console.log("border-left:",tagBorderRight);
      console.log("marginl-left:",tagMarginRight);
    }
    // console.log(i,tags[i],":",sumWidth);
  }
}
window.addEventListener("load", getwidth, false);

あとは簡単にBODY要素の幅と今取得した幅を比較して取得した幅の方が大きければ(横スクロール発生)その要素自体と、全体の幅、各構成要素の幅をコンソールに吐きます。これで一目瞭然、横スクロールが発見しやすくなると思います。

border-boxの際もマージン、ボーダー、パディング、widthを吐きますので慌てずに脳内変換してください。

私もまだ、使い込んでませんので間違い、改良の余地沢山あると思いますのでよしなにしてください。

rssfeed
コメント

コメントよろしくお願いします。

お名前
コメント