如何判断有没有出现滚动条

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何判断有没有出现滚动条相关的知识,希望对你有一定的参考价值。

参考技术A 我们要判断一个div上有没有出现水平方向滚动条,只需判断 scrollWidth 是否大于 clientWidth 即可。

当border为0时,使用 offsetHeight - clientHeight 可以得到水平滚动条的宽度(如果有的话)。

我们要判断一个div上有没有出现垂直方向滚动条,只需判断 scrollHeight 是否大于 clientHeight 即可。

当border为0时,使用 offsetWidth - clientWidth 可以得到垂直滚动条的宽度(如果有的话)。

但是在IE浏览器上,某些特殊情况下,虽然 scrollHeight > clientHeight (或 scrollWidth > clientWidth ),也不一定出现滚动条。

我们发现,在这种特殊情况下,虽然父元素的 scrollHeight (258)> clientHeight (257),并且实际内容的高度是 257.92999267578125 ,的确父元素应该出现滚动条,但实际在IE浏览器上没有显示滚动条,这一点我们从数据 父元素的 offsetWidth (840)- clientWidth (840)= 0 也可得出。

通常情况下,我们可以使用 scrollWidth > clientWidth 判断是否出现水平滚动条, scrollHeight > clientHeight 判断是否出现垂直滚动条。

在需要更精确(非 移动端 且非 overflow: hidden 或 overlay )或要拿到滚动条宽度的情况下,我们可以使用 offsetHeight - clientHeight (水平滚动条) 或 offsetWidth - clientWidth (垂直滚动条),这时,我们不要忘了减去元素的 border 。

有时候情况比较复杂,比如滚动区域宽度是不固定的,我们需要根据是否出现滚动条来设置滚动区域宽度(比如,出现滚动条时给左右加上点击按钮,来切换显示区域)。这时我们就需要来计算内部各元素宽度之和来判断是否出现滚动条。
这里写了个例子,我们来看一下,运行环境为Windows电脑,Chrome浏览器

clientWidth 、 offsetWidth 、 scrollWidth 属性会进行四舍五入并返回整数。当子元素有多个时相加,误差会越来越大。

我们来看一下 getComputedStyle 的值

我们发现内容宽度 341.3753 > 父元素宽度 341.375
getComputedStyle 存在一个小的精度误差。

我们再来看一下 getBoundingClientRect 获取到的宽度

我们发现内容宽度 341.375 与 父元素宽度 341.375 相等。

那我们这里就选用 getBoundingClientRect 吧,但是......

我们把浏览器放大至 150% 再来看一下

我们使用 getBoundingClientRect 的值计算一下

我们发现得到的值为 341.3541793823242 ,与获取到的父元素的值 341.35418701171875 仍存在一个微小的偏差(这个偏差可能是大,也可能是小)。

因此,我们引入一个微小的忽略值 0.1 ,认为当 父元素的宽度 + 0.1 大于 内容宽度计算结果 时,没有滚动条。

参考文档:
MDN Element.clientWidth
MDN htmlElement.offsetWidth
MDN Element.scrollWidth
MDN Element.clientHeight
MDN HTMLElement.offsetHeight
MDN Element.scrollHeight

检测窗口垂直滚动条何时出现

【中文标题】检测窗口垂直滚动条何时出现【英文标题】:Detect when window vertical scrollbar appears 【发布时间】:2011-01-11 16:06:07 【问题描述】:

有没有简单可靠的检测窗口垂直滚动条出现/消失的解决方案?

window.onresize 在 JavaScript DOM 操作页面变得足够高以显示滚动条后不会触发。

在这篇非常相似的帖子Detect if a page has a vertical scrollbar 中描述了如何检测滚动条是否存在的解决方案,但我需要知道它何时出现。

【问题讨论】:

【参考方案1】:

很抱歉让这个死而复生,但我刚刚遇到了这个限制并想出了我自己的解决方案。这有点骇人听闻,但请坚持我...

这个想法是向页面添加一个 100% 宽度的不可见 iframe,并在其内部窗口上侦听调整大小事件。这些事件不仅会检测外部窗口大小的变化,还会检测在外部窗口中添加或移除滚动条时发生的变化。

它会触发一个常规的窗口调整大小事件,因此如果您已经在监听窗口调整大小,它不需要额外的代码。

在最新的 IE9 和 Chrome/Firefox 中测试 - 可能可以在较旧的 IE 中工作,但我的项目不支持这些,所以我没有尝试过。

https://gist.github.com/OrganicPanda/8222636

【讨论】:

对于任何在 React 工作的人,我已经将它改编为 React 组件 gist.github.com/AdamMcCormick/d5f718d2e9569acdf7def25e8266bb2a 改编为 React hook gist.github.com/curran/0e30c621fe4fc612bf7feb0938a68e4d 我还没有测试过这个,但听起来这在 Mac 上是行不通的,因为它们的滚动条不会占用视口的宽度。 Mac/iOS 上的滚动条在出现时不会占用空间,它们只会在滚动时覆盖页面,因此无需担心“调整大小”。出于这个原因,这个技巧仍然适用于“知道文档的大小何时因垂直滚动条而改变”的预期用例。如果您的用例更像是“知道此元素何时溢出视口并导致滚动”,这将不起作用。这两个用例现在都由IntersectionObserver/ResizeObserver 提供更好的服务,但这个答案在 2014 年很酷【参考方案2】:

根据 OrganicPanda 的回答,想出了这个 jquery 的东西

$('<iframe id="scrollbar-listener"/>').css(
    'position'      : 'fixed',
'width'         : '100%',
'height'        : 0, 
'bottom'        : 0,
'border'        : 0,
'background-color'  : 'transparent'
).on('load',function() 
    var vsb     = (document.body.scrollHeight > document.body.clientHeight);
    var timer   = null;
    this.contentWindow.addEventListener('resize', function() 
        clearTimeout(timer);
        timer = setTimeout(function() 
            var vsbnew = (document.body.scrollHeight > document.body.clientHeight);
            if (vsbnew) 
                if (!vsb) 
                    $(top.window).trigger('scrollbar',[true]);
                    vsb=true;
                
             else 
                if (vsb) 
                    $(top.window).trigger('scrollbar',[false]);
                    vsb=false;
                
            
        , 100);
    );
).appendTo('body');

如果出现/消失,这将触发窗口上的“滚动条”事件

至少可以在 chrome/mac 上运行。现在,有人扩展它来检测水平滚动条:-)

【讨论】:

不错的解决方案!不幸的是,它在 IE7 或 IE8 中不起作用(我没有在 9-11 中尝试过),但我想这对大多数人来说并不太担心......【参考方案3】:

通过动态检测浏览器垂直滚动条事件 将 window.innerWidth 与 getBoundingClientRect() 进行比较 使用 Javascript 的 DIV 元素。经最新测试 即 FF 铬。见文档here

【讨论】:

我认为 OP 想要一个可以处理的事件 - 检测滚动条可以通过更快的方法轻松完成 (?)。 这个答案有问题。首先,链接坏了。其次,正如前面的评论者所指出的,OP想要解决的问题不仅仅是如何检测有滚动条,而是如何获取事件通知代码滚动条变为可见或变为隐藏。第三,这个答案的写法,真的很难弄清楚它应该说什么。 你的意思是window.innerHeight?【参考方案4】:

如果您使用的是 AngularJS,您可以使用指令来检测宽度何时发生变化(假设出现/消失的滚动条是垂直的):

app.directive('verticalScroll', function($rootScope)
    return 
        restrict: 'A',
        link: function (scope, element) 
            scope.$watch(
                function() 
                    return element[0].clientWidth;
                ,
                function() 
                    $rootScope.$emit('resize');
                
            );
        
    
);

这会在根作用域上触发一个事件,其他指令或控制器可以监听该事件。

手表由角度摘要循环触发,因此这依赖于 Angular 加载/删除导致滚动条出现/消失的额外内容。

【讨论】:

您能否举例说明另一个指令如何调用此verticalScroll。 @丹【参考方案5】:

独家新闻

可以通过使用ResizeObserver 来检测滚动条可见性的变化,以检查可能带有滚动条的元素大小的变化及其内容大小的变化。

基本原理

我开始使用&lt;iframe&gt; 方法实现一个解决方案,但很快发现要实现一个完整的实现需要打破我的应用程序视图之间的关注点分离。我有一个父视图,它需要知道子视图何时获得垂直滚动条。 (我不关心水平滚动条。)我有两种情况可能会影响垂直滚动条的可见性:

    调整了父视图的大小。这是在用户的直接控制之下。

    子视图的内容变大或变小。这是在用户的间接控制之下。子视图显示搜索结果。结果的数量和类型决定了子视图的大小。

我发现如果我使用&lt;iframe&gt;,我将不得不使用子视图来支持父母的需求。我更喜欢孩子不包含纯粹是父母关心的东西的代码。使用我在这里描述的解决方案,只需要修改父视图。

所以在寻找更好的解决方案时,我找到了 Daniel Herr 的 this answer。他建议使用ResizeObserver 来检测 div 的尺寸何时发生变化。 ResizeObserver尚未在浏览器中原生可用,但有一个强大的 ponyfill/polyfill 用于在原生支持不可用的情况下提供支持。 (这里是spec 对应的ResizeObserver。)

概念验证

我在其 ponyfill 模式下使用this polyfill。这样,全球环境就不会受到影响。此实现依赖于window.requestAnimationFrame,对于不支持window.requestAnimationFrame 的平台,将依赖setTimeout。看着support for requestAnimationFrame 上的“我可以使用...?”,我在那里看到的并不会打扰我。 YMMV。

我有一个直播proof-of-concept。关键是监听可以接受滚动条的DOM元素的大小变化(id为container的元素,绿色)并监听可能需要滚动的内容的大小变化(id为@987654337的元素@)。概念验证使用interact.js 来管理允许调整container 大小的调整大小元素(ID 为resizer,蓝色)。如果拖动resizer 的右下角,它将同时调整resizercontainer 的大小。这两个按钮允许模拟container显示的内容大小的变化。

我在当前处于预发布阶段的代码中使用此方法,这意味着它通过了多个浏览器的测试,并且正在由利益相关者进行评估,但尚未投入生产。

HTML:

<!DOCTYPE html>
<html>

<head>
  <script data-require="interact.js@*" data-semver="1.0.26" src="//rawgit.com/taye/interact.js/v1.0.26/interact.js"></script>
  <script src="//rawgit.com/que-etc/resize-observer-polyfill/master/dist/ResizeObserver.global.js"></script>
  <link rel="stylesheet" href="style.css" />
</head>

<body>
  <div id="resizer">
    <div id="container">
      <ul id="content">
        <li>Something</li>
      </ul>
    </div>
  </div>
  <button id="add">Add to content</button>
  <button id="remove">Remove from content</button>
  <p>Scroll bar is: <span id="visibility"></span></p>
  <ul id="event-log"></ul>
  <script src="script.js"></script>
</body>

</html>

JavaScript:

var container = document.getElementById("container");
var resizer = document.getElementById("resizer");
interact(resizer)
  .resizable(
    restrict: 
      restriction: 
        left: 0,
        top: 0,
        right: window.innerWidth - 10,
        bottom: window.innerHeight - 10
      
    
  )
  .on('resizemove', function(event) 
    var target = resizer;

    var rect = target.getBoundingClientRect();

    var width = rect.width + event.dx;
    var height = rect.height + event.dy;
    target.style.width = width + 'px';
    target.style.height = height + 'px';
  );

var content = document.getElementById("content");
var add = document.getElementById("add");
add.addEventListener("click", function() 
  content.insertAdjacentHTML("beforeend", "<li>Foo</li>");
);

var remove = document.getElementById("remove");
remove.addEventListener("click", function() 
  content.removeChild(content.lastChild);
);

// Here is the code that pertains to the scrollbar visibility

var log = document.getElementById("event-log");
content.addEventListener("scrollbar", function () 
  log.insertAdjacentHTML("beforeend", "<li>Scrollbar changed!</li>");
);

var visiblity = document.getElementById("visibility");
var previouslyVisible;
function refreshVisibility() 
  var visible = container.scrollHeight > container.clientHeight;
  visibility.textContent = visible ? "visible" : "not visible";
  if (visible !== previouslyVisible) 
    content.dispatchEvent(new Event("scrollbar"));
  
  previouslyVisible = visible;

// refreshVisibility();


var ro = new ResizeObserver(refreshVisibility);
ro.observe(container);
ro.observe(content);

CSS:

* 
  box-sizing: border-box;


#container 
  position: relative;
  top: 10%;
  left: 10%;
  height: 80%;
  width: 80%;
  background: green;
  overflow: auto;


#resizer 
  background: blue;
  height: 200px;
  width: 200px;

【讨论】:

【参考方案6】:

这一切都与何时您需要确定滚动条的可见性有关。

OP 谈到了“在 JavaScript DOM 操作之后”的时间。如果该操作发生在您的代码中,那么就是检查滚动条是否可见的时候了。为什么除此之外还需要一个活动?你怎么不知道这个 DOM 操作是什么时候发生的?

我意识到这是一个老问题,但我现在只是在一个纯 javascript 项目中处理这个问题,我知道何时检查滚动条可见性没有问题。触发用户事件或触发系统事件,我知道 DOM 操作何时发生,因为我是通过 javascript 引起的。我没有看到 javascript DOM 操作超出我的代码意识的情况。

也许 scrollbarVisibilityChange 事件会很方便,但肯定不是必需的。 9 年后,这让我觉得这不是问题。我错过了什么吗?

【讨论】:

【参考方案7】:

如果您只需要检测 Windows 浏览器(IE 除外)上的滚动外观,以下是我的解决方案,以 Resize Observer API 垂直滚动为例。 p>

想法

&lt;div&gt;position: fixed 附加到&lt;body&gt; 使其100% 宽度观察尺寸变化 滚动的出现减小&lt;div&gt;宽度,进而调用观察者回调

为什么只有 Windows 浏览器?

移动和 macOS 浏览器有一个消失的滚动条,该滚动条从文档流中取出,不会影响页面布局。

为什么职位应该是fixed而不是absolute

带有position: fixed 的元素相对于由视口建立的初始包含块定位。

如果&lt;body&gt; 也是绝对定位并且宽度与视口不同,position: absolute 可能会失败。

const innerWidthFiller = document.createElement('div')
innerWidthFiller.style.cssText = 'position: fixed; left: 0; right: 0'
document.body.appendChild(innerWidthFiller)

const detectScroll = () => 
  const clientHeight, scrollHeight = document.documentElement
  window.result.value = scrollHeight > clientHeight


const resizeObserver = new ResizeObserver(detectScroll)
resizeObserver.observe(innerWidthFiller)
#test 
  border: 1px solid;
  white-space: nowrap;


output 
  font-weight: bold;
<button onclick="test.style.fontSize='100vh'">Enlarge the text</button>
<button onclick="test.style.fontSize=''">Reset</button>
Page scroll state: <output id="result"></output>

<hr>

<span id="test">Test element</span>

【讨论】:

以上是关于如何判断有没有出现滚动条的主要内容,如果未能解决你的问题,请参考以下文章

利用JS,如何判断滚动条是不是滚动到页面最底部

jquery如何让滚动条滚动到最底部

如何让bootstrap表格自动出现水平滚动条

JS判断页面是否出现滚动条

解决滚动条加载出现的页面抖动

jquery如何获取元素的滚动条高度等实现代码