<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          「實(shí)用推薦」如何優(yōu)雅的判斷元素是否進(jìn)入當(dāng)前視區(qū)

          共 8491字,需瀏覽 17分鐘

           ·

          2021-05-22 02:07


          背景

          在上篇文章:記一次 「 無限列表 」?jié)L動(dòng)優(yōu)化 中, 

          我介紹了「 如何優(yōu)化一個(gè)無限滾動(dòng)列表 」。

          用到了懶加載方案, 一個(gè)關(guān)鍵點(diǎn)是:需要判斷元素是否在當(dāng)前視區(qū)

          我們今天就看看這個(gè)問題。




          今天的主要內(nèi)容包括:

          • 使用元素位置判斷元素是否在當(dāng)前視區(qū)
          • 使用 Intersection Observer 判斷元素是否在當(dāng)前視區(qū)
          • 實(shí)例:懶加載
          • 實(shí)例:無限滾動(dòng)
          • 實(shí)用 npm 包推薦

          正文

          1. 使用元素位置判斷元素是否在當(dāng)前視區(qū)

          這種方法實(shí)現(xiàn)起來比較簡(jiǎn)單, 我們一步一步來。

          首先:編寫一個(gè) util 函數(shù) isVisible,它將僅接收一個(gè)參數(shù),即 element。

          export const isVisible = (el) => { };

          使用 getBoundingClientRect 獲取該元素的位置

          const rect = el.getBoundingClientRect();

          將找到窗口的高度和寬度

          const vWidth = window.innerWidth || document.documentElement.clientWidth;

          const vHeight = window.innerHeight || document.documentElement.clientHeight;

          再編寫一個(gè)函數(shù),該函數(shù)基本上將接收 xy 點(diǎn),并使用elementFromPoint函數(shù)返回元素。

          const elementFromPoint = function (x, y
            return document.elementFromPoint(x, y); 
          };

          檢查元素是否在窗口內(nèi):

          // Return false if it's not in the viewport
          if (rect.right < 0 
            || rect.bottom < 0
            || rect.left > vWidth 
            || rect.top > vHeight) { 
            return false
          }

          邊界檢查:

          // Return true if any of its four corners are visible
           return (
             el.contains(elementFromPoint(rect.left, rect.top))
             || el.contains(efp(rect.right, rect.top))
             || el.contains(efp(rect.right, rect.bottom))
             || el.contains(efp(rect.left, rect.bottom))
           );

          完整代碼:

          export const isVisible = (el) => {
            const rect = el.getBoundingClientRect();
            const vWidth = window.innerWidth || document.documentElement.clientWidth;
            const vHeight = window.innerHeight || document.documentElement.clientHeight;
            const efp = function (x, yreturn document.elementFromPoint(x, y); };

            // Return false if it's not in the viewport
            if (rect.right < 0 || rect.bottom < 0
                      || rect.left > vWidth || rect.top > vHeight) { return false; }

            // Return true if any of its four corners are visible
            return (
              el.contains(
                elementFromPoint(rect.left, rect.top))
                || el.contains(efp(rect.right, rect.top))
                || el.contains(efp(rect.right, rect.bottom))
                || el.contains(efp(rect.left, rect.bottom))
            );
          };

          用法:

          import { isVisible } from '../utils';
          // ...
          const ele = document.getElementById(id);
          return isVisible(ele);

          邏輯并不復(fù)雜,不過多介紹。

          2. 使用 Intersection Observer 判斷元素是否在當(dāng)前視區(qū)

          Intersection Observer 是一種更高效的方式。

          為什么這么說呢?

          比如說,你想跟蹤 DOM 樹里的一個(gè)元素,當(dāng)它進(jìn)入可見窗口時(shí)得到通知。

          可以通過綁定 scroll 事件或者用一個(gè)定時(shí)器,然后再回調(diào)函數(shù)中調(diào)用元素的 getBoundingClientRect 獲取元素位置實(shí)現(xiàn)這個(gè)功能。

          但是,這種實(shí)現(xiàn)方式性能極差

          因?yàn)槊看握{(diào)用 getBoundingClientRect 都會(huì)強(qiáng)制瀏覽器重新計(jì)算整個(gè)頁(yè)面的布局,可能給你的網(wǎng)站造成相當(dāng)大的閃爍。

          如果你的站點(diǎn)被加載到一個(gè) iframe 里,而你想要知道用戶什么時(shí)候能看到某個(gè)元素,這幾乎是不可能的。

          單原模型(Single Origin Model)和瀏覽器不會(huì)讓你獲取 iframe 里的任何數(shù)據(jù)。

          這對(duì)于經(jīng)常在 iframe 里加載的廣告頁(yè)面來說是一個(gè)很常見的問題。

          IntersectionObserver 就是為此而生的。

          它讓檢測(cè)一個(gè)元素是否可見更加高效

          IntersectionObserver 能讓你知道一個(gè)被觀測(cè)的元素什么時(shí)候進(jìn)入或離開瀏覽器的可見窗口。

          使用 IntersectionObserver 也非常簡(jiǎn)單, 兩步走:

          1. 創(chuàng)建 IntersectionObserver
          const observer = new IntersectionObserver((entries, observer) => {
            entries.forEach((entry) => {
              // ...
              console.log(entry);
              // target element:
              //   entry.boundingClientRect
              //   entry.intersectionRatio
              //   entry.intersectionRect
              //   entry.isIntersecting
              //   entry.rootBounds
              //   entry.target
              //   entry.time
            });
          }, options);
          1. 將元素傳遞給 IntersectionObserver
          const element = document.querySelector('.element');
          observer.observe(element);

          entries 參數(shù)會(huì)被傳遞給你的回調(diào)函數(shù),它是一個(gè) IntersectionObserverEntry 對(duì)象數(shù)組。

          每個(gè)對(duì)象都包含更新過的交點(diǎn)數(shù)據(jù)針對(duì)你所觀測(cè)的元素之一。

          從輸出最有用的特性是:

          • isIntersecting
          • target
          • intersectionRect

          isIntersecting:當(dāng)元素與默認(rèn)根(在本例中為視口)相交時(shí),將為true.

          target:這將是我們將要觀察的頁(yè)面上的實(shí)際元素

          intersectionRect:intersectionRect 告訴元素的可見部分。這將包含有關(guān)元素,其高度,寬度,視口位置等的信息。

          在線 Demo: https://codepen.io/myogeshchavan97/pen/pogrWKV?editors=0011

          更多有用的屬性

          現(xiàn)在我們知道:當(dāng)被觀測(cè)的元素部分進(jìn)入可見窗口時(shí)會(huì)觸發(fā)回調(diào)函數(shù)一次,當(dāng)它離開可見窗口時(shí)會(huì)觸發(fā)另一次。

          這樣就回答了一個(gè)問題:元素 X 在不在可見窗口里。

          但在某些場(chǎng)合,僅僅如此還不夠。

          這時(shí)候就輪到 threshold 登場(chǎng)了。

          它允許你定義一個(gè) intersectionRatio 臨界值。

          每次 intersectionRatio 經(jīng)過這些值的時(shí)候,你的回調(diào)函數(shù)都會(huì)被調(diào)用。

          threshold 的默認(rèn)值是[0],就是默認(rèn)行為。

          如果我們把 threshold 改為[0, 0.25, 0.5, 0.75, 1],當(dāng)元素的每四分之一變?yōu)榭梢姇r(shí),我們都會(huì)收到通知:

          還一個(gè)屬性沒在上文列出: rootMargin.

          rootMargin 允許你指定到跟元素的距離,允許你有效的擴(kuò)大或縮小交叉區(qū)域面積。

          這些 margin 使用 CSS 風(fēng)格的字符串,例如: 10px 20px 30px 40px,依次指定上、右、下、左邊距。

          new IntersectionObserver(entries => {
            // do something with entries
          }, {
            // options
            // 用于計(jì)算相交區(qū)域的根元素
            // 如果未提供,使用最上級(jí)文檔的可見窗口
            rootnull,
            // 同 margin,可以是 1、2、3、4 個(gè)值,允許時(shí)負(fù)值。
            // 如果顯式指定了跟元素,該值可以使用百分比,即根元素大小的百分之多少。
            // 如果沒指定根元素,使用百分比會(huì)出錯(cuò)。
            rootMargin"0px",
            // 觸發(fā)回調(diào)函數(shù)的臨界值,用 0 ~ 1 的比率指定,也可以是一個(gè)數(shù)組。
            // 其值是被觀測(cè)元素可視面積 / 總面積。
            // 當(dāng)可視比率經(jīng)過這個(gè)值的時(shí)候,回調(diào)函數(shù)就會(huì)被調(diào)用。
            threshold: [0],
          });

          有一點(diǎn)要注意:IntersectionObserver 不是完美精確到像素級(jí)別,也不是低延時(shí)性的。

          使用它實(shí)現(xiàn)類似依賴滾動(dòng)效果的動(dòng)畫注定會(huì)失敗。

          因?yàn)榛卣{(diào)函數(shù)被調(diào)用的時(shí)候那些數(shù)據(jù)——嚴(yán)格來說已經(jīng)過期了。

          實(shí)例:懶加載(lazy load)

          有時(shí),我們希望某些靜態(tài)資源(比如圖片),只有用戶向下滾動(dòng),它們進(jìn)入視口時(shí)才加載,這樣可以節(jié)省帶寬,提高網(wǎng)頁(yè)性能。這就叫做"惰性加載"。

          有了 IntersectionObserver API,實(shí)現(xiàn)起來就很容易了。

          function query(selector{
            return Array.from(document.querySelectorAll(selector));
          }

          const observer = new IntersectionObserver(
            function(changes{
              changes.forEach(function(change{
                var container = change.target;
                var content = container.querySelector('template').content;
                container.appendChild(content);
                observer.unobserve(container);
              });
            }
          );

          query('.lazy-loaded').forEach(function (item{
            observer.observe(item);
          });

          上面代碼中,只有目標(biāo)區(qū)域可見時(shí),才會(huì)將模板內(nèi)容插入真實(shí) DOM,從而引發(fā)靜態(tài)資源的加載。

          實(shí)例:無限滾動(dòng)

          無限滾動(dòng)(infinite scroll)的實(shí)現(xiàn)也很簡(jiǎn)單:

          const intersectionObserver = new IntersectionObserver(
            function (entries{
              // 如果不可見,就返回
              if (entries[0].intersectionRatio <= 0return;
              loadItems(10);
              console.log('Loaded new items');
            });

          // 開始觀察
          intersectionObserver.observe(
            document.querySelector('.scrollerFooter')
          );

          無限滾動(dòng)時(shí),最好在頁(yè)面底部有一個(gè)頁(yè)尾欄。

          一旦頁(yè)尾欄可見,就表示用戶到達(dá)了頁(yè)面底部,從而加載新的條目放在頁(yè)尾欄前面。

          這樣做的好處是:

          不需要再一次調(diào)用 observe() 方法, 現(xiàn)有的 IntersectionObserver 可以保持使用。

          實(shí)用 Npm 包推薦

          和今天話題相關(guān)的npm 包推薦的是:react-visibility-sensor

          地址:https://www.npmjs.com/package/react-visibility-sensor

          用法也很簡(jiǎn)答:

          import VisibilitySensor from "react-visibility-sensor";
           
          function onChange (isVisible{
            console.log('Element is now %s', isVisible ? 'visible' : 'hidden');
          }
           
          function MyComponent (props{
            return (
              <VisibilitySensor onChange={onChange}>
                <div>...content goes here...</div>
              </VisibilitySensor>

            );
          }

          動(dòng)態(tài)效果演示:

          在線demo : https://codesandbox.io/s/p73kyx9zpm?file=/src/index.js:174-229


          結(jié)尾

          內(nèi)容大概就這么多, 希望對(duì)大家有所啟發(fā)。


          瀏覽 50
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本爱爱激情网 | 人人看人人摸人人操天天看天天摸天天操 | 无码视频第一页 | 中文精品欧美无线码一区 | 中国一级大黄片 |