前端每日實戰 169# 視頻演示如何製做「數略詞」交互動畫(內含2個視頻)

圖片描述

效果預覽

按下右側的「點擊預覽」按鈕能夠在當前頁面預覽,點擊連接能夠全屏預覽。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

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

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

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

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

至此,動畫效果製做完成。

2、擴展應用到多個單詞

「數略詞」有不少,爲了可以一次展現多個單詞,咱們將對現有的程序進行擴展。

先引入 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;
}

大功告成!

相關文章
相關標籤/搜索