QQ天气H5-前端完整解析

前言: 什么是手Q天气

手Q天气是在手Q 6.0版本以上新增的功能,页面会展现当天的气温情况,已经五天温度折线图以及24小时温度图表等。 并且为了更好的交互效果,天气页面会根据8种不同的天气信息,展现相应的天气动画。如下雨下雪,飘云,日光闪烁等动画效果。

在开发手Q天气的时候,学习到许多,发现有许多地方值得写一下。以下是我的总结。

一、REM整体布局

我们知道对于移动端来说,分辨率适配是个常见的问题,设计师往往给与我们的是iphone6(750px)的视觉稿。而对于天气页面来说采用REM来整体布局是个十分不错的选择。

rem 是什么

rem(font size of the root element)是指相对于根元素的字体大小的单位。简单的说它就是一个相对单位。rem计算的规则是依赖根元素。

基本写法

如下面定义html的节点

/*定义html根元素字体大小*/
html{
    font-size:10px;   
}
/*定义子元素,采用rem作为单位*/
.sonDom {
    width: 6rem;  /*相当于 6*10=60px*/
    height: 3rem;   /*相当于 3*10=30px*/
    line-height: 3rem;   /*相当于 3*10=30px*/
    font-size: 1.2rem;   /*相当于 1.2*10=12px*/
    border-radius: .5rem;   /*相当于0.5*10=5px*/
}

从上面可以看出,rem其实相当于一个划算单位

举个例子,我们平时理解的 1米=10分米=100厘米

同理可得: 1rem = 根元素font-size的值

这里一开始设置了 html根元素font-size为10px,即 1rem = 10px

通过上面的,我们可以得出

可以通过改变html的font-size的值而等比改变所有用了rem单位的元素

而这个正是rem实现完美的分辨率适配的原理。

如何动态更改根元素font-size值

为了实现分辨率适配,我们需要用根据屏幕的大小动态去计算根元素的font-size的值 目前普遍的是两种方法:

1、通过媒体查询方式

通过媒体查询的方式,能够满足大部分场景,只需要把常用的屏幕宽度考虑进去即可

/*默认为20px*/
html {
    font-size : 20px;
}
/*判断宽度设置不同的html font-size值去覆盖默认值*/
@media only screen and (min-width: 320px){
    html {
        font-size: 10px;
    }
}
@media only screen and (min-width: 375){
    html {
        font-size: 16; 
    }
}
@media only screen and (min-width: 414px){
    html {
        font-size: 20px; 
    }
}

2、通过js设置

如果希望把所有屏幕大小给考虑进去,可以考虑使用js来计算(天气H5也是使用js来换算),如下面的代码

 //设置fontsize
   var doc = document,
   win = window;
   function initFontSize  () {
        var docEl = doc.documentElement,
            resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
            recalc = function() {
                var clientWidth = docEl.clientWidth; //window.innerWidth;
                if (!clientWidth) return;
                fontSizeRate = (clientWidth / 375);
                var baseFontSize = 15 * fontSizeRate;
                docEl.style.fontSize = baseFontSize + 'px';
            };

        recalc();
        if (!doc.addEventListener) return;
        win.addEventListener(resizeEvt, recalc, false);
        doc.addEventListener('DOMContentLoaded', recalc, false);
    };

rem换算

我们在开发的时候,往往不希望涉及到rem的换算,如一个设计稿按钮是120px,如果我们根节点font-size是14px, 计算 120/14=8.571rem。感觉还是挺麻烦得 而这些工作是可以通过sass等预处理器或者构建去完成

如下面 翔神写的fis插件

https://github.com/imweb/fis-parser-rem

效果图

如下面, rem的适配效果还是挺不错的

rem需注意点

在移动端使用rem的话,兼容性没问题的。但还是存在着一些需要注意的地方:

1、小数数值处理

不同浏览器计算rem转换为px数值时,对于小数点后的数值的处理是有所偏差,rem计算偏差的根源是浏览器内核数字类型的区别,如果浏览器的内核数字类型是float类型,能够较好地支持有小数点的数值。当浏览器内核数字类型是int类型,不支持小数点,会对数字进行四舍五入,这样就会有偏差。如果元素越大偏差得就越明显!

2、雪碧图rem

使用rem的同时又涉及到雪碧图时,由上面我们可以得知,rem的换算成px的尺寸非严格精确尺寸,如果雪碧图如果图标之间的距离过小,就可能导致图标过界,因此图与图之间的间隙需要留相应大一点。

3、单纯的rem没解决高度适配的问题。

单纯的rem没解决高度适配的问题,当然目前也没有特别多高度适配的场景,因此建议如果需要在使用rem基础上还做相应的高度适配,就要通过相应的js去辅助啦。

2、弹性盒子局部布局

我们发现,过去对于页面均分的布局时,我们比较常用下面的方式:

  • 使用float浮动子元素的方法,但需要注意清除浮动
  • 或者是设置子元素为inline-block,但需要注意设置子元素margin值

或许我们可以考虑下css3弹性盒子模型。

兼容性

让人惊喜的是目前的主流智能移动设备操作系统Android和ios的内嵌浏览器对其也有不错的支持。对移动开发来说这真是太美好了,至少对于不太喜欢使用float,padding的我来说是这样的。

手Q天气的使用

如下面这样的布局整个div分成5个部分,每个部分占据同样的宽度。

上面的html结构如下

通过弹性盒子模型,设置其css代码如下:

/*设置容器为盒子*/
.info-day-list {
    display: -webkit-box;
    padding: 1rem 0;
}
/*设置item*/
.info-day-item {
    -webkit-box-flex: 1;  /*设置item占据的比例*/
    width: 1%;
    font-size: 1.4rem;
    line-height: 3rem;
    text-align: center;
}

通过上面,可以发现,弹性盒子将模块的所拥有的空间进行我们自定义比例去分配。上面每个item设置的 box-flex都为1,故其都有父容器剩余空间1比重的宽度。

需注意点

1、弹性盒子模型div块因为文字内容不同而不均分

在开发的时候,我发现在使用弹性盒子模型时,如果涉及到文字的时候需要注意

由于天气的描述文字长度不同,如西南风和微风,分别是三个字和两个字。会有不同的宽度而导致不均分

如上面css所示,我设置了子元素width为1%(只有设置了item是统一的width就行,不一定需要是1%)就可以解决这个问题

HTML5 canvas

我们可以看到在页面中带有温度折线图以及下雪下雨的动画,这个时候我们发现使用dom去绘制这样稍微复杂的动画时,性能并不好也不好操作。这时候我们可以考虑使用到HTML5的canvas画布去实现了。这样可规避渲染树的计算,使渲染更快

由于代码比较篇幅较长,这里只给最终生成效果哈。

折线图表

下雨下雪动画

效果如下, 发现使用canvas在绘制这些动画的时候,还是十分方便的。

具体实现可以看下面这个文章 - 前端如何呼风唤雨

canvas需注意点

1、canvas高清屏模糊

在绘制折线图的时候,我们发现,折线图在高清屏下十分模糊,这是为什么呢?

熟悉retina屏的同学应该都知道,在浏览器的window变量中有一个devicePixelRatio的属性,该属性决定了浏览器会用几个(通常是2个)像素点来渲染1个像素,举例来说,假设devicePixelRatio的值为2,一张100x100像素大小的图片,在retina屏幕下,会用2个像素点的宽度去渲染图片的1个像素点,因此该图片在retina屏幕上实际会占据200x200像素的空间,相当于图片被放大了一倍,因此图片会变得模糊。

因此我们的解决方案时:更加屏幕像素比devicePixelRatio的小同比方法canvas

如下面代码

  //兼容高清屏幕,canvas画布像素也要相应改变
  var c = document.getElementById("canvas");
  //获取devicePixelRatio
  var DPR = window.devicePixelRatio;画布宽高
  //同比设置画板宽高
  c.width = = canvasWidth * DPR;
  c.height = canvasHeight * DPR;

2、内存占用

canvas对内存的消耗是挺大的,如非必要还是不要使用多个canvas

css3 transition animation

我们可以使用CSS3的transition和animation来实现许多交互效果。

使用transition实现滑动Slider

在天气内页有个星座slider,如下面

通过设置每个卡片的类名,使其切换不同的位置

.star-icon-outside-l {
    z-index: 20;
    -webkit-transform: translateX(-18.8rem) scale(.673);
            transform: translateX(-18.8rem) scale(.673);
}
.star-icon-outside-r {
    z-index: 20;
    -webkit-transform: translateX(8.5rem) scale(.673);
            transform: translateX(8.5rem) scale(.673);
}
.star-icon-beside-l {
    z-index: 40;
    -webkit-transform: translateX(-12.5rem) scale(.851);
    -webkit-transform: translateX(-12.5rem) scale(.851);
}
.star-icon-beside-r {
    z-index: 40;
    -webkit-transform: translateX(2.35rem) scale(.851);
            transform: translateX(2.35rem) scale(.851);
}
.star-icon-hide-l {
    z-index: 10;
    -webkit-transform: translateX(-25rem) scale(.673);
            transform: translateX(-25rem) scale(.673);
}
.star-icon-hide-r {
    z-index: 10;
    -webkit-transform: translateX(17rem) scale(.673);
            transform: translateX(17rem) scale(.673);
}
.star-icon-cur {
    z-index: 90;
    -webkit-transform: translateX(-5.05rem) scale(1);
            transform: translateX(-5.05rem) scale(1);
}

animation循环动画

1、渐隐渐现

@-webkit-keyframes toggleShow
{
    0% {
        opacity: 0;
    }
    11% {
        opacity: 0;
    }
    12.5% {
        opacity: 1;
    }
    20%{
        opacity: 0;
    }
    100%{
        opacity: 0;
    }
}

2、放大收缩

@-webkit-keyframes shine
{
    0% {
        -webkit-transform: scale(1,1);
    }
    50% {
        -webkit-transform: scale(1.2,1.2);
    }
    100% {
        -webkit-transform: scale(1,1);
    }
}

3、飘动

@-webkit-keyframes moveToLeft /* Safari 和 Chrome */
{
    from {-webkit-transform: translate(0);}
    to {-webkit-transform: translate(-33.33333333%);}
}

优化

我们知道,在移动端开发,性能和加载速度是十分重要的,这里我们就需要考虑所有前端能优化的点,做好了优化, 才能时你这个页面更好的展示。以下是相关的优化内容

基本动画优化

基本的动画优化,如使用transform的translate来代替left等位移操作 3D加速等,

合理使用RAF(requestAnimationFrame)

使用raf能解决脚本问题引起的丢帧,卡顿问题,并且支持中间状态监听

//首页判断是否可以使用requestAnimFrame来替换setTimeout
window.requestAnimFrame =
    window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    function(callback) {
        return window.setTimeout(callback, 1000 / 30);
    };

canvas优化

使用canvas实现下雨下雪效果,是通过一帧一帧地去重绘下落的雨滴或者雪花。这里雪花雨滴越多,对手机的性能要求就越高。

因此我们需要对不同的手机进行处理,对于一些稍微低端的手机进行一些降级处理和优化。 根据渲染情况,相应的减少雨滴和雪花的个数,减少渲染计算时间

//判断每次update的时间差,如果发现时间长过长,则相应地减少动画的最大雪花个数
 if (new Date - lastTime > 30 && drops.length < OPTS.maxNum && OPTS.maxNum > 21) {
      OPTS.maxNum -= 10;
 }

内存优化

由于我们打开天气广告时,是新开一个webview的 因此我们需要暂停被遮住的天气webview的天气动画,减少内存消耗

if(mqq&&mqq.iOS&&mqq.addEventListener){
      mqq.addEventListener("qbrowserVisibilityChange", function(e){
          cancelAnimationFrame(drp_ticker);
          if(!e.hidden){
              update();
          }
      });
  }

合理使用缓存

为了加快二次加载页面的速度,我们就需要使用好缓存。

天气的数据,都会用localstorage缓存起来

第二次短时间加载则会使用localstorage的数据,加快二次加载速度。

 //判断是否有可用的缓存
if (checkCache('weather-local_weather_info')) {
    //有则使用缓存
    Page.processData(local_weather_info);
    weather_info = local_weather_info;
} else {
    //没有缓存则去请求
    Vinda.getData('data-weatherInfo', function(data) {
        if (data && data.retcode == 0) {
            Page.processData(data);

            //将天气信息存储进来,新增时间戳,用于缓存新鲜度的判断
            $.extend(data.result, {
                searchTime: +Date.now(),
                city: city
            });
            //每次获取都会更新缓存
            $S.save({
                key: 'local_weather_info',
                value: data
            });
    });
}

预加载

DNS预解析

我们可以通过dns 预解析prefetch,提前解析,减少dns请求时间

<link rel="dns-prefetch" href="//pub.idqqimg.com" />

CGI预加载

由于天气页面是强数据页面,对于cgi数据是强依赖的。因为提前预先加载cgi能够使我们更快地去渲染页面而不是等先拉取页面js再去执行页面js去请求cgi的这样的顺序。

代码优化

dom对象池复用

在天气内页有个星座slider,如下面

我们知道总共有12个星座,但我这里却只使用了7个dom(5个可见,2个分别是隐藏的),通过复用来实现循环的12个月。虽然这里并没有减少很多dom的数量,但我觉得dom对象池复用的思想能给多dom节点的的场景带来质的飞跃。

异步加载权重较低的模块

由于整个天气又有折线图,又有动画,又有下雨下雪等东西。因此我们需要对页面进行模块划分。每个部分都是一个模块 我i将天气页面大致分成下面几个模块

//页面总模块
var Page = {/**/};

//头部模块
var headerMod = {/**/};
//时间维度的温度变化图
var timeDegreeMod = {/**/};
//广告模块
var adsMod = {/**/};
//一周天气情况图
var detailMod = {/**/};
//天气动画模块
var AnimationMod ={/**/}

然后通过总模块去管理子模块,由于模块的划分,我们可以很轻松的根据页面展示权重和先后顺序,分别去渲染和异步加载相应的模块

var Page = {
    render: function(){
        //渲染基本页面
        headerMod.render();
        timeDegreeMod.render();
        detailMod.render();
        //加载完天气信息才去加载广告;
        adsMod.getAds();
        //由于权重较低,因此异步加载下雨下雪的动画部分组件
        require.async('./setAnimation', function(AMod) {
            AnimationMod = AMod;
            AnimationMod.init();
        });
    }
}

其余基本优化

雪碧图,文件合并等减少请求数

资源压缩,代码压缩减少请求体积。

内联css, js置后等渲染无阻塞

兼容点

在开发手Q天气时,还遇到下面一些需要兼容和注意的点:

ios 广点通app广告处理逻辑兼容

由于手Q天气涉及到广告,大部分广点通广告是只需要点击链接跳转就可以了

但有些广告由于是app广告,需要引导用户去下载,故在ios上则需要做些兼容。在ios手机需要通过以下判断,改为呼起app store下载页面

  //判断是否为手Q打开且为ios且为app下载广告
var isIosAppAds = mqq.iOS && mqq.device.isMobileQQ() && producttype == 19;
//如果是app ios 广告,
//则jsonp请求广点通给的跳转链接,获取跳转appstore的tencent串
if (self.isIosAppAds) {
    $.ajax({
        url: adsMod.jump_url,
        data: {
            acttype: 1,
        },
        dataType: 'jsonp',
        success: function(o) {
            if (o && o.ret >= 0) {
                var data = o.data || {};
                if (data.dstlink) {
                    //通过手Q接口呼起app
                    mqq.app.launchApp({
                        name: data.dstlink //self.dstlink
                    });
                }
            }
        }
    });
} else {
    //默认打开新webview即可
    mqq.ui.openUrl({
        url: adsMod.jump_url,
        target: 1,
        style: 0
    });
}

X5内核兼容点

由于我们的场景是在手Q上打开,故需要兼容X5内核上的规范。

X5 tbs.1x版本时,伪元素是不能做动画的。

X5 tbs.1x版本时,不支持transition-timing-function 的ease-out曲线

目前了解,貌似到了X5 tbs.2x版本正在开始灰度支持。

更多x5上的问题,可以通过以下链接去查看QQ浏览器官网的 X5技术指南

非a标签跳转 bug

因为天气页面有许多跳转上报,需要先上报再跳转,然后我之前是这样写的

<div id="js-jump-xxx" onclick="reportAndJump();"></div>

但这样写发现在低版本的android机上,只能发起上报请求而不能进行链接跳转

后来经排查,发现低端android机只能使用a标签进行跳转操作

<a id="js-jump-xxx" onclick="reportAndJump();"></a>

不足

  • 由于页面涉及到比较多的动画和canvas,故对内存的消耗比较高,这方面一直没有很好去解决低内存手机的内存消耗问题
  • 页面动画渲染和代码仍需雕琢。当时刚入职,许多方面还是一知半解。故自我觉得仍然有许多可以优化的地方。。。

总结

QQ天气H5是我毕业来到腾讯的第一个独立开发的项目。虽然现在已经交接了。但我时不时都会去看下这个项目的动态和代码提交记录。QQ天气H5这个项目,让我在刚入职时学会了许多。虽然写得并不是很好啦。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Ceph对象存储方案

Luminous版本PG 分布调优

Luminous版本开始新增的balancer模块在PG分布优化方面效果非常明显,操作也非常简便,强烈推荐各位在集群上线之前进行这一操作,能够极大的提升整个集群...

3685
来自专栏我和未来有约会

Silverlight第三方控件专题

这里我收集整理了目前网上silverlight第三方控件的专题,若果有所遗漏请告知我一下。 名称 简介 截图 telerik 商 RadC...

4425
来自专栏落花落雨不落叶

canvas画简单电路图

87611
来自专栏张善友的专栏

LINQ via C# 系列文章

LINQ via C# Recently I am giving a series of talk on LINQ. the name “LINQ via C...

3025
来自专栏张善友的专栏

Miguel de Icaza 细说 Mix 07大会上的Silverlight和DLR

Mono之父Miguel de Icaza 详细报道微软Mix 07大会上的Silverlight和DLR ,上面还谈到了Mono and Silverligh...

3007
来自专栏大内老A

The .NET of Tomorrow

Ed Charbeneau(http://developer.telerik.com/featured/the-net-of-tomorrow/) Exciti...

39410
来自专栏陈仁松博客

ASP.NET Core 'Microsoft.Win32.Registry' 错误修复

今天在发布Asp.net Core应用到Azure的时候出现错误InvalidOperationException: Cannot find compilati...

5248
来自专栏菩提树下的杨过

Flash/Flex学习笔记(23):运动学原理

先写一个公用的小球类Ball: package{ import flash.display.Sprite; //小球 类 public class B...

27410
来自专栏魂祭心

原 canvas绘制clock

5184
来自专栏转载gongluck的CSDN博客

cocos2dx 打灰机

#include "GamePlane.h" #include "PlaneSprite.h" #include "BulletNode.h" #include...

7306

扫码关注云+社区