[贝聊科技] 一个炫酷大屏展示页的打造过程

作者:韩永豪 移动开发部 前端开发工程师

今年的11月初,我们公司参加了「2017年亚洲幼教年会(APEAC)」并取得了很不错的成果。本人有幸负责关于这次展示页的前端开发,特以此文记录开发过程中的关键环节。

展示页分为三大模块:数据展示、动态展示和地图展示。效果如下:

image

数据展示

image

此模块展示我们公司至今为止的各项数据,通过异步请求定时更新。

数字过渡的动态效果为类似于老虎机的效果,对应数位的新数字从下至上替换旧数字,如果该位数的数字没有发生变化,则没有过渡效果。

要实现这种效果,第一步要把数字按位切分:

// 分离每个数字
function split(num) {
    return (num || 0).toString().split('');
}

然后,增加千分位,即从个位开始,每隔三位插入一个逗号,实现代码如下:

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

最后利用样式控制过渡动画,关键代码如下:

<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; // 让动画结束后保持最后一帧
}

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

// 填充数字
function update(fromArr, toArr) {

    // 从个位数开始对齐位数
    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();

    // 渲染节点并激活动画
    var numberHTML = ''
    for (var i = 0; i < toArr.length; i++) {
        // 如果该位数的数字没有发生变化,则没有过渡效果
        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;
    }
}

动态展示

image

此模块是游客现场互动的区域,只要扫一下二维码点赞,就会新增一条动态。
页面运行过程中,可能同时有多个人发动态,所以要有一条线程定时请求数据,并把数据保存在队列中:

image

同时,要有另一条线程读取队列的数据进行渲染:

image

关键代码如下:

var cacheList = []; // 队列列表

var CHECK_INTERVAL = 2000; // 每个两秒检查一下队列
var UPDATE_INTERVAL = 1000; // 插入数据间隔
var MIN_CACHE = 10; // 储备数

// 检查队列
function checkCache() {
    if (cacheList.length < MIN_CACHE) {
        // 异步请求数据
        ajax(function(res) {
            if (res && res.length) {
                cacheList = cacheList.concat(res); // 把新的数据合并到队列列表
                setTimeout(checkCache, CHECK_INTERVAL); // 轮询检查数据
            }
        }
    }
}

// 开始加载
function loadData() {
    if (cacheList.length > 0) {
        render(cacheList[0]);
        cacheList = cacheList.splice(0, 1);
    }
    setTimeout(start, UPDATE_INTERVAL); // 轮询读取数据
}

loadData(); // 数据启动
checkCache(); // 队列启动

地图展示

image

此模块由中国地图、固定的光点、闪烁的光点和动态气泡构成。
因为光点和气泡都与地理位置(精确到省份)有关联,所以首先要在地图上划分出每一个省份。然而,省份的占位是不规则的,划分起来会有一定的难度。
刚开始的想法是每个省份有固定几个点,气泡和光点只会固定出现在那几个位置。虽然能实现效果,但是看起来比较僵硬,并没有达到设计的效果。因此,又换了一种方案。
微积分在计算不规则图形的面积时,就是把一大块不规则图形切分成若干块小矩形,以此铺满整个不规则图形。同理,只需要按省份画出其对应的几个小矩形,并记录下来,就可以根据这些坐标让光点和气泡出现在对应省份的位置。为了便于画出这些小矩形,我做了一个小工具,效果如下图:

image

因为页面是根据屏幕分辨率自适应宽高的,因此地图在页面上的尺寸是不固定的(但是比例是固定的),所以这里做了一点调整,生成的坐标为百分比而不是像素值。例如:

// 记录的省份数据
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%'
        },
        ...
    ],
    广东: [
        {
            ...
        },
        ...
    ],
    ...
};

最后,只需要在划分好的小矩形中选取某一块,然后从这块小矩形的面积中随机抽一个点显示光点和气泡:

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

// 随机获取一个点
function getPosition(province) {
    // 获取省里面随机一个小矩形
    function getPositionArea(areaArray) {
        return areaArray[Math.round(Math.random() * areaArray.length)];
    }
    
    // 选定一个小矩形
    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) + '%'
    };
}

// 获取新的光点
function getPoint($point, province) {
    if (!$point) {
        $point = document.createElement('div');

        // 如果动画结束则移除光点,并重新开始
        $point.addEventListener('animationend', function() {
            $point.classList.remove('active');
            start($point);
        });
    }
    
    // 更新节点坐标和其他样式
    var position = getPosition(provice);
    ...
    
    return $point;
}

// 刷新样式并随机加入地图
function start($point) {
    $point = getPoint($point);
    setTimeout(function() {
        $point.classList('active');
    }, Math.round(Math.radom() * 1000))
}

// 创建六百个光点
var POINT_COUNT = 600;
for (var i = 0; i < POINT_COUNT; i++) {
    start();
}

这样就完成了。

应急处理

由上可见,页面上的数据和动画都非常多,展会的设备未必能满足这样的性能要求。如果遇到性能比较低的机器,就需要减少数据或减弱动画效果。为了能让现场工作人员方便地调整,此页面支持通过URL参数指定性能选项。例如:

/exhibition?pointCount=300

pointCount参数是用于修改光点数量,默认为600个。此外还有其他参数,就不再逐一列出了。
最后,一起来看看效果吧!

image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,398评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 游泳对于水感好、胆子大的人来说,根本不是什么难事,可是对于像我这种即怕水、又胆小的人来讲,就是难上加难的事了。经常...
    清清草园阅读 1,816评论 14 3
  • 中国的家长教育孩子,要听话,不要闯祸,别不务正业。 中国的学校要求孩子整齐划一,不能骄傲,不要标新立异。 美国的教...
    manbanpaiing阅读 518评论 2 3
  • 2017年4月5日,这一天的日子太沉重,我的脑子始终处于慢十二拍的节奏。 中午12:50分,我在凤县检查路况时,小...
    一笑而过2023阅读 477评论 2 2