monolithic kernel

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

November 23, 2012

    前回とは異なる方法でスクロールを無効化する方法を探ってみました。今回はちゃんと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も早く修正されるといいですね。