<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>

          dom 獲取不到?試試 CSS 動(dòng)畫監(jiān)聽(tīng)元素渲染吧

          共 12650字,需瀏覽 26分鐘

           ·

          2024-04-10 18:29

          在數(shù)據(jù)驅(qū)動(dòng)視圖的框架下,你最頭疼的事情是什么?沒(méi)錯(cuò),就是獲取dom。大部分業(yè)務(wù)邏輯都可以在數(shù)據(jù)層面進(jìn)行處理,但有些情況就不得不去獲取真實(shí)的dom,比如獲取元素的寬高

                  
                    
                    
                  
                    dom.offsetHeight

          或者調(diào)用某些dom方法等

                  
                    
                    
                  
                    dom.scrollTop = 100

          通常在框架里,比如說(shuō)vue中,會(huì)如何獲取真實(shí) dom 呢?我想大家可能都用過(guò)這樣一個(gè)方法nextTick,用于在數(shù)據(jù)更新后獲取 dom,如下

                  
                    
                    
                  
                    this.show = true
          this.$nextTick(() => (
          document.getElementById('xx').scrollTop = 100
          ))

          用過(guò)的都知道,這個(gè)方式非常不靠譜,經(jīng)常會(huì)出現(xiàn)諸如類似這樣的錯(cuò)誤

                  
                    
                    
                  
                    Cannot read property 'scrollTo' of undefined

          碰到這種情況,很多同學(xué)可能會(huì)用定時(shí)器,如果500不行,那就換1000,只要延時(shí)夠長(zhǎng),總能獲取到真實(shí)dom的。

                  
                    
                    
                  
                    this.show = true
          settimeout(() => (
          document.getElementById('xx').scrollTop = 0
          ),500)

          或許這些框架底層有其他解決方式,不過(guò)我并不精通這些,那么,從原生角度,有什么比較好的方式去解決這些問(wèn)題呢?換句話說(shuō),如何確保元素渲染時(shí)機(jī)呢?

          一、如何監(jiān)聽(tīng)元素渲染?

          元素監(jiān)聽(tīng)最官方的方式是MutationObserver,這個(gè)API天生就是為了 dom變化檢測(cè)而生的。

          https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver

          功能非常強(qiáng)大,幾乎能監(jiān)聽(tīng)到 dom的所有變化,包括上面提到的元素渲染成功。

          但是,正是因?yàn)檫^(guò)于強(qiáng)大,所以它的api就變得極其繁瑣,下面是MDN里的一段例子

                  
                    
                    
                  
                    
                      // 選擇需要觀察變動(dòng)的節(jié)點(diǎn)
                      
          const targetNode = document.getElementById("some-id");

          // 觀察器的配置(需要觀察什么變動(dòng))
          const config = { attributes: true, childList: true, subtree: true };

          // 當(dāng)觀察到變動(dòng)時(shí)執(zhí)行的回調(diào)函數(shù)
          const callback = function (mutationsList, observer) {
          // Use traditional 'for loops' for IE 11
          for (let mutation of mutationsList) {
          if (mutation.type === "childList") {
          console.log("A child node has been added or removed.");
          } else if (mutation.type === "attributes") {
          console.log("The " + mutation.attributeName + " attribute was modified.");
          }
          }
          };

          // 創(chuàng)建一個(gè)觀察器實(shí)例并傳入回調(diào)函數(shù)
          const observer = new MutationObserver(callback);

          // 以上述配置開始觀察目標(biāo)節(jié)點(diǎn)
          observer.observe(targetNode, config);

          // 之后,可停止觀察
          observer.disconnect();

          我相信,除非特殊需求,沒(méi)人會(huì)愿意寫上這樣一堆代碼吧,定時(shí)器不比這個(gè)“香”多了?

          那么,有沒(méi)有一些簡(jiǎn)潔的、靠譜的監(jiān)聽(tīng)方法呢?

          其實(shí),文章標(biāo)題已經(jīng)暴露了,沒(méi)錯(cuò),我們可以用 CSS 動(dòng)畫來(lái)監(jiān)聽(tīng)元素渲染。

          原理其實(shí)很簡(jiǎn)單,給元素一個(gè)動(dòng)畫,動(dòng)畫會(huì)在元素添加到頁(yè)面時(shí)自動(dòng)播放,進(jìn)而觸發(fā)animation*相關(guān)事件。

          68e5916a56616406456a7a9025c71579.webp

          代碼也很簡(jiǎn)單,先定義一個(gè)無(wú)關(guān)緊要的 CSS 動(dòng)畫,不能影響視覺(jué)效果,比如

                  
                    
                    
                  
                    @keyframes appear{
          to {
          opacity: .99;
          }
          }

          然后給需要監(jiān)聽(tīng)的元素上添加這個(gè)動(dòng)畫

                  
                    
                    
                  
                    div{
          animation: appear .1s;
          }

          最后,只需要在這個(gè)元素或者及其父級(jí)上監(jiān)聽(tīng)動(dòng)畫開始時(shí)機(jī)就行了,如果有多個(gè)元素,建議放在共同父級(jí)上

                  
                    
                    
                  
                    parent.addEventListener('animationstart', (ev) => {
          if (ev.animationName == 'appear') {
          // 元素出現(xiàn)了,可以獲取dom信息了
          }
          })

          下面來(lái)看幾個(gè)實(shí)際例子

          二、多行文本展開收起

          沒(méi)錯(cuò),又是這個(gè)例子。

          前不久,嘗試用 CSS 容器實(shí)現(xiàn)了這個(gè)效果,有興趣的可以參考這篇文章:

          嘗試借助CSS @container實(shí)現(xiàn)多行文本展開收起

          雖然最后實(shí)現(xiàn)了,但是dom結(jié)構(gòu)及其復(fù)雜,如下

                  
                    
                    
                  
                    
                      <div class="text-wrap">
                      
          <div class="text" title="歡迎關(guān)注前端偵探,這里有一些有趣的、你可能不知道的HTML、CSS、JS小技巧技巧。">
          <div class="text-size">
          <div class="text-flex">
          <div class="text-content">
          <label class="expand"><input type="checkbox" hidden></label>
          歡迎關(guān)注前端偵探,這里有一些有趣的、你可能不知道的HTML、CSS、JS小技巧技巧。
          </div>
          </div>
          </div>
          </div>
          <div class="text-content text-place">
          歡迎關(guān)注前端偵探,這里有一些有趣的、你可能不知道的HTML、CSS、JS小技巧技巧。
          </div>
          </div>

          很多重復(fù)的文本和多余的標(biāo)簽,這些都是為了配合容器查詢添加的。

          其實(shí)說(shuō)到底,只是為了判斷一下尺寸,其實(shí) JS 是更好的選擇,麻煩的只是獲取尺寸的時(shí)機(jī)。如果通過(guò) CSS 動(dòng)畫來(lái)監(jiān)聽(tīng),一切就都好辦了。

          我們先回到最基礎(chǔ)的HTML結(jié)構(gòu)

                  
                    
                    
                  
                    
                      <div class="text-wrap">
                      
          <div class="text-content">
          <label class="expand"><input type="checkbox" hidden></label>
          歡迎關(guān)注前端偵探,這里有一些有趣的、你可能不知道的HTML、CSS、JS小技巧技巧。
          </div>
          </div>

          這些結(jié)構(gòu)是為了實(shí)現(xiàn)右下角的“展開”按鈕必不可少的,如果不太清楚是如何布局的,可以回顧一下之前這篇文章:

          CSS 實(shí)現(xiàn)多行文本“展開收起”

          相關(guān) CSS 如下

                  
                    
                    
                  
                    .text-wrap{
          display: flex;
          position: relative;
          width: 300px;
          padding: 8px;
          outline: 1px dashed #9747FF;
          border-radius: 4px;
          line-height: 1.5;
          text-align: justify;
          font-family: cursive;
          }
          .expand{
          font-size: 80%;
          padding: .2em .5em;
          background-color: #9747FF;
          color: #fff;
          border-radius: 4px;
          cursor: pointer;
          float: right;
          clear: both;
          }
          .expand::after{
          content: '展開';
          }
          .text-content{
          display: -webkit-box;
          -webkit-box-orient: vertical;
          -webkit-line-clamp: 3;
          overflow: hidden;
          }
          .text-content::before{
          content: '';
          float: right;
          height: calc(100% - 24px);
          }
          .text-wrap:has(:checked) .text-content{
          -webkit-line-clamp: 999;
          }
          .text-wrap:has(:checked) .expand::after{
          content: '收起';
          }

          效果如下

          a13750988803d01bb98e47e1db391d77.webp

          通過(guò)前一節(jié)的原理,我們給文本容器添加一個(gè)無(wú)關(guān)緊要的動(dòng)畫

                  
                    
                    
                  
                    .text-content{
          /**/
          animation: appear .1s;
          }
          @keyframes appear {
          to {
          opacity: .99;
          }
          }

          然后,我們?cè)诟讣?jí)上監(jiān)聽(tīng)這個(gè)動(dòng)畫,我這里直接監(jiān)聽(tīng)document,這里做的事情很簡(jiǎn)單,判斷一下容器的滾動(dòng)高度和實(shí)際高度,如果滾動(dòng)高度超過(guò)實(shí)際高度,說(shuō)明文本較多,超出了指定行數(shù),這種情況就給容器添加一個(gè)特殊的屬性

                  
                    
                    
                  
                    document.addEventListener('animationstart', (ev) => {
          if (ev.animationName == 'appear') {
          ev.target.dataset.mul = ev.target.scrollHeight > ev.target.offsetHeight;
          }
          })

          然后根據(jù)這個(gè)屬性,判斷“展開”按鈕隱藏或者顯示

                  
                    
                    
                  
                    .expand{
          /**/
          visibility: hidden;
          }
          .text-content[data-mul="true"] .expand{
          visibility: visible;
          }

          這樣只有在文本較多時(shí),“展開”按鈕才會(huì)出現(xiàn),效果如下

          faaa2687b65e520c682fe78e4c606b5f.webp

          是不是要簡(jiǎn)單很多?完整代碼可以參考以下鏈接

          • CSS els with animation (juejin.cn)[1]

          • CSS els with animation (codepen.io)[2]

          三、文本超長(zhǎng)時(shí)自動(dòng)滾動(dòng)

          再來(lái)看一個(gè)例子,相信大家都碰到過(guò)。

          先看效果吧,就是一個(gè)無(wú)限滾動(dòng)的效果,類似與以前的marquee標(biāo)簽

          2bd567b3216c1833293fda53f2813b3f.webp

          首先來(lái)看HTML,并沒(méi)有什么特別之處

                  
                    
                    
                  
                    
                      <div class="marqee">
                      
          <span class="text" title="這是一段可以自動(dòng)滾動(dòng)的文本">這是一段可以自動(dòng)滾動(dòng)的文本</span>
          </div>

          這里是首尾無(wú)縫銜接,所以需要兩份文本,我這里用偽元素生成

                  
                    
                    
                  
                    .text::after{
          content: attr(title);
          padding: 0 20px;
          }

          單純的滾動(dòng)其實(shí)很容易,就一行 CSS,如下

                  
                    
                    
                  
                    .text{
          animation: move 2s linear infinite;
          }
          @keyframes move{
          to {
          transform: translateX(-50%);
          }
          }

          這樣實(shí)現(xiàn)會(huì)有兩個(gè)問(wèn)題,效果如下

          8fb713ad7b7a78686e31512ea3019559.webp

          一是較少的文本也發(fā)生的滾動(dòng),二是滾動(dòng)速度不一致。

          所以,有必要借助 JS來(lái)修正一下。

          還是上面的方式,我們直接用CSS動(dòng)畫來(lái)監(jiān)聽(tīng)元素渲染

                  
                    
                    
                  
                    .marqee{
          /**/
          animation: appear .1s;
          }
          @keyframes appear {
          to {
          opacity: .99;
          }
          }

          然后監(jiān)聽(tīng)動(dòng)畫開始事件,這里要做兩件事,也就是為了修正前面提到的兩個(gè)問(wèn)題,一個(gè)是判斷文本的真實(shí)寬度和容器寬度的關(guān)系,還有一個(gè)是獲取判斷文本寬度和容器寬度的比例關(guān)系,因?yàn)槲谋驹介L(zhǎng),需要滾動(dòng)的時(shí)間也越長(zhǎng)

                  
                    
                    
                  
                    document.addEventListener('animationstart', (ev) => {
          if (ev.animationName == 'appear') {
          ev.target.dataset.mul = ev.target.scrollWidth > ev.target.offsetWidth;
          ev.target.style.setProperty('--speed', ev.target.scrollWidth / ev.target.offsetWidth);
          }
          })

          拿到這些狀態(tài)后,我們改一下前面的動(dòng)畫。

          只有data-multrue的情況下,才執(zhí)行動(dòng)畫,并且動(dòng)畫時(shí)長(zhǎng)是和--speed成比例的,這樣可以保證所有文本的速度是一致的

                  
                    
                    
                  
                    .marqee[data-mul="true"] .text{
          display: inline-block;
          animation: move calc(var(--speed) * 3s) linear infinite;
          }

          還有就是只有data-multrue的情況下才會(huì)生成雙份文本

                  
                    
                    
                  
                    .marqee[data-mul="true"] .text::after{
          content: attr(title);
          padding: 0 20px;
          }

          這樣判斷以后,就能得到我們想要的效果了

          2bd567b3216c1833293fda53f2813b3f.webp

          完整代碼可以參考以下鏈接

          • CSS marquee width animation (juejin.cn)[3]

          • CSS marquee width animation (codepen.io)[4]

          四、元素錨定定位

          最后再來(lái)一個(gè)例子,其實(shí)這個(gè)方式我平時(shí)用的很多了,一個(gè)任務(wù)列表頁(yè)面,我們有時(shí)候會(huì)遇到這樣的需求,在地址欄上傳入一個(gè) id,例如

                  
                    
                    
                  
                    https://xxx.com?id=5

          然后,根據(jù)這個(gè)id自動(dòng)錨定到這個(gè)任務(wù)上(讓這個(gè)任務(wù)滾動(dòng)到屏幕中間)

          由于這個(gè)任務(wù)是通過(guò)接口返回渲染的,所以必須等待 dom渲染完全才能獲取到。

          9140af1f0ea8bbdda93d0878c07b32e5.webp

          傳統(tǒng)的方式可能又要通過(guò)定時(shí)器了,這時(shí)可以考慮用動(dòng)畫監(jiān)聽(tīng)的方式。

                  
                    
                    
                  
                    .item{
          /**/
          animation: appear .1s;
          }
          @keyframes appear {
          to {
          opacity: .99;
          }
          }

          然后我們只需要監(jiān)聽(tīng)動(dòng)畫開始事件,判斷一下元素的 id 是否和我們傳入的一致,如果是一致就直接錨定就行了

                  
                    
                    
                  
                    const current_id = 'item_5';// 假設(shè)這個(gè)是url傳進(jìn)來(lái)的
          document.addEventListener('animationstart', (ev) => {
          if (ev.animationName == 'appear' && ev.target.id === current_id) {
          ev.target.scrollIntoView({
          block: 'center'
          })
          }
          })

          這樣就能準(zhǔn)確無(wú)誤的獲取到錨定元素并且滾動(dòng)定位了,效果如下

          fe28a6bd341ce45f409465ed7286bc1a.webp

          完整代碼可以參考以下鏈接

          • CSS scrollIntoView with animation (juejin.cn)[5]

          • CSS scrollIntoView with animation (codepen.io)[6]

          五、其他注意事項(xiàng)

          在實(shí)際使用中,有一些要注意一下。

          比如,在vue中也可以將這個(gè)監(jiān)聽(tīng)直接綁定在父級(jí)模板上,這樣會(huì)更方便

                  
                    
                    
                  
                    
                      <div @animationstart="apear">
                      

          </div>

          還有一點(diǎn)比較重要,很多時(shí)候我們用的的可能是CSS scoped,比如

                  
                    
                    
                  
                    
                      <style scoped>
                      
          .item{
          /**/
          animation: appear .1s;
          }
          @keyframes appear {
          to {
          opacity: .99;
          }
          }
          </style>

          如果是這種寫法就需要注意了,因?yàn)樵诰幾g過(guò)程中,這個(gè)動(dòng)畫名稱會(huì)加一些哈希后綴,類似于這樣

          19df3210f18faa0d0f94fa2e01705214.webp

          所以,我們?cè)?code style="font-weight:bold;font-family:'-apple-system-font', BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;font-size:1em;color:rgb(145,109,213);">animationstart判斷時(shí)要改動(dòng)一下,比如用startsWith

                  
                    
                    
                  
                    document.addEventListener('animationstart', (ev) => {
          if (ev.animationName.startsWith('appear')) {
          //
          }
          })

          這個(gè)需要額外注意一下

          六、總結(jié)一下

          是不是從來(lái)沒(méi)有用過(guò)這些方式,趕緊試一試吧,相信會(huì)有不一樣的感受,下面總結(jié)一下

          1. 在數(shù)據(jù)驅(qū)動(dòng)視圖的框架下,獲取dom是一件比較頭疼的事情

          2. 很多時(shí)候數(shù)據(jù)更新了,dom還沒(méi)來(lái)得及更新,這時(shí)獲取就出錯(cuò)了

          3. 元素監(jiān)聽(tīng)最官方的方式是MutationObserver,但是比較復(fù)雜,一般情況下不會(huì)有人用

          4. 另辟蹊徑,我們可以用 CSS 動(dòng)畫來(lái)監(jiān)聽(tīng)元素渲染

          5. 原理非常簡(jiǎn)單,給元素一個(gè)動(dòng)畫,動(dòng)畫會(huì)在元素添加到頁(yè)面時(shí)自動(dòng)播放,進(jìn)而觸發(fā)animation*相關(guān)事件

          6. 利用這個(gè)技巧,我們可以很輕松的獲取元素的dom相關(guān)信息已經(jīng)觸發(fā)相關(guān)事件

          7. 注意一下框架里的編譯,可能會(huì)更改動(dòng)畫名稱

          總的來(lái)說(shuō),這是一個(gè)非常實(shí)用的小技巧,雖然沒(méi)有純 CSS那么“高級(jí)”,但是卻是最“實(shí)用”的。最后,如果覺(jué)得還不錯(cuò),對(duì)你有幫助的話,歡迎點(diǎn)贊、收藏、轉(zhuǎn)發(fā) ???


          [1] CSS els with animation (juejin.cn): https://code.juejin.cn/pen/7323120296334983187

          [2] CSS els with animation (codepen.io): https://codepen.io/xboxyan/pen/gOELbxV

          [3] CSS marquee width animation (juejin.cn): https://code.juejin.cn/pen/7323125690973945897

          [4] CSS marquee width animation (codepen.io): https://codepen.io/xboxyan/pen/YzgGmLb

          [5] CSS scrollIntoView with animation (juejin.cn): https://code.juejin.cn/pen/7323419904693469234

          [6] CSS scrollIntoView with animation (codepen.io): https://code.juejin.cn/pen/7323419904693469234

          瀏覽 22
          點(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>
                  秋霞国产午夜精品免费视频 | 天天玩天天操天天插天天日天插射 | 久久秘 成人久久无码 | 亚洲一级内射 | 色婷婷国产在线播放 |