寫一個(gè)文字水平滾動(dòng)跑馬燈效果

最近的一個(gè)頁面需要一個(gè)文字水平滾動(dòng)的效果,效果如圖所示:


文字水平滾動(dòng).gif

本來想在網(wǎng)上直接找代碼一把梭,但發(fā)現(xiàn)現(xiàn)有代碼都是用的 jQuery + animation,不符合項(xiàng)目的技術(shù)棧,就想干脆自己實(shí)現(xiàn)一個(gè)。最終用了18行代碼實(shí)現(xiàn)了上面的效果。

原理

具體原理跟輪播圖類似,把包裹文字的元素復(fù)制多一個(gè)出來,當(dāng)?shù)谝粋€(gè)元素滾動(dòng)到看不見的時(shí)候,把第一個(gè)元素重新插到尾部,因?yàn)閮蓚€(gè)元素是緊挨著的,第一個(gè)元素滾動(dòng)到看不見的一瞬間,第二個(gè)元素也已經(jīng)到了開始的位置,所以肉眼看不出來元素插入的過程。最終實(shí)現(xiàn)了滾動(dòng)效果。

實(shí)現(xiàn)

HTML
<div class="scrollX">
     <p class="scrollTxt">窗前明月光,疑是地上霜,舉頭望明月,低頭思故鄉(xiāng)</p>
</div>

第一步,拿到<p>標(biāo)簽并復(fù)制,插入到父容器中:

const domScroll = document.querySelector('.scrollTxt');
const cloneNode = document.querySelector('.scrollTxt').cloneNode(true);
domScroll.parentNode.appendChild(cloneNode);

此時(shí)的HTML標(biāo)簽就變成了:

<div class="scrollX">
     <p class="scrollTxt">窗前明月光,疑是地上霜,舉頭望明月,低頭思故鄉(xiāng)</p>
     <p class="scrollTxt">窗前明月光,疑是地上霜,舉頭望明月,低頭思故鄉(xiāng)</p>
</div>

這樣有兩個(gè)問題:
①父元素隨著子元素的增大而增大,需要給父元素一個(gè)固定的寬度,且隱藏溢出的部分;
②兩個(gè)<p>標(biāo)簽不在同一行,需要讓兩個(gè)元素在同一行才有水平滾動(dòng)的效果。

針對(duì)第一個(gè)問題,給 scrollX一個(gè)固定寬度加 overflow: hidden就可以解決。
第二個(gè)問題,我是用了 flex布局,在父元素定義 display: flex就讓兩個(gè)子元素在了同一個(gè)水平線上。最終的CSS:

.scrollX {
   display: flex;
   height: 100%;
   width: 500px;
   overflow: hidden;

  p {
    height: 100%;
    lex-shrink: 0;
    padding-right: 20px;
    white-space: nowrap;
   }
 }
讓元素動(dòng)起來

讓元素滾動(dòng)有兩種方法
①將元素設(shè)置為 position: absolute并控制元素的left屬性,讓元素往左移動(dòng)。
②使用transform并控制translateX()讓元素往左移動(dòng)。

沒吃過豬肉也見過豬跑,沒寫過動(dòng)畫的人,也知道第二種方法的性能會(huì)比第一種要好。為什么好呢,我們放在最后面總結(jié),先把動(dòng)畫實(shí)現(xiàn)起來~

我們要讓元素往左滾,那么要滾多遠(yuǎn)呢?就是元素本身的寬度,當(dāng)元素滾動(dòng)的距離超過自身寬度時(shí),進(jìn)行元素插入尾部的操作:

const domScroll2 = document.querySelectorAll('.scrollTxt')[1];
let width = domScroll2.getAttribute('width');
const animation = () => {
  if (width > -100) {
    domScroll.style = `transform: translateX(${width}%)`;
    domScroll2.style = `transform: translateX(${width}%)`;
    width -= 1;
  } else {
    domScroll.parentNode.appendChild(domScroll);
    width = 0;
  }
  setTimeout(animation, 100);    // 使用setTimeout 嵌套代替 setInterval
};
animation();

代碼寫到這里,動(dòng)畫就可以動(dòng)起來了。

這里有一個(gè)實(shí)現(xiàn)的細(xì)節(jié)是,setTimeout 的嵌套代替setInterval,原因是 setInterval 的運(yùn)行機(jī)制并不是真實(shí)的“每隔XX秒后調(diào)用指定的函數(shù)”,因?yàn)?strong>setInterval并不會(huì)將指定函數(shù)需要執(zhí)行的時(shí)間考慮在內(nèi)。比如我們用setInterval每隔100ms調(diào)用一次函數(shù)A,如果A本身的執(zhí)行需要95ms,那么A執(zhí)行完,經(jīng)過至少5ms就會(huì)馬上被調(diào)用,所以為了保證每次執(zhí)行都有一定的間隔,要使用 setTimeout代替setInterval

在查資料的過程中,想起來還有requestAnimationFrame這個(gè)API,這個(gè)API相當(dāng)于一個(gè)時(shí)間被固定為 瀏覽器繪制頻率(一般為16ms一次) 的setTimeout,但它是setTimeout的改進(jìn)版,瀏覽器會(huì)為requestAnimationFrame做優(yōu)化,以每一次瀏覽器的重繪為間隔執(zhí)行動(dòng)畫。而且,當(dāng)瀏覽器不在當(dāng)前 tab 的時(shí)候,requestAnimationFrame調(diào)用的動(dòng)畫會(huì)停止。會(huì)根據(jù)瀏覽器繪制頻率調(diào)整刷新時(shí)機(jī)。

GPU渲染導(dǎo)致的動(dòng)畫中字體模糊的問題

一開始實(shí)現(xiàn)的時(shí)候考慮過用 transition 來實(shí)現(xiàn)運(yùn)動(dòng)效果,發(fā)現(xiàn)運(yùn)動(dòng)過程中字體會(huì)變得模糊,而且不僅是動(dòng)畫中的字體會(huì)模糊,連頁面上的其他字體也變得模糊起來。在實(shí)現(xiàn)了動(dòng)畫后考慮過使用will-change優(yōu)化動(dòng)畫,發(fā)現(xiàn)也存在同樣的問題(區(qū)別是will-change只會(huì)導(dǎo)致運(yùn)動(dòng)的字體變模糊)。
一番查找發(fā)現(xiàn)文字模糊是因?yàn)闉g覽器啟用了GPU渲染,而GPU渲染不具備CPU渲染的次像素抗鋸齒,導(dǎo)致了文字會(huì)出現(xiàn)模糊。

參考文章:
【翻譯】:CSS 的 will-change 屬性詳解 - 前端 - 掘金
CSS3動(dòng)畫那么強(qiáng),requestAnimationFrame還有毛線用? ? 張鑫旭-鑫空間-鑫生活
你所不知道的setInterval | 晚晴幽草軒
深入理解requestAnimationFrame - 最騷的就是你 - 博客園

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容