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 = ‘1000px’,这个 api 是可以监听到这一操作的,但是并不符合我们的场景

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

IntersectionObserver

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ResizeObserver

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

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

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

监听所有资源的 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 的方式来解决。

到此这篇关于JavaScript如何动态监听DOM元素高度详解的文章就介绍到这了,更多相关JS动态监听DOM 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JavaScript DOM实现简单留言板

    本文实例为大家分享了JavaScript DOM实现简单留言板的具体代码,供大家参考,具体内容如下 效果图: 先准备html代码: <body>     <textarea name="" id=""></textarea>     <button>发布</button>     <ul>       </ul> </body> css代码如下: <style>

  • JavaScript的DOM与BOM的区别与用法详解

    目录 1. 简述:何为DOM,何为BOM? 2. DOM及其相关操作 2.1DOM树 2.2DOM的一些常见的操作元素方法 获取节点的DOM方法 获取/设置元素的属性值的DOM方法 创建节点(Node)的DOM方法 增添节点的DOM方法 删除节点的DOM方法 DOM常见的一些属性 3. BOM及其相关操作 3.1 BOM总述 3.2 BOM常见对象的介绍 window对象 location对象 history对象 navigator对象 总结 1. 简述:何为DOM,何为BOM? 在文章开始之初

  • JavaScript操作DOM对象详解

    一.DOM基础 DOM(Document Object Model)即文档对象模型,针对HTML和XML文档的API(应用程序接口).DOM描绘了一个层次化的节点树,运行开发人员添加.移除和修改页面的某一部分.DOM中的三个字母,D(文档)可以理解为整个Web加载的网页文档:O(对象)可以理解为类似Window对象之类的东西,可以调用属性和方法,这里我们说的是document对象:M(模型)可以理解为网页文档的树型结构. 1.节点 加载HTML页面时,Web浏览器生成一个树型结构,用来表示页面内

  • JavaScript Dom对象的操作

    目录 一.核心 1.获得Dom节点 2.更新节点 2.1 实战演练 3.删除Dom节点 4.插入节点 4.1 把已有的标签进行插入 4.2 创建一个新的标签,实现插入 4.3 在子节点前插入(insertBefore) 一.核心 浏览器网页就是一个Dom树形结构 更新:更新Dom节点 遍历Dom节点:获得Dom节点 删除:删除一个Dom节点 添加:添加一个新的节点 要操作一个Dom节点,就必须要先获得这个Dom节点 1.获得Dom节点 <body> <div id="div1&

  • JavaScript中BOM,DOM和事件的用法详解

    目录 BOM 概念 对象组成 Window:窗口对象 Location:地址栏对象 History:历史记录对象 DOM 概念 W3C DOM 标准被分为 3 个不同的部分 核心DOM模型 HTML DOM 事件监听机制 概念 常见的事件 事件简单学习 BOM 概念 BOM全称Browser Object Model浏览器对象模型,将浏览器的各个组成部分封装成对象. 对象组成 Window:窗口对象 Navigator:浏览器对象 Screen:显示器屏幕对象 History:历史记录对象 Lo

  • Javascript DOM的简介,节点和获取元素详解

    目录 DOM 节点 元素节点: 文本节点: 属性节点: 获取元素 getElementById() getElementsByTagName() getElementsByClassName() 总结: DOM 文档:DOM中的"D",当创建一个网页并把它加载到Web浏览器中时,它把编写的网页文档转换为一个文档对象. 对象:DOM中的"O",对象是一种自给自足的数据集合.与某个特定对象相关联的变量被称为这个对象的属性,只能通过某个特定对象去调用的函数被称为这个对象的

  • Javascript 虚拟 DOM详解

    目录 什么是虚拟 dom? 为什么需要虚拟dom? 虚拟dom是如何转换为真实dom的? 模板和虚拟dom的关系 注入 挂载 完整流程 总结 什么是虚拟 dom? 虚拟 dom 本质上就是一个普通的JS对象(mounted 中打印 this. _vnode 就是该对象内容),用于描述视图的界面结构 在vue中,每个组件都有一个render函数,每个render函数都会返回一个虚拟dom树,这也就意味着每个组件都对应一棵虚拟DOM树 vnode 是一个普通的 JS 对象,用于描述界面上应该有什么,

  • JavaScript的DOM事件详解

    目录 1.事件对象 2.事件流 3.事件委托 4.综合案例 总结 1.事件对象 [获取事件对象] 什么是事件对象:是个对象,这个对象里有事件触发时的相关信息. 事件对象的语法 元素.addEventListener('click',function(e){}) [事件对象常用属性] type:获取当前的事件类型 clientX/clientY:获取光标相对于浏览器可见窗口左上角的位置 offsetX/offsetY:获取光标相对于当前DOM元素左上角的位置 key:用户按下的键盘的值 [案例]:

  • JavaScript中DOM操作常用事件总结

    目录 常用事件 演示得到焦点和失去焦点 演示 鼠标划过和离开 点击事件 load加载页面事件 onkeyup 键盘弹起事件 内容变化事件 选中时触发 上一篇聊了如何同JavaScript获得页面元素,而获得页面元素的的目的就是操作这个元素的一行为,而这个行为是通过某个条件进行触发的.而这个一系列在JavaScript中称之为事件. 由此可以看出事件分三个部分: 事件源头: 也就是要操作的元素是谁. 事件类型: 也就是事件触发条件,比如鼠标点击以及鼠标滑过等. 事件处理: 如果触发了这个行为,如何

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

    背景 考虑这样一种情况,产品同学希望达到以下功能: 在我们的网页中有一个固定区域,这个区域会用于渲染从后端拉取的含有图片等资源的富文本字符串. 他需要在内容不超过一个最大高度的时候完全显示所有内容,超过最大内容后仅展示最大高度范围内的内容,超出部分隐藏,并通过一个按钮 “展示更多” 来给用户展示更多的选择. 在这看似简单的需求当中,其实涉及到了一个难点,那就是怎样动态的监听到内容区域的高度变化? 因为在这里面会含有图片资源,他们在渲染的时候会发起网络请求,等待图片加载完成后触发浏览器重排,该区域

  • JavaScript中MutationObServer监听DOM元素详情

    一.基本使用 可以通过MutationObserver构造函数实例化,参数是一个回调函数. let observer = new MutationObserver(() => console.log("change")); console.log(observer); observer对象原型链如下: MutationObserver实例: 可以看到有disconnect.observer.takeRecords方法. 1. observer方法监听 observer方法用于关联

  • Android 动态注册监听网络变化实例详解

    Android 动态注册监听网络变化实例详解 新建一个BroadcastTest项目,然后修改MainActivity中的代码,如下: public class MainActivity extends AppCompatActivity { private IntentFilter intentFilter; private NetworkChangeReceiver networkChangeReceiver; @Override protected void onCreate(Bundle

  • Android ListView里控件添加监听方法的实例详解

    Android ListView里控件添加监听方法的实例详解 关于ListView,算是android中比较常见的控件,在ListView我们通常需要一个模板,这个模板指的不是住模块,而是配置显示在ListView里面的东西,今天做项目的时候发现想要添加一个ImageView监听方法,发现崩了,也许是好久没有动ListView竟然忘了不能直接在主UI的xml文件里面调用其他xml文件的控件,哪怕ListView用的是这个xml文件. [错误示范]: 直接调用ImageView这个控件是ListV

  • SpringMVC拦截器实现监听session是否过期详解

    本文主要向大家介绍了SpringMVC拦截器实现:当用户访问网站资源时,监听session是否过期的代码,具体如下: 一.拦截器配置 <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/user/login"/> <!-- 不拦截登录请求 --> <mvc:exclude-

  • Android 监听网络状态方法详解

    Android 监听网络状态方法详解 一.加入网络权限 获取网络信息需要在AndroidManifest.xml文件中加入相应的权限. <uses-permission Android:name="android.permission.ACCESS_NETWORK_STATE" /> 二.判断手机网络的几个方案 1)判断是否有网络连接 public boolean isMobileConnected(Context context) { if (context != nul

  • JavaScript利用html5新方法操作元素类名详解

    目录 1.classList属性 2.实务应用 早先JavaScript处理起来特别不方便,需要先取到class属性,然后对字符串进行处理. 现在html5给所有元素增加了classList属性来操作类属性,非常方便. 1.classList属性 先看如下代码: <ul class="nav"> <li class="active">栏目1</li> <li>栏目2</li> <li>栏目3&l

  • jQuery添加删除DOM元素方法详解

    本文实例分析了jQuery添加删除DOM元素的方法.分享给大家供大家参考,具体如下: 介绍 DOM是Document Object Modeule的缩写,一般来说,DOM操作分成3个方面. 1.DOM Core DOM Core并不专属于javascript,任何一种支持DOM的程序设计语言都可以使用它,用途也远不止仅限于网页,也可以用来处理任何一种使用标记语言编写出来的文档,如XML. 例如:document,getElementsByTagName("form");//使用DOM

  • jQuery绑定事件监听bind和移除事件监听unbind用法实例详解

    本文实例讲述了jQuery绑定事件监听bind和移除事件监听unbind用法.分享给大家供大家参考,具体如下: 这里分别采用后bind(eventType,[data],Listener)//data为可选参数,one()该方法绑定的事件触发一次后自动删除,unbind(eventType,Listener), 实例: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w

  • Android监听Home键实例详解

    本文实例讲述了Android监听Home键的方法.分享给大家供大家参考,具体如下: 将到android中Home键的监听,很多人第一反应时重写相应Activity的onKeyDown()方法,监听当按下的键的keyCode为KEYCODE_HOME时,进行自己的相应的处理.如: @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_HOME) { stop

随机推荐