自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

開發(fā) 前端
或許這些框架底層有其他解決方式,不過我并不精通這些,那么,從原生角度,有什么比較好的方式去解決這些問題呢?換句話說,如何確保元素渲染時機(jī)呢?

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

dom.offsetHeight

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

dom.scrollTop = 100

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

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

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

Cannot read property 'scrollTo' of undefined

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

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

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

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

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

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

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

但是,正是因為過于強(qiáng)大,所以它的api就變得極其繁瑣,下面是MDN里的一段例子

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

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

// 當(dāng)觀察到變動時執(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)建一個觀察器實(shí)例并傳入回調(diào)函數(shù)
const observer = new MutationObserver(callback);

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

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

我相信,除非特殊需求,沒人會愿意寫上這樣一堆代碼吧,定時器不比這個“香”多了?

那么,有沒有一些簡潔的、靠譜的監(jiān)聽方法呢?

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

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

圖片圖片

代碼也很簡單,先定義一個無關(guān)緊要的 CSS 動畫,不能影響視覺效果,比如

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

然后給需要監(jiān)聽的元素上添加這個動畫

div{
  animation: appear .1s;
}

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

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

下面來看幾個實(shí)際例子

二、多行文本展開收起

沒錯,又是這個例子。

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

嘗試借助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í)說到底,只是為了判斷一下尺寸,其實(shí) JS 是更好的選擇,麻煩的只是獲取尺寸的時機(jī)。如果通過 CSS 動畫來監(jiān)聽,一切就都好辦了。

我們先回到最基礎(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: '收起';
}

效果如下

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

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

然后,我們在父級上監(jiān)聽這個動畫,我這里直接監(jiān)聽document,這里做的事情很簡單,判斷一下容器的滾動高度和實(shí)際高度,如果滾動高度超過實(shí)際高度,說明文本較多,超出了指定行數(shù),這種情況就給容器添加一個特殊的屬性

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

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

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

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

圖片圖片

是不是要簡單很多?完整代碼可以參考以下鏈接

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

三、文本超長時自動滾動

再來看一個例子,相信大家都碰到過。

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

首先來看HTML,并沒有什么特別之處

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

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

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

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

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

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

圖片圖片

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

所以,有必要借助 JS來修正一下。

還是上面的方式,我們直接用CSS動畫來監(jiān)聽元素渲染

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

然后監(jiān)聽動畫開始事件,這里要做兩件事,也就是為了修正前面提到的兩個問題,一個是判斷文本的真實(shí)寬度和容器寬度的關(guān)系,還有一個是獲取判斷文本寬度和容器寬度的比例關(guān)系,因為文本越長,需要滾動的時間也越長

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)后,我們改一下前面的動畫。

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

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

還有就是只有data-mul為true的情況下才會生成雙份文本

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

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

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

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

四、元素錨定定位

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

https://xxx.com?id=5

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

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

圖片圖片

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

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

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

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

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

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

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

五、其他注意事項

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

比如,在vue中也可以將這個監(jiān)聽直接綁定在父級模板上,這樣會更方便

<div @animationstart="apear">
  
</div>

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

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

如果是這種寫法就需要注意了,因為在編譯過程中,這個動畫名稱會加一些哈希后綴,類似于這樣

所以,我們在animationstart判斷時要改動一下,比如用startsWith

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

這個需要額外注意一下

六、總結(jié)一下

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

  1. 在數(shù)據(jù)驅(qū)動視圖的框架下,獲取dom是一件比較頭疼的事情
  2. 很多時候數(shù)據(jù)更新了,dom還沒來得及更新,這時獲取就出錯了
  3. 元素監(jiān)聽最官方的方式是MutationObserver,但是比較復(fù)雜,一般情況下不會有人用
  4. 另辟蹊徑,我們可以用 CSS 動畫來監(jiān)聽元素渲染
  5. 原理非常簡單,給元素一個動畫,動畫會在元素添加到頁面時自動播放,進(jìn)而觸發(fā)animation*相關(guān)事件
  6. 利用這個技巧,我們可以很輕松的獲取元素的dom相關(guān)信息已經(jīng)觸發(fā)相關(guān)事件
  7. 注意一下框架里的編譯,可能會更改動畫名稱

總的來說,這是一個非常實(shí)用的小技巧,雖然沒有純 CSS那么“高級”,但是卻是最“實(shí)用”的。

[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

責(zé)任編輯:武曉燕 來源: 前端偵探
相關(guān)推薦

2010-09-13 16:46:29

JavaScriptHTML DOM節(jié)點(diǎn)

2010-09-28 13:50:20

2020-04-03 14:25:55

diff Meld工具

2024-06-19 10:01:50

2020-10-12 08:56:47

Virtual dom

2021-07-07 08:01:48

CSS DOM解析

2010-09-28 13:40:52

DOM元素

2020-12-02 08:31:47

Elasticsear

2012-06-04 14:47:42

HTML5

2017-12-19 15:54:28

工作流Git二分法

2024-03-11 08:21:49

2021-03-05 22:57:25

遞歸閉包 Python

2022-06-17 11:10:43

PandasPolarsPython

2022-09-22 09:44:39

技術(shù)元素

2021-04-09 18:01:03

前端ReactDOM

2010-09-10 13:06:27

JavaScript

2012-07-20 10:32:32

程序員

2021-09-09 21:10:23

Lite-XL編輯器Lua

2015-12-30 14:16:05

iOS動畫視圖渲染

2015-12-23 09:16:33

ios動畫渲染機(jī)制
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號