1

在实现弹窗效果时通常我们都不希望浏览器还能继续滚动,特别是当弹窗里也有滚动条的时候,有双层滚动条的效果及体验都是极差的。

锁定页面的原理:

  1. 设置<body>标签的样式为overflow: hidden
  2. <body>标签添加一个值为滚动条宽度的padding-right

代码实现:

function lockScroll () {
  let body = document.body;
  // 记录了锁定滚动条之前body的属性,以便在解锁后恢复锁定前的样式
  let originBodyOverflow = body.style.overflow;
  let originBodyPaddingRight = body.style.paddingRight;
  let originBodyPaddingBottom = body.style.paddingBottom;
  let originBodyHasLockClass = body.classList.contains('bs-lock-scroll');
  let hasScroll = hasScroll();
  let scrollWidth = scrollWidth();
  // 标记本次是否锁定了页面
  let locked = false;


  if (!originBodyHasLockClass) {
    body.classList.add('bs-lock-scroll');
  }
  if (originBodyOverflow != 'hidden') {
    body.style.overflow = 'hidden';
    locked = true;
    if (hasScroll.vertical) {
      body.style.paddingRight = scrollWidth.vertical + 'px';
    }
    if (hasScroll.horizontal) {
      body.style.paddingBottom = scrollWidth.horizontal + 'px';
    }
  }

  // 返回一个解除锁定滚动条的函数
  return function () {
    let body = document.body;
    if (!originBodyHasLockClass) {
      body.classList.remove('bs-lock-scroll');
    }
    if (!locked) {
      return;
    }
    if (originBodyOverflow) {
      body.style.overflow = originBodyOverflow;
    } else {
      body.style.overflow = ''; // 移除body上的overflow属性
    }

    if (originBodyPaddingRight && parseFloat(originBodyPaddingRight) !== scrollWidth.vertical) {
      body.style.paddingRight = originBodyPaddingRight;
    } else {
      body.style.paddingRight = ''; // 移除body上的paddingRight属性
    }

    if (originBodyPaddingBottom && parseFloat(originBodyPaddingBottom) !== scrollWidth.horizontal) {
      body.style.paddingBottom = originBodyPaddingBottom;
    } else {
      body.style.paddingBottom = ''; // 移除body上的paddingBottom属性
    }
  };
};

/**
 * 获取元素或浏览器滚动条的宽高
 * @param ele dom元素
 * @returns {{horizontal: number, vertical: number}}
 */
function scrollWidth (ele) {
  var tempDiv;
  var tempInnerDiv = document.createElement('div');
  var result = {
    vertical: 0,
    horizontal: 0
  };
  tempInnerDiv.style.cssText = 'width: 200px;height: 200px';
  if (!ele || ele.nodeType != 1) { // 未传递dom元素则获取浏览器的滚动条
    result.vertical = window.innerWidth - document.documentElement.offsetWidth;
    result.horizontal = window.innerHeight - document.documentElement.clientHeight;
    return result;
  }

  tempDiv = ele.cloneNode(true);
  tempDiv.style.cssText = 'width: 100px;height: 100px;opacity: 0;position:absolute;left: -100px;overflow:auto;';
  tempDiv.appendChild(tempInnerDiv);
  document.body.appendChild(tempDiv);

  result.vertical = tempDiv.offsetWidth - tempDiv.clientWidth;
  result.horizontal = tempDiv.offsetHeight - tempDiv.clientHeight;

  document.body.removeChild(tempDiv);
  tempDiv = tempInnerDiv = null;
  return result;
}

/**
* 判断浏览器或dom元素是否有滚动条
* @returns {{horizontal: boolean, vertical: boolean}}
*/
function hasScroll () {
  return {
    vertical: document.body.scrollHeight > window.innerHeight,
    horizontal: document.body.scrollWidth > window.innerWidth
  };
}

使用:

var unLock = lockScroll();

// 解锁
// unLock();

效果(多次调用锁定):
滚动条锁定效果


heath_learning
1.4k 声望31 粉丝