내부 팝업을 띄운채로 팝업과 Body 각각의 부분만 스크롤하기

스크롤 가능한 레이어 팝업 스크롤시 바디 스크롤 막기가 내부 팝업의 스크롤만 가능하면 되는 비교적 가벼운 문제였다면 이번에 생긴 요청사항은 내부 팝업의 스크롤이 바디 스크롤에 영향을 주면 안되지만 각각을 스크롤하면 스크롤이 되야 하는 지난번에 했던 바디 스크롤 자체를 스크롤 할게 없도록 만드는 방식을 쓸 수 없는 상황이었다.

결론부터 이야기하면 iScroll 플러그인 썼다.

아래 방법은 레이어를 넘어 아래에 있는 바디 영역이 움직이는 상황이 이미 레이어 내부 스크롤이 제일 아래로 내려가 있는데 아래로 무브하거나 이미 제일 위에 있는데 위 방향으로 무브할 때 바디 스크롤이 움직인다는 것을 확인하여 이미 맨위거나, 맨 아래면 터치무브가 해당 방향으로 발생하면 실행되지 않도록 하면 될거라고 생각하고 작성했다.

$.fn.disableOverScroll = function(options) {
  var defaults = {
    gap: 0,
    excludeEl: false // selector
  };

  var opts = $.extend({}, defaults, options);
  var touchY;
  var pass = 0;

  $(this).on({
    touchstart: function(e) {
      touchY = e.originalEvent.touches[0].screenY;

      if (opts.excludeEl) {
        pass = $(e.target).closest(opts.excludeEl).length;
      }
    },
    touchmove: function(e) {
      if (pass) {
        return;
      }

      var $this = $(this);

      var $metas = {
        h: $this.height(),
        t: $this.prop('scrollTop'),
        s: $this.prop('scrollHeight')
      };

      var $pos = {
        top: $metas.t === 0,
        btm: $metas.s === $metas.h + $metas.t + opts.gap
      };

      var directionTop = touchY - e.originalEvent.touches[0].screenY < 0;

      var disableScroll = function() {
        if (pass) {
          pass = 0;
          return;
        }

        $(doc).off('touchmove');
        $(doc).on('touchmove', function(e) {
          e.stopPropagation();
          e.preventDefault();
        });

        setTimeout(function() {
          $(doc).off('touchmove');
        }, 0);
      };

      if ($pos.top && directionTop) {
        disableScroll();
      } else if ($pos.btm && !directionTop) {
        disableScroll();
      } else {
        $(doc).off('touchmove');
      }
    },
    touchend: function() {
      $(doc).off('touchmove');
    }
  });
};

$('.layerBody').disableOverScroll({
  gap: 30
});
.layerBody {
  position: relative;
  height: calc(100% - #{$headerHeight});
  padding-bottom: 3rem;
  overflow: auto;
  border-top: .2rem solid #333;
  box-shadow: 0 -.5rem .5rem rgba(0, 0, 0, .1);
  background-color: #fff;
}

이렇게 해보니 안드로이드에서 안되는걸 알게 됨.. 그렇다면 안드로이드에서만 가림막을 세워보자고 쉽게 생각함.

$.fn.disableOverScroll = function(options) {
  var defaults = {
    gap: 0,
    excludeEl: false // selector ex: '.selector'
  };

  var opts = $.extend({}, defaults, options);
  var touchY;
  var pass = 0;

  var $blocker = $('#blocker');

  var move = 0;
  var $pos;
  var directionTop;

  var isAndroid = function() {
    var win32 = /Win32/.test(navigator.platform);
    var iOS = /iPhone|iPad|iPod/.test(navigator.platform);

    var isAndroid = win32 || iOS ? false : true;

    return isAndroid;
  };

  $blocker.on({
    touchstart: function(e) {
      touchY = e.originalEvent.touches[0].screenY;
      move = 0;
    },
    touchmove: function(e) {
      e.preventDefault();
      directionTop = touchY - e.originalEvent.touches[0].screenY < 0;
      move = 0;
      if ($pos.top && directionTop) {
        move = 1;
      } else if ($pos.btm && !directionTop) {
        move = 1;
      }
    },
    touchend: function() {
      if (!move) {
        $(this).removeClass('on');
      }
      move = 0;
    }
  });

  $(this).on({
    touchstart: function(e) {
      touchY = e.originalEvent.touches[0].screenY;

      if (opts.excludeEl) {
        pass = $(e.target).closest(opts.excludeEl).length;
      }
    },
    touchmove: function(e) {
      if (pass) {
        return;
      }

      var $this = $(this);

      var $metas = {
        h: $this.height(),
        t: $this.prop('scrollTop'),
        s: $this.prop('scrollHeight')
      };

      $pos = {
        top: $metas.t === 0,
        btm: $metas.s === $metas.h + $metas.t + opts.gap
      };

      directionTop = touchY - e.originalEvent.touches[0].screenY < 0;

      var disableScroll = function() {
        if (pass) {
          pass = 0;
          return;
        }

        $(doc).off('touchmove');
        $(doc).on('touchmove', function(e) {
          e.stopPropagation();
          e.preventDefault();
        });

        setTimeout(function() {
          $(doc).off('touchmove');
        }, 0);

        if (isAndroid()) {
          $blocker.addClass('on');
        }
      };

      if ($pos.top && directionTop) {
        disableScroll();
      } else if ($pos.btm && !directionTop) {
        disableScroll();
      } else {
        $(doc).off('touchmove');
        if (isAndroid()) {
          $blocker.removeClass('on');
        }
      }
    },
    touchend: function() {
      $(doc).off('touchmove');
    }
  });
}

그렇게 되는 것 같았지만 하필 보고 있던 테스트 아이폰이 iOS 10버전이었고 내 폰(iOS 11.4)으로 우연히 한번 보다가 안되는걸로 확인되어 부랴부랴 다른 아이폰으로 확인해보니 iOS 11.3(추정) 이상부턴 이게 아예 통하지 않는다는걸 알게 됐다.. 어떻게 보면 이렇게 급하게 들어간 코드는 후에 꼭 문제가 생겨서 어쩌면 다행일지도 모르겠다.

결국은 이렇게 바디, 레이어 각각 따로 스크롤이 가능해야되는 이슈에는 모바일인 경우 iScroll을 쓸 수 밖에 없는건가… iScroll을 최대한 안쓰려고 용썼던거라 하는 수 없이 iScroll 플러그인을 사용하여 전달했다.