前端每日實戰 169# 視頻演示如何爲世界上最長單詞的製作一個“數略詞”交互動畫(內含2個視頻)

圖片描述

效果預覽

按下右側的“點擊預覽”按鈕可以在當前頁面預覽,點擊鏈接可以全屏預覽。

https://codepen.io/comehope/pen/byvRxB

可交互視頻

此視頻是可以交互的,你可以隨時暫停視頻,編輯視頻中的代碼。

請用 chrome, safari, edge 打開觀看。

視頻1: https://scrimba.com/p/pEgDAM/cR4gpGsa
視頻2: https://scrimba.com/p/pEgDAM/czNp3MUZ

源代碼下載

每日前端實戰系列的全部源代碼請從 github 下載:

https://github.com/comehope/front-end-daily-challenges

代碼解讀

大家是否見過 “i18n”、“a11y” 這樣的英文單詞?它們其實是一些單詞的縮寫,“i18n” 代表的是 “internationalization”(國際化),“a11y” 代表的是 “accessibility”(可訪問性),因爲包含的字母太多了,所以縮寫時只保留頭尾的字母,再把餘下的字符個數寫在中間,這種寫法稱爲“Numeronym”,我把它翻譯成“數略詞”。據說最長的單詞是 “pneumonoultramicroscopicsilicovolcanoconiosis”,由 45 個字母組成,意思是一種肺部疾病。

本項目將製作一個交互動畫效果,令其在單詞原詞和“數略詞”之間切換。整個項目分成二個步驟開發,第一步先實現一個固定單詞的交互動畫,第二步改寫爲能自動處理任意的單詞,然後擴展應用到多個單詞上。

一、一個固定單詞的交互動畫

dom 結構如下,最外側的容器名爲 .container,其中包含一個名爲 .word<div> 元素,它代表一個單詞,它的子元素是 4 個 <p> 元素,分別代表單詞的第1個字母、中間字符的個數(.middle.short)、中間的若干字符(.middle.long)、單詞的最後1個字母。因爲動畫時將交替顯示“中間字符的個數”和“中間的若干字符”,所以爲它們設置了特殊類名,以便在隨後的 css 代碼中引用它們,當要同時選擇它們時,就用它們共同的類名 middle,當要分別選擇時,就指定它們各自的類名 shortlong

<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 元素的代碼,其中的第一個字母、中間字符個數、中間的若干字符、最後一個字母,這些內容在模板中分別用變量 firstmiddleLengthmiddlelast 表示:

<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;
}

大功告成!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章