检测文档高度变化

我试图检测我的 document高度什么时候变化。这样做之后,我需要运行一些函数来帮助组织页面布局。

我不是在找 window.onresize。我需要整个文件,它比窗户还大。

我如何观察这种变化?

107122 次浏览

As mentioned by vsync there is no event but you can use a timer or attach the handler somewhere else:

// 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);

Find the jsfiddle here.

The command watch() checks any change in a property.

See this link.

Update (Oct 2020):

resizeObserver is a wonderful API (support table)

// 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


Old answer:

Although a "hack", this simple function continuously "listens" (through setTimeout) to changes in an element's height and fire a callback when a change was detected.

It's important to take into account an element's height might change regardless of any action taken by a user (resize, click, etc.) and so, since it is impossible to know what can cause a height change, all that can be done to absolutely guarantee 100% detection is to place an interval height checker :

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

Just my two cents. If by any chance you're using angular then this would do the job:

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

You can use an absolute positioned iframe with zero width inside the element you want to monitor for height changes, and listen to resize events on its contentWindow. For example:

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 (using jQuery but could be adapted to pure JS)

$('.height-change-listener').each(function() {
$(this.contentWindow).resize(function() {
// Do something more useful
console.log('doc height is ' + $(document).height());
});
});

If for whatever reason you have height:100% set on body you'll need find (or add) another container element to implement this on. If you want to add the iframe dynamically you'll probably need to use the <iframe>.load event to attach the contentWindow.resize listener. If you want this to work in IE7 as well as browsers, you'll need to add the *zoom:1 hack to the container element and also listen to the 'proprietary' resize event on the <iframe> element itself (which will duplicate contentWindow.resize in IE8-10).

Here's a fiddle...

vsync's answer is completely fine. Just in case you don't like to use setTimeout and you can use requestAnimationFrame (see support) and of course you are still interested.

In the example below the body gets an extra event sizechange. And every time the height or width of the body changes it is triggered.

(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);
})();

A live demo

The code can be reduced much if you have an own triggerEvent function in your project. Therefore just remove the complete function trigger and replace the line trigger(document.body, 'sizechange'); with for example in jQuery $(document.body).trigger('sizechange');.

I'm using @vsync's solution, like this. I'm using it for automatic scroll on a page like 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);

You can also add a minimum change, and a lot of other things... But this is working for me

Update: 2020

There is now a way to accomplish this using the new ResizeObserver. This allows you to listen to a whole list of elements for when their element changes size. The basic usage is fairly simple:

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'))

The one downside is that currently there is only support for Chrome/Firefox, but you can find some solid polyfills out there. Here's a codepen example I wrote up:

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