JavaScript如何动态监听DOM元素高度详解

作者:大莲芒 时间:2024-05-22 10:32:00 

背景

考虑这样一种情况,产品同学希望达到以下功能:

在我们的网页中有一个固定区域,这个区域会用于渲染从后端拉取的含有图片等资源的富文本字符串。

他需要在内容不超过一个最大高度的时候完全显示所有内容,超过最大内容后仅展示最大高度范围内的内容,超出部分隐藏,并通过一个按钮 “展示更多” 来给用户展示更多的选择。

JavaScript如何动态监听DOM元素高度详解

在这看似简单的需求当中,其实涉及到了一个难点,那就是怎样动态的监听到内容区域的高度变化?

因为在这里面会含有图片资源,他们在渲染的时候会发起网络请求,等待图片加载完成后触发浏览器重排,该区域的高度被撑开。

因此,内容区域的高度是动态变化,且变化的时间点是未知的,那么怎样知道我们的内容区高度发生了变化呢?

为此我做了以下几种尝试:

  • MutationObserver

  • IntersectionObserver

  • ResizeObserver

  • 监听所有资源的 onload 事件

  • iframe(推荐)

MutationObserver

MutationObserver 接口提供了监视对 DOM 树所做更改的能力。它被设计为旧的 Mutation Events 功能的替代品,该功能是 DOM3 Events 规范的一部分。

observe(target, options)

这个方法会根据传入的 options 配置,观察 DOM 树中的单个 Node 或者所有的子孙节点的变化。

他一共有七个属性,这里就不一一介绍了,可以通过 MutationObserverInit 来获取相应的介绍.

那么我们要怎么使用这个 API 来监听目标区域的高度变化呢?

首先我们要创建对该区域的 dom 根结点引用:

const Details = () => {
   // useRef创建引用
   const contentRef = useRef();
   const [height, setHeight] = useState(-1);
   const [observer, setObserver] = useState<MutationObserver>(null!);
   useEffect(() => {
         const observer = new MutationObserver((mutationList) => {
           if (height !== contentRef.current?.clientHeight) {
               console.log('高度变化了!');
               setHeight(contentRef.current.clientHeight);
           }
         });
         setObserver(observer);
   }, []);
   useEffect(() => {
         if (!observer || !contentRef.current) return
         observer.observe(contentRef, {
           childList: true, // 子节点的变动(新增、删除或者更改)
           attributes: true, // 属性的变动
           characterData: true, // 节点内容或节点文本的变动
           subtree: true// 是否将观察器应用于该节点的所有后代节点
         });
   }, [contentRef.current, observer]);
   // 绑定ref
   return<div className="content" dangerouslySetInnerHTML={{ __html: details }} style={{ maxHeight }} ref={contentRef} />
}

经过上面的一番操作之后,发现根本达不到效果,因为我们的 css 属性根本没有发生变化(我们是通过 maxHeight 来约束容器的高度的), 但是资源加载完毕之后,浏览器重排根本没有产生 css 属性的变化,它的高度是自动计算的

因此这个方案无济于事!但是它确实可以监听到认为修改容器的高度产生的变化,比如:contentRef.current.style.height = &lsquo;1000px&rsquo;,这个 api 是可以监听到这一操作的,但是并不符合我们的场景

此外,它的浏览器兼容性也还行:

JavaScript如何动态监听DOM元素高度详解

IntersectionObserver

经过激情编码,最后发现 MutationObserver 根本达不到我们想要的效果之后,其实我的心态已经产生了一些变化,不过不要紧!

我们可以换一种思路,既然我们无法通过监听容器的高度变化来展示相应的 &ldquo;展开更多&rdquo; 操作,那么我们可不可以将这个 &ldquo;展开更多&rdquo; 固定到一个位置上,然后超出部分隐藏,

当我们的内容自动撑开,达到指定高度后,我们这个 &ldquo;展开更多&rdquo; 的操作的按钮就显示出来了,听上去不错,能达到要求!废话不多说,开撸!

因为这里只涉及到相应的 css 样式的书写,就不做展示了。

经过处理之后,确实在容器高度小于指定高度的时候,&ldquo;展示更多&rdquo; 按钮不会展示,超过最大值之后,会将该按钮展示出来,

但是也遇到了一个问题,操作按钮是有高度的,如果我们的内容高度介于最大高度 - 按钮高度 到 容器的最大高度之间, 按钮会产生显示一部分,同时又隐藏一部分的效果,这可不是我们想要的!

显然这种效果是不符合要求的,我们的 &ldquo;展示更多&rdquo; 按钮,只有两种状态,要么全部展示,要么不展示,没有这种部分展示的效果

因此我查阅了相关资料,了解到了 IntersectionObserver 这个 API,它可以监听一个元素是否进入用户视野,它的相关使用方法可以参考这篇文章:IntersectionObserver API 使用教程

它使用起来和 MutationObserver 几乎一样,只是名字不一样而已

它监听的值里面有一个比较重要的属性:intersectionRatio

JavaScript如何动态监听DOM元素高度详解

借助这个 API,我的设计思路是这样的:

当用户滚动网页的时候(或者不滚动,此时目标区域已经出现在屏幕中),可以得到 intersectionRatio 的值,通过判断这个值是否等于 1 来决定要不要展示 &ldquo;展示更多&rdquo; 按钮

但经过我的编码实现后,发现滚动事件发生的时候,intersectionRatio 的变化是不可靠的,有时候完全可见了,但是它并不等于 1。经过多轮实验,结果依然如此。但是它确实可以用来判断一个元素是否进入用户视野

由于使用上结果的不可靠,我放弃这个方案(可能是我使用方式上出了问题)

它的各浏览器兼容性如下:

JavaScript如何动态监听DOM元素高度详解

ResizeObserver

顾名思义,这个 API 就是专门监听 DOM 尺寸变化的,只不过它还处于试验阶段,各浏览器的兼容性很差,所以基本不考虑

具体使用方法可以参考这篇文章:检测 DOM 尺寸变化 JS API ResizeObserver 简介

它现阶段各浏览器的兼容性情况:

JavaScript如何动态监听DOM元素高度详解

监听所有资源的 onload 事件

既然上述方法都不行,那么我绞尽脑汁,又想出了另外一种方法:监听所有带有 src 属性的 DOM 元素的 onload 事件,通过他的回调来判断当前容器的高度情况

这种实现方式,在思路上是完全符合目的的,具体做法参考如下:

const [height, setHeight] = useState(-1);
const [showMore, setShowMore] = useState(false);
// contentRef 的定义见 MutationObserver 一节
useEffect(() => {
 const sources = contentRef.current.querySelectorAll("[src]");
 sources.onload = () => {
   const height = contentRef?.current?.clientHeight ?? 0;
   const show = height >= parseInt(MAX_HEIGHT, 10);
   setHeight(height);
   setShowMore(show);
 };
}, []);

通过这种方式可以实现对富文本中的图片进行加载后,对容器高度进行相应的判断。

但是这种方式,存在不确定性,即无法判断是否找齐了所有高度由内容撑开的资源。

Iframe

这是终极方案,也是在此背景中所采用的方案。

既然 window 可以监听到 resize 事件,那么我们就可以利用 iframe 来达到同样的效果,具体做法就是在容器里面嵌套一个隐藏的高度为 100% 的 iframe,通过监听他的 resize 事件,来判断当前容器的高度。

话不多说,具体实现方式如下:

const Detail: FC<{}> = () => {
 const ref = useRef<HTMLDivElement>(null);
 const ifr = useRef<HTMLIFrameElement>(null);
 const [height, setHeight] = useState(-1);
 const [showMore, setShowMore] = useState(false);
 const [maxHeight, setMaxHeight] = useState(MAX_HEIGHT);
 const introduceInfo = useAppSelect(
   (state) => state.courseInfo?.data?.introduce_info ?? {}
 );
 const details = introduceInfo.details ?? "";
 const isFolded = maxHeight === MAX_HEIGHT;
 const onresize = useCallback(() => {
   const height = ref?.current?.clientHeight ?? 0;
   const show = height >= parseInt(MAX_HEIGHT, 10);
   setHeight(height);
   setShowMore(show);
   if (ifr.current && show) {
     ifr.current.remove();
   }
 }, []);
 useEffect(() => {
   if (!ref.current || !ifr.current?.contentWindow) return;
   ifr.current.contentWindow.onresize = onresize;
   onresize();
 }, [details]);
 if (!details) returnnull;
 return (
   <section className="section detail-content">
     <div className="content-wrapper">
       <div
         className="content"
         dangerouslySetInnerHTML={{ __html: details }}
         style={{ maxHeight }}
         ref={ref}
       />
       {/* 这个iframe是用来动态监听content高度的变化的 */}
       <iframe title={IFRAME_ID} id={IFRAME_ID} ref={ifr} />
     </div>
     {isFolded && showMore && (
       <>
         <div
           className="show-more"
           onClick={() => {
             setMaxHeight(isFolded ? "none" : MAX_HEIGHT);
           }}
         >
           查看全部
           <IconArrowDown className="icon" />
         </div>
         <div className="mask" />
       </>
     )}
   </section>
 );
};

这种方式实际上就是对 ResizeObserver 的一种 hack,经过多次实践,符合功能要求。

总结:

解决问题要尽可能的考虑多种情况,对比多种方案,采取最为可靠的一种方案。

注: 监听 DOM 元素的高度变化,可以采用内嵌 iframe 的方式来解决。

来源:https://blog.csdn.net/qq_34574204/article/details/125706202

标签:JavaScript,动态监听,DOM
0
投稿

猜你喜欢

  • python实现关键词提取的示例讲解

    2021-09-02 05:38:58
  • git-pycharm配置.ignore文件的详细过程

    2023-04-18 23:05:57
  • python获取文件版本信息、公司名和产品名的方法

    2022-05-31 17:30:23
  • python编写WAF与Sqlmap结合实现指纹探测

    2022-05-23 08:10:17
  • js实现关闭网页出现是否离开提示

    2024-05-09 10:36:13
  • 六个窍门助你提高Python运行效率

    2021-07-19 08:31:15
  • Pytorch1.5.1版本安装的方法步骤

    2021-02-07 00:57:09
  • mysql模糊匹配多个值的两种方法实例

    2024-01-27 10:12:06
  • Python之维度dim的定义及其理解使用方式

    2021-06-10 12:33:04
  • Python装饰器使用方法全面梳理

    2023-04-27 08:22:15
  • Python序列化基础知识(json/pickle)

    2021-04-24 01:09:34
  • 基于centos7快速安装mysql5.7教程解析

    2024-01-26 14:59:28
  • JS实现键值对遍历json数组功能示例

    2024-04-10 10:52:40
  • FCKeditor ASP.NET 上传附件研究

    2023-12-30 05:27:22
  • python进阶之自定义可迭代的类

    2022-09-20 10:18:48
  • Python写的服务监控程序实例

    2022-09-01 13:12:31
  • Python实现提取XML内容并保存到Excel中的方法

    2022-03-14 19:06:43
  • python机器学习基础线性回归与岭回归算法详解

    2021-03-09 07:29:43
  • python使用RNN实现文本分类

    2023-10-17 14:07:18
  • 设计中基于人类学的田野调查与比较研究法 ——浅谈用研与竞品分析方法之理论基础

    2009-08-31 16:45:00
  • asp之家 网络编程 m.aspxhome.com