检测文档高度的变化。

104

我正在尝试检测我的document高度何时发生变化。一旦发生变化,我需要运行几个函数来帮助组织我的页面布局。

我不是在寻找window.onresize。我需要整个大于窗口的文档。

如何观察这种变化?


1
嗯,实际上没有针对那个的事件,我想你可能需要使用计时器函数。 - vsync
我本来希望避免那种递归。而且,它会变得很跳跃。 - Steve Robbins
1
尝试这个:https://dev59.com/pG035IYBdhLWcg3wQtkg - codefactor
我尝试过,但对我没有用。它似乎无法捕捉到我的$.animate(),但它可以在元素被删除/添加时进行选择。 - Steve Robbins
8个回答

156

更新(2020年10月):

resizeObserver 是一个非常优秀的 API (兼容性表格)。

// create an Observer instance
const resizeObserver = new ResizeObserver(entries => 
  console.log('Body height changed:', entries[0].target.clientHeight)
)

// start observing a DOM node
resizeObserver.observe(document.body)

// click anywhere to rnadomize height
window.addEventListener('click', () =>
  document.body.style.height = Math.floor((Math.random() * 5000) + 1) + 'px'
)
click anywhere to change the height


新答案:

虽然这个简单的函数是一种“hack”,但它通过setTimeout不断地“监听”元素高度的变化,并在检测到变化时触发回调。

需要注意的是,一个元素的高度可能会发生变化,而不管用户是否采取任何操作(例如缩放点击等)。因此,为了确保100%的检测准确性,可以使用定时器来定期检查元素的高度:

function onElementHeightChange(elm, callback) {
  var lastHeight = elm.clientHeight, newHeight;

  (function run() {
    newHeight = elm.clientHeight;
    if (lastHeight != newHeight)
      callback(newHeight)
    lastHeight = newHeight

    if (elm.onElementHeightChangeTimer)
      clearTimeout(elm.onElementHeightChangeTimer)

    elm.onElementHeightChangeTimer = setTimeout(run, 200)
  })()
}

// to clear the timer use:
// clearTimeout(document.body.onElementHeightChangeTimer);

// DEMO:
document.write("click anywhere to change the height")

onElementHeightChange(document.body, function(h) {
  console.log('Body height changed:', h)
})

window.addEventListener('click', function() {
  document.body.style.height = Math.floor((Math.random() * 5000) + 1) + 'px'
})
LIVE DEMO


6
我知道我在挖掘一个旧帖子,但是checkDocumentHeight函数会一直运行,对吗?这似乎会使浏览器一遍又一遍地检查文档的高度,从而导致浏览器变慢。 - Cloudkiller
6
@Cloudkiller - 是的,它会持续运行,而且会影响性能,但我不知道更好的方法。 - vsync
4
@ShashwatTripathi - 我认为这个方法能够成功。请调试它并解释哪一部分代码有问题。另外,iPad是一个设备,而不是一个浏览器。哪个浏览器出了问题? - vsync
1
@vsync, 实际上,在一个页面中,我有一个iframe,并且我已经成功地在外部页面的onload和resize事件中设置了iframe的高度= iframe内部内容的高度,以避免滚动条出现。 现在,在iframe的内容中,当鼠标悬停在控件上时,会弹出一个弹出窗口(位于底部)。因此,我的iframe现在有滚动条,而我不希望在悬停该项时出现滚动条。 - VISHMAY
现场演示在Chrome和Safari上输出文档的宽度而不是高度。 - Chewie The Chorkie
显示剩余6条评论

30
您可以在要监测高度变化的元素内部使用宽度为零的绝对定位的iframe,并监听其contentWindow上的resize事件。例如:

HTML

<body>
  Your content...
  <iframe class="height-change-listener" tabindex="-1"></iframe>
</body>

CSS

body {
  position: relative;
}
.height-change-listener {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  height: 100%;
  width: 0;
  border: 0;
  background-color: transparent;
}

JavaScript(使用jQuery,但可以适应纯JS)

$('.height-change-listener').each(function() {
  $(this.contentWindow).resize(function() {
    // Do something more useful
    console.log('doc height is ' + $(document).height());
  });
});
如果因为某些原因您在 body 上设置了 height:100%,则需要找到(或添加)另一个容器元素来实现此功能。如果您想动态添加 iframe,则可能需要使用 <iframe>.load 事件来附加 contentWindow.resize 监听器。如果您希望这也适用于 IE7 和其他浏览器,则需要向容器元素添加 *zoom:1 垫片,并在 <iframe> 元素本身上监听“专有”的 resize 事件(这将在 IE8-10 中复制 contentWindow.resize)。

这是一个演示示例...


4
@Tim - 100vh 是视口(也就是窗口)的高度,而我们想要检测文档的高度(即内容,这不是同一件事),以便在其更改时进行检测,这可以通过使用 100% 来实现。 (使用 window.resize 事件很容易检测视口高度的变化。) - Jake
这真的很聪明 - 谢谢@Jake!如果您定义了一个命名函数,JS甚至可以是一行代码(如果这是您的风格):$($('.height-change-listener')[0] .contentWindow).on('resize',doSomethingMoreUseful); - Bungle
4
如果有人想要将此作为npm包使用:https://github.com/bertyhell/element-height-observer - Berty
1
@Noitidart 这取决于时间和具体的浏览器实现。如果您在“load”事件之前附加,有时可能会起作用,但有时您附加到的内容可能尚未完全初始化。为了涵盖所有情况并尽快附加,请最初进行附加,然后在“load”上检查您的事件处理程序是否实际附加。 - Jake
1
这是一个非常聪明的解决方案! - Anthony M.
显示剩余4条评论

14

更新:2020年

现在可以使用新的 ResizeObserver 来实现此操作。这使您可以监听整个元素列表以便在其元素更改大小时进行响应。基本用法非常简单:

const observer = new ResizeObserver(entries => {
  for (const entry of entries) {
    // each entry is an instance of ResizeObserverEntry
    console.log(entry.contentRect.height)
  }
})
observer.observe(document.querySelector('body'))

唯一的缺点是目前只支持Chrome/Firefox,但你可以找到一些靠谱的polyfills。这里有一个我写的codepen示例:

https://codepen.io/justin-schroeder/pen/poJjGJQ?editors=1111


它能用,但不支持Safari(当然也不支持IE)。 - Sergey Volkov
1
没错,唯一的缺点是目前只支持Chrome/Firefox,但你可以在外面找到一些可靠的polyfills。 - jpschroeder
3
截至2023年3月23日发布的Safari 13.1版本已经支持该功能。 - dimention
现在支持所有主流浏览器。 - aderchox

12

这只是我的个人建议。如果你碰巧正在使用Angular,那么这个代码可以胜任:

$scope.$watch(function(){ 
 return document.height();
},function onHeightChange(newValue, oldValue){
 ...
});

4
你是指 $(document).height(); 吗? - Amyth
3
这个不起作用,它只会在 Angular 范围 $digest 期间检查高度,而这并不能涵盖许多其他方式,如 CSS 动画、图像加载以及任何不触发 Angular digest(例如非 Angular 库)的 JS 代码中高度可能发生变化的情况。 - Benja
1
答案明确地说明了“如果你碰巧在使用Angular”。也就是说,对于基于Angular的应用程序,上述代码可以很好地完成工作。 - Dan Ochiana
Angular是如何检测变化的?它使用某种事件监听器还是使用浪费昂贵的轮询来瘫痪您的网站?我们需要知道。 - Jake

4

正如vsync所提到的,没有事件,但您可以使用计时器或将处理程序附加在其他位置:

// get the height
var refreshDocHeight = function(){
    var h = $(document).height();
    $('#result').html("Document height: " + h);
};

// update the height every 200ms
window.setInterval(refreshDocHeight, 200);

// or attach the handler to all events which are able to change 
// the document height, for example
$('div').keyup(refreshDocHeight);

这里有jsfiddle示例


请绝不要像这样使用轮询。在速度较慢的设备上,它会使您的网站受损。如果没有其他实现所需功能的方法,请返回给老板并告知无法完成。现在,网络上充斥着类似这样的低劣解决方案,有些还出现在主要网站上,几乎变得无法使用。 - Jake

1

vsync的答案完全没问题。如果您不想使用 setTimeout,您可以使用requestAnimationFrame (查看支持),当然,如果您仍然感兴趣。

在下面的示例中,body会获得一个额外的事件sizechange。每次body的高度或宽度发生变化时,都会触发该事件。

(function checkForBodySizeChange() {
    var last_body_size = {
        width: document.body.clientWidth,
        height: document.body.clientHeight
    };

    function checkBodySizeChange()
    {
        var width_changed = last_body_size.width !== document.body.clientWidth,
            height_changed = last_body_size.height !== document.body.clientHeight;


        if(width_changed || height_changed) {
            trigger(document.body, 'sizechange');
            last_body_size = {
                width: document.body.clientWidth,
                height: document.body.clientHeight
            };
        }

        window.requestAnimationFrame(checkBodySizeChange);
    }

    function trigger(element, event_name, event_detail)
    {
        var evt;

        if(document.dispatchEvent) {
            if(typeof CustomEvent === 'undefined') {
                var CustomEvent;

                CustomEvent = function(event, params) {
                    var evt;
                    params = params || {
                        bubbles: false,
                        cancelable: false,
                        detail: undefined
                    };
                    evt = document.createEvent("CustomEvent");
                    evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
                    return evt;
                };

                CustomEvent.prototype = window.Event.prototype;

                window.CustomEvent = CustomEvent;
            }

            evt = new CustomEvent(event_name, {"detail": event_detail});

            element.dispatchEvent(evt);
        }
        else {
            evt = document.createEventObject();
            evt.eventType = event_name;
            evt.eventName = event_name;
            element.fireEvent('on' + event_name, evt);
        }
    }

    window.requestAnimationFrame(checkBodySizeChange);
})();

一个实时演示

如果您在项目中拥有自己的triggerEvent函数,那么代码可以大大减少。因此,只需删除完整的trigger函数,并将trigger(document.body, 'sizechange');这一行替换为例如jQuery中的$(document.body).trigger('sizechange');


2
使用requestAnimationFrame替代setTimout(或者setInterval)的一个缺点是,大小检查循环会每隔几毫秒运行一次。使用setTimeout可以将循环间隔增加到合理的时间。 - Gustavo Straube
@vsync的原始回答不好,非常糟糕。请不要使用轮询,除非你想让你的网站瘫痪。如果没有可以监听的事件,请放弃你正在尝试实现的任何功能,这对每个人都有好处。 - Jake

-1

我正在使用@vsync的解决方案,就像这样。我将其用于类似Twitter页面的自动滚动。

const scrollInterval = (timeInterval, retry, cb) => {
    let tmpHeight = 0;
    const myInterval = setInterval(() => {
        console.log('interval');
        if (retry++ > 3) {
            clearInterval(this);
        }
        const change = document.body.clientHeight - tmpHeight;
        tmpHeight = document.body.clientHeight;
        if (change > 0) {
            cb(change, (retry * timeInterval));
            scrollBy(0, 10000);
        }
        retry = 0;
    }, timeInterval);
    return myInterval;
};

const onBodyChange = (change, timeout) => {
    console.log(`document.body.clientHeight, changed: ${change}, after: ${timeout}`);
}

const createdInterval = scrollInterval(500, 3, onBodyChange);

// stop the scroller on some event
setTimeout(() => {
    clearInterval(createdInterval);
}, 10000);

你也可以添加最小更改和许多其他功能……但这对我有效


这不是一个答案,而是一条评论。 - Jake

-2

命令watch()检查属性的任何更改。

请参见link


WATCH 命令不是标准命令,只受 GECKO 支持。但是你可以使用 Polyfill:https://gist.github.com/eligrey/384583。 - vsync
7
请查看这里的重要警告“一般情况下,应尽可能避免使用 watch() 和 unwatch() 方法。这两个方法仅在 Gecko 中实现,主要用于调试。此外,使用监视点会对性能产生严重的负面影响。” - Tomas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接