微信 8.0 更新的一大特色就是支持動畫表情,如果發(fā)送的消息只有一個內置的表情圖標,這個表情會有一段簡單的動畫,一些特殊的表情還有全屏特效,例如煙花表情有全屏放煙花的特效,炸彈表情有爆炸動畫并且消息和頭像也會隨之震動。
本著前端工程師的職業(yè)精神,我就想看看能不能實現(xiàn)一個類似的特效。折騰許久之后,做出來的效果如下:
項目的核心是使用到了 lottie 動畫庫。lottie 是 Airbnb 出品的、全平臺(Web、Android、IOS、React Native)的動畫庫,它的特點在于能夠直接播放使用 Adobe After Effects 制作的動畫。設計師在 After Effects 中,利用 Bodymovin 插件把動畫導出為 JSON 格式之后,開發(fā)者就能夠通過相應平臺的 SDK 進行播放。(項目地址及示例演示見文末)
在做完這個項目之后我感覺到自己的前端儲備又豐富了一層,在以后應對復雜特效時又有了新的思路,如果你也想進一步提升前端開發(fā)技能,可以跟著這篇文章實踐一下。本篇文章除了使用 Lottie 庫之外,全部都是使用原生 HTML/CSS/JavaScript 實現(xiàn)的,這樣無論你是 React、Vue 還是其它工程師,都可以快速掌握。
本來想跳過 HTML/CSS 部分,但是想到 CSS 可能是大部分人的弱項,所以我決定還是把實現(xiàn)界面的思路寫一下,想看核心部分的可以直接跳到:二、發(fā)送普通消息部分。
首先看 HTML 部分,從效果圖來看:
那么根據(jù)這個結構編寫的 HTML 代碼如下所示:
<main> <div class="chat"> <div class="titleBar">與 XXX 聊天</div> <div class="panel"> <div class="message mine"> <img src="./me.png" alt="" /> <p><span>你好</span></p> </div> <div class="message yours"> <img class="avatar" src="./you.png" alt="" /> <p><span>Hi</span></p> </div> <!-- 省略其它消息 --> </div> <footer> <button class="chooseSticker"> <img src="./emoji.svg" alt="" /> <div class="stickers"></div> </button> <input class="messageInput" type="text" name="" id="" placeholder="請輸入聊天信息" /> <button class="send">發(fā)送</button> </footer> </div> </main>
各個元素所對應的界面部分為:
<main />
元素是一個整體的容器,用于把聊天窗口居中對齊<div class="chat">
是聊天應用的容器,用于布局標題欄、聊天面板和底部發(fā)送框。<div class="titleBar">
用于顯示標題欄。<div class="panel">
是消息面板,用于布局其中的消息。<div class="message">
為消息容器,使用不同的 class 來區(qū)分發(fā)送方, mine
代表我發(fā)送的, yours
代表對方發(fā)送的。每條消息里邊使用 <img class="avatar" >
來展示頭像,使用 <p>
元素來顯示文本, <p>
元素里邊的 <span>
元素將會作為 lottie 的容器來播放表情動畫。<footer>
用于布局底部操作按鈕和消息發(fā)送框。其中:<button class="chooseSticker">
是表情選擇按鈕,使用一個笑臉 svg 圖片表示,里邊的 <div class="stickers">
是表情選擇框彈出層,里邊的表情將在 JS 中動態(tài)加載,目的是為了實現(xiàn)動畫預覽。<input class="messageInput" />
是聊天消息輸入框,沒什么特別的。<button class="send">
是發(fā)送按鈕這個是 HTML 的基本結構,接下來看一下 CSS 樣式。
在項目根目錄下創(chuàng)建一個 style.css 文件并在 index.html 的<head> 標簽中引入:
<link rel="stylesheet" href="style.css" />
2.1 全局樣式
首先定義一些 CSS 變量,CSS 變量是為了方便我們引用同一屬性值的,后邊如果更新樣式時,可以避免多次修改:
:root { --primary-color: hsl(200, 100%, 48%); --inverse-color: hsl(310, 90%, 60%); --shadow-large: 0 0px 24px hsl(0, 0%, 0%, 0.2); --shadow-medium: 0 0 12px hsl(0, 0%, 0%, 0.1); }
這些變量的含義分別是:
--primary-color: hsl(200, 100%, 48%)
,主色調,例如我發(fā)送的消息的藍色背景。--inverse-color: hsl(310, 90%, 60%)
,反色調,或強調色調,與主色調形成鮮明對比,例如發(fā)送按鈕的背景色。--shadow-large: 0 0px 24px hsl(0, 0%, 0%, 0.2)
,大陰影,例如標題欄、底部欄的陰影。--shadow-medium: 0 0 12px hsl(0, 0%, 0%, 0.1)
,小陰影,例如輸入框和表情選擇彈出層。接下來是一些重置樣式:
* { box-sizing: border-box; padding: 0; margin: 0; font-family: Helvetica, "PingFang SC", "Microsoft Yahei", sans-serif; }
這些樣式對所有元素都有效,設置盒子模型為 border-box
,這樣內邊距、邊框都算在寬高之內,設置內間距和外間距為 0,最后設置默認字體。
2.2 Main 容器
Main 容器用于定位聊天應用容器到瀏覽器中間,使用 grid 布局,寬高分別設置為瀏覽器可視區(qū)域的 100%,并把背景色設置為黑灰色:
main { display: grid; place-items: center; width: 100vw; height: 100vh; background-color: hsl(0, 0%, 10%); }
2.3 聊天應用容器
聊天應用容器設置了固定寬高,模擬手機屏幕,并使用 grid 布局來控制標題欄、聊天面板和底部操作欄的位置:
.chat { width: 375px; height: 700px; background: hsl(0, 0%, 100%); border-radius: 8px; display: grid; grid-template-rows: max-content 1fr max-content; }
這里使用了 grid-template-rows
把聊天應用分成了 3 行,第一行的標題欄和最后一行的標底部操作欄的高度分別為內容的最大高度,中間的聊天面板則是浮動高度。
2.4 標題欄
標題欄簡單的設置了一個內間距、文字居中方式和陰影:
.titleBar { padding: 24px 0; text-align: center; box-shadow: var(--shadow-large); }
界面優(yōu)化提示:內間距用來增加留白,在視覺上引起放松,陰影則為了和下邊的聊天面板區(qū)分開
2.5 聊天面板
聊天面板使用 flex 布局對其中的消息進行排列,并設置方向為按列排布,然后設置 overflow 為 auto,在消息整體高度超出面板高度時,出現(xiàn)滾動條:
.panel { display: flex; flex-direction: column; padding: 24px 12px; overflow: auto; }
界面優(yōu)化提示:這里的 padding 同樣是為了留出足夠多的空白,來與其它元素隔開一段距離,以避免擁擠感。
2.6 消息
消息分為消息容器、頭像和消息體 3 個部分。其中消息體和頭像包含在消息容器中,先來看消息容器的樣式。消息容器使用 flex 布局來把消息體和頭像放在一行,寬度最大為面板寬度的 80%,并設置字體和外邊距:
.message { display: flex; max-width: 80%; font-size: 14px; margin: 8px 0; position: relative; }
這里的 position
設置為 relative 是為了定位后邊的全屏特效動畫。
頭像簡單設置了寬高、圓角和距離消息體的間距:
.message img { width: 40px; height: 40px; border-radius: 12px; margin-right: 12px; }
界面優(yōu)化提示:這里不得不再提一下間距的重要性,一定不要把各個元素安排的太過緊湊,否則十分影響視覺效果,最直接的影響就是引起視覺上的擁擠感,造成視覺疲勞。
消息體同樣的設置了間距和圓角,這里的圓角和頭像的保持一致,以增加和諧感。它還設置了陰影,并使用 flex 布局,把里邊的文字或表情消息居中對齊:
.message p { padding: 8px 12px; border-radius: 12px; box-shadow: var(--shadow-large); display: flex; align-items: center; }
這些樣式默認都是基于對方的消息的,如果是我發(fā)送的消息需要放到右邊,并作一些調整。首先對于我發(fā)送的消息,把 flex-flow 改為 row-reverse 這樣頭像和消息體的位置就互換了,然后使用 align-self 對齊到面板的右邊:
.message.mine { flex-flow: row-reverse; align-self: flex-end; }
調整頭像的外邊距,現(xiàn)在應該是距離左邊的消息體的邊距了:
.message.mine img { margin-right: 0; margin-left: 12px; }
設置消息體的背景色為藍色,文字為白色:
.message.mine p { background-color: var(--primary-color); color: white; }
2.7 底部操作欄
先看底部操作欄容器的整體布局,使用 grid 布局把表情選擇按鈕、消息發(fā)送框和發(fā)送按鈕分成 3 列,其中除消息發(fā)送框為浮動寬度外,其它的兩個按鈕為固定寬度,默認居中對齊,最后設置陰影和間距:
footer { display: grid; grid-template-columns: 48px 1fr 75px; justify-items: center; padding: 12px; box-shadow: var(--shadow-large); }
表情選擇按鈕把自己進行了靠左對齊,并設置相對定位,用于定位表情選擇彈出層,然后設置按鈕圖標的大?。?/p>
.chooseSticker { justify-self: start; position: relative; } .chooseSticker img { width: 36px; height: 36px; }
表情選擇彈出層的 CSS 代碼比較多但都很簡單,先看一下代碼:
.stickers { display: grid; grid-template-columns: repeat(auto-fill, 24px); column-gap: 18px; border-radius: 8px; background-color: white; box-shadow: var(--shadow-medium); padding: 6px 12px; font-size: 24px; position: absolute; top: calc(-100% - 18px); width: 300px; opacity: 0; }
這段代碼的含義是:
消息輸入框和按鈕的樣式比較簡單,消息輸入框的寬度占滿整列,發(fā)送按鈕使用 justify-self: end
把自己進行靠右對齊。這里把代碼一次性貼出來:
.messageInput { box-shadow: var(--shadow-medium); padding: 0px 12px; border-radius: 8px; width: 100%; } .send { height: 100%; width: 90%; border-radius: 8px; justify-self: end; color: white; background-color: var(--inverse-color); }
最后再添加一個 .show
樣式,用于在點擊發(fā)送表情按鈕時,給表情彈出層添加該樣式以顯示出來:
.show { opacity: 1; }
在給聊天界面加上功能之前,先編寫一些基礎的 JS 代碼。在項目根目錄創(chuàng)建一個 index.js 文件,并在 index.html 中引用,注意放到 </body> 關閉標簽的上方,這樣當 HTML DOM 加載完成之后才會執(zhí)行 js 中的代碼,防止找不到元素:
<body> <!-- 其它代碼省略 --> <script src="index.js"></script> </body>
在 index.js 中,先獲取要操作的 DOM 元素:
const panelEle = document.querySelector(".panel"); const chooseStickerBtn = document.querySelector(".chooseSticker"); const stickersEle = document.querySelector(".stickers"); const msgInputEle = document.querySelector(".messageInput"); const sendBtn = document.querySelector(".send");
其中:
panelEle
是消息面板元素,用于追加新消息。chooseStickerBtn
是選擇表情按鈕,點擊它會彈出表情選擇框。stickersEle
就是彈出的表情選擇框。msgInputEle
是消息輸入框。sendBtn
為發(fā)送按鈕。然后引入 Lottie 的 js 庫,可以到示例代碼倉庫中下載,也可以在 https://cdnjs.com/libraries/bodymovin 中下載 lottie.min.js,下載完成之后放到項目根目錄,然后在 index.html 中,在引入 index.js 的上方引入它:
<script src="lottie.min.js"></script>
下載表情動畫資源文件,它們都是 json 格式的文件,下載完成之后直接放到項目根目錄即可:
接下來看一下各部分功能是怎么實現(xiàn)的。
發(fā)送普通消息時,用戶在輸入框輸入完消息之后,點擊發(fā)送,就會把該條消息追加到消息列表中,并清空輸入框中的內容。那么這里先給發(fā)送按鈕添加點擊事件:
sendBtn.addEventListener("click", () => { const msg = msgInputEle.value; if (msg) { appendMsg(msg); } });
在事件處理函數(shù)中:
來看一下 appendMsg()
函數(shù)的代碼:
function appendMsg(msg, type) { // 創(chuàng)建消息元素 const msgEle = panelEle.appendChild(document.createElement("div")); msgEle.classList.add("message", "mine"); // 設置為“我“發(fā)送的樣式 msgEle.innerHTML = ` <img class="avatar" src="./me.png" alt="" /> <p><span>${msg}</span></p> `; // 滾動到最新消息 panelEle.scrollTop = panelEle.scrollHeight; msgInputEle.value = ""; }
函數(shù)接收兩個參數(shù),msg 和 type,分別是要追加的消息內容和類型,type 為可選的,不傳則認為是普通文本消息,如果傳遞了 "stickers" 則為表情消息,現(xiàn)在還用不到它。在這個函數(shù)中主要做了下面幾件事情:
<p>
中使用 msg 變量的值。這樣就可以發(fā)送普通的文本消息了。
發(fā)送動畫表情
在發(fā)送動畫表情之前,需要先加載動畫表情。在 index.js 的最上方先定義表情名稱和表情動畫文件路徑的鍵值對信息:
const stickers = { bomb: { path: "./3145-bomb.json", }, pumpkin: { path: "./43215-pumpkins-sticker-4.json", }, };
我們會根據(jù) bomb
、 pumkin
這樣的 key 來找到對應的動畫路徑。接著初始化彈出層中的表情以供用戶選擇:
// 初始化表情面板,也可以在表情選擇窗彈出時再初始化 Object.keys(stickers).forEach((key) => { const lottieEle = stickersEle.appendChild(document.createElement("span")); // 對每個表情創(chuàng)建 lottie 播放器 const player = lottie.loadAnimation({ container: lottieEle, renderer: "svg", loop: true, autoplay: false, path: stickers[key].path, }); // 當選擇表情時,發(fā)送消息,并設置類型為 sticker 表情消息 lottieEle.addEventListener("click", () => { appendMsg(key, "sticker"); }); // 當鼠標劃過時,播放動畫預覽 lottieEle.addEventListener("mouseover", () => { player.play(); }); // 當鼠標劃過時,停止動畫預覽 lottieEle.addEventListener("mouseleave", () => { player.stop(); }); });
這里的代碼分別作了下邊這些操作:
然后后邊則注冊了幾個事件:
接著給發(fā)送表情按鈕添加事件,點擊時,切換表情彈出層的顯示狀態(tài):
chooseStickerBtn.addEventListener("click", () => { stickersEle.classList.toggle("show"); });
這時點擊發(fā)送表情按鈕就可以看到表情選擇彈出層了?,F(xiàn)在還不能發(fā)送表情,因為還沒在 appendMsg() 函數(shù)中處理,現(xiàn)在來修改一下它里邊的代碼。首先判斷:如果是表情消息,則不在消息中的 <p>
元素里添加任何信息:
function appendMsg(msg, type) { // ... msgEle.innerHTML = ` <img class="avatar" src="./me.png" alt="" /> <p><span>${type === "sticker" ? "" : msg}</span></p> `; }
然后在它的下方,調用 playSticker() 函數(shù)來播放動畫:
// 處理表情消息,播放相關動畫 if (type === "sticker") { playSticker(msg, msgEle); }
playSticker() 函數(shù)接收兩個參數(shù),一個是表情的 key,一個是消息元素。此時的 msg 變量的內容就是在 lottieEle 點擊事件中傳遞過來的表情 key。函數(shù)中的代碼如下:
function playSticker(key, msgEle) { // 表情消息,創(chuàng)建 lottie 動畫 const lottieEle = msgEle.querySelector("span"); lottieEle.style.width = "40px"; lottieEle.style.height = "40px"; lottie.loadAnimation({ container: lottieEle, renderer: "svg", loop: false, autoplay: true, path: stickers[key].path, }); }
在這個函數(shù)里主要做了下邊幾項操作:
現(xiàn)在可以發(fā)送表情消息了,相關的動畫也會自動播放,接下來看一下怎么實現(xiàn)炸彈的全屏動畫和對消息元素的晃動效果。
發(fā)送帶全屏特效的表情
對于這種帶全屏特效的表情可以單獨進行判斷,也可以在保存表情的對象中定義相關的操作,這里為了簡單起見,我們單獨判斷用戶是否發(fā)送了炸彈表情,然后施加相應特效。
首先在 appendMsg() 函數(shù)里,進行判斷,如果發(fā)送的消息是表情消息,且表情為炸彈,則播放全屏動畫并晃動消息:
function appendMsg(msg, type) { if (type === "sticker") { playSticker(msg, msgEle); if (msg === "bomb") { // 播放爆炸動畫 setTimeout(() => { playExplosion(msgEle); }, 800); // 晃動消息列表 shakeMessages(); } } }
這里爆炸全屏動畫延遲了 800 毫秒之后再執(zhí)行,目的是在炸彈表情播放到合適的時間時,再播放全屏動畫,播放動畫使用了 playExplosion() 函數(shù),并傳遞了消息元素進去。在爆炸全屏動畫結束之后,調用 shakeMessages() 來晃動消息。這里先看一下 playExplosion() 函數(shù)的代碼:
function playExplosion(anchor) { const explosionAnimeEle = anchor.appendChild(document.createElement("div")); explosionAnimeEle.style.position = "absolute"; explosionAnimeEle.style.width = "200px"; explosionAnimeEle.style.height = "100px"; explosionAnimeEle.style.right = 0; explosionAnimeEle.style.bottom = 0; const explosionPlayer = lottie.loadAnimation({ container: explosionAnimeEle, renderer: "svg", loop: false, autoplay: true, path: "./9990-explosion.json", }); explosionPlayer.setSpeed(0.3); // 播放完成后,銷毀爆炸相關的動畫和元素 explosionPlayer.addEventListener("complete", () => { explosionPlayer.destroy(); explosionAnimeEle.remove(); }); }
playExplosion() 函數(shù)接收一個 anchor 錨點,就是說基于哪個位置開始播放全屏動畫,由于示例中的動畫畫幅比較小,所以把它固定在了最新發(fā)送的消息的下方,這里爆炸動畫的 anchor 就是消息元素,之后函數(shù)做了下邊的這些操作:
這樣全屏動畫的效果就實現(xiàn)了。接下來看消息晃動的代碼:
function shakeMessages() { [...panelEle.children] .reverse() .slice(0, 5) .forEach((messageEle) => { const avatarEle = messageEle.querySelector("img"); const msgContentEle = messageEle.querySelector("p"); avatarEle.classList.remove("shake"); msgContentEle.classList.remove("shake"); setTimeout(() => { avatarEle.classList.add("shake"); msgContentEle.classList.add("shake"); }, 700); }); }
這個函數(shù)的操作是:
接下來看一下 shake class 的定義,在 style.css 中添加下方代碼:
.shake { animation: shake 0.8s ease-in-out; } @keyframes shake { from { transform: translate3d(0, 0px, 0px); } 10% { transform: translate3d(6px, -6px, 0px); } 20% { transform: translate3d(-5px, 5px, 0px); } 30% { transform: translate3d(4px, -4px, 0px); } 35% { transform: translate3d(-3px, 3px, 0px); } 39% { transform: translate3d(2px, -2px, 0px); } 41% { transform: translate3d(-1px, 1px, 0px); } 42% { transform: translate3d(0px, 0px, 0px) rotate(20deg); } 52% { transform: rotate(-15deg); } 60% { transform: rotate(8deg); } 65% { transform: rotate(-3deg); } 67% { transform: rotate(1deg); } 70% { transform: rotate(0deg); } to { transform: translate3d(0px, 0px, 0px) rotate(0); } }
.shake
中使用了 shake keyframes 定義的動畫,執(zhí)行時間為 0.8s,動畫執(zhí)行函數(shù)為 ease-in-out。Keyframes 里的代碼比較多,但是都是很簡單的,就是模擬了爆炸時的效果,移動 x 軸和 y 軸的偏移,每次的偏移幅度越來越小,并且越來越快,可以看到百分比的間隔越來越小。在動畫進行到 42% 的時候,加了一些旋轉動畫,這樣就有了落地時的震動效果。由于使用 rotate() 旋轉時的軸心在元素中間,我們可以把消息氣泡的軸心修改一下來實現(xiàn)更真實的效果:
.message p { transform-origin: left bottom; } .message.mine p { transform-origin: right bottom; }
這里把對方發(fā)送的消息的軸心設置在左下角,自己發(fā)送的消息則設置在了右下角。
現(xiàn)在,這個模擬微信 8.0 動畫表情的功能就實現(xiàn)了。主要就是下邊幾點:
你學會了嗎?如果有問題或建議可以留個評論,喜歡此文章請點個贊或關注我,后邊還有更多更精彩的文章,感謝!
作者:峰華,簡介:前端工程師,以直觀、專業(yè)的方式分享編程知識。Bilibili UP@峰華前端工程師
本文所有地址:
示例地址:https://codechina.csdn.net/mirrors/zxuqian/html-css-examples
代碼地址:https://codechina.csdn.net/mirrors/zxuqian/html-css-examples/-/tree/master/31-05-wechat-emoji-effect
lottie: https://cdnjs.com/libraries/bodymovin ,下載 lottie.min.js
南瓜表情:https://lottiefiles.com/43215-pumpkins-sticker-4
炸彈表情:https://lottiefiles.com/3145-bomb
爆炸動畫:https://lottiefiles.com/9990-explosion
Lottie 官網:https://airbnb.io/lottie
到此這篇關于300 多行css代碼搞定微信 8.0 的炸裂特效的文章就介紹到這了,更多相關微信 8.0 炸裂特效內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持腳本之家!
標簽:萍鄉(xiāng) 保定 延邊 赤峰 平涼 林芝 泰州 大同
巨人網絡通訊聲明:本文標題《300 多行css代碼搞定微信 8.0 的炸裂特效》,本文關鍵詞 ;如發(fā)現(xiàn)本文內容存在版權問題,煩請?zhí)峁┫嚓P信息告之我們,我們將及時溝通與處理。本站內容系統(tǒng)采集于網絡,涉及言論、版權與本站無關。