【问题标题】:Detect Document Height Change检测文档高度变化
【发布时间】:2020-10-27 01:50:16
【问题描述】:

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

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

我如何观察这种变化?

【问题讨论】:

标签: javascript domdocument


【解决方案1】:

更新(2020 年 10 月):

resizeObserver 是一个很棒的 AP​​I (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

旧答案:

虽然是“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

【讨论】:

  • 我知道我在这里挖掘一个旧帖子,但是 checkDocumentheight 函数会一直运行,对吧?这似乎会减慢浏览器一遍又一遍地检查文档高度的速度。
  • @Cloudkiller - 是的,它不断运行,是的,它会损害性能,但我不知道更好的方法。
  • @ShashwatTripathi - 我看不出它为什么不起作用。请调试它并解释哪一段代码不能具体工作。 iPad 也是一种设备,而不是浏览器。哪个浏览器出了问题?
  • 如何在iframe中使用?
  • @vsync,实际上,在我有 iframe 的页面中,我设法设置 iframe 高度 = iframe 的内部内容在 onload 和调整外部页面事件的高度,以避免滚动,现在弹出窗口显示在控件悬停时的 iframe 内容内(位于底部)。所以我的 iframe 现在有滚动,我不想悬停该项目
【解决方案2】:

您可以在要监视高度变化的元素内部使用定位为零的absolute 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,您可能需要使用&lt;iframe&gt;.load 事件来附加contentWindow.resize 侦听器。如果您希望它在 IE7 和浏览器中工作,您需要将 *zoom:1 hack 添加到容器元素,并在 &lt;iframe&gt; 元素本身上侦听“专有”resize 事件(这将在 IE8-10 中重复 contentWindow.resize)。

Here's a fiddle...

【讨论】:

  • 我认为这是一个非常聪明的解决方案,但是由于您描述的原因,使用100vh 而不是100% 会更好。但是,您将失去与一些不支持视口高度的旧浏览器的兼容性。
  • @Tim - 100vh 将是视口(也称为窗口)的高度,而我们希望文档的高度(即内容,这不是同一件事)检测何时发生变化,100% 提供。 (当视口高度随window.resize 事件发生变化时很容易检测到。)
  • 你说得对,不知道为什么我认为这样会更好。
  • 这真的很聪明——谢谢@Jake!如果你要定义一个命名函数,JS 甚至可以是单行的(如果这是你的风格):$($('.height-change-listener')[0].contentWindow).on('resize', doSomethingMoreUseful);
  • 如果有人想要这个作为 npm 包:github.com/bertyhell/element-height-observer
【解决方案3】:

只有我的两分钱。如果您有任何机会使用角度,那么这将完成这项工作:

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

【讨论】:

  • 你的意思是 $(document).height(); ?
  • 这不起作用,它只会检查角度范围 $digest 上的高度,它不包括许多其他可以改变高度的方式,如 css 动画、图像加载和任何 js 代码不会触发角度摘要(例如非角度库)
  • 答案清楚地表明“如果你有任何机会使用角度”。话虽这么说 - 对于基于角度的应用程序,上面的代码可以优雅地完成这项工作。
  • Angular 如何检测变化?它是使用某种事件侦听器,还是使用浪费且昂贵的轮询来破坏您的站点?我们需要知道。
【解决方案4】:

更新: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,但你可以在那里找到一些可靠的 polyfill。这是我写的一个codepen示例:

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

【讨论】:

  • 可以,但是Safari不支持(当然IE也不支持)
  • 没错 > “一个缺点是目前只支持 Chrome/Firefox,但你可以在那里找到一些可靠的 polyfill。”
  • 从 3 月 24 日发布的 Safari 13.1 开始支持。
【解决方案5】:

正如 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 here

【讨论】:

  • 请永远不要像这样使用轮询。它会在较慢的设备上削弱您的网站。如果你想实现的功能没有其他方法,请回去找老板说做不到。现在网络上充斥着像这样的劣质解决方案,一些在主要网站上,几乎无法使用。
【解决方案6】:

vsync 的回答完全没问题。以防万一您不喜欢使用setTimeout,您可以使用requestAnimationFrame (see support),当然您仍然感兴趣。

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

(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

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

【讨论】:

  • 使用requestAnimationFrame 代替setTimout(或者setInterval)的一个缺点是大小检查循环运行太频繁,每隔几毫秒。使用setTimeout,您可以将循环间隔增加到合理的数量。
  • @vsync 的原始答案并不好——太糟糕了。除非您想削弱您的网站,否则请不要使用轮询。如果没有您可以监听的事件,那么请放弃您尝试实施的任何功能,以造福所有人。
【解决方案7】:

我正在使用@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);

您还可以添加一个最小的更改,以及许多其他的东西......但这对我有用

【讨论】:

  • 这不是答案。这是一条评论。
【解决方案8】:

命令 watch() 检查属性的任何变化。

看到这个link

【讨论】:

  • WATCH 命令不是标准的,只有 GECKO 支持。goo.gl/11uqL 但你可以使用 polyfill:gist.github.com/eligrey/384583
  • 看到big red warning here? “一般情况下,您应该尽可能避免使用 watch() 和 unwatch()。这两个方法仅在 Gecko 中实现,主要用于调试用途。此外,使用观察点对性能有严重的负面影响"
猜你喜欢
  • 2012-05-31
  • 1970-01-01
  • 2021-06-18
  • 2012-02-20
  • 1970-01-01
  • 1970-01-01
  • 2017-11-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多