您好,登錄后才能下訂單哦!
這篇文章主要介紹“JavaScript怎么動態監聽DOM元素高度”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“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 樣式的書寫,就不做展示了。
經過處理之后,確實在容器高度小于指定高度的時候,“展示更多” 按鈕不會展示,超過最大值之后,會將該按鈕展示出來,
但是也遇到了一個問題,操作按鈕是有高度的,如果我們的內容高度介于最大高度 - 按鈕高度 到 容器的最大高度之間, 按鈕會產生顯示一部分,同時又隱藏一部分的效果,這可不是我們想要的!
顯然這種效果是不符合要求的,我們的 “展示更多” 按鈕,只有兩種狀態,要么全部展示,要么不展示,沒有這種部分展示的效果
它使用起來和 MutationObserver 幾乎一樣,只是名字不一樣而已
它監聽的值里面有一個比較重要的屬性:intersectionRatio
借助這個 API,我的設計思路是這樣的:
當用戶滾動網頁的時候(或者不滾動,此時目標區域已經出現在屏幕中),可以得到 intersectionRatio 的值,通過判斷這個值是否等于 1 來決定要不要展示 “展示更多” 按鈕
但經過我的編碼實現后,發現滾動事件發生的時候,intersectionRatio 的變化是不可靠的,有時候完全可見了,但是它并不等于 1。經過多輪實驗,結果依然如此。但是它確實可以用來判斷一個元素是否進入用戶視野
由于使用上結果的不可靠,我放棄這個方案(可能是我使用方式上出了問題)
它的各瀏覽器兼容性如下:
ResizeObserver
顧名思義,這個 API 就是專門監聽 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 的方式來解決。
關于“JavaScript怎么動態監聽DOM元素高度”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。