monoの開発ブログ

JavaScriptで一時的にスクロールを無効化する その2

前回とは異なる方法でスクロールを無効化する方法を探ってみました。今回はちゃんとIE (8以上) で動きます!

コード

コードはこちら。addCSSscrollBarWidth はそれぞれコメントで示したURLを参考にさせていただきました。

// http://seussnu.b.sourceforge.jp/?p=22
var addCSS = (function() {
  var style = document.createElement('style');
  style.setAttribute('type', 'text/css');
  document.getElementsByTagName('head')[0].appendChild(style);
  var stylesheet = document.styleSheets[0];
  if (stylesheet.insertRule) {
    return function(selector, property, value) {
      stylesheet.insertRule(selector + '{' + property + ':' + value + ';}', stylesheet.cssRules.length);
    };
  }
  else {
    return function(selector, property, value) {
      stylesheet.addRule(selector, property + ':' + value);
    };
  }
})();

// http://davidwalsh.name/detect-scrollbar-width
var scrollBarWidth = (function() {
  var scrollDiv = document.createElement("div");
  scrollDiv.style.width = '100px';
  scrollDiv.style.height = '100px';
  scrollDiv.style.overflow = 'scroll';
  scrollDiv.style.position = 'absolute';
  scrollDiv.style.top = '-9999px';
  document.body.appendChild(scrollDiv);

  var w = scrollDiv.offsetWidth - scrollDiv.clientWidth;

  document.body.removeChild(scrollDiv);

  return w;
})();

addCSS('.noscroll', 'position', 'relative');
addCSS('.noscroll', 'overflow', 'hidden');
addCSS('.noscroll', 'padding-right', scrollBarWidth + 'px');

使い方

使う時はbody要素のクラスにnoscrollを追加するとスクロールが無効化され、削除すると再びスクロールできるようになります。

var addClass = function(element, className) {
  var classes = element.className.split(/\s+/);
  var i, n;
  for (i = 0, n = classes.length; i < n; ++i) {
    if (classes[i] == className) {
      return;
    }
  }
  classes.push(className);
  element.className = classes.join(' ');
};

var removeClass = function(element, className) {
  var classes = element.className.split(/\s+/);
  var result = [];
  var i, n;
  for (i = 0, n = classes.length; i < n; ++i) {
    if (classes[i] != className) {
      result.push(classes[i]);
    }
  }
  element.className = result.join(' ');
};

addClass(document.body, 'noscroll');
removeClass(document.body, 'noscroll');

jQueryを使っているのであればクラスの操作は以下のようにできます。

$('body').addClass('noscroll');
$('body').removeClass('noscroll');

実現方法

overflow:hiddenをbody要素に適用してスクロールバーを消し、スクロールさせないようにしています。しかし、それだけだとスクロールバーが消えた分描画領域が広がってしまい、一瞬表示が崩れてしまうので、事前にスクロールバーの幅を計算しておき、その分だけbody要素のpadding-rightを設定して縮めることで崩れを防いでいます。

問題点

スクロールバーを消した分の幅はpaddingで埋めているのですが、これでは誤魔化せない場所もあります。例えば、CSSのMedia Queriesの条件に使うスクリーンサイズはスクロールバーの有無で変わってしまうので、スタイルが切り替わる境界付近でスクロールを禁止すると、レイアウトが変化して見苦しくなる可能性があります。

2013-05-26 追記

この挙動、仕様ではなくWebKitのバグだったみたいですね。Blinkでは修正されたようです。WebKitも早く修正されるといいですね。