[貝聊科技] 一個(gè)炫酷大屏展示頁的打造過程

作者:韓永豪 移動開發(fā)部 前端開發(fā)工程師

今年的11月初,我們公司參加了「2017年亞洲幼教年會(APEAC)」并取得了很不錯(cuò)的成果。本人有幸負(fù)責(zé)關(guān)于這次展示頁的前端開發(fā),特以此文記錄開發(fā)過程中的關(guān)鍵環(huán)節(jié)。

展示頁分為三大模塊:數(shù)據(jù)展示、動態(tài)展示和地圖展示。效果如下:

image

數(shù)據(jù)展示

image

此模塊展示我們公司至今為止的各項(xiàng)數(shù)據(jù),通過異步請求定時(shí)更新。

數(shù)字過渡的動態(tài)效果為類似于老虎機(jī)的效果,對應(yīng)數(shù)位的新數(shù)字從下至上替換舊數(shù)字,如果該位數(shù)的數(shù)字沒有發(fā)生變化,則沒有過渡效果。

要實(shí)現(xiàn)這種效果,第一步要把數(shù)字按位切分:

// 分離每個(gè)數(shù)字
function split(num) {
    return (num || 0).toString().split('');
}

然后,增加千分位,即從個(gè)位開始,每隔三位插入一個(gè)逗號,實(shí)現(xiàn)代碼如下:

function toThousands(num) {
    var num = (num || 0).toString(), result = '';
    while (num.length > 3) {
        result = ',' + num.slice(-3) + result;
        num = num.slice(0, num.length - 3);
    }
    if (num) { result = num + result; }
    return result;
}

最后利用樣式控制過渡動畫,關(guān)鍵代碼如下:

<ul id="main" class="number">

    <li class="group">
        <span class="old">1</span>
        <span class="new">1</span>
    </li>

    <li class="group">
        <span class="old">,</span>
        <span class="new">,</span>
    </li>

    <li class="group">
        <span class="old">4</span>
        <span class="new">1</span>
    </li>

    <li class="group">
        <span class="old">5</span>
        <span class="new">5</span>
    </li>

    <li class="group">
        <span class="old">6</span>
        <span class="new">2</span>
    </li>

</ul>
.number li {
    width: 0.18rem;
    height: 0.24rem;
    line-height: 0.24rem;
    display: inline-block;
    overflow: hidden;
}

.number li span {
    display: block;
    transform: translateY(0%);
}

.number li.active span {
    animation: move 0.3s;
    animation-fill-mode: forwards; // 讓動畫結(jié)束后保持最后一幀
}

@keyframes move {
    from {
        transform: translateY(0);
    }
    to {
        transform: translateY(-100%);
    }
}
var $main = document.querySelector('#main');

// 填充數(shù)字
function update(fromArr, toArr) {

    // 從個(gè)位數(shù)開始對齊位數(shù)
    fromArr = fromArr.reverse();
    toArr = toArr.reverse();
    
    if (fromArr.length > toArr.length) {
        toArr.length = fromArr.length
    } else {
        fromArr.length = toArr.length
    }
    
    fromArr = fromArr.reverse();
    toArr = toArr.reverse();

    // 渲染節(jié)點(diǎn)并激活動畫
    var numberHTML = ''
    for (var i = 0; i < toArr.length; i++) {
        // 如果該位數(shù)的數(shù)字沒有發(fā)生變化,則沒有過渡效果
        if (formArr[i] !== toArr[i]) {
            numberHTML += ('<li class="group active">' +
                '<span class="old">' + formArr[i] || '' + '</span>' +
                '<span class="new">' + toArr[i] || '' + '</span>' +
            '</li>');
        } else {
            numberHTML += ('<li class="group">' +
                '<span class="old">' + formArr[i] || '' + '</span>' +
                '<span class="new">' + toArr[i] || '' + '</span>' +
            '</li>');
        }
    }
    
    if (numberHTML) {
        $main.innerHTML = numberHTML;
    }
}

動態(tài)展示

image

此模塊是游客現(xiàn)場互動的區(qū)域,只要掃一下二維碼點(diǎn)贊,就會新增一條動態(tài)。
頁面運(yùn)行過程中,可能同時(shí)有多個(gè)人發(fā)動態(tài),所以要有一條線程定時(shí)請求數(shù)據(jù),并把數(shù)據(jù)保存在隊(duì)列中:

image

同時(shí),要有另一條線程讀取隊(duì)列的數(shù)據(jù)進(jìn)行渲染:

image

關(guān)鍵代碼如下:

var cacheList = []; // 隊(duì)列列表

var CHECK_INTERVAL = 2000; // 每個(gè)兩秒檢查一下隊(duì)列
var UPDATE_INTERVAL = 1000; // 插入數(shù)據(jù)間隔
var MIN_CACHE = 10; // 儲備數(shù)

// 檢查隊(duì)列
function checkCache() {
    if (cacheList.length < MIN_CACHE) {
        // 異步請求數(shù)據(jù)
        ajax(function(res) {
            if (res && res.length) {
                cacheList = cacheList.concat(res); // 把新的數(shù)據(jù)合并到隊(duì)列列表
                setTimeout(checkCache, CHECK_INTERVAL); // 輪詢檢查數(shù)據(jù)
            }
        }
    }
}

// 開始加載
function loadData() {
    if (cacheList.length > 0) {
        render(cacheList[0]);
        cacheList = cacheList.splice(0, 1);
    }
    setTimeout(start, UPDATE_INTERVAL); // 輪詢讀取數(shù)據(jù)
}

loadData(); // 數(shù)據(jù)啟動
checkCache(); // 隊(duì)列啟動

地圖展示

image

此模塊由中國地圖、固定的光點(diǎn)、閃爍的光點(diǎn)和動態(tài)氣泡構(gòu)成。
因?yàn)楣恻c(diǎn)和氣泡都與地理位置(精確到省份)有關(guān)聯(lián),所以首先要在地圖上劃分出每一個(gè)省份。然而,省份的占位是不規(guī)則的,劃分起來會有一定的難度。
剛開始的想法是每個(gè)省份有固定幾個(gè)點(diǎn),氣泡和光點(diǎn)只會固定出現(xiàn)在那幾個(gè)位置。雖然能實(shí)現(xiàn)效果,但是看起來比較僵硬,并沒有達(dá)到設(shè)計(jì)的效果。因此,又換了一種方案。
微積分在計(jì)算不規(guī)則圖形的面積時(shí),就是把一大塊不規(guī)則圖形切分成若干塊小矩形,以此鋪滿整個(gè)不規(guī)則圖形。同理,只需要按省份畫出其對應(yīng)的幾個(gè)小矩形,并記錄下來,就可以根據(jù)這些坐標(biāo)讓光點(diǎn)和氣泡出現(xiàn)在對應(yīng)省份的位置。為了便于畫出這些小矩形,我做了一個(gè)小工具,效果如下圖:

image

因?yàn)轫撁媸歉鶕?jù)屏幕分辨率自適應(yīng)寬高的,因此地圖在頁面上的尺寸是不固定的(但是比例是固定的),所以這里做了一點(diǎn)調(diào)整,生成的坐標(biāo)為百分比而不是像素值。例如:

// 記錄的省份數(shù)據(jù)
var positionData = {
    新疆: [
        {
            startX: '6.7164179104477615%',
            endX: '34.07960199004975%',
            startY: '8.602941176470589%',
            endY: '19.77941176470588%'
        },
        {
            startX: '25.37313432835821%',
            endX: '35.69651741293532%',
            startY: '3.5049019607843137%',
            endY: '8.602941176470589%'
        },
        ...
    ],
    廣東: [
        {
            ...
        },
        ...
    ],
    ...
};

最后,只需要在劃分好的小矩形中選取某一塊,然后從這塊小矩形的面積中隨機(jī)抽一個(gè)點(diǎn)顯示光點(diǎn)和氣泡:

// 地圖
var $map = document.querySelector('#map');

// 隨機(jī)獲取一個(gè)點(diǎn)
function getPosition(province) {
    // 獲取省里面隨機(jī)一個(gè)小矩形
    function getPositionArea(areaArray) {
        return areaArray[Math.round(Math.random() * areaArray.length)];
    }
    
    // 選定一個(gè)小矩形
    var area = getPositionArea(positionData[province]);
    
    var x = parseFloat(area.endX) - parseFloat(area.startX);
    var y = parseFloat(area.endY) - parseFloat(area.startY);

    return {
       x: parseFloat(area.startX) + (Math.random() * x) + '%',
       y: parseFloat(area.startY) + (Math.random() * y) + '%'
    };
}

// 獲取新的光點(diǎn)
function getPoint($point, province) {
    if (!$point) {
        $point = document.createElement('div');

        // 如果動畫結(jié)束則移除光點(diǎn),并重新開始
        $point.addEventListener('animationend', function() {
            $point.classList.remove('active');
            start($point);
        });
    }
    
    // 更新節(jié)點(diǎn)坐標(biāo)和其他樣式
    var position = getPosition(provice);
    ...
    
    return $point;
}

// 刷新樣式并隨機(jī)加入地圖
function start($point) {
    $point = getPoint($point);
    setTimeout(function() {
        $point.classList('active');
    }, Math.round(Math.radom() * 1000))
}

// 創(chuàng)建六百個(gè)光點(diǎn)
var POINT_COUNT = 600;
for (var i = 0; i < POINT_COUNT; i++) {
    start();
}

這樣就完成了。

應(yīng)急處理

由上可見,頁面上的數(shù)據(jù)和動畫都非常多,展會的設(shè)備未必能滿足這樣的性能要求。如果遇到性能比較低的機(jī)器,就需要減少數(shù)據(jù)或減弱動畫效果。為了能讓現(xiàn)場工作人員方便地調(diào)整,此頁面支持通過URL參數(shù)指定性能選項(xiàng)。例如:

/exhibition?pointCount=300

pointCount參數(shù)是用于修改光點(diǎn)數(shù)量,默認(rèn)為600個(gè)。此外還有其他參數(shù),就不再逐一列出了。
最后,一起來看看效果吧!

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,777評論 25 709
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,056評論 4 61
  • 游泳對于水感好、膽子大的人來說,根本不是什么難事,可是對于像我這種即怕水、又膽小的人來講,就是難上加難的事了。經(jīng)常...
    清清草園閱讀 1,940評論 14 3
  • 中國的家長教育孩子,要聽話,不要闖禍,別不務(wù)正業(yè)。 中國的學(xué)校要求孩子整齊劃一,不能驕傲,不要標(biāo)新立異。 美國的教...
    manbanpaiing閱讀 632評論 2 3
  • 2017年4月5日,這一天的日子太沉重,我的腦子始終處于慢十二拍的節(jié)奏。 中午12:50分,我在鳳縣檢查路況時(shí),小...
    一笑而過2023閱讀 543評論 2 2

友情鏈接更多精彩內(nèi)容