按下右側的「點擊預覽」按鈕能夠在當前頁面預覽,點擊連接能夠全屏預覽。css
https://codepen.io/comehope/pen/byvRxBhtml
此視頻是能夠交互的,你能夠隨時暫停視頻,編輯視頻中的代碼。前端
請用 chrome, safari, edge 打開觀看。ios
視頻1: https://scrimba.com/p/pEgDAM/cR4gpGsa
視頻2: https://scrimba.com/p/pEgDAM/czNp3MUZgit
每日前端實戰系列的所有源代碼請從 github 下載:github
https://github.com/comehope/front-end-daily-challengesajax
你們是否見過 「i18n」、「a11y」 這樣的英文單詞?它們實際上是一些單詞的縮寫,「i18n」 表明的是 「internationalization」(國際化),「a11y」 表明的是 「accessibility」(可訪問性),由於包含的字母太多了,因此縮寫時只保留頭尾的字母,再把餘下的字符個數寫在中間,這種寫法稱爲「Numeronym」,我把它翻譯成「數略詞」。聽說最長的單詞是 「pneumonoultramicroscopicsilicovolcanoconiosis」,由 45 個字母組成,意思是一種肺部疾病。chrome
本項目將製做一個交互動畫效果,令其在單詞原詞和「數略詞」之間切換。整個項目分紅二個步驟開發,第一步先實現一個固定單詞的交互動畫,第二步改寫爲能自動處理任意的單詞,而後擴展應用到多個單詞上。數組
dom 結構以下,最外側的容器名爲 .container
,其中包含一個名爲 .word
的 <div>
元素,它表明一個單詞,它的子元素是 4 個 <p>
元素,分別表明單詞的第1個字母、中間字符的個數(.middle.short
)、中間的若干字符(.middle.long
)、單詞的最後1個字母。由於動畫時將交替顯示「中間字符的個數」和「中間的若干字符」,因此爲它們設置了特殊類名,以便在隨後的 css 代碼中引用它們,當要同時選擇它們時,就用它們共同的類名 middle
,當要分別選擇時,就指定它們各自的類名 short
和 long
:app
<div class="container"> <div class="word"> <p>i</p> <p class="middle short"> <span>18</span> </p> <p class="middle long"> <span>nternationalizatio</span> </p> <p>n</p> </div> </div>
令容器居於頁面正中:
body { margin: 0; height: 100vh; display: flex; align-items: center; justify-content: center; background-image: linear-gradient(bisque, lightcyan); }
讓 4 個 <p>
標籤包含的文字橫向排列在容器中部:
.container { width: 100%; } .word { font-size: 35px; font-family: monospace; display: flex; justify-content: center; }
把 2 箇中間的 <p>
元素的文字上色,突出顯示它們:
.middle { color: tomato; }
接下來製做交互動畫效果。
先把中間的若干字符隱藏起來,只顯示中間字符的個數,在 html 代碼中找到 .middle.long
元素,爲它增長一個 hide
樣式類:
<p class="middle long hide">
在 css 中將 .middle.hide
元素的寬度設置爲 0
,而且不顯示超出容器的部分:
.middle { overflow: hidden; } .middle.hide { width: 0; }
令鼠標懸停在單詞上時,鼠標指針變成一隻手,提示用戶此時能夠點擊:
.word:hover { cursor: pointer; }
爲 .word
元素增長鼠標點擊事件,當單詞被點擊時,2 箇中間元素分別切換 hide
類,交替顯示二者中的一個元素,這些代碼寫在一個名爲 initWordElement()
的方法中。在頁面載入時將執行 init()
方法,再在其中調用 initWordElement()
方法。沒有讓 window.onload
直接執行 initWordElement()
方法,而是經過 init()
來調用,是由於在頁面初始化階段還會要作一些其餘操做,後面還會逐漸充實 init()
方法:
window.onload = init function init() { let el = document.querySelector('.word') initWordElement(el) } function initWordElement(el) { let middles = el.querySelectorAll('.middle') el.onclick = () => middles.forEach(m => m.classList.toggle('hide')) }
如今,在頁面上屢次點擊單詞,能看到單詞的中間部分不斷切換了,不過這時尚未動畫效果,接下來爲切換增長緩動效果。
先爲中間的 2 個元素設置寬度,這 2 個值是手工測量獲得的,這不是最終的寫法,後面咱們會改爲用腳本自動測量獲得元素的寬度,不過由於如今咱們要解決的是動畫效果,因此先臨時硬編碼一下:
加緩動:
.middle { transition: 1s; } .middle.short {width: 42px;} .middle.long {width: 378px;}
設置緩時長爲 1 秒:
.middle { transition: 1s; }
如今,點擊單詞時的切換效果,已經有了動畫過程,接下來細化動畫效果。
切換能夠理解由 2 個動做組成:一箇中間元素消失,另外一箇中間元素出現,經過增長緩動延時來實現這個效果:
.middle { transition: 1s; transition-delay: 1s; } .middle.hide { transition: 1s; }
如今,當改變元素寬度時,是以元素的左側爲起點改變寬度的,不夠漂亮,咱們把它改爲以中間爲中點改變寬度,這樣當元素變寬時,就向兩側延伸,當元素變窄時,就向中間收縮:
.middle { position: relative; } .middle span { position: absolute; transform: translateX(0); transition: 1s; transition-delay: 1s; } .middle.hide span { transform: translateX(-50%); transition: 1s; }
接下來修改緩動時長,由 1s
縮短爲 0.5s
,也就是令動畫速度加快一倍。爲了能方便調試和維護,咱們把時長的值定義爲變量 --t
:
.word { --t: 0.5s; } .middle { transition: var(--t); transition-delay: var(--t); } .middle span { transition: var(--t); transition-delay: var(--t); } .middle.hide { transition: var(--t); } .middle.hide span { transition: var(--t); }
至此,動畫效果製做完成。
「數略詞」有不少,爲了可以一次展現多個單詞,咱們將對現有的程序進行擴展。
先引入 lodash 庫,咱們將利用它提供的一個模板函數來處理 html 模板:
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
擴展 dom 結構,.container
容器中將包含不止一個 .word
元素,而是多個 .word
元素了。
建立一個 html 模板,它的內容是 .word
元素的代碼,其中的第一個字母、中間字符個數、中間的若干字符、最後一個字母,這些內容在模板中分別用變量 first
、middleLength
、middle
、last
表示:
<script type="text/x-templ" id="template"> <p><%= first %></p> <p class="middle short"> <span><%= middleLength %></span> </p> <p class="middle long hide"> <span><%= middle %></span> </p> <p><%= last %></p> </script>
而原 .container
元素中的內容都要刪除掉,以便動態填充:
<div class="container"></div>
寫一個名爲 getWordObject()
的獲取單詞對象的函數,輸入是一個單詞,如「internationalization」,輸出是一個對象,這個對象的屬性與 html 模板中的變量相對應:
function getWordObject(w) { return { first: w.slice(0, 1), last: w.slice(-1), middle: w.slice(1, -1), middleLength: w.slice(1, -1).length, } }
接下來寫一個名爲 createWordElement()
的方法,用於建立一個 .word
元素,在這個方法中使用了 lodash 的 _.template()
模板函數。該方法的輸入是一個單詞,將傳遞給 getWordObject()
函數:
function createWordElement(word) { const TEMPLATE = document.querySelector('#template').innerHTML let el = document.createElement('div') el.className = 'word' el.innerHTML = _.template(TEMPLATE)(getWordObject(word)) return el }
在負責頁面初始化的 init()
方法中調用 createWordElement()
方法,整個流程改成先建立一個元素,而後把該元素添加到 .container
容器中,再初始化這個元素:
function init() { let word = 'internationalization' let el = createWordElement(word) document.querySelector('.container').appendChild(el) initWordElement(el) }
如今,運行一下頁面,雖然運行效果沒有任何變化,可是 css 的屬性、頁面元素都已經變成動態生成的了。若是把 init()
方法中的 word
變量值改成其餘單詞,如 「accessibility」,頁面中就會顯示 「a11y」
了。
不過,在單詞變爲 「a11y」 以後,中間元素佔據的寬度就不正確了,這是由於此前中間元素的寬度是硬編碼的,須要把它們改成用腳本賦值。先刪除掉 css 中的這 2 行代碼:
/* .middle.short {width: 42px;} .middle.long {width: 378px;} */
而後爲 .middle
元素設置寬度屬性,屬性值是名爲 --w
的變量:
.middle { width: var(--w); }
而後在 initWordElement()
方法中增長一行,爲變量 --w
賦值:
function initWordElement(el) { let middles = el.querySelectorAll('.middle') middles.forEach(m => m.style.setProperty('--w', window.getComputedStyle(m.querySelector('span')).width)) el.onclick = () => middles.forEach(m => m.classList.toggle('hide')) }
好了,如今不論把單詞換成什麼,都能合適地展示了,至此,單個單詞的動態改造就完成了。
接下來請孫大聖拔下幾根毫毛,幫咱們把一個單詞變成多個單詞吧。
修改 init() 方法,刪除掉 word
變量,定義一個名爲 WORDS
的數組,遍歷這個數組,爲數組中的每一個單詞建立一個 .word
元素:
function init() { const WORDS = [ 'localization', 'accessibility', 'internationalization', 'supercalifragilisticexpialidocious', 'pneumonoultramicroscopicsilicovolcanoconiosis' ] WORDS.forEach(word => { let el = createWordElement(word) document.querySelector('.container').appendChild(el) initWordElement(el) }) }
如今,頁面上已經有 5 個單詞了,點擊那個 「p43s」 看看世界上最長的單詞吧。
最後,由於 <p>
元素的外邊距較大,把它調整得小一點,讓縱向的幾個單詞排列得緊湊一點:
.word p { margin: 0.3em 0; }
大功告成!