前端實(shí)戰(zhàn):使用CSS3實(shí)現(xiàn)類在線直播的隊(duì)列動(dòng)畫
之前在群里有個(gè)朋友問了這樣一個(gè)問題, 就是如何在小程序中實(shí)現(xiàn)類似直播平臺(tái)的用戶上線時(shí)的隊(duì)列動(dòng)畫? 作為一名前端工程師, 解決方案無(wú)非以下2種:
- 使用javascript根據(jù)條件來(lái)控制元素的樣式實(shí)現(xiàn)隊(duì)列動(dòng)畫
- 用純css3配合數(shù)據(jù)驅(qū)動(dòng)模型來(lái)實(shí)現(xiàn).
大家都知道在現(xiàn)代的Web開發(fā)中, 我們能使用Css實(shí)現(xiàn)的效果盡量不要用Js, 所以我們應(yīng)該優(yōu)先考慮用Css3來(lái)實(shí)現(xiàn),但是我們要結(jié)合數(shù)據(jù)流才能實(shí)現(xiàn)真正的隊(duì)列動(dòng)畫, 所以我們可以利用MVVM框架便捷的數(shù)據(jù)驅(qū)動(dòng)模型來(lái)控制動(dòng)畫的走向。
又由于動(dòng)畫的核心在于Css3, 所以在小程序或者是Vue/React中實(shí)現(xiàn)其實(shí)原理都是相似的, 大家不必?fù)?dān)心技術(shù)棧的問題。以下是實(shí)現(xiàn)后的效果圖:
如果以上gif無(wú)法訪問, 可以查看下面的靜態(tài)圖:
其實(shí)這種效果在很多地方都用到, 比如B站的彈幕, 某音樂平臺(tái)直播的粉絲上線動(dòng)畫, 某音的直播等等, 而在Web端, 我們又能怎么實(shí)現(xiàn)它呢? 接下來(lái)筆者將帶大家一步步實(shí)現(xiàn)這樣的動(dòng)畫效果.
正文
要想實(shí)現(xiàn)上面的動(dòng)畫效果, 我們需要先分析一下動(dòng)畫, 上圖的動(dòng)畫結(jié)構(gòu)如下:
動(dòng)畫一共分為以下兩個(gè)過程:
- 用戶進(jìn)入動(dòng)畫
- 用戶淡出動(dòng)畫
還有一個(gè)細(xì)節(jié)就是不管進(jìn)入多少個(gè)用戶, 都是從同一個(gè)位置進(jìn)入的, 此時(shí)上一個(gè)用戶位置會(huì)上移,如下圖所示:
所以要想實(shí)現(xiàn)這樣的效果最好的方式就是使用定位,比如絕對(duì)定位(absolute)或者固定定位(fixed). 并設(shè)置其bottom值, 如下代碼所示:
- .animateWrap {
- position: absolute;
- bottom: 40%;
- left: 12px;
- }
以上位置信息僅供參考,具體數(shù)值可根據(jù)自身需求來(lái)更改.設(shè)置bottom的好處是容器的子元素一旦增加, 會(huì)自動(dòng)將上一個(gè)元素頂上去, 所以不需要我們手動(dòng)去設(shè)置其偏移值.
實(shí)現(xiàn)進(jìn)入動(dòng)畫
我們要想實(shí)現(xiàn)上圖的用戶進(jìn)入動(dòng)畫, 可以使用Css3的過渡動(dòng)畫transition,也可以使用animation動(dòng)畫, 由于使用場(chǎng)景的便捷性這里我們采用animation動(dòng)畫, 首先我們先寫一下dom結(jié)構(gòu):
- <div className={styles.animateWrap}>
- <div className={styles.animate} key={item}><div className={styles.tx}><img src={tx} alt=""/></div><span>李老師上線</span></div>
- <div className={styles.animate} key={item}><div className={styles.tx}><img src={tx} alt=""/></div><span>李老師上線</span></div>
- <div className={styles.animate} key={item}><div className={styles.tx}><img src={tx} alt=""/></div><span>李老師上線</span></div>
- </div>
以上代碼表示創(chuàng)建了一個(gè)動(dòng)畫容器, 并且添加了2個(gè)用戶, 這里我們定義一下關(guān)鍵動(dòng)畫如下:
- .animate {
- margin-bottom: 10px;
- border-radius: 20px;
- background-color: rgba(0,0,0, .3);
- animation: moveIn 1.2s;
- }
- @keyframes moveIn {
- 0% {
- transform: translateX(calc(-100% - 12px));
- }
- 100% {
- transform: translateX(0);
- }
- }
以上即實(shí)現(xiàn)了元素向右移入的動(dòng)畫, 但是此時(shí)我們看到的動(dòng)畫是同時(shí)出現(xiàn)的, 我們要應(yīng)用到真實(shí)場(chǎng)景中, 一定是通過socket或者通過輪循拿到的異步數(shù)據(jù), 因此我們可以使用setInterval來(lái)模擬這一過程. 還有一個(gè)細(xì)節(jié)是我們動(dòng)畫里最多只完整展示2條用戶數(shù)據(jù), 多余的數(shù)據(jù)會(huì)漸出隱藏, 因此我們需要對(duì)數(shù)據(jù)進(jìn)行截流, 代碼如下:
- const [user, setUser] = useState<Array<string>>([])
- useEffect(() => {
- let MAX_USER_COUNT = 2;
- let timer = setInterval(() => {
- setUser(prev => {
- prev.push(Date.now() + '')
- if(prev.length > MAX_USER_COUNT + 1){
- prev.shift()
- return [...prev]
- }else {
- return [...prev]
- }
- })
- }, 2000)
- }, [])
變量MAX_USER_COUNT用來(lái)控制最大展示的用戶數(shù),可以根據(jù)實(shí)際需求更改, setUser里面的邏輯即為截流邏輯, 當(dāng)用戶數(shù)超過指定的最大值時(shí), 會(huì)將頭部元素刪除。
以上即完成了數(shù)據(jù)流轉(zhuǎn)的過程, 我們還需要處理的是用戶漸出邏輯和動(dòng)畫.我們先看看漸出的animation:
- @keyframes moveOut {
- 0% {
- opacity: 1;
- }
- 100% {
- opacity: 0;
- }
- }
其實(shí)動(dòng)畫并不難, 我們需要控制的是如何給頭部元素動(dòng)態(tài)的添加這個(gè)動(dòng)畫, 此時(shí)我們最好的方案是通過類名, 即當(dāng)滿足漸出的條件時(shí), 我們需要給漸出的元素動(dòng)態(tài)設(shè)置漸出類名, 條件如下:
- user.length > MAX_USER_COUNT && i === 0
以上條件指的是當(dāng)用戶數(shù)超過最大展示用戶數(shù)并且當(dāng)且元素為頭部元素時(shí), 那么我們只需要根據(jù)這個(gè)條件來(lái)動(dòng)態(tài)設(shè)置類名即可:
- { user.map((item, i) => {
- return <div
- className={
- classnames(styles.animate,
- user.length > 2 && i === 0 ? styles.hidden : '')
- }
- key={item}
- >
- <div className={styles.tx}>
- <img src={tx} alt=""/>
- </div>
- <span>李老師{item}上線</span>
- </div>
- })
- }
css代碼如下:
- hidden { opacity: 0; animation: moveOut 1.2s;}
通過以上步驟我們就實(shí)現(xiàn)了一個(gè)完整的類在線直播的隊(duì)列動(dòng)畫, 動(dòng)畫完整css代碼如下, 感興趣的盆友可以學(xué)習(xí)參考一下:
- animateWrap {
- position: absolute;
- bottom: 40%;
- left: 12px;
- .animate {
- margin-bottom: 10px;
- border-radius: 20px;
- background-color: rgba(0,0,0, .3);
- animation: moveIn 1.2s;
- .tx {
- display: inline-block;
- width: 36px;
- height: 36px;
- border-radius: 50%;
- overflow: hidden;
- vertical-align: middle;
- margin-right: 10px;
- img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- }
- span {
- margin-right: 12px;
- line-height: 36px;
- font-size: 14px;
- color: #fff;
- }
- }
- .hidden {
- opacity: 0;
- animation: moveOut 1.2s;
- }
- @keyframes moveIn {
- 0% {
- transform: translateX(calc(-100% - 12px));
- }
- 100% {
- transform: translateX(0);
- }
- }
- @keyframes moveOut {
- 0% {
- opacity: 1;
- }
- 100% {
- opacity: 0;
- }
- }
- }
本文轉(zhuǎn)載自微信公眾號(hào)「趣談前端」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系趣談前端公眾號(hào)。




































