標籤:

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

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

今年的11月初,我們公司參加了「2017年亞洲幼教年會(APEAC)」並取得了很不錯的成果。本人有幸負責關於這次展示頁的前端開發,特以此文記錄開發過程中的關鍵環節。

展示頁分為三大模塊:數據展示、動態展示和地圖展示。效果如下:

數據展示

此模塊展示我們公司至今為止的各項數據,通過非同步請求定時更新。

數字過渡的動態效果為類似於老虎機的效果,對應數位的新數字從下至上替換舊數字,如果該位數的數字沒有發生變化,則沒有過渡效果。

要實現這種效果,第一步要把數字按位切分:

// 分離每個數字nfunction split(num) {n return (num || 0).toString().split();n}n

然後,增加千分位,即從個位開始,每隔三位插入一個逗號,實現代碼如下:

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

最後利用樣式控制過渡動畫,關鍵代碼如下:

<ul id="main" class="number">nnt<li class="group">nt <span class="old">1</span>ntt<span class="new">1</span>nt</li>nnt<li class="group">ntt<span class="old">,</span>ntt<span class="new">,</span>nt</li>nnt<li class="group">nt <span class="old">4</span>nt <span class="new">1</span>nt</li>nnt<li class="group">ntt<span class="old">5</span>ntt<span class="new">5</span>nt</li>nnt<li class="group">ntt<span class="old">6</span>ntt<span class="new">2</span>nt</li>nn</ul>n

.number li {ntwidth: 0.18rem;ntheight: 0.24rem;ntline-height: 0.24rem;ntdisplay: inline-block;ntoverflow: hidden;n}nn.number li span {ntdisplay: block;nttransform: translateY(0%);n}nn.number li.active span {n animation: move 0.3s;ntanimation-fill-mode: forwards; // 讓動畫結束後保持最後一幀n}nn@keyframes move {ntfrom {ntttransform: translateY(0);nt}ntto {ntttransform: translateY(-100%);nt}n}n

var $main = document.querySelector(#main);nn// 填充數字nfunction update(fromArr, toArr) {nn // 從個位數開始對齊位數n fromArr = fromArr.reverse();n toArr = toArr.reverse();n n if (fromArr.length > toArr.length) {n toArr.length = fromArr.lengthn } else {n fromArr.length = toArr.lengthn }n n fromArr = fromArr.reverse();n toArr = toArr.reverse();nn // 渲染節點並激活動畫n var numberHTML = n for (var i = 0; i < toArr.length; i++) {n // 如果該位數的數字沒有發生變化,則沒有過渡效果n if (formArr[i] !== toArr[i]) {n numberHTML += (<li class="group active"> +n <span class="old"> + formArr[i] || + </span> +n <span class="new"> + toArr[i] || + </span> +n </li>);n } else {n numberHTML += (<li class="group"> +n <span class="old"> + formArr[i] || + </span> +n <span class="new"> + toArr[i] || + </span> +n </li>);n }n }n n if (numberHTML) {n $main.innerHTML = numberHTML;n }n}n

動態展示

此模塊是遊客現場互動的區域,只要掃一下二維碼點贊,就會新增一條動態。

頁面運行過程中,可能同時有多個人發動態,所以要有一條線程定時請求數據,並把數據保存在隊列中:

同時,要有另一條線程讀取隊列的數據進行渲染:

關鍵代碼如下:

var cacheList = []; // 隊列列表nnvar CHECK_INTERVAL = 2000; // 每個兩秒檢查一下隊列nvar UPDATE_INTERVAL = 1000; // 插入數據間隔nvar MIN_CACHE = 10; // 儲備數nn// 檢查隊列nfunction checkCache() {n if (cacheList.length < MIN_CACHE) {n // 非同步請求數據n ajax(function(res) {n if (res && res.length) {n cacheList = cacheList.concat(res); // 把新的數據合併到隊列列表n setTimeout(checkCache, CHECK_INTERVAL); // 輪詢檢查數據n }n }n }n}nn// 開始載入nfunction loadData() {n if (cacheList.length > 0) {n render(cacheList[0]);n cacheList = cacheList.splice(0, 1);n }n setTimeout(start, UPDATE_INTERVAL); // 輪詢讀取數據n}nnloadData(); // 數據啟動ncheckCache(); // 隊列啟動n

地圖展示

此模塊由中國地圖、固定的光點、閃爍的光點和動態氣泡構成。

因為光點和氣泡都與地理位置(精確到省份)有關聯,所以首先要在地圖上劃分出每一個省份。然而,省份的佔位是不規則的,劃分起來會有一定的難度。

剛開始的想法是每個省份有固定幾個點,氣泡和光點只會固定出現在那幾個位置。雖然能實現效果,但是看起來比較僵硬,並沒有達到設計的效果。因此,又換了一種方案。

微積分在計算不規則圖形的面積時,就是把一大塊不規則圖形切分成若干塊小矩形,以此鋪滿整個不規則圖形。同理,只需要按省份畫出其對應的幾個小矩形,並記錄下來,就可以根據這些坐標讓光點和氣泡出現在對應省份的位置。為了便於畫出這些小矩形,我做了一個小工具,效果如下圖:

因為頁面是根據屏幕解析度自適應寬高的,因此地圖在頁面上的尺寸是不固定的(但是比例是固定的),所以這裡做了一點調整,生成的坐標為百分比而不是像素值。例如:

// 記錄的省份數據nvar positionData = {n 新疆: [n {n ttstartX: 6.7164179104477615%,n ttendX: 34.07960199004975%,n ttstartY: 8.602941176470589%,n ttendY: 19.77941176470588%n t},n t{n ttstartX: 25.37313432835821%,n ttendX: 35.69651741293532%,n ttstartY: 3.5049019607843137%,n ttendY: 8.602941176470589%n t},n t...n ],n 廣東: [n {n ...n },n ...n ],n ...n};n

最後,只需要在劃分好的小矩形中選取某一塊,然後從這塊小矩形的面積中隨機抽一個點顯示光點和氣泡:

// 地圖nvar $map = document.querySelector(#map);nn// 隨機獲取一個點nfunction getPosition(province) {n // 獲取省裡面隨機一個小矩形n function getPositionArea(areaArray) {n treturn areaArray[Math.round(Math.random() * areaArray.length)];n }n n // 選定一個小矩形n var area = getPositionArea(positionData[province]);n n var x = parseFloat(area.endX) - parseFloat(area.startX);n var y = parseFloat(area.endY) - parseFloat(area.startY);nn return {n x: parseFloat(area.startX) + (Math.random() * x) + %,n y: parseFloat(area.startY) + (Math.random() * y) + %n };n}nn// 獲取新的光點nfunction getPoint($point, province) {n if (!$point) {n $point = document.createElement(div);nn // 如果動畫結束則移除光點,並重新開始n $point.addEventListener(animationend, function() {n $point.classList.remove(active);n start($point);n });n }n n // 更新節點坐標和其他樣式n var position = getPosition(provice);n ...n n return $point;n}nn// 刷新樣式並隨機加入地圖nfunction start($point) {n $point = getPoint($point);n setTimeout(function() {n $point.classList(active);n }, Math.round(Math.radom() * 1000))n}nn// 創建六百個光點nvar POINT_COUNT = 600;nfor (var i = 0; i < POINT_COUNT; i++) {n start();n}n

這樣就完成了。

應急處理

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

/exhibition?pointCount=300n

pointCount參數是用於修改光點數量,默認為600個。此外還有其他參數,就不再逐一列出了。

最後,一起來看看效果吧!


推薦閱讀:

天天演算法 | Easy | 11. 計數和描述:Count and Say
升級 React Router 與 Code Splitting 實踐

TAG:前端开发 |