作者:韓永豪 移動(dòng)開(kāi)發(fā)部 前端開(kāi)發(fā)工程師
今年的11月初,我們公司參加了「2017年亞洲幼教年會(huì)(APEAC)」并取得了很不錯(cuò)的成果。本人有幸負(fù)責(zé)關(guān)于這次展示頁(yè)的前端開(kāi)發(fā),特以此文記錄開(kāi)發(fā)過(guò)程中的關(guān)鍵環(huán)節(jié)。
展示頁(yè)分為三大模塊:數(shù)據(jù)展示、動(dòng)態(tài)展示和地圖展示。效果如下:
數(shù)據(jù)展示
此模塊展示我們公司至今為止的各項(xiàng)數(shù)據(jù),通過(guò)異步請(qǐng)求定時(shí)更新。
數(shù)字過(guò)渡的動(dòng)態(tài)效果為類(lèi)似于老虎機(jī)的效果,對(duì)應(yīng)數(shù)位的新數(shù)字從下至上替換舊數(shù)字,如果該位數(shù)的數(shù)字沒(méi)有發(fā)生變化,則沒(méi)有過(guò)渡效果。
要實(shí)現(xiàn)這種效果,第一步要把數(shù)字按位切分:
// 分離每個(gè)數(shù)字
function split(num) {
return (num || 0).toString().split('');
}
然后,增加千分位,即從個(gè)位開(kāi)始,每隔三位插入一個(gè)逗號(hào),實(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ò)渡動(dòng)畫(huà),關(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; // 讓動(dòng)畫(huà)結(jié)束后保持最后一幀
}
@keyframes move {
from {
transform: translateY(0);
}
to {
transform: translateY(-100%);
}
}
var $main = document.querySelector('#main');
// 填充數(shù)字
function update(fromArr, toArr) {
// 從個(gè)位數(shù)開(kāi)始對(duì)齊位數(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)并激活動(dòng)畫(huà)
var numberHTML = ''
for (var i = 0; i < toArr.length; i++) {
// 如果該位數(shù)的數(shù)字沒(méi)有發(fā)生變化,則沒(méi)有過(guò)渡效果
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;
}
}
動(dòng)態(tài)展示
此模塊是游客現(xiàn)場(chǎng)互動(dòng)的區(qū)域,只要掃一下二維碼點(diǎn)贊,就會(huì)新增一條動(dòng)態(tài)。
頁(yè)面運(yùn)行過(guò)程中,可能同時(shí)有多個(gè)人發(fā)動(dòng)態(tài),所以要有一條線程定時(shí)請(qǐng)求數(shù)據(jù),并把數(shù)據(jù)保存在隊(duì)列中:
同時(shí),要有另一條線程讀取隊(duì)列的數(shù)據(jù)進(jìn)行渲染:
關(guān)鍵代碼如下:
var cacheList = []; // 隊(duì)列列表
var CHECK_INTERVAL = 2000; // 每個(gè)兩秒檢查一下隊(duì)列
var UPDATE_INTERVAL = 1000; // 插入數(shù)據(jù)間隔
var MIN_CACHE = 10; // 儲(chǔ)備數(shù)
// 檢查隊(duì)列
function checkCache() {
if (cacheList.length < MIN_CACHE) {
// 異步請(qǐng)求數(shù)據(jù)
ajax(function(res) {
if (res && res.length) {
cacheList = cacheList.concat(res); // 把新的數(shù)據(jù)合并到隊(duì)列列表
setTimeout(checkCache, CHECK_INTERVAL); // 輪詢(xún)檢查數(shù)據(jù)
}
}
}
}
// 開(kāi)始加載
function loadData() {
if (cacheList.length > 0) {
render(cacheList[0]);
cacheList = cacheList.splice(0, 1);
}
setTimeout(start, UPDATE_INTERVAL); // 輪詢(xún)讀取數(shù)據(jù)
}
loadData(); // 數(shù)據(jù)啟動(dòng)
checkCache(); // 隊(duì)列啟動(dòng)
地圖展示
此模塊由中國(guó)地圖、固定的光點(diǎn)、閃爍的光點(diǎn)和動(dòng)態(tài)氣泡構(gòu)成。
因?yàn)楣恻c(diǎn)和氣泡都與地理位置(精確到省份)有關(guān)聯(lián),所以首先要在地圖上劃分出每一個(gè)省份。然而,省份的占位是不規(guī)則的,劃分起來(lái)會(huì)有一定的難度。
剛開(kāi)始的想法是每個(gè)省份有固定幾個(gè)點(diǎn),氣泡和光點(diǎn)只會(huì)固定出現(xiàn)在那幾個(gè)位置。雖然能實(shí)現(xiàn)效果,但是看起來(lái)比較僵硬,并沒(méi)有達(dá)到設(shè)計(jì)的效果。因此,又換了一種方案。
微積分在計(jì)算不規(guī)則圖形的面積時(shí),就是把一大塊不規(guī)則圖形切分成若干塊小矩形,以此鋪滿(mǎn)整個(gè)不規(guī)則圖形。同理,只需要按省份畫(huà)出其對(duì)應(yīng)的幾個(gè)小矩形,并記錄下來(lái),就可以根據(jù)這些坐標(biāo)讓光點(diǎn)和氣泡出現(xiàn)在對(duì)應(yīng)省份的位置。為了便于畫(huà)出這些小矩形,我做了一個(gè)小工具,效果如下圖:
因?yàn)轫?yè)面是根據(jù)屏幕分辨率自適應(yīng)寬高的,因此地圖在頁(yè)面上的尺寸是不固定的(但是比例是固定的),所以這里做了一點(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');
// 如果動(dòng)畫(huà)結(jié)束則移除光點(diǎn),并重新開(kāi)始
$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)急處理
由上可見(jiàn),頁(yè)面上的數(shù)據(jù)和動(dòng)畫(huà)都非常多,展會(huì)的設(shè)備未必能滿(mǎn)足這樣的性能要求。如果遇到性能比較低的機(jī)器,就需要減少數(shù)據(jù)或減弱動(dòng)畫(huà)效果。為了能讓現(xiàn)場(chǎng)工作人員方便地調(diào)整,此頁(yè)面支持通過(guò)URL參數(shù)指定性能選項(xiàng)。例如:
/exhibition?pointCount=300
pointCount參數(shù)是用于修改光點(diǎn)數(shù)量,默認(rèn)為600個(gè)。此外還有其他參數(shù),就不再逐一列出了。
最后,一起來(lái)看看效果吧!