按下右側的「點擊預覽」按鈕能夠在當前頁面預覽,點擊連接能夠全屏預覽。css
https://codepen.io/comehope/pen/byabeGhtml
此視頻是能夠交互的,你能夠隨時暫停視頻,編輯視頻中的代碼。前端
請用 chrome, safari, edge 打開觀看。git
https://scrimba.com/p/pEgDAM/cevPbkfBgithub
(由於 scrimba 不支持 web animation api,因此動畫效果在視頻播放過程當中看不到,不過你能夠隨時暫停視頻,手工刷新預覽窗口查看動畫效果)web
每日前端實戰系列的所有源代碼請從 github 下載:ajax
https://github.com/comehope/front-end-daily-challengeschrome
本做品用於展現若干包含字母組合 OO
的單詞,每點擊一下,OO
就眨眨眼,同時更換一個單詞。segmentfault
總體開發過程分紅 4 步,第 1 步用 CSS 實現頁面的靜態佈局,後面 3 步用 JS 實現動畫和業務邏輯。第 2 步實現單詞中間字母 OO
的眨眼效果,第 3 步實現隨機取單詞的邏輯,第 4 步實現字符的切換動畫。api
眨眼動畫和字符切換動畫都是用 Web Animation API 實現的。雖然用 JS 寫動畫比用 CSS 要麻煩一些,但 API 提供了一些事件 handler,在字符切換動畫中就是利用事件機制來精確控制動畫和在動畫過程當中加入業務邏輯的。
下面開始編碼。
dom 結構很簡單,一個名爲 .word
的 <p>
元素中包含了 4 個 <span>
子元素,每一個子元素容納一個字符:
<p class="word"> <span>b</span> <span>o</span> <span>o</span> <span>k</span> </p>
令頁面中的元素居中,設置頁面背景色爲青藍色:
body { margin: 0; height: 100vh; display: flex; align-items: center; justify-content: center; background-color: steelblue; }
設置單詞的樣式,麻布色,大字號,大寫:
.word { font-size: 100px; color: linen; font-family: monospace; font-weight: bold; display: flex; text-transform: uppercase; cursor: pointer; user-select: none; }
讓單詞兩端的 2 個字符變爲粉色:
.word span:first-child, .word span:last-child { color: pink; }
用徑向漸變給單詞中間的 OO
加上眼珠:
.word span:not(:first-child):not(:last-child) { background-image: radial-gradient( circle at center, linen 0.05em, transparent 0.05em ); }
至此,靜態佈局完成。
爲 .word
元素建立一個單擊事件函數,每當點擊發生時,就先讓中間的 OO
眨眼,而後得到下一個要顯示的單詞,再把當前的單詞換成新的單詞:
document.querySelector('.word').onclick = function() { //第1步:眨眼動畫 //第2步:得到下一個單詞 //第3步:字符切換動畫 }
先來實現第1步-眨眼動畫。在此以前瞭解一下 Web Animation API 的語法,下面是一個簡單的示例:
let keyframes = [ {transform: 'scaleY(1)'}, {transform: 'scaleY(0.1)'}, ] let options = { duration: 200, iterations: 2, } element.animate(keyframes, options)
animate()
方法接收 2 個參數,第 1 個參數是一個數組,用於定義關鍵幀;第 2 個參數是一個對象,用於定義動畫屬性,它們分別對應着 CSS 中的 @keyframes
語句和 animation
屬性。上面的 JS 代碼等價於如下 CSS 代碼:
@keyframes anim { from { transform: scaleY(1); } to { transform: scaleY(0); } } .element { animation-name: anim; animation-duration: 200ms; animation-iteration-count: 2; }
好了,咱們來正式寫眨眼動畫:
function blinkEyes() { let eyes = document.querySelectorAll('.word span:not(:first-child):not(:last-child)') let keyframes = [ {transform: 'scaleY(1)', offset: 0}, {transform: 'scaleY(0.1)', offset: 0.25}, {transform: 'scaleY(1)', offset: 0.5}, {transform: 'scaleY(1)', offset: 1}, ] let options = { duration: 200, iterations: 2, } eyes.forEach(eye => eye.animate(keyframes, options)) }
上面代碼中的 offset
是 @keyframes
中爲每一幀指定的百分比值。這段動畫的意思是每次動畫眨眼 2 次,每次眨眼用時 200ms,這 200ms 的前 50% 時間(即前 100ms)作眨眼動做,後 50% 時間等待,這樣設計的目的是在 2 次眨眼之間插入 100ms 的間隔。
而後,在點擊事件裏調用上面的方法:
document.querySelector('.word').onclick = function() { //第1步:眨眼動畫 blinkEyes() //第2步:得到下一個單詞 //第3步:字符切換動畫 }
至此,當用鼠標點擊文字時,OO
就會眨動。
接下來寫一點業務邏輯,用於隨機取出一個單詞。
引入 lodash 庫:
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
定義一個名爲 Word
的類:
function Word() { const WORDS = ['book', 'boot', 'cook', 'cool', 'door', 'food', 'fool', 'foot', 'good', 'look', 'loop', 'moon', 'noon', 'pool', 'poor', 'room', 'roof','root', 'soon', 'tool', 'wood', 'zoom',] let current = 'book' this.getNext = () => {return current = _(WORDS).without(current).sample()} }
Word
類有一個名爲 getNext()
的方法,用於從預設的數組中隨機取出一個單詞,能夠用下面的代碼測試一下效果,會輸出相似 food
這樣的單詞:
let word = new Word() console.log(word.getNext())
由於接下來的動畫只涉及單詞左右兩側的字母,因此在 getNext()
方法中再把兩端的字符拆出來,返回一個對象:
function Word() { const WORDS = ['book', 'boot', 'cook', 'cool', 'door', 'food', 'fool', 'foot', 'good', 'look', 'loop', 'moon', 'noon', 'pool', 'poor', 'room', 'roof','root', 'soon', 'tool', 'wood', 'zoom',] let current = 'book' this.getNext = () => { current = _(WORDS).without(current).sample() return { first: current.slice(0, 1), last: current.slice(-1) } } }
再測試一下效果,輸出結果會變爲相似 {first: "f", last: "d"}
的對象。
在點擊事件中調用上面的函數,把結果存入一個名爲 chars
的變量中:
let word = new Word() document.querySelector('.word').onclick = function() { //step 1: eyes blink animation blinkEyes() //第2步:得到下一個單詞 let chars = word.getNext() //第3步:字符切換動畫 }
該製做字符切換動畫了。
函數的聲明以下,函數名爲 switchChar
,它接收 2 個參數,第 1 個參數表示對哪一個字符執行動畫,值爲 first
或 last
,第 2 個參數是將被替換成的新字符:
function switchChar(which, char) {}
這樣來調用:
switchChar('first', 'f')
先實現更換邏輯,不包含動畫效果:
function switchChar(which, char) { let letter = { first: { dom: document.querySelector('.word span:first-child'), }, last: { dom: document.querySelector('.word span:last-child'), } }[which] letter.dom.textContent = char }
在點擊事件中調用 switchChar
函數:
document.querySelector('.word').onclick = function() { //step 1: eyes blink animation blinkEyes() //第2步:得到下一個單詞 let chars = word.getNext() //第3步:字符切換動畫 Object.keys(chars).forEach(key => switchChar(key, chars[key])) }
如今運行程序的話,在每次點擊以後,單詞兩側的字符都會更新。
接下來寫動畫效果,方法和寫眨眼動畫相似。這裏有兩點要說明,一是由於有 first
、last
2 個字符、又有入場、出場 2 個動畫,因此實際上一共實現了 4 個動畫效果;二是動畫的流程是先讓舊字符出場,再讓新字符入場,而更換字符的操做放置在這 2 個動畫中間,這是用動畫 API 的 onfinish
事件實現的:
function switchChar(which, char) { let letter = { first: { dom: document.querySelector('.word span:first-child'), to: '-0.5em', from: '0.8em', }, last: { dom: document.querySelector('.word span:last-child'), to: '0.5em', from: '-0.8em', } }[which] let keyframes = { out: [ {transform: `translateX(0)`, filter: 'opacity(1)'}, {transform: `translateX(${letter.to})`, filter: 'opacity(0)'}, ], in: [ {transform: `translateX(${letter.from})`, filter: 'opacity(0)'}, {transform: `translateX(0)`, filter: 'opacity(1)'}, ] } let options = { duration: 500, fill: 'forwards', easing: 'cubic-bezier(0.5, 1.5, 0.5, 1.5)' } letter.dom .animate(keyframes.out, options) .onfinish = function() { letter.dom.animate(keyframes.in, options) letter.dom.textContent = char } }
至此,所有編碼完成。解讀 JS 代碼和解讀 CSS 代碼不同,由於不是每一行代碼都有視覺效果,很難用語言描述。若是你有不理解的地方,必定是我沒有講清楚,那麼請你多看幾遍視頻,仔細體會。
在前端每日實戰的第 162 號做品中也曾使用過 Web Animation API,但那個做品的業務邏輯比這個要複雜,你在理解了這個做品以後若還想再挑戰一下,能夠再去參考它。
大功告成!